Implemented file writing, implemented write permission request, refactored Dialog to use DialogFragment, implemented json de/serializer for Settings

This commit is contained in:
2020-06-28 16:13:50 -05:00
parent b7882444f6
commit e9cf747f12
11 changed files with 409 additions and 55 deletions

39
.idea/misc.xml generated
View File

@@ -1,5 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="org.jetbrains.annotations.Nullable" />
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="12">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="android.annotation.Nullable" />
<item index="7" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="11">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="android.annotation.NonNull" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>

View File

@@ -1,20 +1,34 @@
package org.timecrafters.TimeCraftersConfigurationTool;
import android.Manifest;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend;
import org.timecrafters.TimeCraftersConfigurationTool.dialogs.Dialog;
import org.timecrafters.TimeCraftersConfigurationTool.dialogs.PermissionsRequestDialog;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_WRITE_PERMISSION = 70;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
@@ -26,6 +40,40 @@ public class MainActivity extends AppCompatActivity {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
if (!havePermissions()) {
new PermissionsRequestDialog().show(getSupportFragmentManager(), null);
} else {
new Backend();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_WRITE_PERMISSION: {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
// Permission granted
new Backend();
} else {
// Permission not given
new PermissionsRequestDialog().show(getSupportFragmentManager(), null);
}
}
}
}
private boolean havePermissions() {
return ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED;
}
public void requestStoragePermissions() {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_WRITE_PERMISSION);
}
public void close() {
finish();
}
}

View File

@@ -1,11 +1,30 @@
package org.timecrafters.TimeCraftersConfigurationTool.backend;
import org.timecrafters.TimeCraftersConfigurationTool.tacnet.PacketHandler;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.internal.LinkedTreeMap;
import org.timecrafters.TimeCraftersConfigurationTool.serializers.SettingsDeserializer;
import org.timecrafters.TimeCraftersConfigurationTool.serializers.SettingsSerializer;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
public class Backend {
private static final String TAG = "Backend";
static private Backend instance;
private TACNET tacnet;
private Config config;
@@ -16,7 +35,7 @@ public class Backend {
instance = this;
loadSettings();
if (settings.config != null) {
if (!settings.config.isEmpty()) {
loadConfig(settings.config);
}
tacnet = new TACNET();
@@ -84,28 +103,94 @@ public class Backend {
settingsChanged = true;
}
public boolean isSettingsChanged() {
public boolean haveSettingsChanged() {
return settingsChanged;
}
public boolean loadSettings() {
return false;
public void loadSettings() {
File settingsFile = new File(TAC.SETTINGS_PATH);
if (!settingsFile.exists()) {
Log.i(TAG, "Writing default settings.json");
writeDefaultSettings();
}
try {
settings = gsonForSettings().fromJson(new FileReader(settingsFile), Settings.class);
} catch (FileNotFoundException e) {
// TODO
Log.e(TAG, "Unable to load settings.json");
}
}
public void saveSettings() {
Log.i(TAG, "Settings: " + gsonForSettings().toJson(settings));
writeToFile(TAC.SETTINGS_PATH, gsonForSettings().toJson(settings));
}
public void writeDefaultSettings() {
/*
{
"data":
{
"hostname":TACNET.DEFAULT_HOSTNAME,
"port":TACNET.DEFAULT_PORT,
"config":null,
}
settings = new Settings(TACNET.DEFAULT_HOSTNAME, TACNET.DEFAULT_PORT, "");
saveSettings();
}
private Gson gsonForSettings() {
return new GsonBuilder()
.registerTypeAdapter(Settings.class, new SettingsSerializer())
.registerTypeAdapter(Settings.class, new SettingsDeserializer())
.create();
}
private String readFromFile(String path) {
StringBuilder text = new StringBuilder();
try {
BufferedReader br = new BufferedReader( new FileReader(path) );
String line;
while((line = br.readLine()) != null) {
text.append(line);
text.append("\n");
}
br.close();
} catch (IOException e) {
// TODO
}
return text.toString();
}
protected boolean writeToFile(String filePath, String content) {
try {
if (filePath.startsWith(TAC.ROOT_PATH)) {
createFolders(filePath);
FileWriter writer = new FileWriter(filePath);
writer.write(content);
writer.close();
return true;
} else {
Log.e(TAG, "writeToFile disallowed path: " + filePath);
return false;
}
} catch (IOException e) {
Log.e(TAG, e.getLocalizedMessage());
return false;
}
}
private void createFolders(String filePath) throws IOException {
File rootPath = new File(TAC.ROOT_PATH);
File configsPath = new File(TAC.CONFIGS_PATH);
if (!rootPath.exists()) {
rootPath.mkdir();
}
if (!configsPath.exists()) {
configsPath.mkdir();
}
*/
}
}

View File

@@ -1,45 +1,43 @@
package org.timecrafters.TimeCraftersConfigurationTool.dialogs;
import android.app.ActionBar;
import android.content.Context;
import android.graphics.Point;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import org.timecrafters.TimeCraftersConfigurationTool.R;
public class Dialog extends android.app.Dialog {
public Dialog(@NonNull Context context) {
super(context);
public class Dialog extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = View.inflate(getContext(), R.layout.dialog_base, null);
ImageButton closeButton = v.findViewById(R.id.dialogCloseButton);
if (isCancelable()) {
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
} else {
closeButton.setVisibility(View.GONE);
}
return v;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.dialog_base);
public void onStart() {
super.onStart();
ImageButton closeButton = findViewById(R.id.dialogCloseButton);
closeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
}
@Override
protected void onStart() {
Point point = new Point();
getWindow().getWindowManager().getDefaultDisplay().getSize(point);
getWindow().setLayout((int) (point.x * 0.75), ViewGroup.LayoutParams.WRAP_CONTENT);
getActivity().getWindowManager().getDefaultDisplay().getSize(point);
getDialog().getWindow().setLayout((int) (point.x * 0.8), ViewGroup.LayoutParams.WRAP_CONTENT);
}
}

View File

@@ -0,0 +1,53 @@
package org.timecrafters.TimeCraftersConfigurationTool.dialogs;
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.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.timecrafters.TimeCraftersConfigurationTool.MainActivity;
import org.timecrafters.TimeCraftersConfigurationTool.R;
import org.timecrafters.TimeCraftersConfigurationTool.backend.Backend;
import org.timecrafters.TimeCraftersConfigurationTool.backend.TAC;
public class PermissionsRequestDialog extends Dialog {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setCancelable(false);
View v = super.onCreateView(inflater, container, savedInstanceState);
((TextView)v.findViewById(R.id.dialogTitle)).setText("Storage Permission Required");
LinearLayout view = v.findViewById(R.id.dialogContent);
view.addView(getLayoutInflater().inflate(R.layout.dialog_permission_request, null));
((TextView)view.findViewById(R.id.message)).setText("Permission is required to write to external storage:\n\n" + TAC.ROOT_PATH);
Button quitButton = view.findViewById(R.id.quit_button);
Button continueButton = view.findViewById(R.id.continue_button);
quitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
((MainActivity) getActivity()).close();
}
});
continueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
((MainActivity) getActivity()).requestStoragePermissions();
}
});
return v;
}
}

View File

@@ -0,0 +1,42 @@
package org.timecrafters.TimeCraftersConfigurationTool.dialogs;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.timecrafters.TimeCraftersConfigurationTool.R;
public class VariableDialog extends Dialog {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setCancelable(false);
View v = super.onCreateView(inflater, container, savedInstanceState);
((TextView)v.findViewById(R.id.dialogTitle)).setText("Add Variable");
LinearLayout view = v.findViewById(R.id.dialogContent);
view.addView(getLayoutInflater().inflate(R.layout.dialog_edit_variable, null));
Button cancelButton = view.findViewById(R.id.cancel);
Button mutateButton = view.findViewById(R.id.mutate);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
mutateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
return v;
}
}

View File

@@ -0,0 +1,25 @@
package org.timecrafters.TimeCraftersConfigurationTool.serializers;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import org.timecrafters.TimeCraftersConfigurationTool.backend.Settings;
import java.lang.reflect.Type;
public class SettingsDeserializer implements JsonDeserializer<Settings> {
@Override
public Settings deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonObject data = jsonObject.get("data").getAsJsonObject();
return new Settings(
data.get("hostname").getAsString(),
data.get("port").getAsInt(),
data.get("config").getAsString()
);
}
}

View File

@@ -0,0 +1,30 @@
package org.timecrafters.TimeCraftersConfigurationTool.serializers;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import org.timecrafters.TimeCraftersConfigurationTool.backend.Settings;
import java.lang.reflect.Type;
public class SettingsSerializer implements JsonSerializer<Settings> {
@Override
public JsonElement serialize(Settings settings, Type type, JsonSerializationContext context) {
JsonObject container = new JsonObject();
JsonObject result = new JsonObject();
result.add("hostname", new JsonPrimitive(settings.hostname));
result.add("port", new JsonPrimitive(settings.port));
result.add("config", new JsonPrimitive(settings.config));
container.add("data", result);
return container;
}
}

View File

@@ -5,8 +5,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -15,9 +13,7 @@ import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import org.timecrafters.TimeCraftersConfigurationTool.R;
import org.timecrafters.TimeCraftersConfigurationTool.dialogs.Dialog;
import static android.view.View.inflate;
import org.timecrafters.TimeCraftersConfigurationTool.dialogs.VariableDialog;
public class TACNETFragment extends Fragment {
@@ -38,12 +34,8 @@ public class TACNETFragment extends Fragment {
connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Dialog dialog = new Dialog(getContext());
dialog.show();
((TextView)dialog.findViewById(R.id.dialogTitle)).setText("Add Variable");
LinearLayout view = dialog.findViewById(R.id.dialogContent);
view.addView(getLayoutInflater().inflate(R.layout.dialog_edit_variable, null));
VariableDialog dialog = new VariableDialog();
dialog.show(getFragmentManager(), null);
}
});

View File

@@ -65,14 +65,14 @@
android:orientation="horizontal">
<Button
android:id="@+id/button"
android:id="@+id/cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/dialog_cancel" />
<Button
android:id="@+id/button3"
android:id="@+id/mutate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/quit_button"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Quit" />
<Button
android:id="@+id/continue_button"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Continue" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>