[app] Fix select log text (#1439)
This commit is contained in:
parent
e1cf9551b4
commit
48fd4c042c
|
|
@ -21,10 +21,8 @@
|
||||||
package org.lsposed.manager.ui.fragment;
|
package org.lsposed.manager.ui.fragment;
|
||||||
|
|
||||||
import static org.lsposed.manager.App.TAG;
|
import static org.lsposed.manager.App.TAG;
|
||||||
import static java.lang.Math.max;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
@ -32,11 +30,10 @@ import android.os.Looper;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
|
@ -44,60 +41,47 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
import org.lsposed.manager.App;
|
|
||||||
import org.lsposed.manager.ConfigManager;
|
import org.lsposed.manager.ConfigManager;
|
||||||
import org.lsposed.manager.R;
|
import org.lsposed.manager.R;
|
||||||
import org.lsposed.manager.databinding.FragmentLogsBinding;
|
import org.lsposed.manager.databinding.FragmentLogsBinding;
|
||||||
import org.lsposed.manager.databinding.ItemLogBinding;
|
|
||||||
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
|
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
|
||||||
import org.lsposed.manager.util.SimpleStatefulAdaptor;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import rikka.core.os.FileUtils;
|
import rikka.core.os.FileUtils;
|
||||||
import rikka.recyclerview.RecyclerViewKt;
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
public class LogsFragment extends BaseFragment {
|
public class LogsFragment extends BaseFragment {
|
||||||
private boolean verbose = false;
|
private boolean verbose = false;
|
||||||
private LogsAdapter adapter;
|
|
||||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
private FragmentLogsBinding binding;
|
private FragmentLogsBinding binding;
|
||||||
private LinearLayoutManager layoutManager;
|
|
||||||
private final SharedPreferences preferences = App.getPreferences();
|
|
||||||
private final ActivityResultLauncher<String> saveLogsLauncher = registerForActivityResult(
|
private final ActivityResultLauncher<String> saveLogsLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.CreateDocument(),
|
new ActivityResultContracts.CreateDocument(),
|
||||||
uri -> {
|
uri -> {
|
||||||
if (uri == null) return;
|
if (uri == null) return;
|
||||||
runAsync(() -> {
|
runAsync(() -> {
|
||||||
try (var os = new ZipOutputStream(requireContext().getContentResolver().openOutputStream(uri))) {
|
var context = requireContext();
|
||||||
|
var contentResolver = context.getContentResolver();
|
||||||
|
try (var os = new ZipOutputStream(contentResolver.openOutputStream(uri))) {
|
||||||
os.setLevel(Deflater.BEST_COMPRESSION);
|
os.setLevel(Deflater.BEST_COMPRESSION);
|
||||||
zipLogs(os);
|
zipLogs(os);
|
||||||
os.finish();
|
os.finish();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
var text = App.getInstance().getString(R.string.logs_save_failed2, e.getMessage());
|
var text = context.getString(R.string.logs_save_failed2, e.getMessage());
|
||||||
if (binding != null && isResumed()) {
|
if (binding != null && isResumed()) {
|
||||||
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
Snackbar.make(binding.snackbar, text, Snackbar.LENGTH_LONG).show();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(App.getInstance(), text, Toast.LENGTH_LONG).show();
|
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -127,11 +111,6 @@ public class LogsFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
adapter = new LogsAdapter();
|
|
||||||
RecyclerViewKt.fixEdgeEffect(binding.recyclerView, false, true);
|
|
||||||
binding.recyclerView.setAdapter(adapter);
|
|
||||||
layoutManager = new LinearLayoutManager(requireActivity());
|
|
||||||
binding.recyclerView.setLayoutManager(layoutManager);
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,18 +124,9 @@ public class LogsFragment extends BaseFragment {
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
int itemId = item.getItemId();
|
int itemId = item.getItemId();
|
||||||
if (itemId == R.id.menu_scroll_top) {
|
if (itemId == R.id.menu_scroll_top) {
|
||||||
if (layoutManager.findFirstVisibleItemPosition() > 1000) {
|
binding.scrollView.fullScroll(ScrollView.FOCUS_UP);
|
||||||
binding.recyclerView.scrollToPosition(0);
|
|
||||||
} else {
|
|
||||||
binding.recyclerView.smoothScrollToPosition(0);
|
|
||||||
}
|
|
||||||
binding.recyclerView.smoothScrollToPosition(0);
|
|
||||||
} else if (itemId == R.id.menu_scroll_down) {
|
} else if (itemId == R.id.menu_scroll_down) {
|
||||||
if (adapter.getItemCount() - layoutManager.findLastVisibleItemPosition() > 1000) {
|
binding.scrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||||||
binding.recyclerView.scrollToPosition(adapter.getItemCount() - 1);
|
|
||||||
} else {
|
|
||||||
binding.recyclerView.smoothScrollToPosition(max(adapter.getItemCount() - 1, 0));
|
|
||||||
}
|
|
||||||
} else if (itemId == R.id.menu_refresh) {
|
} else if (itemId == R.id.menu_refresh) {
|
||||||
reloadLogs();
|
reloadLogs();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -166,21 +136,10 @@ public class LogsFragment extends BaseFragment {
|
||||||
} else if (itemId == R.id.menu_clear) {
|
} else if (itemId == R.id.menu_clear) {
|
||||||
clear();
|
clear();
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.item_word_wrap) {
|
|
||||||
item.setChecked(!item.isChecked());
|
|
||||||
preferences.edit().putBoolean("enable_word_wrap", item.isChecked()).apply();
|
|
||||||
reloadLogs();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
|
||||||
super.onPrepareOptionsMenu(menu);
|
|
||||||
menu.findItem(R.id.item_word_wrap).setChecked(preferences.getBoolean("enable_word_wrap", false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|
@ -197,7 +156,7 @@ public class LogsFragment extends BaseFragment {
|
||||||
private void clear() {
|
private void clear() {
|
||||||
if (ConfigManager.clearLogs(verbose)) {
|
if (ConfigManager.clearLogs(verbose)) {
|
||||||
Snackbar.make(binding.snackbar, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(binding.snackbar, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||||
adapter.clearLogs();
|
binding.body.setText("");
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(binding.snackbar, R.string.logs_clear_failed_2, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(binding.snackbar, R.string.logs_clear_failed_2, Snackbar.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +193,7 @@ public class LogsFragment extends BaseFragment {
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private class LogsReader extends AsyncTask<ParcelFileDescriptor, Integer, List<String>> {
|
private class LogsReader extends AsyncTask<ParcelFileDescriptor, Integer, String> {
|
||||||
private AlertDialog mProgressDialog;
|
private AlertDialog mProgressDialog;
|
||||||
private final Runnable mRunnable = () -> {
|
private final Runnable mRunnable = () -> {
|
||||||
synchronized (LogsReader.this) {
|
synchronized (LogsReader.this) {
|
||||||
|
|
@ -253,27 +212,23 @@ public class LogsFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> doInBackground(ParcelFileDescriptor... log) {
|
protected String doInBackground(ParcelFileDescriptor... log) {
|
||||||
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
|
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
|
||||||
|
try (var pfd = log[0];
|
||||||
List<String> logs = new ArrayList<>();
|
var inputStream = new FileInputStream(pfd.getFileDescriptor())) {
|
||||||
|
int size = Math.toIntExact(pfd.getStatSize()); // max 4MiB
|
||||||
try (var pfd = log[0]; InputStream inputStream = new FileInputStream(pfd.getFileDescriptor()); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
var logs = new ByteArrayOutputStream(size);
|
||||||
String line;
|
FileUtils.copy(inputStream, logs);
|
||||||
while ((line = reader.readLine()) != null) {
|
return logs.toString();
|
||||||
logs.add(line);
|
} catch (IOException e) {
|
||||||
}
|
return requireActivity().getResources().getString(R.string.logs_cannot_read)
|
||||||
} catch (Exception e) {
|
+ "\n" + Log.getStackTraceString(e);
|
||||||
logs.add(requireActivity().getResources().getString(R.string.logs_cannot_read));
|
|
||||||
logs.addAll(Arrays.asList(Log.getStackTraceString(e).split("\n")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return logs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
synchronized protected void onPostExecute(List<String> logs) {
|
synchronized protected void onPostExecute(String logs) {
|
||||||
adapter.setLogs(logs);
|
binding.body.setText(logs);
|
||||||
|
|
||||||
handler.removeCallbacks(mRunnable);
|
handler.removeCallbacks(mRunnable);
|
||||||
if (mProgressDialog.isShowing()) {
|
if (mProgressDialog.isShowing()) {
|
||||||
|
|
@ -303,53 +258,4 @@ public class LogsFragment extends BaseFragment {
|
||||||
}
|
}
|
||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LogsAdapter extends SimpleStatefulAdaptor<LogsAdapter.ViewHolder> {
|
|
||||||
ArrayList<String> logs = new ArrayList<>();
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public LogsAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
ItemLogBinding binding = ItemLogBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
|
||||||
return new LogsAdapter.ViewHolder(binding);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull LogsAdapter.ViewHolder holder, int position) {
|
|
||||||
TextView view = holder.textView;
|
|
||||||
view.setText(logs.get(position));
|
|
||||||
view.measure(0, 0);
|
|
||||||
int desiredWidth = (preferences.getBoolean("enable_word_wrap", false)) ? layoutManager.getWidth() : view.getMeasuredWidth();
|
|
||||||
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
|
|
||||||
layoutParams.width = desiredWidth;
|
|
||||||
if (binding.recyclerView.getWidth() < desiredWidth) {
|
|
||||||
binding.recyclerView.requestLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLogs(List<String> logs) {
|
|
||||||
this.logs.clear();
|
|
||||||
this.logs.addAll(logs);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearLogs() {
|
|
||||||
notifyItemRangeRemoved(0, logs.size());
|
|
||||||
logs.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return logs.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
TextView textView;
|
|
||||||
|
|
||||||
ViewHolder(ItemLogBinding binding) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
textView = binding.log;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,17 +79,26 @@
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
<org.lsposed.manager.ui.widget.StatefulRecyclerView
|
<ScrollView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/scrollView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:clipToPadding="false"
|
android:fillViewport="true"
|
||||||
android:fadeScrollbars="true"
|
android:scrollbars="none">
|
||||||
android:scrollbarStyle="insideOverlay"
|
|
||||||
android:scrollbars="vertical"
|
<TextView
|
||||||
app:borderBottomVisibility="never"
|
android:id="@+id/body"
|
||||||
app:borderTopDrawable="@null"
|
android:layout_width="wrap_content"
|
||||||
app:borderTopVisibility="whenTop"
|
android:layout_height="wrap_content"
|
||||||
app:fitsSystemWindowsInsets="bottom" />
|
android:clipToPadding="false"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:borderBottomVisibility="never"
|
||||||
|
app:borderTopDrawable="@null"
|
||||||
|
app:borderTopVisibility="whenTop"
|
||||||
|
app:fitsSystemWindowsInsets="bottom" />
|
||||||
|
</ScrollView>
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<!--
|
|
||||||
~ This file is part of LSPosed.
|
|
||||||
~
|
|
||||||
~ LSPosed is free software: you can redistribute it and/or modify
|
|
||||||
~ it under the terms of the GNU General Public License as published by
|
|
||||||
~ the Free Software Foundation, either version 3 of the License, or
|
|
||||||
~ (at your option) any later version.
|
|
||||||
~
|
|
||||||
~ LSPosed is distributed in the hope that it will be useful,
|
|
||||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
~ GNU General Public License for more details.
|
|
||||||
~
|
|
||||||
~ You should have received a copy of the GNU General Public License
|
|
||||||
~ along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
~
|
|
||||||
~ Copyright (C) 2020 EdXposed Contributors
|
|
||||||
~ Copyright (C) 2021 LSPosed Contributors
|
|
||||||
-->
|
|
||||||
|
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/log"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
|
||||||
android:textColor="?attr/colorOnSurface"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="11sp" />
|
|
||||||
|
|
@ -45,10 +45,5 @@
|
||||||
android:id="@+id/menu_clear"
|
android:id="@+id/menu_clear"
|
||||||
android:showAsAction="never"
|
android:showAsAction="never"
|
||||||
android:title="@string/menuClearLog" />
|
android:title="@string/menuClearLog" />
|
||||||
<item
|
|
||||||
android:id="@+id/item_word_wrap"
|
|
||||||
android:checkable="true"
|
|
||||||
android:checked="false"
|
|
||||||
android:title="@string/menu_enable_word_wrap" />
|
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue