mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 02:58:45 +00:00
Add search functionality to debug log screen.
This commit is contained in:
committed by
Cody Henthorne
parent
962375e422
commit
5e0aa830bf
@@ -12,6 +12,7 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -27,7 +28,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import org.signal.debuglogsviewer.DebugLogsViewer;
|
||||
import org.thoughtcrime.securesms.BaseActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.ConversationSearchBottomBar;
|
||||
import org.thoughtcrime.securesms.components.ProgressCard;
|
||||
import org.thoughtcrime.securesms.components.SearchView;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.LongClickCopySpan;
|
||||
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
|
||||
@@ -48,6 +51,7 @@ public class SubmitDebugLogActivity extends BaseActivity {
|
||||
private View warningBanner;
|
||||
private View editBanner;
|
||||
private CircularProgressMaterialButton submitButton;
|
||||
private ConversationSearchBottomBar searchNav;
|
||||
private View scrollToBottomButton;
|
||||
private View scrollToTopButton;
|
||||
private ProgressCard progressCard;
|
||||
@@ -57,6 +61,11 @@ public class SubmitDebugLogActivity extends BaseActivity {
|
||||
private MenuItem searchMenuItem;
|
||||
private MenuItem saveMenuItem;
|
||||
|
||||
private ImageButton caseSensitiveButton;
|
||||
private TextView searchPosition;
|
||||
private ImageButton searchUpButton;
|
||||
private ImageButton searchDownButton;
|
||||
|
||||
private boolean isWebViewLoaded;
|
||||
private boolean hasPresentedLines;
|
||||
|
||||
@@ -91,33 +100,71 @@ public class SubmitDebugLogActivity extends BaseActivity {
|
||||
this.searchMenuItem = menu.findItem(R.id.menu_search);
|
||||
this.saveMenuItem = menu.findItem(R.id.menu_save);
|
||||
|
||||
// TODO [lisa][debug-log-search]
|
||||
// SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
// SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
|
||||
// @Override
|
||||
// public boolean onQueryTextSubmit(String query) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean onQueryTextChange(String query) {
|
||||
// return true;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
// @Override
|
||||
// public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
// searchView.setOnQueryTextListener(queryListener);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
// searchView.setOnQueryTextListener(null);
|
||||
// return true;
|
||||
// }
|
||||
// });
|
||||
this.searchNav = findViewById(R.id.debug_log_search_nav);
|
||||
this.caseSensitiveButton = findViewById(R.id.case_sensitive_button);
|
||||
this.searchPosition = findViewById(R.id.debug_log_search_position);
|
||||
this.searchUpButton = findViewById(R.id.debug_log_search_up);
|
||||
this.searchDownButton = findViewById(R.id.debug_log_search_down);
|
||||
|
||||
searchUpButton.setOnClickListener(v -> {
|
||||
DebugLogsViewer.onSearchUp(logWebView);
|
||||
DebugLogsViewer.getSearchPosition(logWebView, position -> searchPosition.setText(position));
|
||||
});
|
||||
|
||||
searchDownButton.setOnClickListener(v -> {
|
||||
DebugLogsViewer.onSearchDown(logWebView);
|
||||
DebugLogsViewer.getSearchPosition(logWebView, position -> searchPosition.setText(position));
|
||||
});
|
||||
|
||||
boolean[] isCaseSensitive = {false};
|
||||
|
||||
caseSensitiveButton.setOnClickListener(v -> {
|
||||
DebugLogsViewer.onToggleCaseSensitive(logWebView);
|
||||
DebugLogsViewer.getSearchPosition(logWebView, position -> searchPosition.setText(position));
|
||||
isCaseSensitive[0] = !isCaseSensitive[0];
|
||||
|
||||
int backgroundColor = isCaseSensitive[0] ? R.drawable.circle_tint_darker : R.drawable.circle_touch_highlight_background;
|
||||
caseSensitiveButton.setBackground(getResources().getDrawable(backgroundColor));
|
||||
});
|
||||
|
||||
SearchView searchView = (SearchView) searchMenuItem.getActionView();
|
||||
SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String query) {
|
||||
DebugLogsViewer.onSearch(logWebView, query);
|
||||
DebugLogsViewer.getSearchPosition(logWebView, position -> searchPosition.setText(position));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
searchNav.setVisibility(View.VISIBLE);
|
||||
submitButton.setVisibility(View.GONE);
|
||||
searchView.setOnQueryTextListener(queryListener);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
searchNav.setVisibility(View.GONE);
|
||||
submitButton.setVisibility(View.VISIBLE);
|
||||
DebugLogsViewer.onSearchClose(logWebView);
|
||||
DebugLogsViewer.getSearchPosition(logWebView, position -> searchPosition.setText(position));
|
||||
searchView.setOnQueryTextListener(null);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (viewModel.getMode().getValue() != null) {
|
||||
presentMode(viewModel.getMode().getValue());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -217,16 +264,21 @@ public class SubmitDebugLogActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void presentMode(@NonNull SubmitDebugLogViewModel.Mode mode) {
|
||||
if (editMenuItem == null || doneMenuItem == null || searchMenuItem == null || saveMenuItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case NORMAL:
|
||||
editBanner.setVisibility(View.GONE);
|
||||
searchNav.setVisibility(View.GONE);
|
||||
// TODO [lisa][debug-log-editing]
|
||||
// setEditing(false);
|
||||
saveMenuItem.setVisible(true);
|
||||
// TODO [greyson][log] Not yet implemented
|
||||
// editMenuItem.setVisible(true);
|
||||
// doneMenuItem.setVisible(false);
|
||||
// searchMenuItem.setVisible(true);
|
||||
searchMenuItem.setVisible(true);
|
||||
break;
|
||||
case SUBMITTING:
|
||||
editBanner.setVisibility(View.GONE);
|
||||
|
||||
15
app/src/main/res/drawable/symbol_filter_24.xml
Normal file
15
app/src/main/res/drawable/symbol_filter_24.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M2.13 7c0-0.48 0.39-0.88 0.87-0.88h18c0.48 0 0.88 0.4 0.88 0.88S21.48 7.88 21 7.88H3c-0.48 0-0.88-0.4-0.88-0.88Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M5.38 12c0-0.48 0.39-0.88 0.87-0.88h11.5c0.48 0 0.88 0.4 0.88 0.88s-0.4 0.88-0.88 0.88H6.25c-0.48 0-0.88-0.4-0.88-0.88Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9.5 16.13c-0.48 0-0.88 0.39-0.88 0.87s0.4 0.88 0.88 0.88h5c0.48 0 0.88-0.4 0.88-0.88s-0.4-0.88-0.88-0.88h-5Z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/symbol_match_case_24.xml
Normal file
10
app/src/main/res/drawable/symbol_match_case_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M131,708L296,268L375,268L540,708L464,708L425,596L247,596L207,708L131,708ZM270,532L401,532L337,350L333,350L270,532ZM665,718Q614,718 584,690.5Q554,663 554,618Q554,574 588.5,545.5Q623,517 677,517Q700,517 722,521Q744,525 760,532L760,520Q760,491 739.5,473Q719,455 685,455Q662,455 643,464.5Q624,474 610,492L563,457Q587,428 617.5,414Q648,400 686,400Q755,400 789,432.5Q823,465 823,530L823,708L760,708L760,671L756,671Q742,694 718,706Q694,718 665,718ZM677,664Q712,664 736.5,640Q761,616 761,584Q747,576 727.5,571.5Q708,567 689,567Q657,567 639,581Q621,595 621,618Q621,638 637,651Q653,664 677,664Z"/>
|
||||
</vector>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
@@ -48,10 +47,9 @@
|
||||
android:id="@+id/debug_log_lines"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@id/debug_log_header_barrier"
|
||||
app:layout_constraintBottom_toTopOf="@id/debug_log_submit_button"/>
|
||||
app:layout_constraintBottom_toTopOf="@id/debug_log_footer_barrier"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/debug_log_scroll_to_top"
|
||||
@@ -80,7 +78,83 @@
|
||||
app:tint="@color/grey_600"
|
||||
app:srcCompat="@drawable/ic_chevron_down_20"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/debug_log_submit_button"/>
|
||||
app:layout_constraintBottom_toTopOf="@id/debug_log_footer_barrier"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/debug_log_footer_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="debug_log_submit_button,debug_log_search_nav" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.ConversationSearchBottomBar
|
||||
android:id="@+id/debug_log_search_nav"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/signal_background_secondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/case_sensitive_button"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/circle_touch_highlight_background"
|
||||
android:layout_marginStart="16dp"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/symbol_match_case_24"
|
||||
app:tint="@color/signal_colorOnSurface"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/debug_log_search_position"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/debug_log_search_up"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="37 of 73" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/debug_log_search_up"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/symbol_chevron_up_24"
|
||||
app:tint="@color/core_ultramarine"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/debug_log_search_down"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/debug_log_search_down"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/symbol_chevron_down_24"
|
||||
app:tint="@color/core_ultramarine"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</org.thoughtcrime.securesms.components.ConversationSearchBottomBar>
|
||||
|
||||
<org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton
|
||||
android:id="@+id/debug_log_submit_button"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
android:icon="@drawable/symbol_search_24"
|
||||
android:title="@string/CameraContacts__menu_search"
|
||||
android:visible="false"
|
||||
app:iconTint="@color/signal_colorOnSurface"
|
||||
app:actionViewClass="org.thoughtcrime.securesms.components.SearchView"
|
||||
app:showAsAction="collapseActionView|always" />
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
#container { position: absolute; top: 0; bottom: 0; left: 0; right: 0; height: 100%; width: 100%; }
|
||||
.searchMatches { position: absolute; background-color: rgba(182, 190, 250, 0.4); }
|
||||
|
||||
/* Scrollbar Setup */
|
||||
.ace_scrollbar::-webkit-scrollbar { width: 0; height: 0; }
|
||||
|
||||
@@ -47,3 +47,102 @@ function showScrollBar() {
|
||||
|
||||
editor.session.on("changeScrollTop", showScrollBar);
|
||||
editor.session.on("changeScrollLeft", showScrollBar);
|
||||
|
||||
// Generate highlight markers for all search matches
|
||||
const Range = ace.require("ace/range").Range;
|
||||
const session = editor.getSession();
|
||||
|
||||
let input = ""; // Search query input
|
||||
let markers = []; // IDs of highlighted search markers
|
||||
let matchRanges = []; // Ranges of all search matches
|
||||
let matchCount = 0; // Total number of matches
|
||||
let isCaseSensitive = false;
|
||||
|
||||
// Clear all search markers and match info
|
||||
function clearMarkers() {
|
||||
markers.forEach((id) => session.removeMarker(id));
|
||||
markers = [];
|
||||
matchRanges = [];
|
||||
matchCount = 0;
|
||||
}
|
||||
|
||||
// Highlight all instances of the search term
|
||||
function highlightAllMatches(term) {
|
||||
clearMarkers();
|
||||
if (!term) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchTerm = isCaseSensitive ? term : term.toLowerCase();
|
||||
session
|
||||
.getDocument()
|
||||
.getAllLines()
|
||||
.forEach((line, row) => {
|
||||
let start = 0;
|
||||
const caseLine = isCaseSensitive ? line : line.toLowerCase();
|
||||
while (true) {
|
||||
const index = caseLine.indexOf(searchTerm, start);
|
||||
if (index === -1) {
|
||||
break;
|
||||
}
|
||||
const range = new Range(row, index, row, index + term.length);
|
||||
markers.push(session.addMarker(range, "searchMatches", "text", false));
|
||||
matchRanges.push(range);
|
||||
start = index + term.length;
|
||||
}
|
||||
});
|
||||
matchCount = markers.length;
|
||||
}
|
||||
|
||||
// Return index of current match
|
||||
function getCurrentMatchIndex() {
|
||||
const current = editor.getSelection().getRange();
|
||||
return matchRanges.findIndex(
|
||||
(r) =>
|
||||
r.start.row === current.start.row &&
|
||||
r.start.column === current.start.column &&
|
||||
r.end.row === current.end.row &&
|
||||
r.end.column === current.end.column,
|
||||
);
|
||||
}
|
||||
|
||||
function getSearchPosition() {
|
||||
if (input == "") {
|
||||
return "";
|
||||
}
|
||||
return matchCount == 0 ? "No match" : `${getCurrentMatchIndex() + 1} of ${matchCount}`;
|
||||
}
|
||||
|
||||
function onSearchUp() {
|
||||
editor.find(input, {
|
||||
backwards: true,
|
||||
wrap: true,
|
||||
skipCurrent: true,
|
||||
caseSensitive: isCaseSensitive,
|
||||
});
|
||||
}
|
||||
|
||||
function onSearchDown() {
|
||||
editor.find(input, {
|
||||
backwards: false,
|
||||
wrap: true,
|
||||
skipCurrent: true,
|
||||
caseSensitive: isCaseSensitive,
|
||||
});
|
||||
}
|
||||
|
||||
function onSearchClose() {
|
||||
editor.getSelection().clearSelection();
|
||||
input = "";
|
||||
clearMarkers();
|
||||
}
|
||||
|
||||
function onToggleCaseSensitive() {
|
||||
isCaseSensitive = !isCaseSensitive;
|
||||
highlightAllMatches(input);
|
||||
}
|
||||
|
||||
function onSearchInput(value) {
|
||||
input = value;
|
||||
highlightAllMatches(input);
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.webkit.ValueCallback
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import kotlinx.coroutines.Runnable
|
||||
import org.json.JSONObject
|
||||
import java.util.function.Consumer
|
||||
|
||||
var readOnly = true
|
||||
|
||||
@@ -59,13 +61,37 @@ object DebugLogsViewer {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onFind(webview: WebView) {
|
||||
webview.evaluateJavascript("document.getElementById('searchBar').style.display = 'block';", null)
|
||||
fun onSearch(webview: WebView, query: String) {
|
||||
webview.evaluateJavascript("onSearchInput('$query')", null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onSearchUp(webview: WebView) {
|
||||
webview.evaluateJavascript("onSearchUp();", null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onSearchDown(webview: WebView) {
|
||||
webview.evaluateJavascript("onSearchDown();", null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getSearchPosition(webView: WebView, callback: Consumer<String?>) {
|
||||
webView.evaluateJavascript("getSearchPosition();", ValueCallback { value: String? -> callback.accept(value?.trim('"') ?: "") })
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onToggleCaseSensitive(webview: WebView) {
|
||||
webview.evaluateJavascript("onToggleCaseSensitive();", null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onSearchClose(webview: WebView) {
|
||||
webview.evaluateJavascript("onSearchClose();", null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onFilter(webview: WebView) {
|
||||
webview.evaluateJavascript("document.getElementById('filterLevel').style.display = 'block';", null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
Reference in New Issue
Block a user