[app] Fix select log text (#1439)

This commit is contained in:
南宫雪珊 2021-11-26 01:48:08 +08:00 committed by GitHub
parent e1cf9551b4
commit 48fd4c042c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 162 deletions

View File

@ -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;
}
}
}
} }

View File

@ -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>

View File

@ -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" />

View File

@ -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>