Migrate crash reporter UI to Jetpack Compose
This commit is contained in:
parent
b53aaeec4e
commit
a20707fc3c
@ -1,24 +1,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.mm20.launcher2.crashreporter">
|
package="de.mm20.launcher2.crashreporter">
|
||||||
<application
|
<application>
|
||||||
android:supportsRtl="true">
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="com.balsikandar.crashreporter.CrashReporterInitProvider"
|
android:name="com.balsikandar.crashreporter.CrashReporterInitProvider"
|
||||||
android:authorities="${applicationId}.CrashReporterInitProvider"
|
android:authorities="${applicationId}.CrashReporterInitProvider"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.balsikandar.crashreporter.ui.CrashReporterActivity"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:taskAffinity="com.balsikandar.android.task"
|
|
||||||
android:theme="@style/CrashReporter.Theme" />
|
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="com.balsikandar.crashreporter.ui.LogMessageActivity"
|
|
||||||
android:parentActivityName="com.balsikandar.crashreporter.ui.CrashReporterActivity"
|
|
||||||
android:theme="@style/CrashReporter.Theme" />
|
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@ -3,7 +3,6 @@ package com.balsikandar.crashreporter;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.ui.CrashReporterActivity;
|
|
||||||
import com.balsikandar.crashreporter.utils.CrashReporterNotInitializedException;
|
import com.balsikandar.crashreporter.utils.CrashReporterNotInitializedException;
|
||||||
import com.balsikandar.crashreporter.utils.CrashReporterExceptionHandler;
|
import com.balsikandar.crashreporter.utils.CrashReporterExceptionHandler;
|
||||||
import com.balsikandar.crashreporter.utils.CrashUtil;
|
import com.balsikandar.crashreporter.utils.CrashUtil;
|
||||||
@ -61,10 +60,6 @@ public class CrashReporter {
|
|||||||
CrashUtil.logException(exception);
|
CrashUtil.logException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Intent getLaunchIntent() {
|
|
||||||
return new Intent(applicationContext, CrashReporterActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void disableNotification() {
|
public static void disableNotification() {
|
||||||
isNotificationEnabled = false;
|
isNotificationEnabled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
package com.balsikandar.crashreporter.adapter;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.ui.LogMessageActivity;
|
|
||||||
import com.balsikandar.crashreporter.utils.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import de.mm20.launcher2.crashreporter.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by bali on 10/08/17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class CrashLogAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private ArrayList<File> crashFileList;
|
|
||||||
|
|
||||||
public CrashLogAdapter(Context context, ArrayList<File> allCrashLogs) {
|
|
||||||
this.context = context;
|
|
||||||
crashFileList = allCrashLogs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
View view = LayoutInflater.from(context).inflate(R.layout.custom_item, null);
|
|
||||||
return new CrashLogViewHolder(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
|
||||||
((CrashLogViewHolder) holder).setUpViewHolder(context, crashFileList.get(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return crashFileList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void updateList(ArrayList<File> allCrashLogs) {
|
|
||||||
crashFileList = allCrashLogs;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class CrashLogViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
private TextView textViewMsg, messageLogTime;
|
|
||||||
|
|
||||||
CrashLogViewHolder(View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
messageLogTime = itemView.findViewById(R.id.messageLogTime);
|
|
||||||
textViewMsg = itemView.findViewById(R.id.textViewMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setUpViewHolder(final Context context, final File file) {
|
|
||||||
final String filePath = file.getAbsolutePath();
|
|
||||||
messageLogTime.setText(file.getName().replaceAll("[a-zA-Z_.]", ""));
|
|
||||||
textViewMsg.setText(FileUtils.readFirstLineFromFile(new File(filePath)));
|
|
||||||
|
|
||||||
textViewMsg.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
Intent intent = new Intent(context, LogMessageActivity.class);
|
|
||||||
intent.putExtra("LogMessage", filePath);
|
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
package com.balsikandar.crashreporter.adapter;
|
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.ui.CrashLogFragment;
|
|
||||||
import com.balsikandar.crashreporter.ui.ExceptionLogFragment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by bali on 11/08/17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MainPagerAdapter extends FragmentPagerAdapter {
|
|
||||||
|
|
||||||
private CrashLogFragment crashLogFragment;
|
|
||||||
private ExceptionLogFragment exceptionLogFragment;
|
|
||||||
private String[] titles;
|
|
||||||
|
|
||||||
public MainPagerAdapter(FragmentManager fm, String[] titles) {
|
|
||||||
super(fm);
|
|
||||||
this.titles = titles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment getItem(int position) {
|
|
||||||
if (position == 0) {
|
|
||||||
return crashLogFragment = new CrashLogFragment();
|
|
||||||
} else if (position == 1) {
|
|
||||||
return exceptionLogFragment = new ExceptionLogFragment();
|
|
||||||
} else {
|
|
||||||
return new CrashLogFragment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getPageTitle(int position) {
|
|
||||||
return titles[position];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearLogs() {
|
|
||||||
crashLogFragment.clearLog();
|
|
||||||
exceptionLogFragment.clearLog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
package com.balsikandar.crashreporter.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.CrashReporter;
|
|
||||||
import com.balsikandar.crashreporter.adapter.CrashLogAdapter;
|
|
||||||
import com.balsikandar.crashreporter.utils.Constants;
|
|
||||||
import com.balsikandar.crashreporter.utils.CrashUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import de.mm20.launcher2.crashreporter.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by bali on 11/08/17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class CrashLogFragment extends Fragment {
|
|
||||||
|
|
||||||
private CrashLogAdapter logAdapter;
|
|
||||||
|
|
||||||
private RecyclerView crashRecyclerView;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.crash_log, container, false);
|
|
||||||
crashRecyclerView = (RecyclerView) view.findViewById(R.id.crashRecyclerView);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
loadAdapter(getActivity(), crashRecyclerView);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAdapter(Context context, RecyclerView crashRecyclerView) {
|
|
||||||
|
|
||||||
logAdapter = new CrashLogAdapter(context, getAllCrashes());
|
|
||||||
crashRecyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
|
|
||||||
crashRecyclerView.setAdapter(logAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearLog() {
|
|
||||||
if (logAdapter != null) {
|
|
||||||
logAdapter.updateList(getAllCrashes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private ArrayList<File> getAllCrashes() {
|
|
||||||
String directoryPath;
|
|
||||||
String crashReportPath = CrashReporter.getCrashReportPath();
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(crashReportPath)) {
|
|
||||||
directoryPath = CrashUtil.getDefaultPath();
|
|
||||||
} else {
|
|
||||||
directoryPath = crashReportPath;
|
|
||||||
}
|
|
||||||
File directory = new File(directoryPath);
|
|
||||||
if (!directory.exists() || !directory.isDirectory()) {
|
|
||||||
throw new RuntimeException("The path provided doesn't exists : " + directoryPath);
|
|
||||||
}
|
|
||||||
ArrayList<File> listOfFiles = new ArrayList<>(Arrays.asList(directory.listFiles()));
|
|
||||||
for (Iterator<File> iterator = listOfFiles.iterator(); iterator.hasNext(); ) {
|
|
||||||
if (iterator.next().getName().contains(Constants.EXCEPTION_SUFFIX)) {
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(listOfFiles, Collections.reverseOrder());
|
|
||||||
return listOfFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
package com.balsikandar.crashreporter.ui;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.viewpager.widget.ViewPager;
|
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.CrashReporter;
|
|
||||||
import com.balsikandar.crashreporter.adapter.MainPagerAdapter;
|
|
||||||
import com.balsikandar.crashreporter.utils.Constants;
|
|
||||||
import com.balsikandar.crashreporter.utils.CrashUtil;
|
|
||||||
import com.balsikandar.crashreporter.utils.FileUtils;
|
|
||||||
import com.balsikandar.crashreporter.utils.SimplePageChangeListener;
|
|
||||||
import com.google.android.material.tabs.TabLayout;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import de.mm20.launcher2.crashreporter.R;
|
|
||||||
|
|
||||||
public class CrashReporterActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private MainPagerAdapter mainPagerAdapter;
|
|
||||||
private int selectedTabPosition = 0;
|
|
||||||
|
|
||||||
//region activity callbacks
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.log_main_menu, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.delete_crash_logs) {
|
|
||||||
clearCrashLog();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getTheme().applyStyle(R.style.DefaultColors, true);
|
|
||||||
setContentView(R.layout.crash_reporter_activity);
|
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
toolbar.setTitle(getString(R.string.crash_reporter));
|
|
||||||
toolbar.setSubtitle(getApplicationName());
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
|
|
||||||
ViewPager viewPager = findViewById(R.id.viewpager);
|
|
||||||
if (viewPager != null) {
|
|
||||||
setupViewPager(viewPager);
|
|
||||||
}
|
|
||||||
|
|
||||||
TabLayout tabLayout = findViewById(R.id.tabs);
|
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
private void clearCrashLog() {
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String crashReportPath = TextUtils.isEmpty(CrashReporter.getCrashReportPath()) ?
|
|
||||||
CrashUtil.getDefaultPath() : CrashReporter.getCrashReportPath();
|
|
||||||
|
|
||||||
File[] logs = new File(crashReportPath).listFiles();
|
|
||||||
for (File file : logs) {
|
|
||||||
FileUtils.delete(file);
|
|
||||||
}
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mainPagerAdapter.clearLogs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupViewPager(ViewPager viewPager) {
|
|
||||||
String[] titles = {getString(R.string.crashes), getString(R.string.exceptions)};
|
|
||||||
mainPagerAdapter = new MainPagerAdapter(getSupportFragmentManager(), titles);
|
|
||||||
viewPager.setAdapter(mainPagerAdapter);
|
|
||||||
|
|
||||||
viewPager.addOnPageChangeListener(new SimplePageChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onPageSelected(int position) {
|
|
||||||
selectedTabPosition = position;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent != null && !intent.getBooleanExtra(Constants.LANDING, false)) {
|
|
||||||
selectedTabPosition = 1;
|
|
||||||
}
|
|
||||||
viewPager.setCurrentItem(selectedTabPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getApplicationName() {
|
|
||||||
ApplicationInfo applicationInfo = getApplicationInfo();
|
|
||||||
int stringId = applicationInfo.labelRes;
|
|
||||||
return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : getString(stringId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
package com.balsikandar.crashreporter.ui;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.CrashReporter;
|
|
||||||
import com.balsikandar.crashreporter.adapter.CrashLogAdapter;
|
|
||||||
import com.balsikandar.crashreporter.utils.Constants;
|
|
||||||
import com.balsikandar.crashreporter.utils.CrashUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import de.mm20.launcher2.crashreporter.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by bali on 11/08/17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ExceptionLogFragment extends Fragment {
|
|
||||||
|
|
||||||
private CrashLogAdapter logAdapter;
|
|
||||||
|
|
||||||
private RecyclerView exceptionRecyclerView;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.exception_log, container, false);
|
|
||||||
exceptionRecyclerView = (RecyclerView) view.findViewById(R.id.exceptionRecyclerView);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
loadAdapter(getActivity(), exceptionRecyclerView);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAdapter(Context context, RecyclerView exceptionRecyclerView) {
|
|
||||||
|
|
||||||
logAdapter = new CrashLogAdapter(context, getAllExceptions());
|
|
||||||
exceptionRecyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false));
|
|
||||||
exceptionRecyclerView.setAdapter(logAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearLog() {
|
|
||||||
if (logAdapter != null) {
|
|
||||||
logAdapter.updateList(getAllExceptions());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<File> getAllExceptions() {
|
|
||||||
String directoryPath;
|
|
||||||
String crashReportPath = CrashReporter.getCrashReportPath();
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(crashReportPath)){
|
|
||||||
directoryPath = CrashUtil.getDefaultPath();
|
|
||||||
} else{
|
|
||||||
directoryPath = crashReportPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
File directory = new File(directoryPath);
|
|
||||||
if (!directory.exists() || !directory.isDirectory()){
|
|
||||||
throw new RuntimeException("The path provided doesn't exists : " + directoryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<File> listOfFiles = new ArrayList<>(Arrays.asList(directory.listFiles()));
|
|
||||||
for (Iterator<File> iterator = listOfFiles.iterator(); iterator.hasNext(); ) {
|
|
||||||
if (iterator.next().getName().contains(Constants.CRASH_SUFFIX)) {
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collections.sort(listOfFiles, Collections.reverseOrder());
|
|
||||||
return listOfFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
package com.balsikandar.crashreporter.ui;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.utils.AppUtils;
|
|
||||||
import com.balsikandar.crashreporter.utils.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import de.mm20.launcher2.crashreporter.R;
|
|
||||||
|
|
||||||
public class LogMessageActivity extends AppCompatActivity {
|
|
||||||
|
|
||||||
private TextView appInfo;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_log_message);
|
|
||||||
appInfo = findViewById(R.id.appInfo);
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent != null) {
|
|
||||||
String dirPath = intent.getStringExtra("LogMessage");
|
|
||||||
File file = new File(dirPath);
|
|
||||||
String crashLog = FileUtils.readFromFile(file);
|
|
||||||
TextView textView = findViewById(R.id.logMessage);
|
|
||||||
textView.setText(crashLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
Toolbar myToolbar = findViewById(R.id.toolbar);
|
|
||||||
myToolbar.setTitle(getString(R.string.crash_reporter));
|
|
||||||
setSupportActionBar(myToolbar);
|
|
||||||
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
getAppInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getAppInfo() {
|
|
||||||
appInfo.setText(AppUtils.getDeviceDetails(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.crash_detail_menu, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
Intent intent = getIntent();
|
|
||||||
String filePath = null;
|
|
||||||
if (intent != null) {
|
|
||||||
filePath = intent.getStringExtra("LogMessage");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.getItemId() == R.id.delete_log) {
|
|
||||||
if (FileUtils.delete(filePath)) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.share_crash_log) {
|
|
||||||
shareCrashReport(filePath);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void shareCrashReport(String filePath) {
|
|
||||||
Uri uri = FileProvider.getUriForFile(this,
|
|
||||||
this.getApplicationContext().getPackageName() + ".fileprovider",
|
|
||||||
new File(filePath));
|
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
|
||||||
intent.setType("*/*");
|
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, appInfo.getText().toString());
|
|
||||||
intent.putExtra(Intent.EXTRA_STREAM, uri);
|
|
||||||
startActivity(Intent.createChooser(intent, "Share via"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,5 @@
|
|||||||
package com.balsikandar.crashreporter.utils;
|
package com.balsikandar.crashreporter.utils;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
@ -11,8 +8,6 @@ import android.content.pm.ResolveInfo;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
|
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -40,9 +35,8 @@ public class AppUtils {
|
|||||||
|
|
||||||
public static String getDeviceDetails(Context context) {
|
public static String getDeviceDetails(Context context) {
|
||||||
|
|
||||||
return "Device Information\n"
|
return "APP.VERSION : " + getAppVersion(context)
|
||||||
+ "\nDEVICE.ID : " + getDeviceId(context)
|
+ "\nAPP.VERSIONCODE : " + getAppVersionCode(context)
|
||||||
+ "\nAPP.VERSION : " + getAppVersion(context)
|
|
||||||
+ "\nLAUNCHER.APP : " + getCurrentLauncherApp(context)
|
+ "\nLAUNCHER.APP : " + getCurrentLauncherApp(context)
|
||||||
+ "\nTIMEZONE : " + timeZone()
|
+ "\nTIMEZONE : " + timeZone()
|
||||||
+ "\nVERSION.RELEASE : " + Build.VERSION.RELEASE
|
+ "\nVERSION.RELEASE : " + Build.VERSION.RELEASE
|
||||||
@ -61,12 +55,9 @@ public class AppUtils {
|
|||||||
+ "\nMANUFACTURER : " + Build.MANUFACTURER
|
+ "\nMANUFACTURER : " + Build.MANUFACTURER
|
||||||
+ "\nMODEL : " + Build.MODEL
|
+ "\nMODEL : " + Build.MODEL
|
||||||
+ "\nPRODUCT : " + Build.PRODUCT
|
+ "\nPRODUCT : " + Build.PRODUCT
|
||||||
+ "\nSERIAL : " + Build.SERIAL
|
|
||||||
+ "\nTAGS : " + Build.TAGS
|
+ "\nTAGS : " + Build.TAGS
|
||||||
+ "\nTIME : " + Build.TIME
|
+ "\nTIME : " + Build.TIME
|
||||||
+ "\nTYPE : " + Build.TYPE
|
+ "\nTYPE : " + Build.TYPE;
|
||||||
+ "\nUNKNOWN : " + Build.UNKNOWN
|
|
||||||
+ "\nUSER : " + Build.USER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String timeZone() {
|
private static String timeZone() {
|
||||||
@ -94,7 +85,7 @@ public class AppUtils {
|
|||||||
return androidId;
|
return androidId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getAppVersion(Context context) {
|
private static int getAppVersionCode(Context context) {
|
||||||
try {
|
try {
|
||||||
PackageInfo packageInfo = context.getPackageManager()
|
PackageInfo packageInfo = context.getPackageManager()
|
||||||
.getPackageInfo(context.getPackageName(), 0);
|
.getPackageInfo(context.getPackageName(), 0);
|
||||||
@ -103,4 +94,14 @@ public class AppUtils {
|
|||||||
throw new RuntimeException("Could not get package name: " + e);
|
throw new RuntimeException("Could not get package name: " + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getAppVersion(Context context) {
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = context.getPackageManager()
|
||||||
|
.getPackageInfo(context.getPackageName(), 0);
|
||||||
|
return packageInfo.versionName;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
throw new RuntimeException("Could not get package name: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,28 +3,30 @@ package com.balsikandar.crashreporter.utils;
|
|||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import com.balsikandar.crashreporter.CrashReporter;
|
import com.balsikandar.crashreporter.CrashReporter;
|
||||||
import de.mm20.launcher2.crashreporter.R;
|
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import de.mm20.launcher2.crashreporter.R;
|
||||||
|
|
||||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||||
import static com.balsikandar.crashreporter.utils.Constants.CHANNEL_NOTIFICATION_ID;
|
import static com.balsikandar.crashreporter.utils.Constants.CHANNEL_NOTIFICATION_ID;
|
||||||
|
|
||||||
@ -47,7 +49,13 @@ public class CrashUtil {
|
|||||||
String filename = getCrashLogTime() + Constants.CRASH_SUFFIX + Constants.FILE_EXTENSION;
|
String filename = getCrashLogTime() + Constants.CRASH_SUFFIX + Constants.FILE_EXTENSION;
|
||||||
writeToFile(crashReportPath, filename, getStackTrace(throwable));
|
writeToFile(crashReportPath, filename, getStackTrace(throwable));
|
||||||
|
|
||||||
showNotification(throwable.getLocalizedMessage(), true);
|
//if (crashReportPath.isEmpty()) crashReportPath = getDefaultPath();
|
||||||
|
|
||||||
|
try {
|
||||||
|
showNotification(throwable.getLocalizedMessage(), filename);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void logException(final Exception exception) {
|
public static void logException(final Exception exception) {
|
||||||
@ -91,7 +99,7 @@ public class CrashUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showNotification(String localisedMsg, boolean isCrash) {
|
private static void showNotification(String localisedMsg, String fileName) throws UnsupportedEncodingException {
|
||||||
|
|
||||||
if (CrashReporter.isNotificationEnabled()) {
|
if (CrashReporter.isNotificationEnabled()) {
|
||||||
Context context = CrashReporter.getContext();
|
Context context = CrashReporter.getContext();
|
||||||
@ -101,8 +109,11 @@ public class CrashUtil {
|
|||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_NOTIFICATION_ID);
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_NOTIFICATION_ID);
|
||||||
builder.setSmallIcon(R.drawable.ic_warning_black_24dp);
|
builder.setSmallIcon(R.drawable.ic_warning_black_24dp);
|
||||||
|
|
||||||
Intent intent = CrashReporter.getLaunchIntent();
|
String filePath = new File(getDefaultPath(), fileName).getAbsolutePath();
|
||||||
intent.putExtra(Constants.LANDING, isCrash);
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setComponent(new ComponentName(context.getPackageName(), "de.mm20.launcher2.ui.settings.SettingsActivity"));
|
||||||
|
intent.putExtra("de.mm20.launcher2.settings.ROUTE", "settings/debug/crashreporter/report?fileName=" + URLEncoder.encode(filePath, "utf8"));
|
||||||
intent.setAction(Long.toString(System.currentTimeMillis()));
|
intent.setAction(Long.toString(System.currentTimeMillis()));
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
package de.mm20.launcher2.crashreporter
|
||||||
|
|
||||||
|
import android.icu.text.SimpleDateFormat
|
||||||
|
import android.icu.util.TimeZone
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class CrashReport(
|
||||||
|
val type: CrashReportType,
|
||||||
|
val time: Date,
|
||||||
|
val summary: String,
|
||||||
|
val stacktrace: String?,
|
||||||
|
val filePath: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
suspend fun fromFile(file: File, loadStackTrace: Boolean): CrashReport {
|
||||||
|
val df = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||||
|
val time = df.parse(file.name.replace("[a-zA-Z_.]", ""))
|
||||||
|
val content = if (loadStackTrace) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
file.inputStream().bufferedReader().use {
|
||||||
|
it.readText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
val summary = content?.substringBefore("\n")
|
||||||
|
?: withContext(Dispatchers.IO) {
|
||||||
|
file.inputStream().bufferedReader().use {
|
||||||
|
it.readLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CrashReport(
|
||||||
|
type = if (file.name.endsWith("_crash.txt")) CrashReportType.Crash else CrashReportType.Exception,
|
||||||
|
time = time,
|
||||||
|
summary = summary,
|
||||||
|
stacktrace = content,
|
||||||
|
filePath = file.absolutePath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class CrashReportType {
|
||||||
|
Exception,
|
||||||
|
Crash
|
||||||
|
}
|
||||||
@ -1,8 +1,15 @@
|
|||||||
package de.mm20.launcher2.crashreporter
|
package de.mm20.launcher2.crashreporter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.balsikandar.crashreporter.CrashReporter
|
||||||
|
import com.balsikandar.crashreporter.utils.AppUtils
|
||||||
|
import com.balsikandar.crashreporter.utils.CrashUtil
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
object CrashReporter {
|
object CrashReporter {
|
||||||
fun logException(e: Exception) {
|
fun logException(e: Exception) {
|
||||||
@ -12,7 +19,23 @@ object CrashReporter {
|
|||||||
Log.e("MM20", Log.getStackTraceString(e))
|
Log.e("MM20", Log.getStackTraceString(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLaunchIntent(): Intent {
|
suspend fun getCrashReports(): List<CrashReport> {
|
||||||
return com.balsikandar.crashreporter.CrashReporter.getLaunchIntent()
|
val files = withContext(Dispatchers.IO) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val path = CrashReporter.getCrashReportPath()?.takeIf { it.isEmpty() } ?: CrashUtil.getDefaultPath()
|
||||||
|
File(path).listFiles { f ->
|
||||||
|
f.lastModified() > now - 7 * 24 * 60 * 60 * 1000L
|
||||||
|
}?.sortedByDescending { it.lastModified() }
|
||||||
|
}
|
||||||
|
return files?.map { CrashReport.fromFile(it, false) } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCrashReport(filePath: String): CrashReport {
|
||||||
|
val path = CrashReporter.getCrashReportPath()?.takeIf { it.isEmpty() } ?: CrashUtil.getDefaultPath()
|
||||||
|
return CrashReport.fromFile(File(filePath), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDeviceInformation(context: Context): String {
|
||||||
|
return AppUtils.getDeviceDetails(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,45 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
tools:context="com.balsikandar.crashreporter.ui.LogMessageActivity">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/appbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<HorizontalScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/logMessage"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:textColor="?colorAccent" />
|
|
||||||
</HorizontalScrollView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/appInfo"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:textColor="?android:textColorPrimary" />
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/crashRecyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context="com.balsikandar.crashreporter.ui.CrashReporterActivity">
|
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/appbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/tabs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager
|
|
||||||
android:id="@+id/viewpager"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_below="@+id/appbar"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/messageLogTime"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:layout_marginLeft="10dp"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:layout_marginRight="10dp"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:textColor="?android:textColorPrimary"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textViewMsg"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_below="@+id/messageLogTime"
|
|
||||||
android:maxLines="4"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="5dp"
|
|
||||||
android:layout_marginLeft="10dp"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:layout_marginRight="10dp"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:textColor="?android:textColorSecondary"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_below="@+id/textViewMsg"
|
|
||||||
android:layout_marginTop="3dp"
|
|
||||||
android:background="#dcdada" />
|
|
||||||
</RelativeLayout>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/exceptionRecyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
@ -2,6 +2,7 @@ package de.mm20.launcher2.ui.component.preferences
|
|||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@ -23,7 +24,8 @@ import de.mm20.launcher2.ui.locals.LocalNavController
|
|||||||
fun PreferenceScreen(
|
fun PreferenceScreen(
|
||||||
title: String,
|
title: String,
|
||||||
floatingActionButton: @Composable () -> Unit = {},
|
floatingActionButton: @Composable () -> Unit = {},
|
||||||
content: LazyListScope.() -> Unit
|
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||||
|
content: LazyListScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
@ -50,6 +52,7 @@ fun PreferenceScreen(
|
|||||||
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions = topBarActions
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|||||||
@ -6,10 +6,7 @@ import androidx.compose.animation.ExperimentalAnimationApi
|
|||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||||
import com.google.accompanist.navigation.animation.composable
|
import com.google.accompanist.navigation.animation.composable
|
||||||
@ -30,6 +27,8 @@ import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
|
|||||||
import de.mm20.launcher2.ui.settings.calendarwidget.CalendarWidgetSettingsScreen
|
import de.mm20.launcher2.ui.settings.calendarwidget.CalendarWidgetSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
|
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen
|
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.crashreporter.CrashReportScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.crashreporter.CrashReporterScreen
|
||||||
import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen
|
import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.easteregg.EasterEggSettingsScreen
|
import de.mm20.launcher2.ui.settings.easteregg.EasterEggSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen
|
import de.mm20.launcher2.ui.settings.filesearch.FileSearchSettingsScreen
|
||||||
@ -55,6 +54,12 @@ class SettingsActivity : BaseActivity() {
|
|||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val navController = rememberAnimatedNavController()
|
val navController = rememberAnimatedNavController()
|
||||||
|
|
||||||
|
LaunchedEffect(intent) {
|
||||||
|
intent.getStringExtra("de.mm20.launcher2.settings.ROUTE")
|
||||||
|
?.let { navController.navigate(it) }
|
||||||
|
}
|
||||||
|
|
||||||
val cardStyle by remember {
|
val cardStyle by remember {
|
||||||
dataStore.data.map { it.cards }.distinctUntilChanged()
|
dataStore.data.map { it.cards }.distinctUntilChanged()
|
||||||
}.collectAsState(
|
}.collectAsState(
|
||||||
@ -127,6 +132,17 @@ class SettingsActivity : BaseActivity() {
|
|||||||
composable("settings/debug") {
|
composable("settings/debug") {
|
||||||
DebugSettingsScreen()
|
DebugSettingsScreen()
|
||||||
}
|
}
|
||||||
|
composable("settings/debug/crashreporter") {
|
||||||
|
CrashReporterScreen()
|
||||||
|
}
|
||||||
|
composable("settings/debug/crashreporter/report?fileName={fileName}",
|
||||||
|
arguments = listOf(navArgument("fileName") {
|
||||||
|
nullable = false
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
val fileName = it.arguments?.getString("fileName")
|
||||||
|
CrashReportScreen(fileName!!)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
"settings/license?library={libraryName}",
|
"settings/license?library={libraryName}",
|
||||||
arguments = listOf(navArgument("libraryName") {
|
arguments = listOf(navArgument("libraryName") {
|
||||||
|
|||||||
@ -0,0 +1,92 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.crashreporter
|
||||||
|
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.BugReport
|
||||||
|
import androidx.compose.material.icons.rounded.Share
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReportType
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CrashReportScreen(fileName: String) {
|
||||||
|
val viewModel: CrashReportScreenVM = viewModel()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val crashReport by remember(fileName) { viewModel.getCrashReport(fileName) }.observeAsState()
|
||||||
|
PreferenceScreen(
|
||||||
|
title = when (crashReport?.type) {
|
||||||
|
CrashReportType.Exception -> "Exception"
|
||||||
|
CrashReportType.Crash -> "Crash"
|
||||||
|
null -> ""
|
||||||
|
},
|
||||||
|
topBarActions = {
|
||||||
|
IconButton(onClick = { crashReport?.let { viewModel.shareCrashReport(context, it) } }) {
|
||||||
|
Icon(imageVector = Icons.Rounded.Share, contentDescription = null)
|
||||||
|
}
|
||||||
|
if (crashReport?.type == CrashReportType.Crash) {
|
||||||
|
IconButton(onClick = { crashReport?.let { viewModel.createIssue(context, it) } }) {
|
||||||
|
Icon(imageVector = Icons.Rounded.BugReport, contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
color = if (crashReport?.type == CrashReportType.Crash) {
|
||||||
|
MaterialTheme.colorScheme.errorContainer
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(
|
||||||
|
rememberScrollState()
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
crashReport?.stacktrace?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Text(text = "Device Information", style = MaterialTheme.typography.titleMedium)
|
||||||
|
val deviceInformation = remember { viewModel.getDeviceInformation(context) }
|
||||||
|
Text(
|
||||||
|
text = deviceInformation,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.crashreporter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.liveData
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReport
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
class CrashReportScreenVM : ViewModel() {
|
||||||
|
fun getCrashReport(fileName: String) = liveData<CrashReport?> {
|
||||||
|
emit(CrashReporter.getCrashReport(fileName))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDeviceInformation(context: Context): String {
|
||||||
|
return CrashReporter.getDeviceInformation(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createIssue(context: Context, crashReport: CrashReport) {
|
||||||
|
val stacktrace = crashReport.stacktrace?.lines()?.let {
|
||||||
|
if (it.size > 15) it.subList(0, 15)
|
||||||
|
.joinToString("\n") + "\n[${it.size - 15} lines truncated]"
|
||||||
|
else it.joinToString("\n")
|
||||||
|
} ?: ""
|
||||||
|
val body =
|
||||||
|
"## Description\n\n" +
|
||||||
|
"*Please provide as many information about the crash as possible (What did you do before the crash happened? Steps to reproduce?)*\n\n" +
|
||||||
|
"## Strack trace\n\n" +
|
||||||
|
"```\n" +
|
||||||
|
"${stacktrace}\n" +
|
||||||
|
"```\n\n" +
|
||||||
|
"## Device info\n" +
|
||||||
|
"${getDeviceInformation(context).replace("\n", "<br>")}\n"
|
||||||
|
val url = "https://github.com/MM2-0/Kvaesitso/issues/new?labels=crash+report&body=${
|
||||||
|
URLEncoder.encode(
|
||||||
|
body,
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
data = Uri.parse(url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareCrashReport(context: Context, crashReport: CrashReport) {
|
||||||
|
|
||||||
|
val uri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
context.applicationContext.packageName + ".fileprovider",
|
||||||
|
File(crashReport.filePath)
|
||||||
|
)
|
||||||
|
val intent = Intent(Intent.ACTION_SEND)
|
||||||
|
intent.type = "*/*"
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, CrashReporter.getDeviceInformation(context))
|
||||||
|
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
context.startActivity(Intent.createChooser(intent, "Share via"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.crashreporter
|
||||||
|
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Error
|
||||||
|
import androidx.compose.material.icons.rounded.ErrorOutline
|
||||||
|
import androidx.compose.material.icons.rounded.Warning
|
||||||
|
import androidx.compose.material.icons.rounded.WarningAmber
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReportType
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun CrashReporterScreen() {
|
||||||
|
val viewModel: CrashReporterScreenVM = viewModel()
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val reports by viewModel.reports.observeAsState()
|
||||||
|
val showExceptions by viewModel.showExceptions.observeAsState(true)
|
||||||
|
val showCrashes by viewModel.showCrashes.observeAsState(true)
|
||||||
|
PreferenceScreen(title = stringResource(R.string.preference_crash_reporter)) {
|
||||||
|
reports?.let {
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
IconToggleButton(checked = showExceptions, onCheckedChange = { value ->
|
||||||
|
viewModel.setShowExceptions(value)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (showExceptions) Icons.Rounded.Warning else Icons.Rounded.WarningAmber,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.alpha(if (showExceptions) 1f else 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconToggleButton(checked = showCrashes, onCheckedChange = { value ->
|
||||||
|
viewModel.setShowCrashes(value)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (showCrashes) Icons.Rounded.Error else Icons.Rounded.ErrorOutline,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.alpha(if (showCrashes) 1f else 0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(it) {
|
||||||
|
OutlinedCard(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 4.dp, horizontal = 8.dp)
|
||||||
|
,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
navController?.navigate("settings/debug/crashreporter/report?fileName=${it.filePath}")
|
||||||
|
}
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = DateUtils.formatDateTime(
|
||||||
|
LocalContext.current,
|
||||||
|
it.time.time,
|
||||||
|
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides if (it.type == CrashReportType.Exception) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
|
imageVector = if (it.type == CrashReportType.Exception) Icons.Rounded.Warning else Icons.Rounded.Error,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (it.type == CrashReportType.Exception) "Exception" else "Crash",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = it.summary,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: item {
|
||||||
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.crashreporter
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.liveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReport
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReportType
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class CrashReporterScreenVM: ViewModel() {
|
||||||
|
fun setShowCrashes(showCrashes: Boolean) {
|
||||||
|
this.showCrashes.value = showCrashes
|
||||||
|
updateReports()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShowExceptions(showExceptions: Boolean) {
|
||||||
|
this.showExceptions.value = showExceptions
|
||||||
|
updateReports()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateReports() {
|
||||||
|
val exceptions = showExceptions.value == true
|
||||||
|
val crashes = showCrashes.value == true
|
||||||
|
reports.value = _reports?.filter {
|
||||||
|
it.type == CrashReportType.Exception && exceptions ||
|
||||||
|
it.type == CrashReportType.Crash && crashes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val showExceptions = MutableLiveData(true)
|
||||||
|
val showCrashes = MutableLiveData(true)
|
||||||
|
|
||||||
|
val reports = MutableLiveData<List<CrashReport>?>(null)
|
||||||
|
private var _reports: List<CrashReport>? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_reports = CrashReporter.getCrashReports()
|
||||||
|
reports.value = _reports
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import de.mm20.launcher2.ktx.tryStartActivity
|
|||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import java.io.File
|
|||||||
fun DebugSettingsScreen() {
|
fun DebugSettingsScreen() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val navController = LocalNavController.current
|
||||||
PreferenceScreen(
|
PreferenceScreen(
|
||||||
stringResource(R.string.preference_screen_debug)
|
stringResource(R.string.preference_screen_debug)
|
||||||
) {
|
) {
|
||||||
@ -27,7 +29,7 @@ fun DebugSettingsScreen() {
|
|||||||
title = stringResource(R.string.preference_crash_reporter),
|
title = stringResource(R.string.preference_crash_reporter),
|
||||||
summary = stringResource(R.string.preference_crash_reporter_summary),
|
summary = stringResource(R.string.preference_crash_reporter_summary),
|
||||||
onClick = {
|
onClick = {
|
||||||
context.startActivity(CrashReporter.getLaunchIntent())
|
navController?.navigate("settings/debug/crashreporter")
|
||||||
})
|
})
|
||||||
|
|
||||||
Preference(
|
Preference(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user