diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/Dialogs.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/Dialogs.java index a501b057d..c1bd8c33c 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/Dialogs.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/Dialogs.java @@ -46,14 +46,15 @@ import com.gpl.rpg.AndorsTrail.model.item.Loot; import com.gpl.rpg.AndorsTrail.model.map.MapObject; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; import com.gpl.rpg.AndorsTrail.view.ItemContainerAdapter; public final class Dialogs { - private static void showDialogAndPause(Dialog d, final ControllerContext context) { + private static void showDialogAndPause(CustomDialog d, final ControllerContext context) { showDialogAndPause(d, context, null); } - private static void showDialogAndPause(Dialog d, final ControllerContext context, final OnDismissListener onDismiss) { + private static void showDialogAndPause(CustomDialog d, final ControllerContext context, final OnDismissListener onDismiss) { context.gameRoundController.pause(); CustomDialogFactory.setDismissListener(d, new OnDismissListener() { @Override @@ -195,7 +196,7 @@ public final class Dialogs { // itemList.setPadding(20, 0, 20, 20); itemList.setAdapter(new ItemContainerAdapter(mainActivity, world.tileManager, combinedLoot.items, world.model.player)); - final Dialog d = CustomDialogFactory.createDialog(mainActivity, + final CustomDialog d = CustomDialogFactory.createDialog(mainActivity, mainActivity.getResources().getString(title), mainActivity.getResources().getDrawable(R.drawable.ui_icon_equipment), msg, @@ -249,7 +250,7 @@ public final class Dialogs { } public static void showHeroDied(final MainActivity mainActivity, final ControllerContext controllers) { - final Dialog d = CustomDialogFactory.createDialog(mainActivity, + final CustomDialog d = CustomDialogFactory.createDialog(mainActivity, mainActivity.getResources().getString(R.string.dialog_game_over_title), mainActivity.getResources().getDrawable(R.drawable.ui_icon_combat), mainActivity.getResources().getString(R.string.dialog_game_over_text), @@ -286,7 +287,7 @@ public final class Dialogs { } public static void showConfirmRest(final Activity currentActivity, final ControllerContext controllerContext, final MapObject area) { - final Dialog d = CustomDialogFactory.createDialog(currentActivity, + final CustomDialog d = CustomDialogFactory.createDialog(currentActivity, currentActivity.getResources().getString(R.string.dialog_rest_title), null, currentActivity.getResources().getString(R.string.dialog_rest_confirm_message), @@ -310,7 +311,7 @@ public final class Dialogs { // .setMessage(R.string.dialog_rest_message) // .setNeutralButton(android.R.string.ok, null) // .create(); - final Dialog d = CustomDialogFactory.createDialog(currentActivity, + final CustomDialog d = CustomDialogFactory.createDialog(currentActivity, currentActivity.getResources().getString(R.string.dialog_rest_title), null, currentActivity.getResources().getString(R.string.dialog_rest_message), @@ -336,7 +337,7 @@ public final class Dialogs { text += currentActivity.getResources().getString(R.string.dialog_newversion_permission_information); } - final Dialog d = CustomDialogFactory.createDialog(currentActivity, + final CustomDialog d = CustomDialogFactory.createDialog(currentActivity, currentActivity.getResources().getString(R.string.dialog_newversion_title), null, text, @@ -371,7 +372,7 @@ public final class Dialogs { } if (!world.model.statistics.hasUnlimitedSaves()) { - final Dialog d = CustomDialogFactory.createDialog(mainActivity, + final CustomDialog d = CustomDialogFactory.createDialog(mainActivity, mainActivity.getResources().getString(R.string.menu_save_switch_character_title), null, mainActivity.getResources().getString(R.string.menu_save_switch_character), @@ -460,7 +461,7 @@ public final class Dialogs { itemList.setAdapter(new ArrayAdapter(context, R.layout.combatlog_row, android.R.id.text1, combatLogMessages)); view = itemList; - final Dialog d = CustomDialogFactory.createDialog(context, + final CustomDialog d = CustomDialogFactory.createDialog(context, context.getResources().getString(R.string.combat_log_title), context.getResources().getDrawable(R.drawable.ui_icon_combat), null, diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/BulkSelectionInterface.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/BulkSelectionInterface.java index f7b6ae87b..3c928623d 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/BulkSelectionInterface.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/BulkSelectionInterface.java @@ -23,6 +23,7 @@ import com.gpl.rpg.AndorsTrail.controller.ItemController; import com.gpl.rpg.AndorsTrail.model.item.ItemType; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; /** * @author ejwessel @@ -206,7 +207,7 @@ public final class BulkSelectionInterface extends AndorsTrailBaseActivity implem // }) // .setNegativeButton(android.R.string.no, null) // .show(); - final Dialog d = CustomDialogFactory.createDialog(v.getContext(), + final CustomDialog d = CustomDialogFactory.createDialog(v.getContext(), v.getContext().getResources().getString(R.string.bulkselection_sell_confirmation_title), v.getContext().getResources().getDrawable(android.R.drawable.ic_dialog_info), message, diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java index 2c92e8910..a8e726270 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadSaveActivity.java @@ -1,16 +1,29 @@ package com.gpl.rpg.AndorsTrail.activity; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; import android.app.Dialog; +import android.content.ClipData; +import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.annotation.RequiresApi; +import android.support.v4.provider.DocumentFile; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -22,241 +35,732 @@ import android.widget.Toast; import com.gpl.rpg.AndorsTrail.AndorsTrailApplication; import com.gpl.rpg.AndorsTrail.AndorsTrailPreferences; import com.gpl.rpg.AndorsTrail.R; +import com.gpl.rpg.AndorsTrail.controller.Constants; import com.gpl.rpg.AndorsTrail.model.ModelContainer; import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager; import com.gpl.rpg.AndorsTrail.savegames.Savegames; import com.gpl.rpg.AndorsTrail.savegames.Savegames.FileHeader; +import com.gpl.rpg.AndorsTrail.util.AndroidStorage; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; public final class LoadSaveActivity extends AndorsTrailBaseActivity implements OnClickListener { - private boolean isLoading = true; - private static final int SLOT_NUMBER_CREATE_NEW_SLOT = -1; - private static final int SLOT_NUMBER_FIRST_SLOT = 1; - private ModelContainer model; - private TileManager tileManager; - private AndorsTrailPreferences preferences; + private boolean isLoading = true; + //region special slot numbers + private static final int SLOT_NUMBER_CREATE_NEW_SLOT = -1; + public static final int SLOT_NUMBER_EXPORT_SAVEGAMES = -2; + public static final int SLOT_NUMBER_IMPORT_SAVEGAMES = -3; + public static final int SLOT_NUMBER_IMPORT_WORLDMAP = -4; + private static final int SLOT_NUMBER_FIRST_SLOT = 1; + //endregion + private ModelContainer model; + private TileManager tileManager; + private AndorsTrailPreferences preferences; - @Override - public void onCreate(Bundle savedInstanceState) { - setTheme(ThemeHelper.getDialogTheme()); - super.onCreate(savedInstanceState); + @Override + public void onCreate(Bundle savedInstanceState) { + setTheme(ThemeHelper.getDialogTheme()); + super.onCreate(savedInstanceState); - final AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this); - app.setWindowParameters(this); - this.model = app.getWorld().model; - this.preferences = app.getPreferences(); - this.tileManager = app.getWorld().tileManager; + final AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(this); + app.setWindowParameters(this); + this.model = app.getWorld().model; + this.preferences = app.getPreferences(); + this.tileManager = app.getWorld().tileManager; - String loadsave = getIntent().getData().getLastPathSegment(); - isLoading = (loadsave.equalsIgnoreCase("load")); + String loadsave = getIntent().getData().getLastPathSegment(); + isLoading = (loadsave.equalsIgnoreCase("load")); - setContentView(R.layout.loadsave); + setContentView(R.layout.loadsave); - TextView tv = (TextView) findViewById(R.id.loadsave_title); - if (isLoading) { - tv.setCompoundDrawablesWithIntrinsicBounds(android.R.drawable.ic_menu_search, 0, 0, 0); - tv.setText(R.string.loadsave_title_load); - } else { - tv.setCompoundDrawablesWithIntrinsicBounds(android.R.drawable.ic_menu_save, 0, 0, 0); - tv.setText(R.string.loadsave_title_save); - } + TextView tv = (TextView) findViewById(R.id.loadsave_title); + if (isLoading) { + tv.setCompoundDrawablesWithIntrinsicBounds(android.R.drawable.ic_menu_search, 0, 0, 0); + tv.setText(R.string.loadsave_title_load); + } else { + tv.setCompoundDrawablesWithIntrinsicBounds(android.R.drawable.ic_menu_save, 0, 0, 0); + tv.setText(R.string.loadsave_title_save); + } - ViewGroup slotList = (ViewGroup) findViewById(R.id.loadsave_slot_list); - Button slotTemplateButton = (Button) findViewById(R.id.loadsave_slot_n); - LayoutParams params = slotTemplateButton.getLayoutParams(); - slotList.removeView(slotTemplateButton); + ViewGroup slotList = (ViewGroup) findViewById(R.id.loadsave_slot_list); + Button slotTemplateButton = (Button) findViewById(R.id.loadsave_slot_n); + LayoutParams params = slotTemplateButton.getLayoutParams(); + slotList.removeView(slotTemplateButton); - ViewGroup newSlotContainer = (ViewGroup) findViewById(R.id.loadsave_save_to_new_slot_container); - Button createNewSlot = (Button) findViewById(R.id.loadsave_save_to_new_slot); + ViewGroup newSlotContainer = (ViewGroup) findViewById(R.id.loadsave_save_to_new_slot_container); + Button createNewSlot = (Button) findViewById(R.id.loadsave_save_to_new_slot); - addSavegameSlotButtons(slotList, params, Savegames.getUsedSavegameSlots(this)); + Button exportSaves = (Button) findViewById(R.id.loadsave_export_save); + Button importSaves = (Button) findViewById(R.id.loadsave_import_save); + Button importWorldmap = (Button) findViewById(R.id.loadsave_import_worldmap); - checkAndRequestPermissions(); - - if (!isLoading) { - createNewSlot.setTag(SLOT_NUMBER_CREATE_NEW_SLOT); - createNewSlot.setOnClickListener(this); - newSlotContainer.setVisibility(View.VISIBLE); - } else { - newSlotContainer.setVisibility(View.GONE); - } - } + exportSaves.setTag(SLOT_NUMBER_EXPORT_SAVEGAMES); + importSaves.setTag(SLOT_NUMBER_IMPORT_SAVEGAMES); + importWorldmap.setTag(SLOT_NUMBER_IMPORT_WORLDMAP); - private static final int READ_EXTERNAL_STORAGE_REQUEST=1; - private static final int WRITE_EXTERNAL_STORAGE_REQUEST=2; - - @TargetApi(23) - private void checkAndRequestPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { - 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) { - this.requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_REQUEST); - } - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, R.string.storage_permissions_mandatory, Toast.LENGTH_LONG).show(); - ((AndorsTrailApplication)getApplication()).discardWorld(); - finish(); - } - } - - private void addSavegameSlotButtons(ViewGroup parent, LayoutParams params, List usedSavegameSlots) { - int unused = 1; - for (int slot : usedSavegameSlots) { - final FileHeader header = Savegames.quickload(this, slot); - if (header == null) continue; - - while (unused < slot){ - Button b = new Button(this); - b.setLayoutParams(params); - b.setTag(unused); - b.setOnClickListener(this); - b.setText(getString(R.string.loadsave_empty_slot, unused)); - tileManager.setImageViewTileForPlayer(getResources(), b, header.iconID); - parent.addView(b, params); - unused++; - } - unused++; - - Button b = new Button(this); - b.setLayoutParams(params); - b.setTag(slot); - b.setOnClickListener(this); - b.setText(slot + ". " + header.describe()); - tileManager.setImageViewTileForPlayer(getResources(), b, header.iconID); - parent.addView(b, params); - } - } - - public void loadsave(int slot) { - if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) { - List usedSlots = Savegames.getUsedSavegameSlots(this); - if (usedSlots.isEmpty()) slot = SLOT_NUMBER_FIRST_SLOT; - else slot = Collections.max(usedSlots) + 1; - } - if (slot < SLOT_NUMBER_FIRST_SLOT) slot = SLOT_NUMBER_FIRST_SLOT; - - Intent i = new Intent(); - i.putExtra("slot", slot); - setResult(Activity.RESULT_OK, i); - LoadSaveActivity.this.finish(); - } - - private String getConfirmOverwriteQuestion(int slot) { - if (isLoading) return null; - if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) return null; // if we're creating a new slot - if (!Savegames.getSlotFile(slot, this).exists()) return null; - - if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) { - return getString(R.string.loadsave_save_overwrite_confirmation_all); - } - if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_NEVER) { - return null; - } - - final String currentPlayerName = model.player.getName(); - final FileHeader header = Savegames.quickload(this, slot); - if (header == null) return null; - - final String savedPlayerName = header.playerName; - if (currentPlayerName.equals(savedPlayerName)) return null; // if the names match - - return getString(R.string.loadsave_save_overwrite_confirmation, savedPlayerName, currentPlayerName); - } - - @Override - public void onClick(View view) { - final int slot = (Integer) view.getTag(); - - if (!isLoading && slot != SLOT_NUMBER_CREATE_NEW_SLOT && AndorsTrailApplication.CURRENT_VERSION == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) { - final FileHeader header = Savegames.quickload(this, slot); - if (header != null && header.fileversion != AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) { - final Dialog d = CustomDialogFactory.createDialog(this, - "Overwriting not allowed", - getResources().getDrawable(android.R.drawable.ic_dialog_alert), - "You are currently using a development version of Andor's trail. Overwriting a regular savegame is not allowed in development mode.", - null, - true); - CustomDialogFactory.addDismissButton(d, android.R.string.ok); - CustomDialogFactory.show(d); - return; - } - } + ViewGroup exportImportContainer = (ViewGroup) findViewById(R.id.loadsave_export_import_save_container); - if (isLoading) { - if(!Savegames.getSlotFile(slot, this).exists()) { - showErrorLoadingEmptySlot(); - } else { - final FileHeader header = Savegames.quickload(this, slot); - if (header != null && !header.hasUnlimitedSaves) { - showSlotGetsDeletedOnLoadWarning(slot); - } else { - loadsave(slot); - } - } - } else { - final String message = getConfirmOverwriteQuestion(slot); - if (message != null) { - showConfirmoverwriteQuestion(slot, message); - } else { - loadsave(slot); - } - } - } + addSavegameSlotButtons(slotList, params, Savegames.getUsedSavegameSlots(this)); - private void showErrorLoadingEmptySlot() { - final Dialog d = CustomDialogFactory.createDialog(this, - getString(R.string.startscreen_error_loading_game), - getResources().getDrawable(android.R.drawable.ic_dialog_alert), - getString(R.string.startscreen_error_loading_empty_slot), - null, - true); - CustomDialogFactory.addDismissButton(d, android.R.string.ok); - CustomDialogFactory.show(d); - } + checkAndRequestPermissions(); - private void showSlotGetsDeletedOnLoadWarning(final int slot) { - final Dialog d = CustomDialogFactory.createDialog(this, - getString(R.string.startscreen_attention_slot_gets_delete_on_load), - getResources().getDrawable(android.R.drawable.ic_dialog_alert), - getString(R.string.startscreen_attention_message_slot_gets_delete_on_load), - null, - true); - CustomDialogFactory.addButton(d, android.R.string.ok, new View.OnClickListener() { - @Override - public void onClick(View v) { - loadsave(slot); - } - }); - CustomDialogFactory.show(d); - } + if (!isLoading) { + createNewSlot.setTag(SLOT_NUMBER_CREATE_NEW_SLOT); + createNewSlot.setOnClickListener(this); + newSlotContainer.setVisibility(View.VISIBLE); + exportImportContainer.setVisibility(View.GONE); + } else { + newSlotContainer.setVisibility(View.GONE); - private void showConfirmoverwriteQuestion(final int slot, String message) { - final String title = - getString(R.string.loadsave_save_overwrite_confirmation_title) + ' ' - + getString(R.string.loadsave_save_overwrite_confirmation_slot, slot); - final Dialog d = CustomDialogFactory.createDialog(this, - title, - getResources().getDrawable(android.R.drawable.ic_dialog_alert), - message, - null, - true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + exportSaves.setOnClickListener(this); + importSaves.setOnClickListener(this); + importWorldmap.setOnClickListener(this); + exportImportContainer.setVisibility(View.VISIBLE); + + boolean hasSavegames = !Savegames.getUsedSavegameSlots(this).isEmpty(); + exportSaves.setEnabled(hasSavegames); + } + else{ + exportImportContainer.setVisibility(View.GONE); + } + } + } + + private static final int READ_EXTERNAL_STORAGE_REQUEST = 1; + private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 2; + + private void checkAndRequestPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + 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) { + this.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_REQUEST); + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, R.string.storage_permissions_mandatory, Toast.LENGTH_LONG).show(); + ((AndorsTrailApplication) getApplication()).discardWorld(); + finish(); + } + } + + private void addSavegameSlotButtons(ViewGroup parent, LayoutParams params, List usedSavegameSlots) { + int unused = 1; + for (int slot : usedSavegameSlots) { + final FileHeader header = Savegames.quickload(this, slot); + if (header == null) continue; + + while (unused < slot) { + Button b = new Button(this); + b.setLayoutParams(params); + b.setTag(unused); + b.setOnClickListener(this); + b.setText(getString(R.string.loadsave_empty_slot, unused)); + tileManager.setImageViewTileForPlayer(getResources(), b, header.iconID); + parent.addView(b, params); + unused++; + } + unused++; + + Button b = new Button(this); + b.setLayoutParams(params); + b.setTag(slot); + b.setOnClickListener(this); + b.setText(slot + ". " + header.describe()); + tileManager.setImageViewTileForPlayer(getResources(), b, header.iconID); + parent.addView(b, params); + } + } + + private void cancelLoadSaveActivity(int slot){ + completeLoadSaveActivity(slot, false); + } + + private void completeLoadSaveActivity(int slot) { + completeLoadSaveActivity(slot, true); + } + private void completeLoadSaveActivity(int slot, boolean success) { + Intent i = new Intent(); + if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) { + slot = getFirstFreeSlot(); + } else if (slot == SLOT_NUMBER_EXPORT_SAVEGAMES + || slot == SLOT_NUMBER_IMPORT_SAVEGAMES + || slot == SLOT_NUMBER_IMPORT_WORLDMAP) { + i.putExtra("import_export", true); + + if(slot == SLOT_NUMBER_IMPORT_WORLDMAP){ + i.putExtra("import_worldmap", true); + } + if(slot == SLOT_NUMBER_IMPORT_SAVEGAMES){ + i.putExtra("import_savegames", true); + } + if(slot == SLOT_NUMBER_EXPORT_SAVEGAMES){ + i.putExtra("export", true); + } + + } else if (slot < SLOT_NUMBER_FIRST_SLOT) + slot = SLOT_NUMBER_FIRST_SLOT; + + i.putExtra("slot", slot); + if(success) setResult(Activity.RESULT_OK, i); + else setResult(Activity.RESULT_CANCELED, i); + LoadSaveActivity.this.finish(); + } + + private int getFirstFreeSlot() { + int slot; + List usedSlots = Savegames.getUsedSavegameSlots(this); + if (usedSlots.isEmpty()) + slot = SLOT_NUMBER_FIRST_SLOT; + else slot = Collections.max(usedSlots) + 1; + return slot; + } + + private String getConfirmOverwriteQuestion(int slot) { + if (isLoading) + return null; + + return getConfirmOverwriteQuestionIgnoringLoading(slot); + } + + private String getConfirmOverwriteQuestionIgnoringLoading(int slot) { + if (slot == SLOT_NUMBER_CREATE_NEW_SLOT) + return null;//creating a new savegame + + if (!Savegames.getSlotFile(slot, this).exists()) + return null;//nothing in slot to overwrite + + if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_ALWAYS) { + return getString(R.string.loadsave_save_overwrite_confirmation_all); + } + if (preferences.displayOverwriteSavegame == AndorsTrailPreferences.CONFIRM_OVERWRITE_SAVEGAME_NEVER) { + return null; + } + + final String currentPlayerName = model.player.getName(); + final FileHeader header = Savegames.quickload(this, slot); + if (header == null) return null; + + final String savedPlayerName = header.playerName; + if (currentPlayerName.equals(savedPlayerName)) return null; //if the names match + + return getString(R.string.loadsave_save_overwrite_confirmation, savedPlayerName, currentPlayerName); + } + + @Override + public void onClick(View view) { + final int slot = (Integer) view.getTag(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + switch (slot) { + case SLOT_NUMBER_IMPORT_WORLDMAP: + clickImportWorldmap(); + return; + case SLOT_NUMBER_IMPORT_SAVEGAMES: + clickImportSaveGames(); + return; + case SLOT_NUMBER_EXPORT_SAVEGAMES: + clickExportSaveGames(); + return; + } + } + if (!isLoading + && slot != SLOT_NUMBER_CREATE_NEW_SLOT + && AndorsTrailApplication.CURRENT_VERSION == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION) { + if (!isOverwriteTargetInIncompatibleVersion(slot)) { + saveOrOverwriteSavegame(slot); + } + } else if (isLoading) { + loadSaveGame(slot); + } else { + saveOrOverwriteSavegame(slot); + } + } + + private void saveOrOverwriteSavegame(int slot) { + final String message = getConfirmOverwriteQuestion(slot); + if (message != null) { + showConfirmoverwriteQuestion(slot, message); + } else { + completeLoadSaveActivity(slot); + } + } + + private boolean isOverwriteTargetInIncompatibleVersion(int slot) { + final FileHeader header = Savegames.quickload(this, slot); + if (header != null && 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); + return true; + } + return false; + } + + //region Imports/Exports + + @RequiresApi(api = Build.VERSION_CODES.P) + private void exportSaveGames(Intent data) { + Uri uri = data.getData(); + + Context context = getApplicationContext(); + ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver(); + + File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); + DocumentFile source = DocumentFile.fromFile(storageDir); + DocumentFile target = DocumentFile.fromTreeUri(context, uri); + if (target == null) { + return; + } + + DocumentFile[] files = source.listFiles(); + + boolean hasExistingFiles = false; + for (DocumentFile file : + files) { + String fileName = file.getName(); + if (fileName == null) + continue; + + DocumentFile existingFile = target.findFile(fileName); + if (existingFile != null) { + hasExistingFiles = true; + break; + } + } + + if (hasExistingFiles) { + showConfirmOverwriteByExportQuestion(resolver, target, files); + } else { + exportSaveGamesFolderContentToFolder(resolver, target, files); + } + } + + @RequiresApi(api = Build.VERSION_CODES.P) + private void exportSaveGamesFolderContentToFolder(ContentResolver resolver, DocumentFile target, DocumentFile[] files) { + DocumentFile[] sourceFiles = new DocumentFile[files.length]; + + DocumentFile[] worldmapFiles = null; + + for (int i = 0; i < files.length; i++) { + DocumentFile file = files[i]; + if (file.isFile()) { + sourceFiles[i] = file; + } else if (file.isDirectory() && Objects.equals(file.getName(), Constants.FILENAME_WORLDMAP_DIRECTORY)) { + worldmapFiles = file.listFiles(); + } + } + Context context =this; + DocumentFile[] finalWorldmapFiles = worldmapFiles; + AndroidStorage.copyDocumentFilesToDirAsync(sourceFiles, + 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.N) + private void importSaveGames(Intent data) { + Uri uri = data.getData(); + ClipData uris = data.getClipData(); + + if (uri == null && uris == null) { + //no file was selected + return; + } + + Context context = getApplicationContext(); + ContentResolver resolver = context.getContentResolver(); + + File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); + DocumentFile appSavegameFolder = DocumentFile.fromFile(storageDir); + + List uriList = new ArrayList<>(); + if (uri != null) { + uriList.add(uri); + } else { + for (int i = 0; i < uris.getItemCount(); i++) + uriList.add(uris.getItemAt(i).getUri()); + } + importSaveGamesFromUris(context, resolver, appSavegameFolder, uriList); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private void importSaveGamesFromUris(Context context, ContentResolver resolver, DocumentFile appSavegameFolder, List uriList) { + int count = uriList.size(); + + ArrayList alreadyExistingFiles = new ArrayList<>(); + ArrayList newFiles = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + Uri item = uriList.get(i); + DocumentFile itemFile = DocumentFile.fromSingleUri(context, item); + boolean fileAlreadyExists = getExistsSavegameInOwnFiles(itemFile, appSavegameFolder); + if (fileAlreadyExists) + alreadyExistingFiles.add(itemFile); + else + newFiles.add(itemFile); + } + + if (alreadyExistingFiles.size() > 0) { + showConfirmOverwriteByImportQuestion(resolver, appSavegameFolder, alreadyExistingFiles, newFiles); + } else { + importSaveGames(resolver, appSavegameFolder, newFiles); + } + } + + private void importSaveGames(ContentResolver resolver, DocumentFile appSavegameFolder, List saveFiles) { + int size = saveFiles.size(); + DocumentFile[] sources = new DocumentFile[size]; + DocumentFile[] targets = new DocumentFile[size]; + + boolean saveAsNew = false; + for (int i = 0; i < size; i++) { + DocumentFile file = saveFiles.get(i); + if (file == null) {//null is value a marker that the next should be saved as new + saveAsNew = true; + continue; + } + + int slot = getSlotFromSavegameFileName(file.getName()); + if (slot == -1) { + //invalid file name + continue; + } + + if (saveAsNew) { + slot = getFirstFreeSlot(); + saveAsNew = false; + } + + String targetName = Savegames.getSlotFileName(slot); + sources[i] = file; + targets[i] = getOrCreateDocumentFile(appSavegameFolder, targetName); + } + + AndroidStorage.copyDocumentFilesFromToAsync(sources, + this, + targets, + (sucess) -> completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES, sucess)); + } + + private void completeSavegameImportAndCheckIfDone(List importsNeedingConfirmation, int slot) { + importsNeedingConfirmation.remove((Object) slot); + if (importsNeedingConfirmation.isEmpty()) { + completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES); + } + } + + private boolean getExistsSavegameInOwnFiles(DocumentFile savegameFile, DocumentFile appSavegameFolder) { + if (savegameFile == null) + return false; + + DocumentFile foundFile = appSavegameFolder.findFile(Objects.requireNonNull(savegameFile.getName())); + return foundFile != null && foundFile.exists(); + } + + private int getSlotFromSavegameFileName(String fileName) { + 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; + } + String slotStr = fileName.substring(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX.length()); + + int slot; + try { + slot = Integer.parseInt(slotStr); + return slot; + } catch (NumberFormatException e) { + //TODO: Maybe output a message that the file didn't have the right name? + 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) { + DocumentFile targetFile = folder.findFile(targetName);//try finding the file + if (targetFile == null)//no file found, creating new one + targetFile = folder.createFile(Constants.NO_FILE_EXTENSION_MIME_TYPE, targetName); + return targetFile; + } + + @RequiresApi(api = Build.VERSION_CODES.P) + private void importWorldmap(Intent data) { + Uri uri = data.getData(); + + Context context = getApplicationContext(); + ContentResolver resolver = AndorsTrailApplication.getApplicationFromActivity(this).getContentResolver(); + + File storageDir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); + DocumentFile storageFolder = DocumentFile.fromFile(storageDir); + 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(); + return; + } + if (!Constants.FILENAME_WORLDMAP_DIRECTORY.equals(chosenFolder.getName())) { + //user did not select the worldmap folder directly + DocumentFile file = chosenFolder.findFile(Constants.FILENAME_WORLDMAP_DIRECTORY); + if (file == null || !file.isDirectory() || !Constants.FILENAME_WORLDMAP_DIRECTORY.equals(file.getName())) { + //could not find a worldmap folder in the users selection + showErrorImportingWorldmapWrongDirectory(); + return; + } + + chosenFolder = file; + } + + AndroidStorage.copyDocumentFilesToDirAsync(chosenFolder.listFiles(), + this, + ownWorldmapFolder, + (success) -> completeLoadSaveActivity(SLOT_NUMBER_IMPORT_WORLDMAP, success)); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private void clickExportSaveGames() { + startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_EXPORT_SAVEGAMES); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private void clickImportSaveGames() { + startActivityForResult(AndroidStorage.getNewSelectMultipleSavegameFilesIntent(), -SLOT_NUMBER_IMPORT_SAVEGAMES); + + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private void clickImportWorldmap() { + startActivityForResult(AndroidStorage.getNewOpenDirectoryIntent(), -SLOT_NUMBER_IMPORT_WORLDMAP); + + } + + @RequiresApi(api = Build.VERSION_CODES.P) + private void showConfirmOverwriteByExportQuestion(ContentResolver resolver, DocumentFile targetFolder, DocumentFile[] files) { + final CustomDialog d = CustomDialogFactory.createDialog(this, + getString(R.string.loadsave_export_overwrite_confirmation_title), + getResources().getDrawable(android.R.drawable.ic_dialog_alert), + getString(R.string.loadsave_export_overwrite_confirmation), + null, + true); + + CustomDialogFactory.addButton(d, android.R.string.yes, v -> exportSaveGamesFolderContentToFolder(resolver, targetFolder, files)); + CustomDialogFactory.addDismissButton(d, android.R.string.no); + + CustomDialogFactory.show(d); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private void showConfirmOverwriteByImportQuestion(ContentResolver resolver, + DocumentFile appSavegameFolder, + List alreadyExistingFiles, + List newFiles) { + final String title = getString(R.string.loadsave_import_overwrite_confirmation_title); + String message = getString(R.string.loadsave_import_file_exists_question); + + StringBuilder sb = new StringBuilder(); + sb.append('\n'); + int amount = alreadyExistingFiles.size(); + + Context context = AndorsTrailApplication.getApplicationFromActivity(this).getApplicationContext(); + + ArrayList dialogs = new ArrayList(amount) ; + + for (int i = 0; i < amount ; i++) { + DocumentFile alreadyExistingFile = alreadyExistingFiles.get(i); + int slot = getSlotFromSavegameFileName(alreadyExistingFile.getName()); + FileHeader existingFileHeader = Savegames.quickload(context, slot); + FileHeader importedFileHeader = null; + try (InputStream stream = resolver.openInputStream(alreadyExistingFile.getUri()); + DataInputStream dataStream = new DataInputStream(stream)) { + importedFileHeader = new FileHeader(dataStream, true); + } catch (FileNotFoundException e) { + e.printStackTrace(); + continue; + } catch (IOException e) { + e.printStackTrace(); + continue; + } + + StringBuilder messageSb = new StringBuilder(); + String existingFileDescription = getString(R.string.loadsave_import_existing_description, slot, existingFileHeader.describe()); + String importedFileDescription = getString(R.string.loadsave_import_imported_description, slot, importedFileHeader.describe()); + messageSb.append(getString(R.string.loadsave_import_file_exists_question, existingFileDescription, importedFileDescription)); + + + String m = messageSb.toString(); + CustomDialog dialog = CustomDialogFactory.createDialog(this, + title, + getResources().getDrawable(android.R.drawable.ic_dialog_alert), + m, + null, + true, + false, + true); + + CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_keep_existing, v -> { + //do nothing + GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs); + }); + + CustomDialogFactory.addButton(dialog, R.string.loadsave_import_option_keep_imported, v -> { + newFiles.add(alreadyExistingFile); + GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs); + }); + + 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(alreadyExistingFile); + GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs); + }); + + CustomDialogFactory.addCancelButton(dialog, android.R.string.cancel); + CustomDialogFactory.setCancelListener(dialog, v -> { + completeLoadSaveActivity(SLOT_NUMBER_IMPORT_SAVEGAMES, false); + }); + + dialogs.add(dialog); + } + + GoToNextConflictOrFinish(resolver, appSavegameFolder, newFiles, dialogs); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + private void GoToNextConflictOrFinish(ContentResolver resolver, DocumentFile appSavegameFolder, List newFiles, ArrayList dialogs) { + if(dialogs.stream().count() > 0){ + CustomDialog d = dialogs.remove(0); + CustomDialogFactory.show(d); + } + else{ + importSaveGames(resolver, appSavegameFolder, newFiles); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode != Activity.RESULT_OK) + return; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + switch (-requestCode) { + case SLOT_NUMBER_EXPORT_SAVEGAMES: + exportSaveGames(data); + return; + case SLOT_NUMBER_IMPORT_SAVEGAMES: + importSaveGames(data); + return; + case SLOT_NUMBER_IMPORT_WORLDMAP: + importWorldmap(data); + return; + } + } + + } + + //endregion + + private void loadSaveGame(int slot) { + if (!Savegames.getSlotFile(slot, this).exists()) { + showErrorLoadingEmptySlot(); + } else { + final FileHeader header = Savegames.quickload(this, slot); + if (header != null && !header.hasUnlimitedSaves) { + showSlotGetsDeletedOnLoadWarning(slot); + } else { + completeLoadSaveActivity(slot); + } + } + } + + //region show Dialogs + + private void showErrorImportingWorldmapWrongDirectory() { + final CustomDialog d = CustomDialogFactory.createErrorDialog(this, + getString(R.string.loadsave_import_worldmap_unsuccessfull), + getString(R.string.loadsave_import_worldmap_wrong_directory)); + CustomDialogFactory.show(d); + } + + private void showErrorImportingSaveGameUnknown() { + final CustomDialog d = CustomDialogFactory.createErrorDialog(this, + getString(R.string.loadsave_import_save_unsuccessfull), + getString(R.string.loadsave_import_save_error_unknown)); + CustomDialogFactory.show(d); + } + + private void showErrorLoadingEmptySlot() { + final CustomDialog d = CustomDialogFactory.createErrorDialog(this, + getString(R.string.startscreen_error_loading_game), + getString(R.string.startscreen_error_loading_empty_slot)); + CustomDialogFactory.show(d); + } + + private void showSlotGetsDeletedOnLoadWarning(final int slot) { + final CustomDialog d = CustomDialogFactory.createDialog(this, + getString(R.string.startscreen_attention_slot_gets_delete_on_load), + getResources().getDrawable(android.R.drawable.ic_dialog_alert), + getString(R.string.startscreen_attention_message_slot_gets_delete_on_load), + null, + true); + CustomDialogFactory.addButton(d, android.R.string.ok, v -> completeLoadSaveActivity(slot)); + CustomDialogFactory.show(d); + } + + private void showConfirmoverwriteQuestion(final int slot, String message) { + final String title = + getString(R.string.loadsave_save_overwrite_confirmation_title) + ' ' + + getString(R.string.loadsave_save_overwrite_confirmation_slot, slot); + final CustomDialog d = CustomDialogFactory.createDialog(this, + title, + getResources().getDrawable(android.R.drawable.ic_dialog_alert), + message, + null, + true); + + CustomDialogFactory.addButton(d, android.R.string.yes, v -> completeLoadSaveActivity(slot)); + CustomDialogFactory.addDismissButton(d, android.R.string.no); + CustomDialogFactory.show(d); + } + + //endregion - CustomDialogFactory.addButton(d, android.R.string.yes, new View.OnClickListener() { - @Override - public void onClick(View v) { - loadsave(slot); - } - }); - CustomDialogFactory.addDismissButton(d, android.R.string.no); - CustomDialogFactory.show(d); - } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java index 9b5e3fbb8..a8d9bc232 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/LoadingActivity.java @@ -20,11 +20,12 @@ import com.gpl.rpg.AndorsTrail.savegames.Savegames; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.view.CloudsAnimatorView; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; public final class LoadingActivity extends AndorsTrailBaseActivity implements OnResourcesLoadedListener, OnSceneLoadedListener { private WorldSetup setup; - private Dialog progressDialog; + private CustomDialog progressDialog; private CloudsAnimatorView clouds_back, clouds_mid, clouds_front; boolean loaded = false; @@ -165,7 +166,7 @@ public final class LoadingActivity extends AndorsTrailBaseActivity implements On } private void showLoadingFailedDialog(int messageResourceID) { - final Dialog d = CustomDialogFactory.createDialog(this, getResources().getString(R.string.dialog_loading_failed_title), null, getResources().getString(messageResourceID), null, true); + final CustomDialog d = CustomDialogFactory.createDialog(this, getResources().getString(R.string.dialog_loading_failed_title), null, getResources().getString(messageResourceID), null, true); CustomDialogFactory.addDismissButton(d, android.R.string.ok); CustomDialogFactory.setDismissListener(d, new OnDismissListener() { @Override diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/MainActivity.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/MainActivity.java index e02869b75..17bb2c7dc 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/MainActivity.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/MainActivity.java @@ -43,6 +43,7 @@ import com.gpl.rpg.AndorsTrail.util.Coord; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.view.CombatView; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; import com.gpl.rpg.AndorsTrail.view.DisplayActiveActorConditionIcons; import com.gpl.rpg.AndorsTrail.view.ItemContainerAdapter; import com.gpl.rpg.AndorsTrail.view.MainView; @@ -247,7 +248,7 @@ public final class MainActivity final ItemContainerAdapter inventoryListAdapter = new QuickslotsItemContainerAdapter(lv.getContext(), world.tileManager, world.model.player.inventory.usableItems(), world.model.player, wornTiles); lv.setAdapter(inventoryListAdapter); - final Dialog d = CustomDialogFactory.createDialog(v.getContext(), + final CustomDialog d = CustomDialogFactory.createDialog(v.getContext(), v.getResources().getString(R.string.inventory_assign), v.getResources().getDrawable(R.drawable.ui_icon_equipment), v.getResources().getString(R.string.inventory_selectitem), view, false); diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/StartScreenActivity.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/StartScreenActivity.java index cfddf7b10..64804672a 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/StartScreenActivity.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/StartScreenActivity.java @@ -11,6 +11,7 @@ import com.gpl.rpg.AndorsTrail.resource.tiles.TileManager; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.view.CloudsAnimatorView; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; import android.app.Activity; import android.app.Dialog; @@ -114,7 +115,7 @@ public final class StartScreenActivity extends AndorsTrailBaseFragmentActivity i public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { - final Dialog d = CustomDialogFactory.createDialog(this, + final CustomDialog d = CustomDialogFactory.createDialog(this, getResources().getString(R.string.dialog_permission_information_title), getResources().getDrawable(android.R.drawable.ic_dialog_info), getResources().getString(R.string.dialog_permission_information), diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java index 92385a563..14bb8598e 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/activity/fragment/StartScreenActivity_MainMenu.java @@ -38,25 +38,26 @@ import com.gpl.rpg.AndorsTrail.util.AndroidStorage; import com.gpl.rpg.AndorsTrail.util.L; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; public class StartScreenActivity_MainMenu extends Fragment { - private static final int INTENTREQUEST_PREFERENCES = 7; - public static final int INTENTREQUEST_LOADGAME = 9; + private static final int INTENTREQUEST_PREFERENCES = 7; + public static final int INTENTREQUEST_LOADGAME = 9; - private boolean hasExistingGame = false; - private Button startscreen_continue; - private Button startscreen_newgame; - private Button startscreen_load; - private ViewGroup save_preview_holder; - private ImageView save_preview_hero_icon; - private TextView save_preview_hero_desc; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - updatePreferences(false); - super.onCreateView(inflater, container, savedInstanceState); + private boolean hasExistingGame = false; + private Button startscreen_continue; + private Button startscreen_newgame; + private Button startscreen_load; + private ViewGroup save_preview_holder; + private ImageView save_preview_hero_icon; + private TextView save_preview_hero_desc; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + updatePreferences(false); + super.onCreateView(inflater, container, savedInstanceState); if (container != null) { @@ -64,11 +65,11 @@ public class StartScreenActivity_MainMenu extends Fragment { } View root = inflater.inflate(R.layout.startscreen_mainmenu, container, false); - + save_preview_holder = (ViewGroup) root.findViewById(R.id.save_preview_holder); save_preview_hero_icon = (ImageView) root.findViewById(R.id.save_preview_hero_icon); save_preview_hero_desc = (TextView) root.findViewById(R.id.save_preview_hero_desc); - + startscreen_continue = (Button) root.findViewById(R.id.startscreen_continue); startscreen_continue.setOnClickListener(new OnClickListener() { @@ -114,7 +115,7 @@ public class StartScreenActivity_MainMenu extends Fragment { AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(getActivity()); if (hasExistingGame && app != null && app.getWorld() != null && app.getWorld().model != null && app.getWorld().model.statistics != null && !app.getWorld().model.statistics.hasUnlimitedSaves()) { - final Dialog d = CustomDialogFactory.createDialog(getActivity(), + final CustomDialog d = CustomDialogFactory.createDialog(getActivity(), getString(R.string.startscreen_load_game), getResources().getDrawable(android.R.drawable.ic_delete), getString(R.string.startscreen_load_game_confirm), @@ -134,7 +135,7 @@ public class StartScreenActivity_MainMenu extends Fragment { } } }); - + if (AndorsTrailApplication.DEVELOPMENT_FORCE_STARTNEWGAME) { if (AndorsTrailApplication.DEVELOPMENT_DEBUGRESOURCES) { @@ -152,14 +153,14 @@ public class StartScreenActivity_MainMenu extends Fragment { checkAndRequestPermissions(getActivity()); migrateDataOnDemand(getActivity()); } - + return root; } - + @Override public void onResume() { super.onResume(); - + String playerName; String displayInfo = null; int iconID = TileManager.CHAR_HERO; @@ -189,14 +190,10 @@ public class StartScreenActivity_MainMenu extends Fragment { setCurrentVersionForVersionCheck(); checkAndRequestPermissions(getActivity()); migrateDataOnDemand(getActivity()); - boolean hasSavegames = !Savegames.getUsedSavegameSlots(getActivity()).isEmpty(); - startscreen_load.setEnabled(hasSavegames); } }); } - boolean hasSavegames = !Savegames.getUsedSavegameSlots(getActivity()).isEmpty(); - startscreen_load.setEnabled(hasSavegames); } @TargetApi(29) @@ -204,23 +201,16 @@ public class StartScreenActivity_MainMenu extends Fragment { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (activity.getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { if (AndroidStorage.shouldMigrateToInternalStorage(activity.getApplicationContext())) { - final Dialog d = CustomDialogFactory.createDialog(activity, + final CustomDialog d = CustomDialogFactory.createDialog(activity, getString(R.string.startscreen_migration_title), activity.getResources().getDrawable(android.R.drawable.ic_dialog_alert), getString(R.string.startscreen_migration_text), null, true); CustomDialogFactory.addDismissButton(d, android.R.string.ok); - d.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface arg0) { - boolean hasSavegames = !Savegames.getUsedSavegameSlots(getActivity()).isEmpty(); - startscreen_load.setEnabled(hasSavegames); - } - }); CustomDialogFactory.show(d); if (!AndroidStorage.migrateToInternalStorage(activity.getApplicationContext())) { - final Dialog errorDlg = CustomDialogFactory.createDialog(activity, + final CustomDialog errorDlg = CustomDialogFactory.createDialog(activity, getString(R.string.startscreen_migration_title), activity.getResources().getDrawable(android.R.drawable.ic_dialog_alert), getString(R.string.startscreen_migration_failure), @@ -259,13 +249,13 @@ public class StartScreenActivity_MainMenu extends Fragment { super.onAttach(activity); listener = (OnNewGameRequestedListener) activity; } - + @Override public void onDetach() { super.onDetach(); listener = null; } - + private void setButtonState(final String playerName, final String displayInfo, int iconID, boolean isDead) { startscreen_continue.setEnabled(hasExistingGame && !isDead); startscreen_newgame.setEnabled(true); @@ -305,9 +295,9 @@ public class StartScreenActivity_MainMenu extends Fragment { // .create().show(); // // - final Dialog d = CustomDialogFactory.createDialog(getActivity(), - getString(R.string.startscreen_newgame), - getResources().getDrawable(android.R.drawable.ic_delete), + final CustomDialog d = CustomDialogFactory.createDialog(getActivity(), + getString(R.string.startscreen_newgame), + getResources().getDrawable(android.R.drawable.ic_delete), getResources().getString(R.string.startscreen_newgame_confirm), null, true); @@ -318,9 +308,9 @@ public class StartScreenActivity_MainMenu extends Fragment { } }); CustomDialogFactory.addDismissButton(d, android.R.string.cancel); - + CustomDialogFactory.show(d); - + } private static final String versionCheck = "lastversion"; @@ -337,64 +327,90 @@ public class StartScreenActivity_MainMenu extends Fragment { e.putInt(versionCheck, AndorsTrailApplication.CURRENT_VERSION); e.commit(); } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case INTENTREQUEST_LOADGAME: - if (resultCode != Activity.RESULT_OK) break; - final int slot = data.getIntExtra("slot", 1); - continueGame(false, slot, null); - break; - case INTENTREQUEST_PREFERENCES: - updatePreferences(true); - break; + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case INTENTREQUEST_LOADGAME: + boolean unsuccessful = resultCode != Activity.RESULT_OK; + if(data == null) break; + + final boolean wasImportOrExport = data.getBooleanExtra("import_export", false); + if (wasImportOrExport) { + String message = getImportExportMessage(!unsuccessful, data); + Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show(); + break; + } + if (unsuccessful) break; + final int slot = data.getIntExtra("slot", 1); + continueGame(false, slot, null); + break; + case INTENTREQUEST_PREFERENCES: + updatePreferences(true); + break; + } + } + + private String getImportExportMessage(boolean successful, Intent data) { + String message = ""; + boolean isImportWorldmap = data.getBooleanExtra("import_worldmap", false); + boolean isImportSaves = data.getBooleanExtra("import_savegames", false); + boolean isExport = data.getBooleanExtra("export", false); + + if(isImportWorldmap) { + message = getString(successful ? R.string.loadsave_import_worldmap_successfull : R.string.loadsave_import_worldmap_unsuccessfull); + } else if(isImportSaves) { + message = getString(successful ? R.string.loadsave_import_save_successfull : R.string.loadsave_import_save_unsuccessfull); + } else if(isExport) { + message = getString(successful ? R.string.loadsave_export_successfull : R.string.loadsave_export_unsuccessfull); } + + return message; } - + private void updatePreferences(boolean alreadyStartedLoadingResources) { - AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(getActivity()); - AndorsTrailPreferences preferences = app.getPreferences(); - preferences.read(getActivity()); - if (app.setLocale(getActivity())) { - if (alreadyStartedLoadingResources) { - // Changing the locale after having loaded the game requires resources to - // be re-loaded. Therefore, we just exit here. - Toast.makeText(getActivity(), R.string.change_locale_requires_restart, Toast.LENGTH_LONG).show(); - doFinish(); - return; - } - } - if (ThemeHelper.changeTheme(preferences.selectedTheme)) { - // Changing the theme requires a restart to re-create all activities. - Toast.makeText(getActivity(), R.string.change_theme_requires_restart, Toast.LENGTH_LONG).show(); - doFinish(); - return; - } - app.getWorld().tileManager.updatePreferences(preferences); - } - - @SuppressLint("NewApi") - private void doFinish() { - //For Lollipop and above - ((AndorsTrailApplication)getActivity().getApplication()).discardWorld(); - getActivity().finish(); - } + AndorsTrailApplication app = AndorsTrailApplication.getApplicationFromActivity(getActivity()); + AndorsTrailPreferences preferences = app.getPreferences(); + preferences.read(getActivity()); + if (app.setLocale(getActivity())) { + if (alreadyStartedLoadingResources) { + // Changing the locale after having loaded the game requires resources to + // be re-loaded. Therefore, we just exit here. + Toast.makeText(getActivity(), R.string.change_locale_requires_restart, Toast.LENGTH_LONG).show(); + doFinish(); + return; + } + } + if (ThemeHelper.changeTheme(preferences.selectedTheme)) { + // Changing the theme requires a restart to re-create all activities. + Toast.makeText(getActivity(), R.string.change_theme_requires_restart, Toast.LENGTH_LONG).show(); + doFinish(); + return; + } + app.getWorld().tileManager.updatePreferences(preferences); + } + + @SuppressLint("NewApi") + private void doFinish() { + //For Lollipop and above + ((AndorsTrailApplication) getActivity().getApplication()).discardWorld(); + getActivity().finish(); + } + - public interface OnNewGameRequestedListener { public void onNewGameRequested(); } - + private OnNewGameRequestedListener listener = null; - + private void createNewGame() { if (listener != null) { listener.onNewGameRequested(); } } - + } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/controller/Constants.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/controller/Constants.java index 01e554c20..ca4767bbd 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/controller/Constants.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/controller/Constants.java @@ -51,6 +51,10 @@ public final class Constants { public static final String CHEAT_DETECTION_FOLDER = "dEAGyGE3YojqXjI3x4x7"; public static final String PASSIVE_ACHIEVEMENT_CHECK_PHRASE = "passive_achievement_check"; + public static final String SAVEGAME_FILE_MIME_TYPE = "application/octet-stream"; + public static final String WORLDMAP_FILE_MIME_TYPE = "image/png"; + public static final String NO_FILE_EXTENSION_MIME_TYPE = "application/no_file_extension_mime_type"; + public static final Random rnd = new Random(); public static int rollValue(final ConstRange r) { return rollValue(r.max, r.current); } public static int rollValue(final ConstRange r, int bias) { return rollValue((r.max + 1) * 100 -1, r.current * 100 + bias)/100; } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java index ade6f155f..caf3567cd 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/savegames/Savegames.java @@ -86,7 +86,7 @@ public final class Savegames { private static void writeBackup(Context androidContext, byte[] savegame, String playerId) throws IOException { File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); - if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir(); + ensureDirExists(cheatDetectionFolder); File backupFile = new File(cheatDetectionFolder, playerId + "X"); FileOutputStream fileOutputStream = new FileOutputStream(backupFile); fileOutputStream.write(savegame); @@ -127,34 +127,33 @@ public final class Savegames { } } - private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException { - long savedVersionToCheck = 0; - File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); - if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir(); - File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId); - if (cheatDetectionFile.exists()) { - FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile); - DataInputStream dataInputStream = new DataInputStream(fileInputStream); - final CheatDetection cheatDetection = new CheatDetection(dataInputStream); - savedVersionToCheck = cheatDetection.savedVersion; - dataInputStream.close(); - fileInputStream.close(); - } + private static boolean triedToCheat(Context androidContext, FileHeader fh) throws IOException { + long savedVersionToCheck = 0; + File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); + ensureDirExists(cheatDetectionFolder); + File cheatDetectionFile = new File(cheatDetectionFolder, fh.playerId); + if (cheatDetectionFile.exists()) { + FileInputStream fileInputStream = new FileInputStream(cheatDetectionFile); + DataInputStream dataInputStream = new DataInputStream(fileInputStream); + final CheatDetection cheatDetection = new CheatDetection(dataInputStream); + savedVersionToCheck = cheatDetection.savedVersion; + dataInputStream.close(); + fileInputStream.close(); + } if (savedVersionToCheck == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) { return true; } - if (androidContext.getFileStreamPath(fh.playerId).exists()) { - FileInputStream fileInputStream = androidContext.openFileInput(fh.playerId); - DataInputStream dataInputStream = new DataInputStream(fileInputStream); - final CheatDetection cheatDetection = new CheatDetection(dataInputStream); - if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) { - savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED; - } - else if (cheatDetection.savedVersion > savedVersionToCheck) { - savedVersionToCheck = cheatDetection.savedVersion; - } + if (androidContext.getFileStreamPath(fh.playerId).exists()) { + FileInputStream fileInputStream = androidContext.openFileInput(fh.playerId); + DataInputStream dataInputStream = new DataInputStream(fileInputStream); + final CheatDetection cheatDetection = new CheatDetection(dataInputStream); + if (cheatDetection.savedVersion == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED) { + savedVersionToCheck = DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED; + } else if (cheatDetection.savedVersion > savedVersionToCheck) { + savedVersionToCheck = cheatDetection.savedVersion; + } if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) { L.log("Internal cheatcheck file savedVersion: " + cheatDetection.savedVersion); @@ -167,31 +166,48 @@ public final class Savegames { return (savedVersionToCheck == DENY_LOADING_BECAUSE_GAME_IS_CURRENTLY_PLAYED || fh.savedVersion < savedVersionToCheck); } - private static FileOutputStream getOutputFile(Context androidContext, int slot) throws IOException { - if (slot == SLOT_QUICKSAVE) { - return androidContext.openFileOutput(Constants.FILENAME_SAVEGAME_QUICKSAVE, Context.MODE_PRIVATE); - } else { - ensureSavegameDirectoryExists(androidContext); - return new FileOutputStream(getSlotFile(slot, androidContext)); - } - } - private static void ensureSavegameDirectoryExists(Context context) { - File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); - if (!dir.exists()) dir.mkdir(); - } - private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException { - if (slot == SLOT_QUICKSAVE) { - return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE); - } else { - return new FileInputStream(getSlotFile(slot, androidContext)); - } - } + private static FileOutputStream getOutputFile(Context androidContext, int slot) throws IOException { + if (slot == SLOT_QUICKSAVE) { + return androidContext.openFileOutput(Constants.FILENAME_SAVEGAME_QUICKSAVE, Context.MODE_PRIVATE); + } else { + ensureSavegameDirectoryExists(androidContext); + return new FileOutputStream(getSlotFile(slot, androidContext)); + } + } - public static File getSlotFile(int slot, Context context) { - File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); - return new File(root, Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot); - } + private static void ensureSavegameDirectoryExists(Context context) { + File dir = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); + ensureDirExists(dir); + } + public static boolean ensureDirExists(File dir) { + if (!dir.exists()) { + boolean worked = dir.mkdir(); + return worked; + } + return true; + } + + private static FileInputStream getInputFile(Context androidContext, int slot) throws IOException { + if (slot == SLOT_QUICKSAVE) { + return androidContext.openFileInput(Constants.FILENAME_SAVEGAME_QUICKSAVE); + } else { + return new FileInputStream(getSlotFile(slot, androidContext)); + } + } + + public static File getSlotFile(int slot, Context context) { + File root = AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY); + return getSlotFile(slot, root); + } + + public static File getSlotFile(int slot, File directory) { + return new File(directory, getSlotFileName(slot)); + } + + public static String getSlotFileName(int slot) { + return Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + slot; + } public static void saveWorld(WorldContext world, OutputStream outStream, String displayInfo) throws IOException { @@ -207,10 +223,11 @@ public final class Savegames { dest.close(); } - public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, InputStream inState, FileHeader fh) throws IOException { - DataInputStream src = new DataInputStream(inState); - final FileHeader header = new FileHeader(src, fh.skipIcon); - if (header.fileversion > AndorsTrailApplication.CURRENT_VERSION) return LoadSavegameResult.savegameIsFromAFutureVersion; + public static LoadSavegameResult loadWorld(Resources res, WorldContext world, ControllerContext controllers, InputStream inState, FileHeader fh) throws IOException { + DataInputStream src = new DataInputStream(inState); + final FileHeader header = new FileHeader(src, fh.skipIcon); + if (header.fileversion > AndorsTrailApplication.CURRENT_VERSION) + return LoadSavegameResult.savegameIsFromAFutureVersion; world.maps.readFromParcel(src, world, controllers, header.fileversion); world.model = new ModelContainer(src, world, controllers, header.fileversion); @@ -249,15 +266,15 @@ public final class Savegames { } } - private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException { - File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); - if (!cheatDetectionFolder.exists()) cheatDetectionFolder.mkdir(); - File cheatDetectionFile = new File(cheatDetectionFolder, playerId); - FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile); - DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); - CheatDetection.writeToParcel(dataOutputStream, savedVersion); - dataOutputStream.close(); - fileOutputStream.close(); + private static void writeCheatCheck(Context androidContext, long savedVersion, String playerId) throws IOException { + File cheatDetectionFolder = AndroidStorage.getStorageDirectory(androidContext, Constants.CHEAT_DETECTION_FOLDER); + ensureDirExists(cheatDetectionFolder); + File cheatDetectionFile = new File(cheatDetectionFolder, playerId); + FileOutputStream fileOutputStream = new FileOutputStream(cheatDetectionFile); + DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); + CheatDetection.writeToParcel(dataOutputStream, savedVersion); + dataOutputStream.close(); + fileOutputStream.close(); fileOutputStream = androidContext.openFileOutput(playerId, Context.MODE_PRIVATE); dataOutputStream = new DataOutputStream(fileOutputStream); @@ -268,26 +285,26 @@ public final class Savegames { private static final Pattern savegameFilenamePattern = Pattern.compile(Constants.FILENAME_SAVEGAME_FILENAME_PREFIX + "(\\d+)"); - public static List getUsedSavegameSlots(Context context) { - try { - final List result = new ArrayList(); - AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY).listFiles(new FilenameFilter() { - @Override - public boolean accept(File f, String filename) { - Matcher m = savegameFilenamePattern.matcher(filename); - if (m != null && m.matches()) { - result.add(Integer.parseInt(m.group(1))); - return true; - } - return false; - } - }); - Collections.sort(result); - return result; - } catch (Exception e) { - return null; - } - } + public static List getUsedSavegameSlots(Context context) { + try { + final List result = new ArrayList(); + AndroidStorage.getStorageDirectory(context, Constants.FILENAME_SAVEGAME_DIRECTORY).listFiles(new FilenameFilter() { + @Override + public boolean accept(File f, String filename) { + Matcher m = savegameFilenamePattern.matcher(filename); + if (m != null && m.matches()) { + result.add(Integer.parseInt(m.group(1))); + return true; + } + return false; + } + }); + Collections.sort(result); + return result; + } catch (Exception e) { + return new ArrayList(); + } + } private static final class CheatDetection { public final int fileversion; @@ -307,17 +324,16 @@ public final class Savegames { } - - public static final class FileHeader { - public final int fileversion; - public final String playerName; - public final String displayInfo; - public final int iconID; - public boolean skipIcon = false; - public final boolean isDead; - public final boolean hasUnlimitedSaves; - public final String playerId; - public final long savedVersion; + public static final class FileHeader { + public final int fileversion; + public final String playerName; + public final String displayInfo; + public final int iconID; + public boolean skipIcon = false; + public final boolean isDead; + public final boolean hasUnlimitedSaves; + public final String playerId; + public final long savedVersion; public String describe() { return (fileversion == AndorsTrailApplication.DEVELOPMENT_INCOMPATIBLE_SAVEGAME_VERSION ? "(D) " : "") + playerName + ", " + displayInfo; @@ -326,17 +342,18 @@ public final class Savegames { // ====== PARCELABLE =================================================================== - public FileHeader(DataInputStream src, boolean skipIcon) throws IOException { - int fileversion = src.readInt(); - if (fileversion == 11) fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11. - this.fileversion = fileversion; - if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header. - this.playerName = src.readUTF(); - this.displayInfo = src.readUTF(); - } else { - this.playerName = null; - this.displayInfo = null; - } + public FileHeader(DataInputStream src, boolean skipIcon) throws IOException { + int fileversion = src.readInt(); + if (fileversion == 11) + fileversion = 5; // Fileversion 5 had no version identifier, but the first byte was 11. + this.fileversion = fileversion; + if (fileversion >= 14) { // Before fileversion 14 (0.6.7), we had no file header. + this.playerName = src.readUTF(); + this.displayInfo = src.readUTF(); + } else { + this.playerName = null; + this.displayInfo = null; + } if (fileversion >= 43) { int id = src.readInt(); diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/AndroidStorage.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/AndroidStorage.java index 010ae2051..a64a3d5ad 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/AndroidStorage.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/AndroidStorage.java @@ -1,12 +1,24 @@ package com.gpl.rpg.AndorsTrail.util; +import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; import android.support.v4.content.FileProvider; +import android.support.v4.provider.DocumentFile; +import android.os.Handler; +import android.os.Looper; +import android.webkit.MimeTypeMap; + +import com.gpl.rpg.AndorsTrail.R; import com.gpl.rpg.AndorsTrail.controller.Constants; +import com.gpl.rpg.AndorsTrail.util.BackgroundWorker.BackgroundWorkerCallback; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory; import java.io.File; import java.io.FileInputStream; @@ -14,10 +26,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.concurrent.CancellationException; +import java.util.function.Consumer; public final class AndroidStorage { public static File getStorageDirectory(Context context, String name) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return context.getExternalFilesDir(name); } else { @@ -25,6 +39,7 @@ public final class AndroidStorage { return new File(root, name); } } + public static boolean shouldMigrateToInternalStorage(Context context) { boolean ret = false; File externalSaveGameDirectory = new File(Environment.getExternalStorageDirectory(), Constants.FILENAME_SAVEGAME_DIRECTORY); @@ -76,35 +91,236 @@ public final class AndroidStorage { } } - private static void copyFile(File source, File target) throws IOException { - InputStream in = null; - OutputStream out = null; - try { - in = new FileInputStream(source); - out = new FileOutputStream(target); - byte[] buf = new byte[1024]; - int length; - while ((length = in.read(buf)) > 0) { - out.write(buf, 0, length); - } - } finally { - if (in != null) { - in.close(); - } - if (out != null) { - out.close(); - } + public static void copyFile(File source, File target) throws IOException { + try (InputStream in = new FileInputStream(source); OutputStream out = new FileOutputStream(target)) { + copyStream(in, out); } } + public static void copyStream(InputStream in, OutputStream out) throws IOException { + byte[] buf = new byte[1024]; + int length; + while ((length = in.read(buf)) > 0) { + out.write(buf, 0, length); + } + } + + + 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 { + String fileName = sourceFile.getName(); + DocumentFile file = targetFolder.findFile(fileName); + if (file == null) + file = targetFolder.createFile(mimeType, fileName); + if (file == null) + return; + + AndroidStorage.copyDocumentFile(sourceFile, resolver, file); + } + + public static void copyDocumentFile(DocumentFile sourceFile, ContentResolver resolver, DocumentFile targetFile) throws IOException { + try (OutputStream outputStream = resolver.openOutputStream(targetFile.getUri()); + InputStream inputStream = resolver.openInputStream(sourceFile.getUri())) { + copyStream(inputStream, outputStream); + } + } + + + /** + * Gets the MIME-Type for a file.

+ * Fallback value is '* / *' (without spaces)

+ * Mostly copied together from: StackOverflow + */ + @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) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { String applicationId = context.getPackageName(); -// Uri uri = FileProvider.getUriForFile(context, "com.gpl.rpg.AndorsTrail.fileprovider", worldmap); Uri uri = FileProvider.getUriForFile(context, applicationId + ".fileprovider", worldmap); return uri.toString(); } else { return "file://" + worldmap.getAbsolutePath(); } } + + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static Intent getNewOpenDirectoryIntent() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + return intent; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static Intent getNewSelectMultipleSavegameFilesIntent() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + intent.setType(Constants.SAVEGAME_FILE_MIME_TYPE); + return intent; + } + + public static void copyDocumentFilesFromToAsync(DocumentFile[] sources, Context context, DocumentFile[] targets, Consumer callback) { + if(sources.length != targets.length) + { + throw new IllegalArgumentException("Both arrays, target & source have to have the same size"); + } + + BackgroundWorker worker = new BackgroundWorker(); + CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context); + progressDialog.setOnCancelListener(dialog -> worker.cancel()); + ContentResolver resolver = context.getContentResolver(); + Handler handler = Handler.createAsync(Looper.getMainLooper()); + + worker.setTask(new BackgroundWorker.worker() { + @Override + public void doWork(BackgroundWorkerCallback callback) { + try { + 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()) { + callback.onFailure(new CancellationException("Cancelled")); + return; + } + } catch (Exception e) { + callback.onFailure(e); + } + } + }); + worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback)); + worker.run(); + } + + @RequiresApi(api = Build.VERSION_CODES.P) + public static void copyDocumentFilesToDirAsync(DocumentFile[] files, + Context context, + DocumentFile targetDirectory, + Consumer callback) { + BackgroundWorker worker = new BackgroundWorker<>(); + CustomDialogFactory.CustomDialog progressDialog = getLoadingDialog(context); + progressDialog.setOnCancelListener(dialog -> worker.cancel()); + ContentResolver resolver = context.getContentResolver(); + Handler handler = Handler.createAsync(Looper.getMainLooper()); + + worker.setTask(workerCallback -> { + try { + workerCallback.onInitialize(); + for (int i = 0; i < files.length; i++) { + if (worker.isCancelled()) { + workerCallback.onFailure(new CancellationException("Cancelled")); + return; + } + DocumentFile file = files[i]; + if(file == null) + continue; + + copyDocumentFileToNewOrExistingFile(file, resolver, targetDirectory); + float progress = i /(float) files.length; + workerCallback.onProgress(progress); + } + workerCallback.onComplete(true); + } catch (NullPointerException e) { + if (worker.isCancelled()) { + workerCallback.onFailure(new CancellationException("Cancelled")); + } + } catch (Exception e) { + workerCallback.onFailure(e); + } + }); + worker.setCallback(getDefaultBackgroundWorkerCallback(handler, progressDialog, callback)); + worker.run(); + } + + private static BackgroundWorkerCallback getDefaultBackgroundWorkerCallback(Handler handler, + CustomDialogFactory.CustomDialog progressDialog, + Consumer callback) { + return new BackgroundWorkerCallback() { + private int progress = -1; + @Override + public void onInitialize() { + handler.post(() -> { + CustomDialogFactory.show(progressDialog); + }); + } + + @Override + public void onProgress(float progress) { + handler.post(() -> { + int intProgress = (int) (progress * 100); + if(this.progress == intProgress) + return; + + this.progress = intProgress; + CustomDialogFactory.setDesc(progressDialog, intProgress + "%"); + }); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void onFailure(Exception e) { + handler.post(() -> { + progressDialog.dismiss(); + callback.accept(false); + }); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @Override + public void onComplete(Boolean result) { + handler.post(() -> { + progressDialog.dismiss(); + callback.accept(true); + }); + } + }; + } + + private static CustomDialogFactory.CustomDialog getLoadingDialog(Context context) { + return CustomDialogFactory.createDialog(context, + context.getResources().getString(R.string.dialog_loading_message), + context.getResources().getDrawable(R.drawable.loading_anim), + null, + null, + false, + false); + } + } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/BackgroundWorker.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/BackgroundWorker.java new file mode 100644 index 000000000..79e3343cd --- /dev/null +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/util/BackgroundWorker.java @@ -0,0 +1,46 @@ +package com.gpl.rpg.AndorsTrail.util; + +import java.util.concurrent.Executors; + +public final class BackgroundWorker { + boolean cancelled = false; + worker task; + BackgroundWorkerCallback callback; + + public void setTask(worker task) { + this.task = task; + } + + public void setCallback(BackgroundWorkerCallback callback) { + this.callback = callback; + } + + public void cancel() { + cancelled = true; + } + + interface worker { + void doWork(BackgroundWorkerCallback callback); + } + + interface BackgroundWorkerCallback { + void onInitialize(); + + default void onProgress(float progress) { + } + + void onFailure(Exception e); + + void onComplete(T result); + } + + public void run() { + Executors.newSingleThreadExecutor().execute(() -> { + task.doWork(callback); + }); + } + + public boolean isCancelled() { + return cancelled; + } +} diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/CustomDialogFactory.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/CustomDialogFactory.java index d554f2885..fb1de0977 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/CustomDialogFactory.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/CustomDialogFactory.java @@ -1,8 +1,8 @@ package com.gpl.rpg.AndorsTrail.view; -import android.app.Dialog; import android.content.Context; import android.content.DialogInterface.OnDismissListener; +import android.content.DialogInterface.OnCancelListener; import android.graphics.Rect; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; @@ -22,25 +22,33 @@ import com.gpl.rpg.AndorsTrail.R; import com.gpl.rpg.AndorsTrail.util.ThemeHelper; public class CustomDialogFactory { - - public static class CustomDialog extends Dialog { + + public static class CustomDialog extends android.app.Dialog { public CustomDialog(Context context) { super(context); } - + boolean verticalButtons = false; } - - public static CustomDialog createDialog(final Context context, String title, Drawable icon, String desc, View content, boolean hasButtons) { + + public static CustomDialog createDialog(final Context context, String title, Drawable icon, + String desc, View content, boolean hasButtons, boolean canDismiss) { + return createDialog(context, title, icon, desc, content, hasButtons, canDismiss, false); + } + + public static CustomDialog createDialog(final Context context, String title, Drawable icon, + String desc, View content, boolean hasButtons) { return createDialog(context, title, icon, desc, content, hasButtons, true); } - - public static CustomDialog createDialog(final Context context, String title, Drawable icon, String desc, View content, boolean hasButtons, final boolean canDismiss) { + + public static CustomDialog createDialog(final Context context, String title, Drawable icon, + String desc, View content, boolean hasButtons, + final boolean canDismiss, final boolean verticalButtons) { final CustomDialog dialog = new CustomDialog(new ContextThemeWrapper(context, ThemeHelper.getDialogTheme())) { @Override public boolean onTouchEvent(MotionEvent event) { Rect r = new Rect(); this.getWindow().getDecorView().findViewById(R.id.dialog_hitrect).getHitRect(r); - + if (r.contains((int)event.getX(), (int)event.getY())) { return super.onTouchEvent(event); } else { @@ -51,7 +59,7 @@ public class CustomDialogFactory { return false; } } - + @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); @@ -63,7 +71,8 @@ public class CustomDialogFactory { } } }; - + dialog.verticalButtons = verticalButtons; + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setContentView(R.layout.custom_dialog_title_icon); dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); @@ -72,23 +81,37 @@ public class CustomDialogFactory { } else { dialog.getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN); } - + setTitle(dialog, title, icon); - + setDesc(dialog, desc); - + setContent(dialog, content); - - ViewGroup buttonsHolder = (ViewGroup) dialog.findViewById(R.id.dialog_button_container); + + ViewGroup buttonsHolder = getButtonContainer(dialog); + ViewGroup unusedButtonsHolder = getUnusedButtonContainer(dialog); + + unusedButtonsHolder.setVisibility(View.GONE); if (hasButtons) { buttonsHolder.setVisibility(View.VISIBLE); } else { buttonsHolder.setVisibility(View.GONE); } - + return dialog; } - + + public static CustomDialog createErrorDialog(final Context context, String title, String description) { + final CustomDialog d = createDialog(context, + title, + context.getResources().getDrawable(android.R.drawable.ic_dialog_alert), + description, + null, + true); + CustomDialogFactory.addDismissButton(d, android.R.string.ok); + return d; + } + public static CustomDialog setTitle(final CustomDialog dialog, String title, Drawable icon) { TextView titleView = (TextView) dialog.findViewById(R.id.dialog_title); if (title != null || icon != null) { @@ -100,7 +123,7 @@ public class CustomDialogFactory { } return dialog; } - + public static CustomDialog setDesc(final CustomDialog dialog, String desc) { TextView descView = (TextView) dialog.findViewById(R.id.dialog_description); ViewGroup descHolder = (ViewGroup) dialog.findViewById(R.id.dialog_description_container); @@ -113,7 +136,7 @@ public class CustomDialogFactory { } return dialog; } - + public static CustomDialog setContent(final CustomDialog dialog, View content) { ViewGroup contentHolder = (ViewGroup) dialog.findViewById(R.id.dialog_content_container); if (content != null) { @@ -124,54 +147,101 @@ public class CustomDialogFactory { } return dialog; } - - public static Dialog addButton(final Dialog dialog, int textId, final OnClickListener listener) { - - Button template = (Button) dialog.findViewById(R.id.dialog_template_button); + + public static CustomDialog addButton(final CustomDialog dialog, String text, final OnClickListener listener) { + return addButton(dialog, -1, text, listener); + } + public static CustomDialog addButton(final CustomDialog dialog, int textId, final OnClickListener listener) { + return addButton(dialog, textId, null, listener); + } + public static CustomDialog addButton(final CustomDialog dialog, int textId, String text, final OnClickListener listener) { + Button template = getButtonTemplate(dialog); LayoutParams params = template.getLayoutParams(); - ViewGroup buttonsHolder = (ViewGroup) dialog.findViewById(R.id.dialog_button_container); - + ViewGroup buttonsHolder = getButtonContainer(dialog); + Button b = new Button(dialog.getContext()); b.setLayoutParams(params); //Old android versions need this "reminder" b.setBackgroundDrawable(ThemeHelper.getThemeDrawable(dialog.getContext(), R.attr.ui_theme_textbutton_drawable)); b.setTextColor(ThemeHelper.getThemeColor(dialog.getContext(), R.attr.ui_theme_dialogue_light_color)); - - b.setText(textId); + + if(textId != -1) { + b.setText(textId); + } else { + b.setText(text); + } + b.setOnClickListener(new OnClickListener() { - + @Override public void onClick(View v) { listener.onClick(v); dialog.dismiss(); } }); - + buttonsHolder.addView(b, params); return dialog; } - - public static Dialog addDismissButton(final Dialog dialog, int textId) { + + public static CustomDialog addDismissButton(final CustomDialog dialog, int textId) { return CustomDialogFactory.addButton(dialog, textId, new OnClickListener() { - + @Override public void onClick(View v) { dialog.dismiss(); } }); } - - public static Dialog setDismissListener(Dialog dialog, OnDismissListener listener) { + public static CustomDialog addCancelButton(final CustomDialog dialog, int textId) { + return CustomDialogFactory.addButton(dialog, textId, new OnClickListener() { + + @Override + public void onClick(View v) { + dialog.cancel(); + } + }); + } + + public static CustomDialog setDismissListener(CustomDialog dialog, OnDismissListener listener) { dialog.setOnDismissListener(listener); - + return dialog; } - - public static void show(Dialog dialog) { - + + public static CustomDialog setCancelListener(CustomDialog dialog, OnCancelListener listener) { + dialog.setOnCancelListener(listener); + return dialog; + } + + public static void show(CustomDialog dialog) { + dialog.findViewById(R.id.dialog_template_button).setVisibility(View.GONE); + dialog.findViewById(R.id.dialog_template_button_vertical).setVisibility(View.GONE); dialog.show(); - + + } + + + private static ViewGroup getUnusedButtonContainer(CustomDialog dialog) { + if (dialog.verticalButtons) + return (ViewGroup) dialog.findViewById(R.id.dialog_button_container); + else + return (ViewGroup) dialog.findViewById(R.id.dialog_button_container_vertical); + } + + private static ViewGroup getButtonContainer(CustomDialog dialog) { + if (dialog.verticalButtons) + return (ViewGroup) dialog.findViewById(R.id.dialog_button_container_vertical); + else + return (ViewGroup) dialog.findViewById(R.id.dialog_button_container); + } + + private static Button getButtonTemplate(CustomDialog dialog) { + if (dialog.verticalButtons) + return (Button) dialog.findViewById(R.id.dialog_template_button_vertical); + else + return (Button) dialog.findViewById(R.id.dialog_template_button); } } diff --git a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/SpinnerEmulator.java b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/SpinnerEmulator.java index d3cec5082..84ae1e6ae 100644 --- a/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/SpinnerEmulator.java +++ b/AndorsTrail/app/src/main/java/com/gpl/rpg/AndorsTrail/view/SpinnerEmulator.java @@ -9,13 +9,15 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; +import com.gpl.rpg.AndorsTrail.view.CustomDialogFactory.CustomDialog; + /** * Simply instantiate this class, implement abstract methods in an anonymous type, and tada, your Button is a Spinner! */ public abstract class SpinnerEmulator { private Button spinnerButton; - private Dialog spinnerDialog = null; + private CustomDialog spinnerDialog = null; private ListView choicesList; private Context context; diff --git a/AndorsTrail/res/layout/custom_dialog_title_icon.xml b/AndorsTrail/res/layout/custom_dialog_title_icon.xml index 886758ef8..7a686e500 100644 --- a/AndorsTrail/res/layout/custom_dialog_title_icon.xml +++ b/AndorsTrail/res/layout/custom_dialog_title_icon.xml @@ -71,6 +71,29 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> + + + + + +