/*
- * 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/>.
*
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
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
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.
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.
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
// 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)
}
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
}
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)
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 {
}
}
}
+
+ // 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.
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
// 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)
// 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)
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.
// 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.
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
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
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
// 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()
+ }
+
+ }
+ }
}
import com.stoutner.privacybrowser.activities.MainWebViewActivity
import com.stoutner.privacybrowser.dataclasses.RequestDataClass
-import java.util.Collections
import java.util.Date
import kotlin.collections.ArrayList
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
// 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()
}
<?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
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"
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
<!--
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"
<!--
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. -->
+++ /dev/null
-<?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" />
--- /dev/null
+<?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>
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