diff --git a/app/build.gradle b/app/build.gradle index 49f9537..c161885 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'com.google.android.material:material:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'androidx.navigation:navigation-fragment:2.1.0' implementation 'androidx.navigation:navigation-ui:2.1.0' diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java index bc8b77a..8d16654 100644 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/backend/Backend.java @@ -33,11 +33,17 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; public class Backend { private static final String TAG = "Backend"; + static private HashMap storage = new HashMap<>(); static private Backend instance; private TACNET tacnet; private Server server; @@ -46,6 +52,10 @@ public class Backend { private Settings settings; private boolean configChanged, settingsChanged; + public static HashMap getStorage() { + return storage; + } + public Backend() { if (Backend.instance() != null) { throw(new RuntimeException("Backend instance already exists!")); @@ -114,10 +124,14 @@ public class Backend { saveConfig(); } - public boolean isConfigChanged() { return configChanged; } + public boolean hasConfigChanged() { return configChanged; } + + public String configPath(String name) { + return TAC.CONFIGS_PATH + File.separator + name + ".json"; + } public void loadConfig(String name) { - String path = "" + TAC.CONFIGS_PATH + File.separator + name + ".json"; + String path = configPath(name); File file = new File(path); if (file.exists() && file.isFile()) { @@ -129,12 +143,38 @@ public class Backend { public boolean saveConfig() { if (config == null) { return false; } - final String path = "" + TAC.CONFIGS_PATH + File.separator + getConfig().getName() + ".json"; + final String path = configPath(getConfig().getName()); configChanged = false; return writeToFile(path, gsonForConfig().toJson(config)); } + public boolean moveConfig(String oldName, String newName) { + final String oldPath = configPath(oldName); + final String newPath = configPath(newName); + + final File oldFile = new File(oldPath); + final File newFile = new File(newPath); + + if (!oldFile.exists() || !oldFile.isFile()) { + Log.e(TAG, "moveConfig: Can not move config file \"" + oldPath + "\" does not exists!"); + return false; + } + + if (newFile.exists() && newFile.isFile()) { + Log.e(TAG, "moveConfig: Config file \"" + newPath + "\" already exists!"); + return false; + } + + return oldFile.renameTo(newFile); + } + + public boolean deleteConfig(String name) { + File file = new File(configPath(name)); + + return file.delete(); + } + public void uploadConfig() { if (config != null && tacnet.isConnected()) { String json = ""; @@ -149,7 +189,7 @@ public class Backend { } public void writeNewConfig(String name) { - String path = TAC.CONFIGS_PATH + File.separator + name + ".json"; + String path = configPath(name); File file = new File(path); Config config = new Config(name); diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfigurationDialog.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfigurationDialog.java index 8da833a..dcee2c4 100644 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfigurationDialog.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfigurationDialog.java @@ -18,6 +18,7 @@ import java.util.regex.Pattern; public class ConfigurationDialog extends TimeCraftersDialog { private static final String TAG = "ConfigurationDialog"; + private String configName; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -31,7 +32,14 @@ public class ConfigurationDialog extends TimeCraftersDialog { final Button cancel = view.findViewById(R.id.cancel); final Button mutate = view.findViewById(R.id.mutate); - title.setText("Create Configuration"); + if (getArguments() != null) { + configName = getArguments().getString("config_name"); + title.setText("Editing " + configName); + name.setText(configName); + mutate.setText(getResources().getString(R.string.dialog_update)); + } else { + title.setText("Create Configuration"); + } cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -39,16 +47,20 @@ public class ConfigurationDialog extends TimeCraftersDialog { } }); -// mutate.setText(getResources().getString(R.string.dialog_update)); mutate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - String configName = name.getText().toString(); + final String newConfigName = name.getText().toString(); - if (isValid(configName)) { - Backend.instance().writeNewConfig(configName); + if (isValid(newConfigName)) { + if (configName != null) { + Backend.instance().moveConfig(configName, newConfigName); + } else { + Backend.instance().writeNewConfig(newConfigName); + } dismiss(); } else { + // TODO: Show friendly error message Log.d(TAG, "onClick: InValid"); } } diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfirmationDialog.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfirmationDialog.java index 6196f0e..8dc925c 100644 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfirmationDialog.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/dialogs/ConfirmationDialog.java @@ -1,6 +1,11 @@ package org.timecrafters.TimeCraftersConfigurationTool.dialogs; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,25 +15,30 @@ import android.widget.TextView; import androidx.constraintlayout.widget.ConstraintLayout; +import com.google.android.material.resources.TextAppearance; + import org.timecrafters.TimeCraftersConfigurationTool.R; +import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend; import org.timecrafters.TimeCraftersConfigurationTool.library.TimeCraftersDialog; public class ConfirmationDialog extends TimeCraftersDialog { private String title, message; private Runnable action; - public ConfirmationDialog() {} - - public ConfirmationDialog(String title, String message, Runnable action) { - this.title = title; - this.message = message; - this.action = action; - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = super.onCreateView(inflater, container, savedInstanceState); + if (getArguments() != null) { + this.title = getArguments().getString("title", "Are You Sure?"); + this.message = getArguments().getString("message", ""); + + final String actionKey = getArguments().getString("action", null); + if (actionKey != null && Backend.getStorage().containsKey(actionKey)) { + this.action = (Runnable) Backend.getStorage().get(actionKey); + } + } + final TextView title = root.findViewById(R.id.dialogTitle); final ConstraintLayout titlebar = root.findViewById(R.id.titlebar); final LinearLayout view = root.findViewById(R.id.dialogContent); @@ -37,13 +47,29 @@ public class ConfirmationDialog extends TimeCraftersDialog { final Button cancel = root.findViewById(R.id.cancel); final Button confirm = root.findViewById(R.id.confirm); - titlebar.setBackgroundColor(getResources().getColor(R.color.dialogAlert)); + if (getArguments() != null && getArguments().getBoolean("extreme_danger", false)) { + titlebar.setBackgroundColor(getResources().getColor(R.color.dialogError)); + getDialog().getWindow().setDimAmount(0.8f); + cancel.setTypeface(cancel.getTypeface(), Typeface.BOLD); + } else { + titlebar.setBackgroundColor(getResources().getColor(R.color.dialogAlert)); + } title.setText(this.title); messageView.setText(message); + cancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); confirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + if (action != null) { + action.run(); + } + dismiss(); } }); diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/editor/GroupsFragment.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/editor/GroupsFragment.java index 700e90a..0ab3efc 100644 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/editor/GroupsFragment.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/editor/GroupsFragment.java @@ -104,7 +104,12 @@ public class GroupsFragment extends TimeCraftersFragment { delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - ConfirmationDialog dialog = new ConfirmationDialog("Are you sure?", "Really delete " + group.name + "?", null); + ConfirmationDialog dialog = new ConfirmationDialog(); + Bundle bundle = new Bundle(); + bundle.putString("title", "Are you sure?"); + bundle.putString("message", "Delete group " + group.name + "?"); + dialog.setArguments(bundle); + dialog.show(getFragmentManager(), null); } }); diff --git a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/settings/configurations/ConfigurationsFragment.java b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/settings/configurations/ConfigurationsFragment.java index 858b728..9a3eb32 100644 --- a/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/settings/configurations/ConfigurationsFragment.java +++ b/app/src/main/java/org/timecrafters/TimeCraftersConfigurationTool/ui/settings/configurations/ConfigurationsFragment.java @@ -1,28 +1,38 @@ package org.timecrafters.TimeCraftersConfigurationTool.ui.settings.configurations; +import android.content.BroadcastReceiver; +import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ScrollView; import androidx.annotation.NonNull; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; import org.timecrafters.TimeCraftersConfigurationTool.R; import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend; import org.timecrafters.TimeCraftersConfigurationTool.dialogs.ConfigurationDialog; +import org.timecrafters.TimeCraftersConfigurationTool.dialogs.ConfirmationDialog; import org.timecrafters.TimeCraftersConfigurationTool.library.TimeCraftersFragment; public class ConfigurationsFragment extends TimeCraftersFragment { + private LayoutInflater inflater; + private LinearLayout configsContainer; + private View root; + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final View root = inflater.inflate(R.layout.fragment_configuration, container, false); + this.inflater = inflater; + this.root = inflater.inflate(R.layout.fragment_configuration, container, false); final ScrollView scrollview = root.findViewById(R.id.scrollview); - final LinearLayout configsContainer = root.findViewById(R.id.container); + configsContainer = root.findViewById(R.id.container); final FloatingActionButton actionButton = root.findViewById(R.id.actionButton); floatingActionButtonAutoHide(actionButton, scrollview); @@ -34,8 +44,16 @@ public class ConfigurationsFragment extends TimeCraftersFragment { } }); + populateConfigFiles(); + + return root; + } + + private void populateConfigFiles() { + configsContainer.removeAllViews(); + int i = 0; - for (String configFile : Backend.instance().configsList()) { + for (final String configFile : Backend.instance().configsList()) { final String config = configFile.replace(".json", ""); View view = inflater.inflate(R.layout.fragment_part_configuration, null); @@ -45,21 +63,62 @@ public class ConfigurationsFragment extends TimeCraftersFragment { view.setBackgroundColor(getResources().getColor(R.color.list_odd)); } - Button configName = view.findViewById(R.id.name); + final Button configName = view.findViewById(R.id.name); + final ImageButton rename = view.findViewById(R.id.rename); + final ImageButton delete = view.findViewById(R.id.delete); configName.setText(config); configName.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + if (Backend.instance().getSettings().config.equals(config)) { + return; + } + Backend.instance().getSettings().config = config; Backend.instance().loadConfig(config); Backend.instance().saveSettings(); + + View snackbarHost = getActivity().findViewById(R.id.snackbar_host); + Snackbar.make(snackbarHost, "Loaded config: " + config, Snackbar.LENGTH_LONG).show(); + } + }); + + rename.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ConfigurationDialog dialog = new ConfigurationDialog(); + Bundle bundle = new Bundle(); + bundle.putString("config_name", config); + dialog.setArguments(bundle); + dialog.show(getFragmentManager(), null); + } + }); + + delete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ConfirmationDialog dialog = new ConfirmationDialog(); + Bundle bundle = new Bundle(); + final String actionKey = "delete_configuration"; + bundle.putString("title", "Are you sure?"); + bundle.putString("message", "Destroy configuration " + config + "?"); + bundle.putString("action", actionKey); + bundle.putBoolean("extreme_danger", true); + Runnable action = new Runnable() { + @Override + public void run() { + Backend.instance().deleteConfig(config); + } + } ; + Backend.getStorage().put(actionKey, action); + dialog.setArguments(bundle); + + dialog.show(getFragmentManager(), null); } }); i++; configsContainer.addView(view); } - - return root; } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5f239a1..aa40ee1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -20,6 +20,13 @@ app:layout_constraintVertical_bias="0.0" app:navGraph="@navigation/mobile_navigation" /> + +