diff --git a/app/build.gradle b/app/build.gradle
index 2aae321..467fc4a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ android {
applicationId "com.sjapps.jsonlist"
minSdk 23
targetSdk 34
- versionCode 5
+ versionCode 8
versionName "1.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0cdb808..1f93d44 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,14 +16,18 @@
android:largeHeap="true"
android:theme="@style/Theme.JsonList">
+
+
@@ -42,6 +46,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sjapps/adapters/ListAdapter.java b/app/src/main/java/com/sjapps/adapters/ListAdapter.java
index da9ea4b..f5110ed 100644
--- a/app/src/main/java/com/sjapps/adapters/ListAdapter.java
+++ b/app/src/main/java/com/sjapps/adapters/ListAdapter.java
@@ -3,13 +3,25 @@
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
+import android.graphics.Color;
+import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.OvershootInterpolator;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.AnimRes;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
import com.sjapps.jsonlist.java.JsonData;
import com.sjapps.jsonlist.java.ListItem;
import com.sjapps.jsonlist.MainActivity;
@@ -17,13 +29,74 @@
import java.util.ArrayList;
-public class ListAdapter extends BaseAdapter {
+public class ListAdapter extends RecyclerView.Adapter {
+
+ Handler handler = new Handler();
ArrayList list;
Context context;
MainActivity activity;
String path;
public int selectedItem = -1;
+ public int highlightedItem = -1;
+
+
+
+ static class ViewHolderShort extends RecyclerView.ViewHolder{
+
+ TextView title;
+
+ public ViewHolderShort(View itemView) {
+ super(itemView);
+ title = itemView.findViewById(R.id.itemName);
+ }
+ public TextView getTitleTxt(){
+ return title;
+ }
+
+ public View getView(){
+ return itemView;
+ }
+
+ }
+
+ static class ViewHolderLong extends RecyclerView.ViewHolder{
+
+ TextView title, value;
+
+ public ViewHolderLong(View itemView) {
+ super(itemView);
+ title = itemView.findViewById(R.id.itemName);
+ value = itemView.findViewById(R.id.itemValue);
+ }
+ public TextView getTitleTxt(){
+ return title;
+ }
+
+ public TextView getValueTxt(){
+ return value;
+ }
+
+ public View getView(){
+ return itemView;
+ }
+
+ }
+
+ static class ViewHolderSpace extends RecyclerView.ViewHolder{
+
+
+ public ViewHolderSpace(View itemView) {
+ super(itemView);
+
+ }
+
+ public View getView(){
+ return itemView;
+ }
+
+ }
+
public ListAdapter(ArrayList list, Context context,String path){
this.list = list;
@@ -33,70 +106,82 @@ public ListAdapter(ArrayList list, Context context,String path){
}
@Override
- public int getCount() {
- if (list == null)
- return 0;
-
- if (list.size() == 0)
- return 0;
+ public int getItemViewType(int position) {
- if (!list.get(getLast()).isSpace())
- return list.size();
- return getLast();
- }
+ ListItem item = list.get(position);
- @Override
- public Object getItem(int i) {
- return list.get(i);
+ return (item.isArray() || item.isObject())?0:item.isSpace()?2:1;
}
+ @NonNull
@Override
- public long getItemId(int i) {
- return i;
- }
-
- private int getLast(){
- return (list.size()>0?list.size()-1:0);
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case 0:
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_layout,parent,false);
+ return new ViewHolderShort(view);
+ case 1:
+ view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_layout2,parent,false);
+ return new ViewHolderLong(view);
+ case 2:
+ view = LayoutInflater.from(parent.getContext()).inflate(R.layout.space_layout,parent,false);
+ return new ViewHolderSpace(view);
+ }
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_layout,parent,false);
+ return new ViewHolderShort(view);
}
-
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int pos) {
- ListItem item = list.get(position);
+ ListItem item = list.get(pos);
if(item.isSpace()) {
- return LayoutInflater.from(context).inflate(R.layout.space_layout, parent, false);
+ return;
}
+
+ int position = pos;
+
if (item.isArray() || item.isObject()) {
- View view = LayoutInflater.from(context).inflate(R.layout.list_layout,parent,false);
- TextView titleTxt = view.findViewById(R.id.itemName);
+ ViewHolderShort currentHolder = (ViewHolderShort) holder;
+
+ TextView titleTxt = currentHolder.getTitleTxt();
titleTxt.setText(item.getName());
+ View view = currentHolder.getView();
+
if (selectedItem == position){
view.findViewById(R.id.copyBtn).setVisibility(View.VISIBLE);
+ }else view.findViewById(R.id.copyBtn).setVisibility(View.GONE);
+
+ if (highlightedItem == position){
+ setAnimation(context,view,R.anim.button_prev,new OvershootInterpolator());
+ highlightedItem = -1;
}
+
String newPath = path + (path.equals("") ? "": "///" + (item.getId()!=-1?"{" + item.getId() + "}":"")) + item.getName();
- view.findViewById(R.id.btn).setOnClickListener(view1 -> activity.open(JsonData.getPathFormat(newPath),newPath));
+ view.findViewById(R.id.btn).setOnClickListener(view1 -> activity.open(JsonData.getPathFormat(newPath),newPath,position));
view.findViewById(R.id.copyBtn).setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clipData = ClipData.newPlainText("Text",item.getName());
- clipboard.setPrimaryClip(clipData);
- Toast.makeText(v.getContext(),"Copied to clipboard",Toast.LENGTH_SHORT).show();
- selectedItem = -1;
- notifyDataSetChanged();
+ ClipData clipData = ClipData.newPlainText("Text",item.getName());
+ clipboard.setPrimaryClip(clipData);
+ Toast.makeText(v.getContext(),"Copied to clipboard",Toast.LENGTH_SHORT).show();
+ selectedItem = -1;
+ notifyItemChanged(position);
});
view.findViewById(R.id.btn).setOnLongClickListener(v -> {
+ notifyItemChanged(selectedItem);
selectedItem = position;
- notifyDataSetChanged();
+ notifyItemChanged(position);
return true;
});
- return view;
-
+ return;
}
- View view = LayoutInflater.from(context).inflate(R.layout.list_layout2,parent,false);
- TextView titleTxt = view.findViewById(R.id.itemName);
- TextView valueTxt = view.findViewById(R.id.itemValue);
+
+ ViewHolderLong currentHolder = (ViewHolderLong) holder;
+ View view = currentHolder.getView();
+ TextView titleTxt = currentHolder.getTitleTxt();
+ TextView valueTxt = currentHolder.getValueTxt();
if (item.getName() == null)
titleTxt.setVisibility(View.GONE);
else {
@@ -107,6 +192,38 @@ public View getView(int position, View convertView, ViewGroup parent) {
valueTxt.setText(item.getValue().isEmpty() ? "\"\"" : item.getValue());
- return view;
+ }
+
+ @Override
+ public int getItemCount() {
+ if (list == null)
+ return 0;
+
+ if (list.size() == 0)
+ return 0;
+
+ if (!list.get(getLast()).isSpace())
+ return list.size();
+ return getLast();
+ }
+
+ private int getLast(){
+ return (list.size()>0?list.size()-1:0);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public void setHighlightItem(int position){
+ highlightedItem = position;
+ }
+
+ public static void setAnimation(Context context, @NonNull View view, @AnimRes int animationRes, Interpolator interpolator) {
+ Animation animation = AnimationUtils.loadAnimation(context, animationRes);
+ if (interpolator != null)
+ animation.setInterpolator(interpolator);
+ view.startAnimation(animation);
}
}
diff --git a/app/src/main/java/com/sjapps/jsonlist/AppState.java b/app/src/main/java/com/sjapps/jsonlist/AppState.java
new file mode 100644
index 0000000..d1ffdf7
--- /dev/null
+++ b/app/src/main/java/com/sjapps/jsonlist/AppState.java
@@ -0,0 +1,30 @@
+package com.sjapps.jsonlist;
+
+public class AppState {
+ boolean hasNewCrash;
+ boolean hasCrashLogs;
+
+ public boolean hasNewCrash() {
+ return hasNewCrash;
+ }
+
+ public void setHasNewCrash(boolean hasNewCrash) {
+ this.hasNewCrash = hasNewCrash;
+ }
+
+ public boolean hasCrashLogs() {
+ return hasCrashLogs;
+ }
+
+ public void setHasCrashLogs(boolean hasCrashLogs) {
+ this.hasCrashLogs = hasCrashLogs;
+ }
+
+ @Override
+ public String toString() {
+ return "AppState{" +
+ "hasNewCrash=" + hasNewCrash +
+ ", hasCrashLogs=" + hasCrashLogs +
+ '}';
+ }
+}
diff --git a/app/src/main/java/com/sjapps/jsonlist/FileSystem.java b/app/src/main/java/com/sjapps/jsonlist/FileSystem.java
index 2f8719c..31bb6b4 100644
--- a/app/src/main/java/com/sjapps/jsonlist/FileSystem.java
+++ b/app/src/main/java/com/sjapps/jsonlist/FileSystem.java
@@ -1,18 +1,28 @@
package com.sjapps.jsonlist;
+import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
+
+import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
+import com.sjapps.logs.CrashLogs;
import java.io.BufferedReader;
-import java.io.FileInputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
+
public class FileSystem {
+ final static String LogFile = "Log.json";
+ final static String StateFile = "CheckState.json";
public static JsonObject loadDataToJsonObj(JsonElement data){
return data.getAsJsonObject();
@@ -21,7 +31,7 @@ public static JsonArray loadDataToJsonArray(JsonElement data) {
return data.getAsJsonArray();
}
- public static String LoadDataFromFile(MainActivity mainActivity, Uri uri) {
+ public static String LoadDataFromFile(Context context, Uri uri, InputStream inputStream, AssetFileDescriptor fileDescriptor) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
String path = uri.getPath();
@@ -34,18 +44,14 @@ public static String LoadDataFromFile(MainActivity mainActivity, Uri uri) {
StringBuilder sb = new StringBuilder();
try {
- FileInputStream inputStream = (FileInputStream) mainActivity.getContentResolver().openInputStream(uri);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-
- AssetFileDescriptor fileDescriptor = mainActivity.getContentResolver().openAssetFileDescriptor(uri , "r");
-
long currentBytes = 0;
long fileSize = fileDescriptor.getLength();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
currentBytes += line.length();
- mainActivity.updateProgress((int)((currentBytes/(float)fileSize)*100));
+ ((MainActivity) context).updateProgress((int)((currentBytes/(float)fileSize)*100));
}
fileDescriptor.close();
@@ -58,4 +64,86 @@ public static String LoadDataFromFile(MainActivity mainActivity, Uri uri) {
return null;
}
}
+
+ static String LoadData(Context context,String FileName){
+ File file = new File(context.getFilesDir(), FileName);
+ try {
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ FileReader fileReader = new FileReader(file.getAbsolutePath());
+
+
+ StringBuilder builder = new StringBuilder();
+ String jsonString = null;
+ BufferedReader bufferedReader = new BufferedReader(fileReader);
+ while ((jsonString = bufferedReader.readLine()) != null) {
+ builder.append(jsonString);
+ }
+ bufferedReader.close();
+ if (builder.toString().equals("") || builder.toString().equals("{}"))
+ return null;
+
+ return new String(builder);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ static void SaveData(Context context, String FileName, String data){
+ File file = new File(context.getFilesDir(), FileName);
+ try {
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ FileWriter fileWriter = new FileWriter(file);
+ fileWriter.write(data);
+ fileWriter.close();
+ }catch (IOException e){
+ e.printStackTrace();
+ }
+ }
+
+ public static AppState loadStateData(Context context) {
+ String data = LoadData(context, StateFile);
+ if (data == null)
+ return new AppState();
+ return new Gson().fromJson(data, AppState.class);
+ }
+
+ public static CrashLogs loadLogData(Context context){
+ String data = LoadData(context,LogFile);
+ if (data == null)
+ return new CrashLogs();
+ return new Gson().fromJson(data,CrashLogs.class);
+ }
+
+ public static void SaveState(Context context, String data) {
+ SaveData(context,StateFile,data);
+ }
+
+ public static void SaveLog(Context context, String data) {
+ SaveData(context,LogFile,data);
+ }
+
+ public static File createTempFile(Context context, String data, String fileName) {
+ File file = new File(context.getCacheDir(),fileName);
+ try {
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ FileWriter fileWriter = new FileWriter(file);
+ fileWriter.write(data);
+ fileWriter.close();
+ }catch (IOException e){
+ e.printStackTrace();
+ }
+ return file;
+ }
+
+ public static boolean deleteTempFile(Context context, String fileName){
+ File file = new File(context.getCacheDir(),fileName);
+ return file.delete();
+ }
}
diff --git a/app/src/main/java/com/sjapps/jsonlist/MainActivity.java b/app/src/main/java/com/sjapps/jsonlist/MainActivity.java
index a75fc33..ea1519f 100644
--- a/app/src/main/java/com/sjapps/jsonlist/MainActivity.java
+++ b/app/src/main/java/com/sjapps/jsonlist/MainActivity.java
@@ -8,16 +8,24 @@
import androidx.annotation.AnimRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import android.app.Activity;
import android.content.Context;
+import android.content.ClipData;
import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.transition.AutoTransition;
import android.transition.TransitionManager;
import android.util.Log;
+import android.util.TypedValue;
+import android.view.DragAndDropPermissions;
+import android.view.DragEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
@@ -29,8 +37,8 @@
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.ListView;
import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -40,13 +48,17 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
+
import com.sjapps.about.AboutActivity;
import com.sjapps.adapters.ListAdapter;
import com.sjapps.jsonlist.java.JsonData;
import com.sjapps.jsonlist.java.ListItem;
import com.sjapps.library.customdialog.BasicDialog;
+import com.sjapps.logs.CustomExceptionHandler;
+import com.sjapps.logs.LogActivity;
-
+import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
@@ -57,7 +69,7 @@ public class MainActivity extends AppCompatActivity {
ImageView fileImg;
Button openFileBtn;
TextView titleTxt, emptyListTxt;
- ListView list;
+ RecyclerView list;
JsonData data = new JsonData();
LinearLayout progressView;
ProgressBar progressBar;
@@ -68,16 +80,23 @@ public class MainActivity extends AppCompatActivity {
AutoTransition autoTransition = new AutoTransition();
Handler handler = new Handler();
Thread readFileThread;
+ RelativeLayout dropTarget;
@Override
protected void onResume() {
super.onResume();
+ checkCrashLogs();
Log.d(TAG, "onResume: resume");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
+ Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this));
+ }
+
setContentView(R.layout.activity_main);
initialize();
@@ -98,10 +117,14 @@ protected void onCreate(Bundle savedInstanceState) {
OpenAbout();
open_closeMenu();
});
+ menu.findViewById(R.id.logBtn).setOnClickListener(view -> {
+ OpenLogPage();
+ open_closeMenu();
+ });
dim_bg.setOnClickListener(view -> open_closeMenu());
- Intent intent = getIntent();
+ Intent intent = getIntent();
Log.d(TAG, "onCreate: " + intent);
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
ReadFile(intent.getData());
@@ -109,12 +132,110 @@ protected void onCreate(Bundle savedInstanceState) {
if (intent.getAction().equals("android.intent.action.OPEN_FILE")){
ImportFromFile();
}
+
+ dropTarget.setOnDragListener((v, event) -> {
+
+ TextView dropTargetTxt = v.findViewById(R.id.dropTargetText);
+ View dropTargetBackground = v.findViewById(R.id.dropTargetBackground);
+
+ String MIMEType = Build.VERSION.SDK_INT > Build.VERSION_CODES.P?"application/json":"application/*";
+
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ dropTarget.setAlpha(1);
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENTERED:
+ if(event.getClipDescription().getMimeTypeCount() > 1){
+ dropTargetTxt.setText(R.string.only_one_file_is_allowed);
+ dropTargetBackground.setBackgroundColor(setColor(R.attr.colorError));
+ dropTargetBackground.setAlpha(.8f);
+ return false;
+ }
+ if (!event.getClipDescription().hasMimeType(MIMEType)) {
+ dropTargetTxt.setText(R.string.this_is_not_json_file);
+ dropTargetBackground.setBackgroundColor(setColor(R.attr.colorError));
+ dropTargetBackground.setAlpha(.8f);
+ return false;
+ }
+
+ dropTargetBackground.setBackgroundColor(setColor(R.attr.colorPrimary));
+ dropTargetBackground.setAlpha(.8f);
+ return true;
+
+ case DragEvent.ACTION_DRAG_EXITED:
+ dropTargetTxt.setText(R.string.drop_json_file_here);
+ dropTargetBackground.setBackgroundColor(setColor(R.attr.colorOnBackground));
+ dropTargetBackground.setAlpha(.5f);
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENDED:
+ dropTargetTxt.setText(R.string.drop_json_file_here);
+ dropTargetBackground.setBackgroundColor(setColor(R.attr.colorOnBackground));
+ dropTarget.setAlpha(0);
+ return true;
+
+ case DragEvent.ACTION_DROP:
+ if (event.getClipData().getItemCount() > 1){
+ return false;
+ }
+ if (!event.getClipDescription().hasMimeType(MIMEType))
+ return false;
+ if (readFileThread != null && readFileThread.isAlive()) {
+ Snackbar.make(getWindow().getDecorView(),"Loading file in progress, try again later", BaseTransientBottomBar.LENGTH_SHORT).show();
+ return false;
+ }
+
+ ClipData.Item item = event.getClipData().getItemAt(0);
+
+ DragAndDropPermissions dropPermissions = null;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
+ dropPermissions = requestDragAndDropPermissions(event);
+
+ ReadFile(item.getUri());
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N && dropPermissions != null)
+ dropPermissions.release();
+
+ return true;
+ }
+ return false;
+ });
+ }
+
+ void checkCrashLogs() {
+
+ AppState state = FileSystem.loadStateData(this);
+ TextView logBtn = menu.findViewById(R.id.logBtn);
+ if (!state.hasCrashLogs()) {
+ logBtn.setVisibility(View.GONE);
+ return;
+ }
+ logBtn.setVisibility(View.VISIBLE);
+
+ TypedValue typedValue = new TypedValue();
+
+ if (state.hasNewCrash()) {
+ getTheme().resolveAttribute(R.attr.colorOnError, typedValue, true);
+ logBtn.setTextColor(typedValue.data);
+ logBtn.setBackgroundResource(R.drawable.ripple_red);
+ menuBtn.setImageResource(R.drawable.menu_with_dot);
+ return;
+ }
+ getTheme().resolveAttribute(R.attr.colorOnSurfaceVariant, typedValue, true);
+ logBtn.setTextColor(typedValue.data);
+ logBtn.setBackgroundResource(R.drawable.ripple_list2);
+ menuBtn.setImageResource(R.drawable.ic_menu);
}
private void OpenAbout() {
startActivity(new Intent(MainActivity.this, AboutActivity.class));
}
+ private void OpenLogPage() {
+ startActivity(new Intent(MainActivity.this, LogActivity.class));
+ }
+
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
@@ -125,7 +246,7 @@ public void handleOnBackPressed() {
if (adapter!= null && adapter.selectedItem != -1){
adapter.selectedItem = -1;
- adapter.notifyDataSetChanged();
+ adapter.notifyItemRangeChanged(0,adapter.getItemCount());
return;
}
@@ -144,7 +265,7 @@ public void handleOnBackPressed() {
TransitionManager.beginDelayedTransition(viewGroup, autoTransition);
data.goBack();
- open(JsonData.getPathFormat(data.getPath()), data.getPath());
+ open(JsonData.getPathFormat(data.getPath()), data.getPath(),-1);
if (data.isEmptyPath()) {
backBtn.setVisibility(View.GONE);
}
@@ -168,6 +289,8 @@ private void initialize() {
dim_bg.bringToFront();
menu.bringToFront();
menuBtn.bringToFront();
+ dropTarget = findViewById(R.id.dropTarget);
+ list.setLayoutManager(new LinearLayoutManager(this));
}
private void open_closeMenu() {
@@ -254,7 +377,7 @@ private void LoadData(String Data) {
readFileThread.start();
}
- public void open(String Title, String path) {
+ public void open(String Title, String path, int previousPosition) {
TransitionManager.beginDelayedTransition(viewGroup, autoTransition);
if (isMenuOpen)
@@ -268,6 +391,18 @@ public void open(String Title, String path) {
ArrayList arrayList = getListFromPath(path,data.getRootList());
adapter = new ListAdapter(arrayList, this, path);
list.setAdapter(adapter);
+
+ if (previousPosition == -1) {
+ handler.postDelayed(() -> {
+ list.smoothScrollToPosition(data.getPreviousPos()+2);
+ adapter.setHighlightItem(data.getPreviousPos());
+ }, 500);
+ handler.postDelayed(() -> {
+ adapter.notifyItemChanged(data.getPreviousPos());
+ }, 600);
+ }
+ else data.addPreviousPos(previousPosition);
+
if (arrayList.size() == 0) {
emptyListTxt.setVisibility(View.VISIBLE);
}
@@ -286,7 +421,7 @@ private void ImportFromFile() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("application/json");
+ intent.setType(Build.VERSION.SDK_INT > Build.VERSION_CODES.P?"application/json":"application/*");
ActivityResultData.launch(intent);
}
@@ -314,21 +449,27 @@ void ReadFile(Uri uri){
}
loadingStarted("Reading file");
+ try {
+ InputStream inputStream = getContentResolver().openInputStream(uri);
+ AssetFileDescriptor fileDescriptor = getContentResolver().openAssetFileDescriptor(uri , "r");
- readFileThread = new Thread(() -> {
- String Data = FileSystem.LoadDataFromFile(MainActivity.this, uri);
+ readFileThread = new Thread(() -> {
- if (Data == null) {
- Log.d(TAG, "ReadFile: null data");
- return;
- }
- handler.post(() -> {
- LoadData(Data);
- });
+ String Data = FileSystem.LoadDataFromFile(MainActivity.this, uri, inputStream, fileDescriptor);
- });
- readFileThread.start();
+ if (Data == null) {
+ Log.d(TAG, "ReadFile: null data");
+ return;
+ }
+ handler.post(() -> {
+ LoadData(Data);
+ });
+ });
+ readFileThread.start();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
void loadingStarted(){
@@ -379,6 +520,12 @@ void loadingFinished(boolean isFinished){
}
+ int setColor(int resid){
+ TypedValue typedValue = new TypedValue();
+ getTheme().resolveAttribute(resid, typedValue, true);
+ return typedValue.data;
+ }
+
public static void setAnimation(Context context, @NonNull View view, @AnimRes int animationRes) {
setAnimation(context,view,animationRes,null);
}
diff --git a/app/src/main/java/com/sjapps/jsonlist/functions.java b/app/src/main/java/com/sjapps/jsonlist/functions.java
new file mode 100644
index 0000000..a02edaf
--- /dev/null
+++ b/app/src/main/java/com/sjapps/jsonlist/functions.java
@@ -0,0 +1,21 @@
+package com.sjapps.jsonlist;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+public class functions {
+ public static String timeFormat(Calendar c){
+ if (c == null)
+ return "N/A";
+ SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, MMM d h:mm:ss a, yyyy");
+ return dateFormat.format(c.getTime());
+ }
+
+ public static String timeFormatShort(Calendar c){
+ if (c == null)
+ return "N/A";
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
+ return dateFormat.format(c.getTime());
+ }
+
+}
diff --git a/app/src/main/java/com/sjapps/jsonlist/java/JsonData.java b/app/src/main/java/com/sjapps/jsonlist/java/JsonData.java
index ceb9ec9..3093d3f 100644
--- a/app/src/main/java/com/sjapps/jsonlist/java/JsonData.java
+++ b/app/src/main/java/com/sjapps/jsonlist/java/JsonData.java
@@ -1,10 +1,14 @@
package com.sjapps.jsonlist.java;
import java.util.ArrayList;
+import java.util.Stack;
public class JsonData {
String path = "";
ArrayList rootList = new ArrayList<>();
+ Stack previousPosStack = new Stack<>();
+
+ int previousPos = -1;
public String getPath() {
return path;
@@ -41,6 +45,9 @@ public boolean isRootListNull(){
public void goBack(){
+ if (!previousPosStack.isEmpty())
+ previousPos = previousPosStack.pop();
+
String[] pathStrings = splitPath();
clearPath();
for (int i = 0; i < pathStrings.length-1; i++) {
@@ -49,6 +56,14 @@ public void goBack(){
}
+ public void addPreviousPos(int pos){
+ previousPosStack.push(pos);
+ }
+
+ public int getPreviousPos(){
+ return previousPos;
+ }
+
public static String getPathFormat(String path){
String[] pathStrings = splitPath(path);
StringBuilder builder = new StringBuilder();
diff --git a/app/src/main/java/com/sjapps/logs/CrashLogs.java b/app/src/main/java/com/sjapps/logs/CrashLogs.java
new file mode 100644
index 0000000..3d83f56
--- /dev/null
+++ b/app/src/main/java/com/sjapps/logs/CrashLogs.java
@@ -0,0 +1,27 @@
+package com.sjapps.logs;
+
+import java.util.ArrayList;
+
+public class CrashLogs {
+
+ ArrayList logs = new ArrayList<>();
+
+ public void addLog(String s){
+ logs.add(s);
+ }
+
+ public ArrayList getLogs() {
+ return logs;
+ }
+
+ public void setLogs(ArrayList logs) {
+ this.logs = logs;
+ }
+
+ @Override
+ public String toString() {
+ return "RuntimeExceptions{" +
+ "logs=" + logs +
+ '}';
+ }
+}
diff --git a/app/src/main/java/com/sjapps/logs/CustomExceptionHandler.java b/app/src/main/java/com/sjapps/logs/CustomExceptionHandler.java
new file mode 100644
index 0000000..88da5e5
--- /dev/null
+++ b/app/src/main/java/com/sjapps/logs/CustomExceptionHandler.java
@@ -0,0 +1,58 @@
+package com.sjapps.logs;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+import com.sjapps.jsonlist.AppState;
+import com.sjapps.jsonlist.FileSystem;
+import com.sjapps.jsonlist.functions;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Calendar;
+
+
+public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler {
+
+ private Thread.UncaughtExceptionHandler defaultUEH;
+ Context context;
+ Calendar calendar;
+
+
+ public CustomExceptionHandler(Context context) {
+ defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
+ this.context = context;
+
+ }
+
+ @Override
+ public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
+
+ AppState state = FileSystem.loadStateData(context);
+
+ if (!state.hasCrashLogs())
+ state.setHasCrashLogs(true);
+
+ state.setHasNewCrash(true);
+ FileSystem.SaveState(context,new Gson().toJson(state));
+
+ final Writer result = new StringWriter();
+ final PrintWriter printWriter = new PrintWriter(result);
+ e.printStackTrace(printWriter);
+ String stacktrace = result.toString();
+ printWriter.close();
+ calendar = Calendar.getInstance();
+ String log = "";
+ log += "\n\n-- " + functions.timeFormat(calendar) + " -- \n";
+ log += stacktrace;
+
+ CrashLogs exceptions = FileSystem.loadLogData(context);
+
+ exceptions.addLog(log);
+ FileSystem.SaveLog(context,new Gson().toJson(exceptions));
+ defaultUEH.uncaughtException(t, e);
+ }
+}
diff --git a/app/src/main/java/com/sjapps/logs/LogActivity.java b/app/src/main/java/com/sjapps/logs/LogActivity.java
new file mode 100644
index 0000000..665b568
--- /dev/null
+++ b/app/src/main/java/com/sjapps/logs/LogActivity.java
@@ -0,0 +1,156 @@
+package com.sjapps.logs;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.FileProvider;
+
+import com.google.gson.Gson;
+import com.sjapps.jsonlist.AppState;
+import com.sjapps.jsonlist.FileSystem;
+import com.sjapps.jsonlist.R;
+import com.sjapps.jsonlist.functions;
+import com.sjapps.library.customdialog.BasicDialog;
+import com.sjapps.library.customdialog.ListDialog;
+import com.sjapps.library.customdialog.MessageDialog;
+
+
+import java.util.Calendar;
+
+public class LogActivity extends AppCompatActivity {
+
+ TextView logTxt;
+ String exportFileName = "logFile.txt";
+ int numberOfLogs = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_log);
+
+ logTxt = findViewById(R.id.logTxt);
+ update();
+
+ AppState state = FileSystem.loadStateData(this);
+
+ if (state.hasNewCrash()){
+ state.setHasNewCrash(false);
+ FileSystem.SaveState(this,new Gson().toJson(state));
+ }
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ FileSystem.deleteTempFile(this,exportFileName);
+
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+
+
+ public void Back(View view) {
+ finish();
+ }
+
+ public void deleteLog(View view) {
+ BasicDialog dialog = new BasicDialog();
+ dialog.Delete(this,true)
+ .setTitle("Delete logs?")
+ .onButtonClick(() ->{
+ dialog.dismiss();
+ FileSystem.SaveLog(this,new Gson().toJson(new CrashLogs()));
+ update();
+ })
+ .show();
+ }
+
+ private void update() {
+
+ CrashLogs logs = FileSystem.loadLogData(this);
+
+ AppState state = FileSystem.loadStateData(this);
+
+ if (state.hasCrashLogs() && logs.getLogs().isEmpty()){
+ state.setHasCrashLogs(false);
+ FileSystem.SaveState(this,new Gson().toJson(state));
+ }
+ StringBuilder log = new StringBuilder();
+
+ numberOfLogs = logs.getLogs().size();
+
+ for (String s : logs.getLogs()){
+ log.append(s);
+ }
+ logTxt.setText(log.toString());
+ }
+
+ public void shareLog(View view) {
+
+ if (logTxt.getText().toString().equals("")) {
+ Toast.makeText(this, "File is empty", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ String[] options = {"Copy logs to clipboard","Share logs"};
+
+ ListDialog dialog = new ListDialog();
+ dialog.Builder(this,true)
+ .setTitle("Chose action")
+ .setItems(options,(position, value) -> {
+ dialog.dismiss();
+ switch (position){
+ case 0: copyToClipboard();
+ break;
+ case 1: share();
+ break;
+ }
+ });
+
+ MessageDialog warningDialog = new MessageDialog();
+ warningDialog.ErrorDialogBuilder(this,true)
+ .setTitle("Warning!")
+ .setMessage("This reports may include personal information. Check before sharing to anyone")
+ .show();
+ warningDialog.dialog.setOnDismissListener(dialogInterface -> {
+ warningDialog.dismiss();
+ dialog.show();
+ });
+ }
+
+ private void share() {
+
+ exportFileName = "logFile " + functions.timeFormatShort(Calendar.getInstance()) + ".txt";
+
+ String appName = getResources().getString(R.string.app_name);
+
+ Intent intentShare = new Intent(Intent.ACTION_SEND);
+ intentShare.setType("text/plain");
+ intentShare.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".logs.provider",
+ FileSystem.createTempFile(this,logTxt.getText().toString(),exportFileName)));
+ intentShare.putExtra(Intent.EXTRA_SUBJECT, appName + " Crash logs");
+ intentShare.putExtra(Intent.EXTRA_TEXT, numberOfLogs + " Crash log" + (numberOfLogs>1?"s":"") + " for " + appName);
+
+ startActivity(Intent.createChooser(intentShare,"Share file"));
+ }
+
+ private void copyToClipboard() {
+
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clipData = ClipData.newPlainText("log",logTxt.getText().toString());
+ clipboard.setPrimaryClip(clipData);
+ Toast.makeText(this, "logs is copied to clipboard", Toast.LENGTH_SHORT).show();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/anim/button_prev.xml b/app/src/main/res/anim/button_prev.xml
new file mode 100644
index 0000000..5196dcb
--- /dev/null
+++ b/app/src/main/res/anim/button_prev.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 0000000..41fa9d6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml
index e0409ca..a917303 100644
--- a/app/src/main/res/drawable/ic_menu.xml
+++ b/app/src/main/res/drawable/ic_menu.xml
@@ -6,5 +6,5 @@
android:tint="?attr/colorControlNormal">
+ android:pathData="M4,18h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,16c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM4,13h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM3,7c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,6c-0.55,0 -1,0.45 -1,1z"/>
diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml
new file mode 100644
index 0000000..d013943
--- /dev/null
+++ b/app/src/main/res/drawable/ic_share.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/src/main/res/drawable/menu_with_dot.xml b/app/src/main/res/drawable/menu_with_dot.xml
new file mode 100644
index 0000000..bef5432
--- /dev/null
+++ b/app/src/main/res/drawable/menu_with_dot.xml
@@ -0,0 +1,21 @@
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ripple_red.xml b/app/src/main/res/drawable/ripple_red.xml
new file mode 100644
index 0000000..5f5a63b
--- /dev/null
+++ b/app/src/main/res/drawable/ripple_red.xml
@@ -0,0 +1,14 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index 28d0504..58e274b 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -72,7 +72,7 @@
android:layout_marginTop="20dp"
android:layout_marginBottom="5dp"
- android:text="Libraries"
+ android:text="Open source libraries"
android:textSize="25sp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 18320d3..15c5013 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -76,7 +76,7 @@
android:layout_height="match_parent"
android:background="#70000000"
android:visibility="invisible" />
-
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_menu.xml b/app/src/main/res/layout/activity_menu.xml
index 2f7b157..3deff0a 100644
--- a/app/src/main/res/layout/activity_menu.xml
+++ b/app/src/main/res/layout/activity_menu.xml
@@ -39,7 +39,17 @@
android:paddingHorizontal="20dp"
android:background="@drawable/ripple_list2"
android:text="About"/>
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5d8256b..3cd16e9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,4 +2,7 @@
Json List
Open file
Back
+ Drop JSON file here
+ This is not JSON file
+ Only one file is allowed!
\ No newline at end of file
diff --git a/app/src/main/res/xml/file_path.xml b/app/src/main/res/xml/file_path.xml
new file mode 100644
index 0000000..24b6e4e
--- /dev/null
+++ b/app/src/main/res/xml/file_path.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file