diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6651510..7c75791 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ "master" ] + branches: [ "master", "dev" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "master" ] + branches: [ "master", "dev" ] schedule: - cron: '26 5 * * 0' diff --git a/README.md b/README.md index 598cd91..be6161f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ +![repo preview](https://repository-images.githubusercontent.com/507594462/8b9d54af-1231-43e3-a84a-5d19da2e5e38) # JsonList -Android app for previewing JSON files in list form +Introducing a Material You Android application for previewing JSON files in a user-friendly list format. + +![JsonList](images/jsonlist_main.jpg) + +Use the convenient split view feature that allows simultaneous display of the raw JSON string alongside the list representation. + +![JsonList split view](images/jsonlist_splitview.jpg) + +![JsonList split view landscape](images/jsonlist_splitview_landscape.jpg) + +Enjoy the flexibility of dynamic color support, adapting to your Material Design preferences. + +![JsonList dynamic colors](images/jsonlist_material_colots.gif) diff --git a/app/build.gradle b/app/build.gradle index b33b64d..ebc74ec 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,14 +3,14 @@ plugins { } android { - compileSdk 31 + compileSdk 34 defaultConfig { applicationId "com.sjapps.jsonlist" minSdk 23 - targetSdk 31 - versionCode 5 - versionName "1.3" + targetSdk 34 + versionCode 10 + versionName "1.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -22,7 +22,7 @@ android { } debug { applicationIdSuffix '.debug' - versionNameSuffix ' debug' + versionNameSuffix ' dev' } } compileOptions { @@ -33,14 +33,15 @@ android { dependencies { - implementation 'androidx.appcompat:appcompat:1.4.2' - implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.12.0-alpha03' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - implementation 'com.google.code.gson:gson:2.8.9' - implementation 'com.github.slavce14:SJ-Library:1.4' + implementation 'com.google.code.gson:gson:2.10.1' + implementation 'com.github.slavce14:sj-dialog:1.6.1' + implementation "androidx.core:core-splashscreen:1.0.1" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cce6d97..1f93d44 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + + + + + @@ -38,6 +46,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/ic_open_file-playstore.png b/app/src/main/ic_open_file-playstore.png deleted file mode 100644 index a1f66a1..0000000 Binary files a/app/src/main/ic_open_file-playstore.png and /dev/null differ diff --git a/app/src/main/java/com/sjapps/about/AboutActivity.java b/app/src/main/java/com/sjapps/about/AboutActivity.java index f623421..cbbb4de 100644 --- a/app/src/main/java/com/sjapps/about/AboutActivity.java +++ b/app/src/main/java/com/sjapps/about/AboutActivity.java @@ -3,7 +3,10 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import android.view.View; import android.view.animation.Animation; @@ -12,16 +15,20 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.content.res.AppCompatResources; import androidx.core.widget.NestedScrollView; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.sjapps.jsonlist.R; +import com.sjapps.library.customdialog.ImageListItem; +import com.sjapps.library.customdialog.ListDialog; import java.util.ArrayList; public class AboutActivity extends AppCompatActivity { + private static final String GITHUB_REPOSITORY_RELEASES = "https://github.com/SlaVcE14/JsonList/releases"; final String STORE_PACKAGE_NAME = "com.sjapps.sjstore"; ImageView logo; @@ -29,6 +36,8 @@ public class AboutActivity extends AppCompatActivity { RecyclerView ListRV,LibListRV; ArrayList appInfoItems = new ArrayList<>(); ArrayList libsItems; + boolean isStoreInstalled; + Drawable storeIcon; @Override protected void onCreate(Bundle savedInstanceState) { @@ -69,13 +78,41 @@ void initialize(){ ListRV = findViewById(R.id.aboutList); LibListRV = findViewById(R.id.LibrariesList); nestedScrollView = findViewById(R.id.nestedList); - + findViewById(R.id.updateBtn).setVisibility(View.VISIBLE); if (CheckStoreIsInstalled()){ - findViewById(R.id.updateBtn).setVisibility(View.VISIBLE); + isStoreInstalled = true; } } public void CheckForUpdate(View view) { + if (!isStoreInstalled) { + openGitHub(); + return; + } + + ListDialog dialog = new ListDialog(); + + ArrayList items = new ArrayList<>(); + items.add(new ImageListItem("GitHub", AppCompatResources.getDrawable(this,R.drawable.github_logo), (ImageItemClick) this::openGitHub)); + items.add(new ImageListItem("SJ Store", storeIcon, (ImageItemClick) this::openStore)); + + dialog.Builder(this,true) + .setTitle("Open...") + .setImageItems(items, (position, obj) -> { + if (obj.getData() == null) + return; + ((ImageItemClick) obj.getData()).onClick(); + dialog.dismiss(); + }) + .show(); + } + + private void openGitHub(){ + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(GITHUB_REPOSITORY_RELEASES)); + startActivity(intent); + } + + private void openStore(){ Intent intent = new Intent(); intent.setComponent(new ComponentName(STORE_PACKAGE_NAME,STORE_PACKAGE_NAME + ".AppActivity")); intent.putExtra("packageName", getPackageName()); @@ -83,10 +120,11 @@ public void CheckForUpdate(View view) { startActivity(intent); } - public boolean CheckStoreIsInstalled(){ + private boolean CheckStoreIsInstalled(){ PackageManager packageManager = getPackageManager(); try { - packageManager.getPackageInfo(STORE_PACKAGE_NAME,0); + PackageInfo packageInfo = packageManager.getPackageInfo(STORE_PACKAGE_NAME,0); + storeIcon = packageInfo.applicationInfo.loadIcon(packageManager); return true; } catch (PackageManager.NameNotFoundException e) { return false; diff --git a/app/src/main/java/com/sjapps/about/ImageItemClick.java b/app/src/main/java/com/sjapps/about/ImageItemClick.java new file mode 100644 index 0000000..52c3a14 --- /dev/null +++ b/app/src/main/java/com/sjapps/about/ImageItemClick.java @@ -0,0 +1,5 @@ +package com.sjapps.about; + +public interface ImageItemClick { + void onClick(); +} diff --git a/app/src/main/java/com/sjapps/about/LibraryList.java b/app/src/main/java/com/sjapps/about/LibraryList.java index fa1509e..9d46ca8 100644 --- a/app/src/main/java/com/sjapps/about/LibraryList.java +++ b/app/src/main/java/com/sjapps/about/LibraryList.java @@ -1,10 +1,14 @@ package com.sjapps.about; +import com.google.gson.internal.GsonBuildConfig; +import com.sjapps.library.BuildConfig; + public class LibraryList extends ListGenerator{ @Override public void init() { - addItem("SJ Library","1.4", "https://github.com/SlaVcE14/SJ-Library"); - addItem("gson","2.8.9", "https://github.com/google/gson"); + addItem("SJ Dialog", BuildConfig.VERSION_NAME, "https://github.com/SlaVcE14/SJ-Dialog"); + addItem("gson", GsonBuildConfig.VERSION, "https://github.com/google/gson"); + addItem("core-splashscreen","1.0.1","https://developer.android.com/develop/ui/views/launch/splash-screen"); } } diff --git a/app/src/main/java/com/sjapps/adapters/ListAdapter.java b/app/src/main/java/com/sjapps/adapters/ListAdapter.java index 60a2987..884ba6b 100644 --- a/app/src/main/java/com/sjapps/adapters/ListAdapter.java +++ b/app/src/main/java/com/sjapps/adapters/ListAdapter.java @@ -6,23 +6,87 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.BaseAdapter; +import android.view.animation.OvershootInterpolator; import android.widget.TextView; import android.widget.Toast; -import com.sjapps.jsonlist.ListItem; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.sjapps.jsonlist.functions; +import com.sjapps.jsonlist.java.JsonData; +import com.sjapps.jsonlist.java.ListItem; import com.sjapps.jsonlist.MainActivity; import com.sjapps.jsonlist.R; import java.util.ArrayList; -public class ListAdapter extends BaseAdapter { +public class ListAdapter extends RecyclerView.Adapter { 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; @@ -32,73 +96,117 @@ public ListAdapter(ArrayList list, Context context,String path){ } @Override - public int getCount() { - if (list == null) - return 0; + public int getItemViewType(int position) { - if (list.size() == 0) - return 0; + ListItem item = list.get(position); - if (!list.get(getLast()).isSpace()) - return list.size(); - return getLast(); + return (item.isArray() || item.isObject())?0:item.isSpace()?2:1; } + @NonNull @Override - public Object getItem(int i) { - return list.get(i); + 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 long getItemId(int i) { - return i; - } + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int pos) { - private int getLast(){ - return (list.size()>0?list.size()-1:0); - } + ListItem item = list.get(pos); + if(item.isSpace()) { + return; + } + int position = pos; - @Override - public View getView(int position, View convertView, ViewGroup parent) { + if (item.isArray() || item.isObject()) { - ListItem item = list.get(position); - if(item.isSpace()) { - return LayoutInflater.from(context).inflate(R.layout.space_layout, parent, false); - } - if (item.isArrayOfObjects() || item.isObject()) { + ViewHolderShort currentHolder = (ViewHolderShort) holder; - View view = LayoutInflater.from(context).inflate(R.layout.list_layout,parent,false); - TextView titleTxt = view.findViewById(R.id.itemName); + 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){ + functions.setAnimation(context,view,R.anim.button_prev,new OvershootInterpolator()); + highlightedItem = -1; } - view.findViewById(R.id.btn).setOnClickListener(view1 -> activity.open(item.getName(),path + (path.equals("") ? "":"///") + item.getName())); + + 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,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; + } + 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 { + titleTxt.setVisibility(View.VISIBLE); + titleTxt.setText(item.getName()); } - 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); view.setClickable(false); - titleTxt.setText(item.getName()); - valueTxt.setText(item.getValue()); - return view; + valueTxt.setText(item.getValue().isEmpty() ? "\"\"" : item.getValue()); + + } + + @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; } } 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 dc6af44..31bb6b4 100644 --- a/app/src/main/java/com/sjapps/jsonlist/FileSystem.java +++ b/app/src/main/java/com/sjapps/jsonlist/FileSystem.java @@ -1,14 +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 java.io.FileInputStream; +import com.sjapps.logs.CrashLogs; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; -import java.util.Scanner; +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(); @@ -17,41 +31,119 @@ 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(); + if (path.contains("../")) + throw new SecurityException(); + Path normalized = java.nio.file.FileSystems.getDefault().getPath(path).normalize(); + if (normalized.startsWith("/data")) + throw new SecurityException(); + } StringBuilder sb = new StringBuilder(); - FileInputStream inputStream = null; - Scanner sc = null; try { - inputStream = (FileInputStream) mainActivity.getContentResolver().openInputStream(uri); - sc = new Scanner(inputStream, "UTF-8"); - - while (sc.hasNextLine()) { - String line = sc.nextLine(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + long currentBytes = 0; + long fileSize = fileDescriptor.getLength(); + String line; + while ((line = reader.readLine()) != null) { sb.append(line); + currentBytes += line.length(); + ((MainActivity) context).updateProgress((int)((currentBytes/(float)fileSize)*100)); } - if (sc.ioException() != null) { - throw sc.ioException(); - } - + fileDescriptor.close(); + inputStream.close(); + reader.close(); + return sb.toString(); } catch (IOException e) { e.printStackTrace(); return null; - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } + } + } + + static String LoadData(Context context,String FileName){ + File file = new File(context.getFilesDir(), FileName); + try { + if (!file.exists()) { + file.createNewFile(); } - if (sc != null) { - sc.close(); + 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); } - return sb.toString(); + 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 e37f1e8..549adea 100644 --- a/app/src/main/java/com/sjapps/jsonlist/MainActivity.java +++ b/app/src/main/java/com/sjapps/jsonlist/MainActivity.java @@ -1,74 +1,116 @@ package com.sjapps.jsonlist; -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultCallback; +import static com.sjapps.jsonlist.java.JsonFunctions.*; + +import androidx.activity.OnBackPressedCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; +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.ClipData; import android.content.Intent; +import android.content.res.AssetFileDescriptor; +import android.content.res.Configuration; 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.AccelerateDecelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.OvershootInterpolator; import android.widget.Button; import android.widget.ImageButton; -import android.widget.ListView; -import android.widget.ProgressBar; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; - +import com.google.android.material.progressindicator.LinearProgressIndicator; +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import com.google.gson.JsonArray; 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.library.customdialog.SetupDialog; - +import com.sjapps.jsonlist.java.JsonData; +import com.sjapps.jsonlist.java.JsonFunctions; +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; -import java.util.Set; public class MainActivity extends AppCompatActivity { final String TAG = "MainActivity"; - ImageButton backBtn, menuBtn; + ImageButton backBtn, menuBtn, splitViewBtn; + ImageView fileImg; Button openFileBtn; - TextView titleTxt, emptyListTxt; - ListView list; - String path = ""; - ProgressBar progressBar; - TextView loadingFileTxt; - boolean isMenuOpen; + TextView titleTxt, emptyListTxt, jsonTxt; + RecyclerView list; + JsonData data = new JsonData(); + LinearLayout progressView, mainLL; + LinearProgressIndicator progressBar; + boolean isMenuOpen, showJson, isRawJsonLoaded, isVertical = true; ListAdapter adapter; - ArrayList rootList = new ArrayList<>(); - View menu, dim_bg; + View menu, dim_bg, jsonView; ViewGroup viewGroup; 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(); + + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){ + isVertical = false; + mainLL.setOrientation(LinearLayout.HORIZONTAL); + } + + functions.setAnimation(this,fileImg,R.anim.scale_in_file_img, new DecelerateInterpolator()); + functions.setAnimation(this,openFileBtn,R.anim.button_pop, new OvershootInterpolator()); + autoTransition.setDuration(150); menuBtn.setOnClickListener(view -> open_closeMenu()); - backBtn.setOnClickListener(view -> onBackPressed()); + backBtn.setOnClickListener(view -> { + if(!data.isEmptyPath()) getOnBackPressedDispatcher().onBackPressed(); + }); openFileBtn.setOnClickListener(view -> ImportFromFile()); menu.findViewById(R.id.openFileBtn2).setOnClickListener(view -> { @@ -79,92 +121,195 @@ 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(); + splitViewBtn.setOnClickListener(view -> open_closeSplitView()); + Intent intent = getIntent(); Log.d(TAG, "onCreate: " + intent); - if (Intent.ACTION_VIEW.equals(intent.getAction())){ - - Log.d(TAG, "onCreate: " + intent.getData()); - - String FileData = FileSystem.LoadDataFromFile(this,intent.getData()); -// Log.d(TAG, "onCreate: " + FileData); - if (FileData != null) - LoadData(FileData); - else - Log.d(TAG, "onCreate: null data"); + if (Intent.ACTION_VIEW.equals(intent.getAction())) { + ReadFile(intent.getData()); } if (intent.getAction().equals("android.intent.action.OPEN_FILE")){ ImportFromFile(); } - } - private void OpenAbout() { - startActivity(new Intent(MainActivity.this, AboutActivity.class)); - } + dropTarget.setOnDragListener((v, event) -> { - @Override - public void onBackPressed() { + TextView dropTargetTxt = v.findViewById(R.id.dropTargetText); + View dropTargetBackground = v.findViewById(R.id.dropTargetBackground); - if (isMenuOpen) { - open_closeMenu(); - return; - } + String MIMEType = Build.VERSION.SDK_INT > Build.VERSION_CODES.P?"application/json":"application/*"; - if (adapter.selectedItem != -1){ - adapter.selectedItem = -1; - adapter.notifyDataSetChanged(); - return; - } + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + dropTarget.setAlpha(1); + return true; - if(path.equals("")){ - SetupDialog dialog = new SetupDialog(); - String Title = "Exit?"; - String btn2Txt = "Yes"; - dialog.Short(this,Title,btn2Txt) - .onButtonClick(this::finish).show(); - return; - } + 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; + } - TransitionManager.beginDelayedTransition(viewGroup,autoTransition); + 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()); - String[] pathStrings = path.split("///"); - path = ""; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N && dropPermissions != null) + dropPermissions.release(); - String Title = ""; - for(int i = 0; i < pathStrings.length-1; i++) { - path = path.concat((path.equals("")?"":"///") + pathStrings[i]); + return true; + } + return false; + }); + } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){ + isVertical = false; + mainLL.setOrientation(LinearLayout.HORIZONTAL); + }else { + isVertical = true; + mainLL.setOrientation(LinearLayout.VERTICAL); } - if (pathStrings.length > 1) { - Title = pathStrings[pathStrings.length-2]; + } + + 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); - open(Title, path); - if(path.equals("")) { - backBtn.setVisibility(View.GONE); + 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 initialize(){ + private void OpenLogPage() { + startActivity(new Intent(MainActivity.this, LogActivity.class)); + } + + OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (isMenuOpen) { + open_closeMenu(); + return; + } + + if (adapter!= null && adapter.selectedItem != -1){ + adapter.selectedItem = -1; + adapter.notifyItemRangeChanged(0,adapter.getItemCount()); + return; + } + + if (data.isEmptyPath()){ + BasicDialog dialog = new BasicDialog(); + dialog.Builder(MainActivity.this, true) + .setTitle("Exit?") + .setRightButtonText("Yes") + .onButtonClick(() ->{ + dialog.dismiss(); + MainActivity.this.finish(); + }) + .show(); + return; + } + TransitionManager.endTransitions(viewGroup); + TransitionManager.beginDelayedTransition(viewGroup, autoTransition); + data.goBack(); + open(JsonData.getPathFormat(data.getPath()), data.getPath(),-1); + if (data.isEmptyPath()) { + backBtn.setVisibility(View.GONE); + } + } + }; + + private void initialize() { + getOnBackPressedDispatcher().addCallback(this, backPressedCallback); backBtn = findViewById(R.id.backBtn); menuBtn = findViewById(R.id.menuBtn); + mainLL = findViewById(R.id.mainLL); + splitViewBtn = findViewById(R.id.splitViewBtn); titleTxt = findViewById(R.id.titleTxt); + jsonTxt = findViewById(R.id.jsonTxt); emptyListTxt = findViewById(R.id.emptyListTxt); list = findViewById(R.id.list); openFileBtn = findViewById(R.id.openFileBtn); viewGroup = findViewById(R.id.content); menu = findViewById(R.id.menu); dim_bg = findViewById(R.id.dim_layout); + progressView = findViewById(R.id.loadingView); progressBar = findViewById(R.id.progressBar); - loadingFileTxt = findViewById(R.id.LoadFileTxt); + fileImg = findViewById(R.id.fileImg); dim_bg.bringToFront(); menu.bringToFront(); + jsonView = findViewById(R.id.rawJsonView); menuBtn.bringToFront(); + dropTarget = findViewById(R.id.dropTarget); + list.setLayoutManager(new LinearLayoutManager(this)); } private void open_closeMenu() { @@ -184,248 +329,291 @@ private void open_closeMenu() { } - private void LoadData(String Data){ + private void LoadData(String Data) { - progressBar.setVisibility(View.VISIBLE); + loadingStarted("loading json"); + emptyListTxt.setVisibility(View.GONE); - new Thread(() -> { - ArrayList temp = rootList; + readFileThread = new Thread(() -> { + ArrayList temp = data.getRootList(); JsonElement element; try { element = JsonParser.parseString(Data); - }catch (OutOfMemoryError | Exception e){ + } catch (OutOfMemoryError e) { e.printStackTrace(); - handler.post(() -> { - Toast.makeText(MainActivity.this, "File is too large", Toast.LENGTH_SHORT).show(); - progressBar.setVisibility(View.GONE); - }); + fileTooLargeException(); return; - } - if (element == null){ - handler.post(() -> { - Toast.makeText(MainActivity.this, "Filed to load file!", Toast.LENGTH_SHORT).show(); - progressBar.setVisibility(View.GONE); - }); + } catch (Exception e){ + e.printStackTrace(); + fileNotLoadedException(); return; } - - if (element instanceof JsonObject){ - Log.d(TAG, "run: Json object"); - JsonObject object = FileSystem.loadDataToJsonObj(element); - Log.d(TAG, "LoadData: " + object); - rootList = getJsonObject(object); + if (element == null) { + fileNotLoadedException(); + return; } - if (element instanceof JsonArray){ - Log.d(TAG, "run: Json array"); - JsonArray array = FileSystem.loadDataToJsonArray(element); - Log.d(TAG, "LoadData: " + array); - rootList = getJsonArrayRoot(array); + readFileThread.setName("readFileThread"); + handler.post(()-> loadingStarted("creating list")); + try { + if (element instanceof JsonObject) { + Log.d(TAG, "run: Json object"); + JsonObject object = FileSystem.loadDataToJsonObj(element); + data.setRootList(getJsonObject(object)); + } + if (element instanceof JsonArray) { + Log.d(TAG, "run: Json array"); + JsonArray array = FileSystem.loadDataToJsonArray(element); + data.setRootList(getJsonArrayRoot(array)); + } + if (Data.length()<500000) + data.setRawData(Data); + else data.setRawData("-1"); + data.clearPreviousPos(); + } catch (Exception e){ + e.printStackTrace(); + creatingListException(); + data.setRootList(null); } - if (rootList != null) { + if (!data.isRootListNull()) { handler.post(() -> { - TransitionManager.beginDelayedTransition(viewGroup,autoTransition); - adapter = new ListAdapter(rootList, MainActivity.this, ""); + TransitionManager.beginDelayedTransition(viewGroup, autoTransition); + adapter = new ListAdapter(data.getRootList(), MainActivity.this, ""); list.setAdapter(adapter); + fileImg.clearAnimation(); + openFileBtn.clearAnimation(); + fileImg.setVisibility(View.GONE); openFileBtn.setVisibility(View.GONE); + functions.setAnimation(MainActivity.this,list,R.anim.scale_in2,new DecelerateInterpolator()); + list.setVisibility(View.VISIBLE); backBtn.setVisibility(View.GONE); titleTxt.setText(""); - path = ""; + data.clearPath(); }); - }else rootList = temp; + } else data.setRootList(temp); + isRawJsonLoaded = false; + if (showJson) + handler.post(this::ShowJSON); + else handler.post(() -> loadingFinished(true)); - handler.post(() -> progressBar.setVisibility(View.GONE)); - - }).start(); + }); + readFileThread.start(); + } + public void open(String Title, String path, int previousPosition) { + TransitionManager.endTransitions(viewGroup); + TransitionManager.beginDelayedTransition(viewGroup, autoTransition); - } - ArrayList getJsonArrayRoot(JsonArray array) { - ArrayList Mainlist = new ArrayList<>(); - ListItem item = new ListItem(); - item.setName("Json Array"); - item.setIsArrayOfObjects(true); - item.setListObjects(getJsonArray(array)); - Mainlist.add(item); - - return Mainlist; - } + if (isMenuOpen) + open_closeMenu(); - ArrayList> getJsonArray(JsonArray array) { - ArrayList> ArrList = new ArrayList<>(); - for (int i = 0; i < array.size(); i++) { - if (array.get(i) instanceof JsonObject) { - ArrayList ListOfItems = getJsonObject((JsonObject) array.get(i)); - ArrList.add(ListOfItems); - } - } + if (emptyListTxt.getVisibility() == View.VISIBLE) + emptyListTxt.setVisibility(View.GONE); - return ArrList; + data.setPath(path); + titleTxt.setText(Title); + 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); - boolean isArrayHasObjects(JsonArray array){ - for (int i = 0; i < array.size(); i++) { - if (!(array.get(i) instanceof JsonObject)) { - return false; - } + if (arrayList.size() == 0) { + emptyListTxt.setVisibility(View.VISIBLE); + } + System.out.println("path = " + path); + if (!path.equals("")) { + backBtn.setVisibility(View.VISIBLE); } - return true; } - ArrayList getJsonObject(JsonObject obj) { + private void open_closeSplitView(){ + TransitionManager.endTransitions(viewGroup); + TransitionManager.beginDelayedTransition(viewGroup, autoTransition); - ArrayList Mainlist = new ArrayList<>(); - Set keys = obj.keySet(); + if (showJson){ + functions.setAnimation(this,jsonView,isVertical?R.anim.slide_bottom_out:R.anim.slide_right_out,new AccelerateDecelerateInterpolator()); + handler.postDelayed(()-> jsonView.setVisibility(View.GONE),400); + showJson = false; + return; + } + showJson = true; + jsonView.setVisibility(View.VISIBLE); + functions.setAnimation(this,jsonView,isVertical?R.anim.slide_bottom_in:R.anim.slide_right_in,new DecelerateInterpolator()); + if (!isRawJsonLoaded) + ShowJSON(); - Object[] keysArray = keys.toArray(); + } - try { + private void ShowJSON(){ + if (data.getRawData().equals("-1")) { + Snackbar.make(getWindow().getDecorView(),"File is to large to be shown in a split screen!", BaseTransientBottomBar.LENGTH_SHORT).show(); + if (progressView.getVisibility() == View.VISIBLE) + loadingFinished(true); + if (showJson) + open_closeSplitView(); + return; + } + if (data.getRawData().equals("")) + return; - for (Object o : keysArray) { - ListItem item = new ListItem(); - item.setName(o.toString()); - - if (obj.get(o.toString()) instanceof JsonObject) { - item.setIsObject(true); - ArrayList objList = getJsonObject((JsonObject) obj.get(o.toString())); - item.setObjects(objList); - - } else if (obj.get(o.toString()) instanceof JsonArray) { - JsonArray array = (JsonArray) obj.get(o.toString()); - Log.d(TAG, "isArrayHasObjects: " + isArrayHasObjects(array)); - if (isArrayHasObjects(array)){ - item.setIsArrayOfObjects(true); - ArrayList> ArrList = getJsonArray(array); - item.setListObjects(ArrList); - }else{ - item.setIsArray(true); - item.setValue(array.toString()); - } + loadingStarted("Displaying json..."); - } else { + Thread thread = new Thread(() -> { + String dataStr = JsonFunctions.getAsPrettyPrint(data.getRawData()); + handler.post(()-> { + jsonTxt.setText(dataStr); + loadingFinished(true); + isRawJsonLoaded = true; + }); + }); + thread.setName("loadingJson"); + thread.start(); - item.setValue(obj.get(o.toString()).toString()); + } - } - Mainlist.add(item); - } - }catch (Exception e){ - Log.e(TAG, "getJsonObject: Failed to load data"); - handler.post(() -> Toast.makeText(MainActivity.this, "Failed to load data!", Toast.LENGTH_SHORT).show()); - return null; + private void ImportFromFile() { + if (readFileThread != null && readFileThread.isAlive()) { + Snackbar.make(getWindow().getDecorView(),"Loading file in progress, try again later", BaseTransientBottomBar.LENGTH_SHORT).show(); + return; } - return Mainlist; + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(Build.VERSION.SDK_INT > Build.VERSION_CODES.P?"application/json":"application/*"); + ActivityResultData.launch(intent); } - ArrayList getArrayList(ArrayList> list){ - ArrayList newList = new ArrayList<>(); - for(ArrayList lists : list) { - newList.addAll(lists); - newList.add(new ListItem().Space()); - } - return newList; - } - ArrayList getListFromPath(){ + ActivityResultLauncher ActivityResultData = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() != Activity.RESULT_OK) { + if(result.getResultCode() == Activity.RESULT_CANCELED){ + Toast.makeText(MainActivity.this,"Import data canceled",Toast.LENGTH_SHORT).show(); + } + return; + } + if (result.getData() == null || result.getData().getData() == null){ + Toast.makeText(MainActivity.this, "Fail to load data", Toast.LENGTH_SHORT).show(); + return; + } + //File + ReadFile(result.getData().getData()); + }); - String[] pathStrings = path.split("///"); + void ReadFile(Uri uri){ + if (readFileThread != null && readFileThread.isAlive()){ + return; + } + loadingStarted("Reading file"); - ArrayList list = rootList; + try { + InputStream inputStream = getContentResolver().openInputStream(uri); + AssetFileDescriptor fileDescriptor = getContentResolver().openAssetFileDescriptor(uri , "r"); - for (String pathString : pathStrings) { + readFileThread = new Thread(() -> { - for (ListItem item : list) { + String Data = FileSystem.LoadDataFromFile(MainActivity.this, uri, inputStream, fileDescriptor); - if (item.getName() == null || !item.getName().equals(pathString)) { - continue; + if (Data == null) { + Log.d(TAG, "ReadFile: null data"); + return; } + handler.post(() -> { + LoadData(Data); + }); - if (item.isArrayOfObjects()) { - list = getArrayList(item.getListObjects()); - break; - } - list = list.get(list.indexOf(item)).getObjects(); - if (list == null) - list = new ArrayList<>(); - break; - } + }); + readFileThread.setName("readFileThread"); + readFileThread.start(); + } catch (IOException e) { + throw new RuntimeException(e); } - return list; - } - public void open(String Title,String path) { - TransitionManager.beginDelayedTransition(viewGroup,autoTransition); + void loadingStarted(){ + loadingStarted("loading..."); - if (isMenuOpen) - open_closeMenu(); + } - if (emptyListTxt.getVisibility() == View.VISIBLE) - emptyListTxt.setVisibility(View.GONE); + void loadingStarted(String txt){ + TextView text = progressView.findViewById(R.id.loadingTxt); + progressBar.setIndeterminate(true); + text.setText(txt); + handler.postDelayed(() -> { + if (progressView.getVisibility() != View.VISIBLE) { + functions.setAnimation(this, progressView, R.anim.scale_in); + text.setVisibility(View.VISIBLE); + progressView.setVisibility(View.VISIBLE); + } + },300); - this.path = path; - titleTxt.setText(Title); - ArrayList arrayList = getListFromPath(); - adapter = new ListAdapter(arrayList, this, path); - list.setAdapter(adapter); - if (arrayList.size() == 0){ - emptyListTxt.setVisibility(View.VISIBLE); - } - System.out.println("path = " + path); - if(!path.equals("")) { - backBtn.setVisibility(View.VISIBLE); - } + } + public void updateProgress(int val){ + handler.post(()->{ + progressBar.setProgressCompat(val,true); + }); } - private void ImportFromFile() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("application/json"); - ActivityResultData.launch(intent); + void loadingFinished(boolean isFinished){ + + if (!isFinished){ + handler.postDelayed(()-> { + functions.setAnimation(this, progressView,R.anim.scale_out); + progressView.setVisibility(View.INVISIBLE); + },300); + return; + } + + progressBar.setIndeterminate(false); + progressBar.setProgressCompat(100,true); + + TextView text = progressView.findViewById(R.id.loadingTxt); + handler.postDelayed(() -> text.setText( "finished"),500); + handler.postDelayed(() -> { + },700); + handler.postDelayed(() -> text.setVisibility(View.INVISIBLE),900); + handler.postDelayed(() -> { + functions.setAnimation(this, progressView,R.anim.scale_out); + progressView.setVisibility(View.INVISIBLE); + },1000); } - ActivityResultLauncher ActivityResultData = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - new ActivityResultCallback() { - @Override - public void onActivityResult(ActivityResult result) { - if (result.getResultCode() != Activity.RESULT_OK) { - if(result.getResultCode() == Activity.RESULT_CANCELED){ - Toast.makeText(MainActivity.this,"Import data canceled",Toast.LENGTH_SHORT).show(); - } - return; - } - if (result.getData() == null || result.getData().getData() == null){ - Toast.makeText(MainActivity.this, "Fail to load data", Toast.LENGTH_SHORT).show(); - return; - } - //File - Uri uri = result.getData().getData(); - progressBar.setVisibility(View.VISIBLE); - loadingFileTxt.setVisibility(View.VISIBLE); - new Thread(() -> { - String Data = FileSystem.LoadDataFromFile(MainActivity.this, uri); - - if (Data == null) { - Log.d(TAG, "onActivityResult: null data"); - return; - } - handler.post(() -> { - progressBar.setVisibility(View.GONE); - loadingFileTxt.setVisibility(View.GONE); - LoadData(Data); - }); - - }).start(); + int setColor(int resid){ + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(resid, typedValue, true); + return typedValue.data; + } - } - }); + void fileTooLargeException(){ + postMessageException("File is too large"); + } + void fileNotLoadedException(){ + postMessageException("Fail to load file!"); + } + void creatingListException(){ + postMessageException("Fail to create list!"); + } + void postMessageException(String msg){ + handler.post(() -> { + Toast.makeText(MainActivity.this,msg, Toast.LENGTH_SHORT).show(); + loadingFinished(false); + }); + } } \ No newline at end of file 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..b40b0ec --- /dev/null +++ b/app/src/main/java/com/sjapps/jsonlist/functions.java @@ -0,0 +1,41 @@ +package com.sjapps.jsonlist; + +import android.content.Context; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +import androidx.annotation.AnimRes; +import androidx.annotation.NonNull; + +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()); + } + + public static void setAnimation(Context context, @NonNull View view, @AnimRes int animationRes) { + setAnimation(context,view,animationRes,null); + } + + 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/java/JsonData.java b/app/src/main/java/com/sjapps/jsonlist/java/JsonData.java new file mode 100644 index 0000000..1c6b616 --- /dev/null +++ b/app/src/main/java/com/sjapps/jsonlist/java/JsonData.java @@ -0,0 +1,97 @@ +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<>(); + String rawData = ""; + + int previousPos = -1; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public ArrayList getRootList() { + return rootList; + } + + public void setRootList(ArrayList rootList) { + this.rootList = rootList; + } + + public void setRawData(String data) { + this.rawData = data; + } + + public String getRawData() { + return rawData; + } + + public boolean isEmptyPath(){ + return path.equals(""); + } + public void clearPath(){ + path = ""; + } + public String[] splitPath(){ + return path.split("///"); + } + public static String[] splitPath(String path){ + return path.split("///"); + } + + public boolean isRootListNull(){ + return rootList == null; + } + + public void goBack(){ + + if (!previousPosStack.isEmpty()) + previousPos = previousPosStack.pop(); + + String[] pathStrings = splitPath(); + clearPath(); + for (int i = 0; i < pathStrings.length-1; i++) { + setPath(path.concat((isEmptyPath()?"":"///") + pathStrings[i])); + } + + } + + public void addPreviousPos(int pos){ + previousPosStack.push(pos); + } + + public int getPreviousPos(){ + return previousPos; + } + + public void clearPreviousPos(){ + previousPosStack.clear(); + } + + public static String getPathFormat(String path){ + String[] pathStrings = splitPath(path); + StringBuilder builder = new StringBuilder(); + builder.append(pathStrings.length > 3 ? "..." : pathStrings[0]); + + for (int i = pathStrings.length > 3? pathStrings.length-3 : 1; i < pathStrings.length; i++) { + builder.append("/").append(getName(pathStrings[i])); + } + + return builder.toString(); + } + + public static String getName(String str){ + if (str.startsWith("{") && str.contains("}") && str.substring(1, str.indexOf("}")).matches("^[0-9]+")) + return str.substring(str.indexOf("}")+1); + return str; + } +} diff --git a/app/src/main/java/com/sjapps/jsonlist/java/JsonFunctions.java b/app/src/main/java/com/sjapps/jsonlist/java/JsonFunctions.java new file mode 100644 index 0000000..285f5af --- /dev/null +++ b/app/src/main/java/com/sjapps/jsonlist/java/JsonFunctions.java @@ -0,0 +1,178 @@ +package com.sjapps.jsonlist.java; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.util.ArrayList; +import java.util.Set; + +public class JsonFunctions { + + public static ArrayList getJsonArrayRoot(JsonArray array) { + ArrayList mainList = new ArrayList<>(); + ListItem item = new ListItem(); + setArrayName(array,item); + item.setIsArray(true); + item.setListObjects(getJsonArray(array)); + mainList.add(item); + return mainList; + } + + public static ArrayList> getJsonArray(JsonArray array) { + ArrayList> ArrList = new ArrayList<>(); + for (int i = 0; i < array.size(); i++) { + if (array.get(i) instanceof JsonObject) { + ArrayList ListOfItems = getJsonObject((JsonObject) array.get(i)); + ArrList.add(ListOfItems); + continue; + } + if (array.get(i) instanceof JsonArray){ + + ArrayList> ListOfItems = getJsonArray((JsonArray) array.get(i)); + + ArrayList itemsInList = new ArrayList<>(); + ListItem arrItem = new ListItem(); + + setArrayName((JsonArray) array.get(i),arrItem); + arrItem.setIsArray(true); + arrItem.setListObjects(ListOfItems); + + itemsInList.add(arrItem); + ArrList.add(itemsInList); + continue; + } + ListItem item = new ListItem(); + item.setValue(getStringFromJson(array.get(i).toString())); + ArrayList items = new ArrayList<>(); + items.add(item); + ArrList.add(items); + } + return ArrList; + } + + static boolean isArrayOfObjects(JsonArray array) { + for (int i = 0; i < array.size(); i++) { + if (!(array.get(i) instanceof JsonObject)) { + return false; + } + } + return true; + } + + static boolean isArrayOfArray(JsonArray array) { + for (int i = 0; i < array.size(); i++) { + if (!(array.get(i) instanceof JsonArray)) { + return false; + } + } + return true; + } + + public static ArrayList getJsonObject(JsonObject obj) { + ArrayList mainList = new ArrayList<>(); + Set keys = obj.keySet(); + Object[] keysArray = keys.toArray(); + + for (Object o : keysArray) { + ListItem item = new ListItem(); + item.setName(o.toString()); + setItem(obj,o,item); + mainList.add(item); + } + return mainList; + } + + private static void setArrayName(JsonArray array, ListItem item){ + if(isArrayOfObjects(array)) { + item.setName("Objects Array"); + return; + } + if (isArrayOfArray(array)){ + item.setName("Array"); + return; + } + item.setName("Array items"); + } + private static String getStringFromJson(String value){ + return value.startsWith("\"") && value.endsWith("\"") ? value.substring(1,value.length()-1) : value; + } + + private static void setItem(JsonObject obj, Object o, ListItem item){ + if (obj.get(o.toString()) instanceof JsonObject) { + item.setIsObject(true); + ArrayList objList = getJsonObject((JsonObject) obj.get(o.toString())); + item.setObjects(objList); + return; + } + if (obj.get(o.toString()) instanceof JsonArray) { + JsonArray array = (JsonArray) obj.get(o.toString()); + + item.setIsArray(true); + item.setListObjects(getJsonArray(array)); + return; + } + item.setValue(getStringFromJson(obj.get(o.toString()).toString())); + } + + static ArrayList getArrayList(ArrayList> list) { + ArrayList newList = new ArrayList<>(); + for (ArrayList lists : list) { + setId(lists, list.indexOf(lists)); + newList.addAll(lists); + newList.add(new ListItem().Space()); + } + return newList; + } + + private static void setId(ArrayList lists, int id) { + + for (ListItem listItem : lists) { + listItem.setId(id); + } + } + + public static ArrayList getListFromPath(String path, ArrayList rootList) { + + + String[] pathStrings = path.split("///"); + + ArrayList list = rootList; + + for (String pathString : pathStrings) { + + int id = -1; + + if (pathString.startsWith("{") && pathString.contains("}") && pathString.substring(1, pathString.indexOf("}")).matches("^[0-9]+")) { + id = Integer.parseInt(pathString.substring(1, pathString.indexOf("}"))); + } + + for (ListItem item : list) { + if (item.getName() == null || !item.getName().equals(id != -1 ? pathString.substring(pathString.indexOf("}") + 1) : pathString)) + continue; + + if (id != -1 && item.getId() != id) + continue; + + if (item.isArray()) { + list = getArrayList(item.getListObjects()); + break; + } + list = list.get(list.indexOf(item)).getObjects(); + if (list == null) + list = new ArrayList<>(); + break; + } + } + return list; + + } + + public static String getAsPrettyPrint(String data){ + JsonElement json = JsonParser.parseString(data); + Gson gson = new Gson().newBuilder().setPrettyPrinting().create(); + return gson.toJson(json); + } +} diff --git a/app/src/main/java/com/sjapps/jsonlist/ListItem.java b/app/src/main/java/com/sjapps/jsonlist/java/ListItem.java similarity index 51% rename from app/src/main/java/com/sjapps/jsonlist/ListItem.java rename to app/src/main/java/com/sjapps/jsonlist/java/ListItem.java index aea15dc..efca171 100644 --- a/app/src/main/java/com/sjapps/jsonlist/ListItem.java +++ b/app/src/main/java/com/sjapps/jsonlist/java/ListItem.java @@ -1,4 +1,4 @@ -package com.sjapps.jsonlist; +package com.sjapps.jsonlist.java; import java.util.ArrayList; @@ -6,12 +6,12 @@ public class ListItem { private String Name; private String Value; - private boolean isArrayOfObjects; private boolean isArray; private boolean isObject; private boolean isSpace; private ArrayList Objects; private ArrayList> ListObjects; + private int id = -1; public ListItem(){ } @@ -33,14 +33,6 @@ public void setValue(String value) { Value = value; } - public boolean isArrayOfObjects() { - return isArrayOfObjects; - } - - public void setIsArrayOfObjects(boolean array) { - isArrayOfObjects = array; - } - public boolean isArray() { return isArray; } @@ -81,17 +73,25 @@ public void setListObjects(ArrayList> listObjects) { ListObjects = listObjects; } + public void setId(int id) { + this.id = id; + } + + public int getId() { + return id; + } + @Override public String toString() { - return "ListItem{" + - "Name='" + Name + '\'' + - ", Value='" + Value + '\'' + - ", isArrayOfObjects=" + isArrayOfObjects + - ", isArray=" + isArray + - ", isObject=" + isObject + - ", isSpace=" + isSpace + - ", Objects=" + Objects + - ", ListObjects=" + ListObjects + + return "{" + + "\"ID\":" + id + + ",\"Name\":" +(Name!=null && !Name.startsWith("\"")?"\"":"") + Name + (Name!=null && !Name.startsWith("\"")?"\"":"") + + ", \"Value\":" + (Value!=null && !Value.startsWith("\"")?"\"":"") + Value + (Value!=null && !Value.startsWith("\"")?"\"":"") + + ", \"isArray\":" + isArray + + ", \"isObject\":" + isObject + + ", \"isSpace\":" + isSpace + + ", \"Objects\":" + Objects + + ", \"ListObjects\":" + ListObjects + '}'; } @@ -99,4 +99,17 @@ public ListItem Space() { setIsSpace(true); return this; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListItem)) return false; + ListItem item = (ListItem) o; + return isArray() == item.isArray() && isObject() == item.isObject() && isSpace() == item.isSpace() && java.util.Objects.equals(getName(), item.getName()) && java.util.Objects.equals(getValue(), item.getValue()) && java.util.Objects.equals(getObjects(), item.getObjects()) && java.util.Objects.equals(getListObjects(), item.getListObjects()); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(getName(), getValue(), isArray(), isObject(), isSpace(), getObjects(), getListObjects()); + } } 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..10bad74 --- /dev/null +++ b/app/src/main/java/com/sjapps/logs/LogActivity.java @@ -0,0 +1,194 @@ +package com.sjapps.logs; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.util.DisplayMetrics; +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(); + + log.append(getDeviceInfo()); + + for (String s : logs.getLogs()){ + log.append(s); + } + logTxt.setText(log.toString()); + } + + private String getDeviceInfo(){ + + String s = ""; + try { + PackageInfo pInfo = getPackageManager().getPackageInfo( + getPackageName(), PackageManager.GET_META_DATA); + s += "\n App Version Name: " + pInfo.versionName; + s += "\n App Version Code: " + pInfo.versionCode; + s += "\n"; + } catch (PackageManager.NameNotFoundException ignored) {} + s += "\n OS Version: " + System.getProperty("os.version") + " (" + + Build.VERSION.INCREMENTAL + ")"; + s += "\n OS API Level: " + Build.VERSION.SDK_INT; + s += "\n Device: " + Build.DEVICE; + s += "\n Model (and Product): " + Build.MODEL + " (" + Build.PRODUCT + ")"; + s += "\n Manufacturer: " + Build.MANUFACTURER; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + s += "\n screenWidth: " + getWindowManager().getCurrentWindowMetrics().getBounds().width(); + s += "\n screenHeight: " + getWindowManager().getCurrentWindowMetrics().getBounds().height(); + }else { + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + s += "\n screenWidth: " + metrics.widthPixels; + s += "\n screenHeight: " + metrics.heightPixels; + } + + s += "\n"; + + return s; + } + + 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_pop.xml b/app/src/main/res/anim/button_pop.xml new file mode 100644 index 0000000..05b7596 --- /dev/null +++ b/app/src/main/res/anim/button_pop.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + 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/anim/scale_in.xml b/app/src/main/res/anim/scale_in.xml new file mode 100644 index 0000000..7580a87 --- /dev/null +++ b/app/src/main/res/anim/scale_in.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/scale_in2.xml b/app/src/main/res/anim/scale_in2.xml new file mode 100644 index 0000000..11d602b --- /dev/null +++ b/app/src/main/res/anim/scale_in2.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/scale_in_file_img.xml b/app/src/main/res/anim/scale_in_file_img.xml new file mode 100644 index 0000000..03f955c --- /dev/null +++ b/app/src/main/res/anim/scale_in_file_img.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/anim/scale_out.xml b/app/src/main/res/anim/scale_out.xml new file mode 100644 index 0000000..b3440f4 --- /dev/null +++ b/app/src/main/res/anim/scale_out.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_bottom_in.xml b/app/src/main/res/anim/slide_bottom_in.xml new file mode 100644 index 0000000..99edc3b --- /dev/null +++ b/app/src/main/res/anim/slide_bottom_in.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_bottom_out.xml b/app/src/main/res/anim/slide_bottom_out.xml new file mode 100644 index 0000000..8ee0230 --- /dev/null +++ b/app/src/main/res/anim/slide_bottom_out.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_right_in.xml b/app/src/main/res/anim/slide_right_in.xml new file mode 100644 index 0000000..8a34a20 --- /dev/null +++ b/app/src/main/res/anim/slide_right_in.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_right_out.xml b/app/src/main/res/anim/slide_right_out.xml new file mode 100644 index 0000000..6e32903 --- /dev/null +++ b/app/src/main/res/anim/slide_right_out.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_transparent.xml b/app/src/main/res/drawable/background_transparent.xml new file mode 100644 index 0000000..79dbd43 --- /dev/null +++ b/app/src/main/res/drawable/background_transparent.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/github_logo.xml b/app/src/main/res/drawable/github_logo.xml new file mode 100644 index 0000000..30350d2 --- /dev/null +++ b/app/src/main/res/drawable/github_logo.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml index 4d4019e..d2ad538 100644 --- a/app/src/main/res/drawable/ic_back.xml +++ b/app/src/main/res/drawable/ic_back.xml @@ -6,5 +6,5 @@ android:tint="?attr/colorControlNormal"> + android:pathData="M15.5 7.5c.5-.5.3-1.2 0-1.5s-1-.5-1.5 0L9 11C8.5 11.5 8.5 11.9 8.5 12S8.5 12.5 9 13L14 18c.5.5 1.2.3 1.5 0s.5-1 0-1.5L12 13C11.5 12.5 11.5 12.1 11.5 12S11.5 11.5 12 11z"/> diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml index aea55ad..e483926 100644 --- a/app/src/main/res/drawable/ic_close.xml +++ b/app/src/main/res/drawable/ic_close.xml @@ -6,5 +6,5 @@ android:tint="?attr/colorControlNormal"> + android:pathData="M18.295 7.115C18.6475 6.7625 18.6475 6.0575 18.295 5.705S17.2375 5.3525 16.885 5.705L12.7 9.885c-.3525.3525-.5.4-.705.4s-.465-.165-.7-.4L7.115 5.705C6.7625 5.3525 6.0575 5.3525 5.705 5.705S5.3525 6.7625 5.705 7.115L9.885 11.295c.3525.3525.4.5.4.705S10.2375 12.3525 9.885 12.705L5.7 16.885C5.3475 17.2375 5.3525 17.9425 5.705 18.295S6.7625 18.6475 7.115 18.295L11.295 14.115c.3525-.3525.5-.4.705-.4S12.3525 13.7625 12.705 14.115L16.885 18.295C17.2375 18.6475 17.9425 18.6475 18.295 18.295S18.6475 17.2375 18.295 16.885L14.115 12.705c-.3525-.3525-.4-.5-.4-.705S13.7625 11.6475 14.115 11.295z"/> 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/ic_splitscreen.xml b/app/src/main/res/drawable/ic_splitscreen.xml new file mode 100644 index 0000000..5bf0a95 --- /dev/null +++ b/app/src/main/res/drawable/ic_splitscreen.xml @@ -0,0 +1,5 @@ + + + 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/drawable/split_view_bg.xml b/app/src/main/res/drawable/split_view_bg.xml new file mode 100644 index 0000000..9677f9a --- /dev/null +++ b/app/src/main/res/drawable/split_view_bg.xml @@ -0,0 +1,6 @@ + + + + + + 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 e78542e..1c6cdc9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -28,16 +28,16 @@ + + - + android:weightSum="2" + android:orientation="vertical" + > + + + + + + + + +