]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Create search functionality for resource requests. https://redmine.stoutner.com... master
authorSoren Stoutner <soren@stoutner.com>
Fri, 21 Aug 2026 00:05:05 +0000 (17:05 -0700)
committerSoren Stoutner <soren@stoutner.com>
Fri, 21 Aug 2026 00:05:05 +0000 (17:05 -0700)
15 files changed:
app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/FilterListsActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.kt
app/src/main/java/com/stoutner/privacybrowser/activities/RequestsActivity.kt
app/src/main/java/com/stoutner/privacybrowser/adapters/FilterListArrayAdapter.kt
app/src/main/java/com/stoutner/privacybrowser/adapters/RequestsArrayAdapter.kt
app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.kt
app/src/main/res/layout/bookmarks_databaseview_top_appbar.xml
app/src/main/res/layout/filter_lists_bottom_appbar.xml
app/src/main/res/layout/filter_lists_top_appbar.xml
app/src/main/res/layout/requests_bottom_appbar.xml
app/src/main/res/layout/requests_top_appbar.xml
app/src/main/res/layout/spinner.xml [deleted file]
app/src/main/res/layout/spinner_app_bar.xml [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.properties

index 5d7ca2d78475eddccf9eb123eeb5fb807e1ad8c9..58304b654a4eade2a6478768a7075d2f16692714 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2024 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2016-2024, 2026 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
  *
@@ -49,6 +49,7 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.appcompat.widget.Toolbar
 import androidx.core.graphics.drawable.toBitmap
+import androidx.core.util.size
 import androidx.cursoradapter.widget.CursorAdapter
 import androidx.cursoradapter.widget.ResourceCursorAdapter
 import androidx.fragment.app.DialogFragment
@@ -139,7 +140,7 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
         val appBar = supportActionBar!!
 
         // Set the app bar custom view.
-        appBar.setCustomView(R.layout.spinner)
+        appBar.setCustomView(R.layout.spinner_app_bar)
 
         // Display the back arrow in the app bar.
         appBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_HOME_AS_UP
@@ -528,7 +529,7 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
                                         updateBookmarksListView()
 
                                         // Re-select the previously selected bookmarks.
-                                        for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size())
+                                        for (i in 0 until selectedBookmarksPositionsSparseBooleanArray.size)
                                             bookmarksListView.setItemChecked(selectedBookmarksPositionsSparseBooleanArray.keyAt(i), true)
                                     } else {  // The snackbar was dismissed without the undo button being pushed.
                                         // Delete each selected bookmark.
@@ -802,7 +803,7 @@ class BookmarksDatabaseViewActivity : AppCompatActivity(), EditBookmarkDatabaseV
             bookmarksDeletedSnackbar!!.dismiss()
         } else {  // Go home immediately.
             // Update the current folder in the bookmarks activity.
-            if ((currentFolderDatabaseId == ALL_FOLDERS_DATABASE_ID) || (currentFolderDatabaseId == HOME_FOLDER_DATABASE_ID)) {  // All folders or the the home folder are currently displayed.
+            if ((currentFolderDatabaseId == ALL_FOLDERS_DATABASE_ID) || (currentFolderDatabaseId == HOME_FOLDER_DATABASE_ID)) {  // All folders or the home folder are currently displayed.
                 // Load the home folder.
                 BookmarksActivity.currentFolderId = HOME_FOLDER_ID
             } else {  // A subfolder is currently displayed.
index 92ef65919f111cb00fcd90166651ab84b8ce5c66..13f6a9702b7723909bb670764ffb50ca492dfef4 100644 (file)
@@ -115,7 +115,7 @@ class FilterListsActivity : AppCompatActivity(), ViewFilterListEntryDialog.ViewF
         val appBar = supportActionBar!!
 
         // Set the app bar custom view.
-        appBar.setCustomView(R.layout.spinner)
+        appBar.setCustomView(R.layout.spinner_app_bar)
 
         // Display the back arrow in the app bar.
         appBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_HOME_AS_UP
@@ -195,7 +195,7 @@ class FilterListsActivity : AppCompatActivity(), ViewFilterListEntryDialog.ViewF
                 // Populate the list view with the current filter list data class adapter.
                 filterListListView.adapter = currentFilterListDataClassArrayAdapter
 
-                // Reapply the search if the search edit is not empty.
+                // Reapply the search if the search edit text is not empty.
                 if (searchEditText.text.isNotEmpty())
                     currentFilterListDataClassArrayAdapter.filter.filter(searchEditText.text)
             }
index 749946b80264dc5217936a0329fbdb09223a7a48..f37a48d24e0a1c64b95a09552ad1017ffa38bcb2 100644 (file)
@@ -67,6 +67,7 @@ import android.view.WindowManager
 import android.view.inputmethod.InputMethodManager
 import android.webkit.CookieManager
 import android.webkit.HttpAuthHandler
+import android.webkit.RenderProcessGoneDetail
 import android.webkit.SslErrorHandler
 import android.webkit.ValueCallback
 import android.webkit.WebChromeClient
@@ -2261,12 +2262,12 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
             }
 
             R.id.requests -> {  // Requests.
-                // Populate the resource requests.
-                RequestsActivity.resourceRequestsDataClassList = currentWebView!!.getResourceRequestsDataClassList()
-
                 // Create an intent to launch the Requests activity.
                 val requestsIntent = Intent(this, RequestsActivity::class.java)
 
+                // Add the resource requests to the intent.
+                requestsIntent.putExtra(REQUEST_DATA_CLASS_ARRAY_LIST, currentWebView!!.getResourceRequestsDataClassList())
+
                 // Add the block third-party requests status to the intent.
                 requestsIntent.putExtra(BLOCK_ALL_THIRD_PARTY_REQUESTS, currentWebView!!.blockAllThirdPartyRequests)
 
@@ -5156,6 +5157,7 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                 return true
             }
         }
+
         nestedScrollWebView.webViewClient = object : WebViewClient() {
             // `shouldOverrideUrlLoading` makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
             override fun shouldOverrideUrlLoading(view: WebView, webResourceRequest: WebResourceRequest): Boolean {
@@ -5668,6 +5670,14 @@ class MainWebViewActivity : AppCompatActivity(), CreateBookmarkDialog.CreateBook
                     }
                 }
             }
+
+            // Handle crashes of the rendering process.
+            override fun onRenderProcessGone(view: WebView?, detail: RenderProcessGoneDetail?): Boolean {
+                // Do nothing.
+
+                // Return true.
+                return true
+            }
         }
 
         // Check to see if the state is being restored.
index 9ad0d84b05ba30b9319cde50a19397b10be35fdc..8ce18d2f773fe4d5d9b3dcca108788d4e170d289 100644 (file)
@@ -23,12 +23,16 @@ import android.content.Context
 import android.content.Intent
 import android.database.Cursor
 import android.database.MatrixCursor
+import android.os.Build
 import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
 import android.view.Menu
 import android.view.MenuItem
 import android.view.View
 import android.view.WindowManager
 import android.widget.AdapterView
+import android.widget.EditText
 import android.widget.ListView
 import android.widget.Spinner
 import android.widget.TextView
@@ -48,26 +52,25 @@ import com.stoutner.privacybrowser.dialogs.ViewRequestDialog
 
 // Define the public constants.
 const val BLOCK_ALL_THIRD_PARTY_REQUESTS = "block_all_third_party_requests"
+const val REQUEST_DATA_CLASS_ARRAY_LIST = "request_data_class_array_list"
 
 // Define the private class constants.
 private const val LISTVIEW_POSITION = "listview_position"
 
 // Define the spinner entry class constants.
-private const val ALL_RESOURCE_REQUESTS = 0
-private const val DEFAULT_RESOURCE_REQUESTS = 1
-private const val ALLOWED_RESOURCE_REQUESTS = 2
-private const val BLOCKED_RESOURCE_REQUESTS = 3
-private const val THIRD_PARTY_RESOURCE_REQUESTS = 4
+private const val ALL_REQUESTS = 0
+private const val DEFAULT_REQUESTS = 1
+private const val ALLOWED_REQUESTS = 2
+private const val BLOCKED_REQUESTS = 3
+private const val THIRD_PARTY_REQUESTS = 4
 
 class RequestsActivity : AppCompatActivity() {
-    companion object {
-        // The resource requests are populated by `MainWebViewActivity` before `RequestsActivity` is launched.
-        lateinit var resourceRequestsDataClassList: List<RequestDataClass>
-    }
-
     // Define the class views.
     private lateinit var requestsListView: ListView
 
+    // Declare the class variables.
+    private lateinit var currentRequestsDataClassArrayAdapter: RequestsArrayAdapter
+
     public override fun onCreate(savedInstanceState: Bundle?) {
         // Get a handle for the shared preferences.
         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
@@ -89,6 +92,12 @@ class RequestsActivity : AppCompatActivity() {
         // Get the status of the third-party filter list.
         val blockAllThirdPartyRequests = intent.getBooleanExtra(BLOCK_ALL_THIRD_PARTY_REQUESTS, false)
 
+        // Get the requests data class list.  This can be simplified once the minimum API >= 33.
+        @Suppress("DEPRECATION") val requestsDataClassList = if (Build.VERSION.SDK_INT >= 33)
+            intent.getParcelableArrayListExtra(REQUEST_DATA_CLASS_ARRAY_LIST, RequestDataClass::class.java)!!
+        else
+            intent.getParcelableArrayListExtra(REQUEST_DATA_CLASS_ARRAY_LIST)!!
+
         // Set the content view.
         if (bottomAppBar)
             setContentView(R.layout.requests_bottom_appbar)
@@ -105,57 +114,58 @@ class RequestsActivity : AppCompatActivity() {
         val appBar = supportActionBar!!
 
         // Set the app bar custom view.
-        appBar.setCustomView(R.layout.spinner)
+        appBar.setCustomView(R.layout.spinner_app_bar)
 
         // Display the back arrow in the app bar.
         appBar.displayOptions = ActionBar.DISPLAY_SHOW_CUSTOM or ActionBar.DISPLAY_HOME_AS_UP
 
         // Get handles for the views.
         val appBarSpinner = findViewById<Spinner>(R.id.spinner)
+        val searchEditText = findViewById<EditText>(R.id.search_edittext)
         requestsListView = findViewById(R.id.requests_listview)
 
-        // Initialize the resource requests data class lists.  A list is needed for all the resource requests, or the activity can crash if `MainWebViewActivity.resourceRequests` is modified after the activity loads.
-        val allResourceRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
-        val defaultResourceRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
-        val allowedResourceRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
-        val blockedResourceRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
-        val thirdPartyResourceRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
+        // Initialize the requests data class lists.  A list is needed for all the requests, or the activity can crash if `MainWebViewActivity.resourceRequests` is modified after the activity loads.
+        val allRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
+        val defaultRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
+        val allowedRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
+        val blockedRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
+        val thirdPartyRequestsDataClassList: MutableList<RequestDataClass> = mutableListOf()
 
-        // Populate the resource array lists.
-        for (requestDataClass in resourceRequestsDataClassList) {
+        // Populate the requests array lists.
+        for (requestDataClass in requestsDataClassList) {
             // Add the request data class to the list of all requests.
-            allResourceRequestsDataClassList.add(requestDataClass)
+            allRequestsDataClassList.add(requestDataClass)
 
             // Add the request data class to the other, specific, list of requests.
             when (requestDataClass.disposition) {
-                RequestDisposition.Default -> defaultResourceRequestsDataClassList.add(requestDataClass)
-                RequestDisposition.Allowed -> allowedResourceRequestsDataClassList.add(requestDataClass)
-                RequestDisposition.Blocked -> blockedResourceRequestsDataClassList.add(requestDataClass)
-                RequestDisposition.ThirdParty -> thirdPartyResourceRequestsDataClassList.add(requestDataClass)
+                RequestDisposition.Default -> defaultRequestsDataClassList.add(requestDataClass)
+                RequestDisposition.Allowed -> allowedRequestsDataClassList.add(requestDataClass)
+                RequestDisposition.Blocked -> blockedRequestsDataClassList.add(requestDataClass)
+                RequestDisposition.ThirdParty -> thirdPartyRequestsDataClassList.add(requestDataClass)
             }
         }
 
-        // Setup a matrix cursor for the resource lists.
+        // Set up a matrix cursor for the requests lists.
         val spinnerCursor = MatrixCursor(arrayOf("_id", "Requests"))
-        spinnerCursor.addRow(arrayOf<Any>(ALL_RESOURCE_REQUESTS, getString(R.string.all) + " - " + allResourceRequestsDataClassList.size))
-        spinnerCursor.addRow(arrayOf<Any>(DEFAULT_RESOURCE_REQUESTS, getString(R.string.default_label) + " - " + defaultResourceRequestsDataClassList.size))
-        spinnerCursor.addRow(arrayOf<Any>(ALLOWED_RESOURCE_REQUESTS, getString(R.string.allowed_plural) + " - " + allowedResourceRequestsDataClassList.size))
-        spinnerCursor.addRow(arrayOf<Any>(BLOCKED_RESOURCE_REQUESTS, getString(R.string.blocked_plural) + " - " + blockedResourceRequestsDataClassList.size))
+        spinnerCursor.addRow(arrayOf<Any>(ALL_REQUESTS, getString(R.string.all) + " - " + allRequestsDataClassList.size))
+        spinnerCursor.addRow(arrayOf<Any>(DEFAULT_REQUESTS, getString(R.string.default_label) + " - " + defaultRequestsDataClassList.size))
+        spinnerCursor.addRow(arrayOf<Any>(ALLOWED_REQUESTS, getString(R.string.allowed_plural) + " - " + allowedRequestsDataClassList.size))
+        spinnerCursor.addRow(arrayOf<Any>(BLOCKED_REQUESTS, getString(R.string.blocked_plural) + " - " + blockedRequestsDataClassList.size))
         if (blockAllThirdPartyRequests)
-            spinnerCursor.addRow(arrayOf<Any>(THIRD_PARTY_RESOURCE_REQUESTS, getString(R.string.third_party_plural) + " - " + thirdPartyResourceRequestsDataClassList.size))
+            spinnerCursor.addRow(arrayOf<Any>(THIRD_PARTY_REQUESTS, getString(R.string.third_party_plural) + " - " + thirdPartyRequestsDataClassList.size))
 
-        // Create a resource cursor adapter for the spinner.
+        // Create a requests cursor adapter for the spinner.
         val spinnerCursorAdapter: ResourceCursorAdapter = object : ResourceCursorAdapter(this, R.layout.appbar_spinner_item, spinnerCursor, 0) {
             override fun bindView(view: View, context: Context, cursor: Cursor) {
                 // Get a handle for the spinner item text view.
                 val spinnerItemTextView = view.findViewById<TextView>(R.id.spinner_item_textview)
 
-                // Set the text view to display the resource list.
+                // Set the text view to display the request list.
                 spinnerItemTextView.text = cursor.getString(1)
             }
         }
 
-        // Set the resource cursor adapter drop down view resource.
+        // Set the request cursor adapter drop down view resource.
         spinnerCursorAdapter.setDropDownViewResource(R.layout.appbar_spinner_dropdown_item)
 
         // Set the app bar spinner adapter.
@@ -167,53 +177,48 @@ class RequestsActivity : AppCompatActivity() {
         // Handle taps on the spinner dropdown.
         appBarSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
             override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
-                when (id.toInt()) {
-                    ALL_RESOURCE_REQUESTS -> {
-                        // Get an adapter for all the requests.
-                        val allResourceRequestsArrayAdapter = RequestsArrayAdapter(context, allResourceRequestsDataClassList)
-
-                        // Display the adapter in the list view.
-                        requestsListView.adapter = allResourceRequestsArrayAdapter
-                    }
-
-                    DEFAULT_RESOURCE_REQUESTS -> {
-                        // Get an adapter for the default requests.
-                        val defaultResourceRequestsArrayAdapter = RequestsArrayAdapter(context, defaultResourceRequestsDataClassList)
-
-                        // Display the adapter in the list view.
-                        requestsListView.adapter = defaultResourceRequestsArrayAdapter
-                    }
-
-                    ALLOWED_RESOURCE_REQUESTS -> {
-                        // Get an adapter for the allowed requests.
-                        val allowedResourceRequestsArrayAdapter = RequestsArrayAdapter(context, allowedResourceRequestsDataClassList)
-
-                        // Display the adapter in the list view.
-                        requestsListView.adapter = allowedResourceRequestsArrayAdapter
-                    }
-
-                    BLOCKED_RESOURCE_REQUESTS -> {
-                        // Get an adapter for the blocked requests.
-                        val blockedResourceRequestsArrayAdapter = RequestsArrayAdapter(context, blockedResourceRequestsDataClassList)
-
-                        // Display the adapter in the list view.
-                        requestsListView.adapter = blockedResourceRequestsArrayAdapter
-                    }
-
-                    THIRD_PARTY_RESOURCE_REQUESTS -> {
-                        // Get an adapter for the third-party requests.
-                        val thirdPartyResourceRequestsArrayAdapter = RequestsArrayAdapter(context, thirdPartyResourceRequestsDataClassList)
-
-                        //Display the adapter in the list view.
-                        requestsListView.adapter = thirdPartyResourceRequestsArrayAdapter
-                    }
+                // Get the current requests data class list.
+                val currentRequestsDataClassList = when (id.toInt()) {
+                    ALL_REQUESTS -> allRequestsDataClassList
+                    DEFAULT_REQUESTS -> defaultRequestsDataClassList
+                    ALLOWED_REQUESTS -> allowedRequestsDataClassList
+                    BLOCKED_REQUESTS -> blockedRequestsDataClassList
+                    THIRD_PARTY_REQUESTS -> thirdPartyRequestsDataClassList
+                    else -> allRequestsDataClassList
                 }
+
+                // Get an adapter for the current requests data class.
+                currentRequestsDataClassArrayAdapter = RequestsArrayAdapter(context, currentRequestsDataClassList)
+
+                // Populate the list view with the current requests data class adapter.
+                requestsListView.adapter = currentRequestsDataClassArrayAdapter
+
+                // Reapply the search if the search edit text is not empty.
+                if (searchEditText.text.isNotEmpty())
+                    currentRequestsDataClassArrayAdapter.filter.filter(searchEditText.text)
             }
 
             override fun onNothingSelected(parent: AdapterView<*>?) {
                 // Do nothing.
             }
         }
+
+        // Search for the string in the list view whenever a character changes in the search edit text.
+        searchEditText.addTextChangedListener(object : TextWatcher {
+            override fun beforeTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) {
+                // Do nothing.
+            }
+
+            override fun onTextChanged(charSequence: CharSequence, start: Int, before: Int, count: Int) {
+                // Do nothing.
+            }
+
+            override fun afterTextChanged(editable: Editable) {
+                // Search for the text in the list view.
+                currentRequestsDataClassArrayAdapter.filter.filter(editable.toString())
+            }
+        })
+
         // Listen for taps on entries in the list view.
         requestsListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
             // Display the view request dialog.  The list view is 0 based, so the position must be incremented by 1.
index 6d0c406f645ada94f39df9ee4bab78ecd7d91a26..8674ab10e53ce863b0b86c6439eb07fdf5dbb7b9 100644 (file)
@@ -33,7 +33,8 @@ import com.stoutner.privacybrowser.dataclasses.FilterListEntryDataClass
 import java.util.Locale.getDefault
 
 // `0` is the `textViewResourceId`, which is unused in this implementation.
-class FilterListArrayAdapter(context: Context, private val entireFilterListEntryDataClassList: List<FilterListEntryDataClass>) : ArrayAdapter<FilterListEntryDataClass>(context, 0, entireFilterListEntryDataClassList) {
+class FilterListArrayAdapter(context: Context, private val entireFilterListEntryDataClassList: List<FilterListEntryDataClass>) :
+                             ArrayAdapter<FilterListEntryDataClass>(context, 0,entireFilterListEntryDataClassList) {
     // Define the class variables.
     private var searchedFilterListEntryDataClassList = entireFilterListEntryDataClassList
 
index b595c7c89eaf1b1b34df27bc6652bc87d4cf2f5e..3a35fe73f96ecfcfb81f4635fd160cde312716e8 100644 (file)
@@ -24,15 +24,36 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ArrayAdapter
+import android.widget.Filter
 import android.widget.LinearLayout
 import android.widget.TextView
 
 import com.stoutner.privacybrowser.R
 import com.stoutner.privacybrowser.dataclasses.RequestDataClass
 import com.stoutner.privacybrowser.dataclasses.RequestDisposition
+import java.util.Locale.getDefault
 
 // `0` is the `textViewResourceId`, which is unused in this implementation.
-class RequestsArrayAdapter(context: Context, resourceRequestsDataClassList: MutableList<RequestDataClass>) : ArrayAdapter<RequestDataClass>(context, 0, resourceRequestsDataClassList) {
+class RequestsArrayAdapter(context: Context, private val entireRequestsDataClassList: List<RequestDataClass>) :
+                           ArrayAdapter<RequestDataClass>(context, 0, entireRequestsDataClassList) {
+    // Define the class variables.
+    private var searchedRequestsDataClassList = entireRequestsDataClassList
+
+    override fun getCount(): Int {
+        // Return the searched requests data class list size.
+        return searchedRequestsDataClassList.size
+    }
+
+    override fun getItem(position: Int): RequestDataClass {
+        // Return the searched requests data class list item.
+        return searchedRequestsDataClassList[position]
+    }
+
+    override fun getItemId(position: Int): Long {
+        // Return the position as a long.
+        return position.toLong()
+    }
+
     override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
         // Copy the input view to a new view.
         var newView = convertView
@@ -48,7 +69,7 @@ class RequestsArrayAdapter(context: Context, resourceRequestsDataClassList: Muta
         val urlTextView = newView.findViewById<TextView>(R.id.request_item_url)
 
         // Get the request data class.
-        val requestDataClass = getItem(position)!!
+        val requestDataClass = getItem(position)
 
         // The ID is one greater than the position because it is 0 based.
         val id = position + 1
@@ -94,4 +115,41 @@ class RequestsArrayAdapter(context: Context, resourceRequestsDataClassList: Muta
         // Return the modified view.
         return newView
     }
+
+    override fun getFilter(): Filter {
+        // Return the custom filter.
+        return object : Filter() {
+            override fun performFiltering(charSequence: CharSequence?): FilterResults {
+                // Get the search string, converting to lower case.
+                val searchString = charSequence?.toString()?.lowercase(getDefault())
+
+                // Create a new filter results.
+                val filterResults = FilterResults()
+
+                // Filter the list.
+                filterResults.values = if (searchString.isNullOrEmpty()) {  // The search string is null or empty.
+                    // Return the entire requests data class list.
+                    entireRequestsDataClassList
+                } else {  // The search string contains data.
+                    // Filter the list by the request URL string.
+                    entireRequestsDataClassList.filter {
+                        it.requestUrlString.lowercase(getDefault()).contains(searchString)
+                    }
+                }
+
+                // Return the filter results.
+                return filterResults
+            }
+
+            override fun publishResults(charSequence: CharSequence?, filterResults: FilterResults) {
+                @Suppress("UNCHECKED_CAST")
+                // Store the searched requests data class list.
+                searchedRequestsDataClassList = filterResults.values as List<RequestDataClass>
+
+                // Update the GUI.
+                notifyDataSetChanged()
+            }
+
+        }
+    }
 }
index 25abd9c54315bf2052af8156c8904ef23bd4aac4..875f10f2a17edb24d199b94988160eedb6d0d995 100644 (file)
@@ -39,7 +39,6 @@ import androidx.core.view.ViewCompat
 import com.stoutner.privacybrowser.activities.MainWebViewActivity
 import com.stoutner.privacybrowser.dataclasses.RequestDataClass
 
-import java.util.Collections
 import java.util.Date
 
 import kotlin.collections.ArrayList
@@ -124,7 +123,7 @@ class NestedScrollWebView @JvmOverloads constructor(context: Context, attributeS
     private var pinnedSslIssuedByUName = ""
     private var pinnedSslStartDate = Date(0)
     private var pinnedSslEndDate = Date(0)
-    private val resourceRequestsDataClassList = Collections.synchronizedList(ArrayList<RequestDataClass>())  // Using a synchronized list makes adding resource requests thread safe.
+    private val resourceRequestsDataClassArrayList = ArrayList<RequestDataClass>()
     private var blockedRequests = 0
     private var easyListBlockedRequests = 0
     private var easyPrivacyBlockedRequests = 0
@@ -234,17 +233,17 @@ class NestedScrollWebView @JvmOverloads constructor(context: Context, attributeS
     // Resource requests.
     fun addResourceRequest(resourceRequestDataClass: RequestDataClass) {
         // Add the resource request to the list.
-        resourceRequestsDataClassList.add(resourceRequestDataClass)
+        resourceRequestsDataClassArrayList.add(resourceRequestDataClass)
     }
 
-    fun getResourceRequestsDataClassList(): List<RequestDataClass> {
+    fun getResourceRequestsDataClassList(): ArrayList<RequestDataClass> {
         // Return the list of resource requests as an array list.
-        return resourceRequestsDataClassList
+        return resourceRequestsDataClassArrayList
     }
 
     fun clearResourceRequestsDataClassList() {
         // Clear the resource requests.
-        resourceRequestsDataClassList.clear()
+        resourceRequestsDataClassArrayList.clear()
     }
 
 
index 1398d8e7aeef99a111cd631a87d59c4fba43aebc..ca3be10b4e293f3ea1b76f1a53661b93f6324fef 100644 (file)
@@ -1,28 +1,31 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2016-2020,2022 Soren Stoutner <soren@stoutner.com>.
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2016-2020, 2022, 2026 Soren Stoutner <soren@stoutner.com>
 
-  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
-  Privacy Browser Android 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.
+  This program 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.
 
-  Privacy Browser Android 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.
+  This program 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 Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
+  You should have received a copy of the GNU General Public License along with
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
 
+<!-- `android:fitsSystemWindows="true"` is required for correct layout starting with API >= 35 (Android 15) -->
 <androidx.coordinatorlayout.widget.CoordinatorLayout
     android:id="@+id/bookmarks_databaseview_coordinatorlayout"
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="match_parent"
-    android:layout_width="match_parent" >
+    android:layout_width="match_parent"
+    android:fitsSystemWindows="true" >
 
     <!-- The linear layout with `orientation="vertical"` moves the list view below the app bar layout. -->
     <LinearLayout
index b8c47f945fc26e22098977f94377acd2aa6319e5..5a9adb4ca8be255fd4695816eddd1f4e691db932 100644 (file)
                 android:imeOptions="actionDone"
                 android:importantForAutofill="no" />
 
-            <LinearLayout
+            <!-- The sublist spinner. -->
+            <Spinner
+                android:id="@+id/filter_sublist_spinner"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
-                android:orientation="horizontal" >
-
-                <!-- The sublist spinner. -->
-                <Spinner
-                    android:id="@+id/filter_sublist_spinner"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:layout_marginTop="15dp" />
-            </LinearLayout>
+                android:layout_marginTop="15dp" />
 
+            <!-- The main filter list spinner is populated inside the toolbar. -->
             <androidx.appcompat.widget.Toolbar
                 android:id="@+id/filter_lists_toolbar"
                 android:layout_height="wrap_content"
index 17c97a71a355c260179e09575360812614b24ffb..8b4036ca22c75b5397ac23e3af6fe425fed64a9b 100644 (file)
             android:background="?android:attr/colorBackground"
             android:theme="@style/PrivacyBrowserAppBar" >
 
+            <!-- The main filter list spinner is populated inside the toolbar. -->
             <androidx.appcompat.widget.Toolbar
                 android:id="@+id/filter_lists_toolbar"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent" />
 
-            <LinearLayout
+            <!-- The sublist spinner. -->
+            <Spinner
+                android:id="@+id/filter_sublist_spinner"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent"
-                android:orientation="horizontal" >
-
-                <!-- The sublist spinner. -->
-                <Spinner
-                    android:id="@+id/filter_sublist_spinner"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:layout_marginBottom="15dp" />
-            </LinearLayout>
+                android:layout_marginBottom="15dp" />
 
             <!-- `android:imeOptions="actionDone"` sets the keyboard to have a `check mark` key instead of a `new line` key. -->
             <EditText
index 0d79a86e361ae36fc0a5c8b73f173067e5274601..e11259ea48ee44eda44b1cc3df06aeb064fba06c 100644 (file)
@@ -2,7 +2,7 @@
 
 <!--
   SPDX-License-Identifier: GPL-3.0-or-later
-  SPDX-FileCopyrightText: 2018-2022, 2024-2025 Soren Stoutner <soren@stoutner.com>
+  SPDX-FileCopyrightText: 2018-2022, 2024-2026 Soren Stoutner <soren@stoutner.com>
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
             android:background="?android:attr/colorBackground"
             android:theme="@style/PrivacyBrowserAppBar" >
 
+            <!-- `android:imeOptions="actionDone"` sets the keyboard to have a `check mark` key instead of a `new line` key. -->
+            <EditText
+                android:id="@+id/search_edittext"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:layout_marginStart="7dp"
+                android:layout_marginEnd="15dp"
+                android:hint="@string/search"
+                android:inputType="text"
+                android:imeOptions="actionDone"
+                android:importantForAutofill="no" />
+
+            <!-- The requests spinner is populated inside the toolbar. -->
             <androidx.appcompat.widget.Toolbar
                 android:id="@+id/requests_toolbar"
                 android:layout_height="wrap_content"
index 482cea8745efbf91cd4c804dc7c95d26f97a1f99..0fa00d3240d655ec38c9501c24340de3ac4e4e3e 100644 (file)
@@ -2,7 +2,7 @@
 
 <!--
   SPDX-License-Identifier: GPL-3.0-or-later
-  SPDX-FileCopyrightText: 2018-2022, 2024-2025 Soren Stoutner <soren@stoutner.com>
+  SPDX-FileCopyrightText: 2018-2022, 2024-2026 Soren Stoutner <soren@stoutner.com>
 
   This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
 
             android:background="?android:attr/colorBackground"
             android:theme="@style/PrivacyBrowserAppBar" >
 
+            <!-- The requests spinner is populated inside the toolbar. -->
             <androidx.appcompat.widget.Toolbar
                 android:id="@+id/requests_toolbar"
                 android:layout_height="wrap_content"
                 android:layout_width="match_parent" />
+
+            <!-- `android:imeOptions="actionDone"` sets the keyboard to have a `check mark` key instead of a `new line` key. -->
+            <EditText
+                android:id="@+id/search_edittext"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:layout_marginStart="7dp"
+                android:layout_marginEnd="15dp"
+                android:hint="@string/search"
+                android:inputType="text"
+                android:imeOptions="actionDone"
+                android:importantForAutofill="no" />
         </com.google.android.material.appbar.AppBarLayout>
 
         <!-- `android:dividerHeight` must be at least `1dp` or the list view is inconsistent in calculating how many requests are displayed. -->
diff --git a/app/src/main/res/layout/spinner.xml b/app/src/main/res/layout/spinner.xml
deleted file mode 100644 (file)
index 59fdb4d..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  Copyright © 2017-2018,2022 Soren Stoutner <soren@stoutner.com>.
-
-  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
-
-  Privacy Browser Android 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.
-
-  Privacy Browser Android 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 Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>. -->
-
-<Spinner
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/spinner"
-    android:layout_height="wrap_content"
-    android:layout_width="wrap_content" />
diff --git a/app/src/main/res/layout/spinner_app_bar.xml b/app/src/main/res/layout/spinner_app_bar.xml
new file mode 100644 (file)
index 0000000..c930e18
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  SPDX-License-Identifier: GPL-3.0-or-later
+  SPDX-FileCopyrightText: 2017-2018, 2022, 2026 Soren Stoutner <soren@stoutner.com>
+
+  This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
+
+  This program 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.
+
+  This program 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
+  this program.  If not, see <https://www.gnu.org/licenses/>. -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent" >
+
+    <Spinner
+        android:id="@+id/spinner"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent" />
+</RelativeLayout>
index fddfb8b47519c35d65a66eaf8ca1cfe44259c94c..c3650a9ca0c8ca673691aa4cba53c30da5cc4e1d 100644 (file)
@@ -18,7 +18,7 @@
 
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
 networkTimeout=10000
 retries=0
 retryBackOffMs=500