import android.database.MatrixCursor
import android.os.Bundle
import android.os.Handler
+import android.text.Editable
+import android.text.TextWatcher
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
import com.stoutner.privacybrowser.R
import com.stoutner.privacybrowser.adapters.FilterListArrayAdapter
import com.stoutner.privacybrowser.dataclasses.FilterListDataClass
+import com.stoutner.privacybrowser.dataclasses.FilterListEntryDataClass
import com.stoutner.privacybrowser.dialogs.ViewFilterListEntryDialog
import com.stoutner.privacybrowser.helpers.easyListDataClass
import com.stoutner.privacybrowser.helpers.easyPrivacyDataClass
private lateinit var filterListListView: ListView
private lateinit var filterSublistSpinner: Spinner
- // Define the class variables
+ // Define the class variables.
private var filterSublistSpinnerSelectedPosition = 0
+ // Declare the class variables.
+ private lateinit var currentFilterListDataClassArrayAdapter: FilterListArrayAdapter
+
public override fun onCreate(savedInstanceState: Bundle?) {
// Get a handle for the shared preferences.
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
appBarSpinner = findViewById(R.id.spinner)
filterSublistSpinner = findViewById(R.id.filter_sublist_spinner)
filterListListView = findViewById(R.id.filter_list_listview)
+ val searchEditText = findViewById<EditText>(R.id.search_edittext)
// Store the activity context.
activityContext = this
- // Setup a matrix cursor for the app bar spinner.
+ // Set up a matrix cursor for the app bar spinner.
val appBarSpinnerCursor = MatrixCursor(arrayOf("_id", "Filter List"))
appBarSpinnerCursor.addRow(arrayOf<Any>(ULTRAPRIVACY, getString(R.string.ultraprivacy) + " - " + ultraPrivacyDataClass.versionString))
appBarSpinnerCursor.addRow(arrayOf<Any>(ULTRALIST, getString(R.string.ultralist) + " - " + ultraListDataClass.versionString))
}
}
+ // Handle taps on the filter sublist spinner dropdown.
filterSublistSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// Update the filter sublist spinner selected position.
}
// Get an adapter for the current filter list data class.
- val currentFilterListDataClassArrayAdapter = FilterListArrayAdapter(activityContext, currentFilterListDataClass)
+ currentFilterListDataClassArrayAdapter = FilterListArrayAdapter(activityContext, currentFilterListDataClass)
// 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.
+ if (searchEditText.text.isNotEmpty())
+ currentFilterListDataClassArrayAdapter.filter.filter(searchEditText.text)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
+ // 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.
+ currentFilterListDataClassArrayAdapter.filter.filter(editable.toString())
+ }
+ })
+
// Listen for taps on entries in the list view.
filterListListView.onItemClickListener = AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
// Display the view filter list entry dialog. the list view is 0 based, so the position must be incremented by 1.
// Determine if this is the last entry in the list.
val isLastEntry = (id == filterListListView.count)
+ // Get the filter list entry data class. The list is 0 based, so the position is the ID - 1.
+ val filterListEntryDataClass = filterListListView.getItemAtPosition(id - 1) as FilterListEntryDataClass
+
// Create a view filter list entry dialog.
val viewFilterListsEntryDialogFragment: DialogFragment = ViewFilterListEntryDialog.entry(id, isLastEntry, appBarSpinner.selectedItemPosition,
- filterSublistSpinner.selectedItemPosition)
+ filterSublistSpinner.selectedItemPosition, filterListEntryDataClass)
// Make it so.
viewFilterListsEntryDialogFragment.show(supportFragmentManager, getString(R.string.filterlist_entry))
}
})
- // Set the `check mark` button for the find on page edit text keyboard to close the soft keyboard.
- findOnPageEditText.setOnKeyListener { _: View?, keyCode: Int, keyEvent: KeyEvent ->
- if ((keyEvent.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
- // Hide the soft keyboard.
- inputMethodManager.hideSoftInputFromWindow(currentWebView!!.windowToken, 0)
-
- // Consume the event.
- return@setOnKeyListener true
- } else { // A different key was pressed.
- // Do not consume the event.
- return@setOnKeyListener false
- }
- }
-
// Implement swipe to refresh.
swipeRefreshLayout.setOnRefreshListener {
// Reload the website.
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
+import android.widget.Filter
import android.widget.TextView
import com.stoutner.privacybrowser.R
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, filterListEntryDataClassList: List<FilterListEntryDataClass>) : ArrayAdapter<FilterListEntryDataClass>(context, 0, filterListEntryDataClassList) {
+class FilterListArrayAdapter(context: Context, private val entireFilterListEntryDataClassList: List<FilterListEntryDataClass>) : ArrayAdapter<FilterListEntryDataClass>(context, 0, entireFilterListEntryDataClassList) {
+ // Define the class variables.
+ private var searchedFilterListEntryDataClassList = entireFilterListEntryDataClassList
+
+ override fun getCount(): Int {
+ // Return the searched filter list entry data class list size.
+ return searchedFilterListEntryDataClassList.size
+ }
+
+ override fun getItem(position: Int): FilterListEntryDataClass {
+ // Return the searched filter list entry data class list item.
+ return searchedFilterListEntryDataClassList[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 originalEntryTextView = newView!!.findViewById<TextView>(R.id.original_entry_textview)
// Get this filter list entry data class.
- val filterListEntryDataClass = getItem(position)!!
+ val filterListEntryDataClass = 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 filter list entry data class list.
+ entireFilterListEntryDataClassList
+ } else { // The search string contains data.
+ // Filter the list by the original entry string.
+ entireFilterListEntryDataClassList.filter {
+ it.originalEntryString.lowercase(getDefault()).contains(searchString)
+ }
+ }
+
+ // Return the filter results.
+ return filterResults
+ }
+
+ override fun publishResults(charSequence: CharSequence?, filterResults: FilterResults) {
+ @Suppress("UNCHECKED_CAST")
+ // Store the searched data filter list entry data class list.
+ searchedFilterListEntryDataClassList = filterResults.values as List<FilterListEntryDataClass>
+
+ // Update the GUI.
+ notifyDataSetChanged()
+ }
+
+ }
+ }
}
package com.stoutner.privacybrowser.dataclasses
+import android.os.Parcelable
+
+import kotlinx.parcelize.Parcelize
+
enum class FilterOptionDisposition(val int: Int) {
Null (0),
Apply (1),
Override (2)
}
+@Parcelize
data class FilterListEntryDataClass (
// The strings.
var originalEntryString: String = "",
var filterList: FilterList = FilterList.UltraPrivacy,
var sublist: Sublist = Sublist.MainAllowList,
var thirdParty: FilterOptionDisposition = FilterOptionDisposition.Null
-)
+) : Parcelable
import com.stoutner.privacybrowser.activities.REGULAR_EXPRESSION_BLOCKLIST
import com.stoutner.privacybrowser.activities.ULTRALIST
import com.stoutner.privacybrowser.activities.ULTRAPRIVACY
-import com.stoutner.privacybrowser.dataclasses.FilterListDataClass
import com.stoutner.privacybrowser.dataclasses.FilterListEntryDataClass
import com.stoutner.privacybrowser.dataclasses.FilterOptionDisposition
-import com.stoutner.privacybrowser.helpers.easyListDataClass
-import com.stoutner.privacybrowser.helpers.easyPrivacyDataClass
-import com.stoutner.privacybrowser.helpers.fanboysAnnoyanceDataClass
-import com.stoutner.privacybrowser.helpers.ultraListDataClass
-import com.stoutner.privacybrowser.helpers.ultraPrivacyDataClass
// Define the class constants.
private const val ENTRY_ID = "entry_id"
private const val FILTER_LIST_INT = "filter_list_int"
private const val IS_LAST_ENTRY = "is_last_entry"
private const val SUBLIST_INT = "sublist_int"
+private const val FILTER_LIST_ENTRY_DATA_CLASS = "filter_list_entry_data_class"
// Define the private variables.
private var blueColor = 0
class ViewFilterListEntryDialog : DialogFragment() {
companion object {
- fun entry(entryId: Int, isLastEntry: Boolean, filterListInt: Int, sublistInt: Int): ViewFilterListEntryDialog {
+ fun entry(entryId: Int, isLastEntry: Boolean, filterListInt: Int, sublistInt: Int, filterListEntryDataClass: FilterListEntryDataClass): ViewFilterListEntryDialog {
// Create a bundle.
val bundle = Bundle()
bundle.putBoolean(IS_LAST_ENTRY, isLastEntry)
bundle.putInt(FILTER_LIST_INT, filterListInt)
bundle.putInt(SUBLIST_INT, sublistInt)
+ bundle.putParcelable(FILTER_LIST_ENTRY_DATA_CLASS, filterListEntryDataClass)
// Create a new instance of the view filter list entry dialog.
val viewFilterListEntryDialog = ViewFilterListEntryDialog()
val isLastEntry = requireArguments().getBoolean(IS_LAST_ENTRY)
val filterListInt = requireArguments().getInt(FILTER_LIST_INT)
val sublistInt = requireArguments().getInt(SUBLIST_INT)
+ // The deprecated `getParcelable()` can be replaced once the minimum API >= 33.
+ @Suppress("DEPRECATION") val filterListEntryDataClass = requireArguments().getParcelable<FilterListEntryDataClass>(FILTER_LIST_ENTRY_DATA_CLASS)!!
// Use an alert dialog builder to create the alert dialog.
val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
// Disable the next button if the last filter list entry is displayed.
nextButton.isEnabled = !isLastEntry
- // Get the filter list entry data class.
- val filterListEntryDataClass = getFilterListEntryDataClass(filterListInt, sublistInt, entryId)
-
// Populate the filter list entry views.
filterListTextView.text = getFilterListName(filterListInt)
sublistTextView.text = getSublistName(sublistInt)
return stringBuilder.toString()
}
- private fun getFilterListEntryDataClass(filterListInt: Int, sublistInt: Int, entryId: Int) : FilterListEntryDataClass {
- // Get the filter list entry according to the filter list.
- return when (filterListInt) {
- ULTRAPRIVACY -> getFilterListEntryDataClassFromSublist(ultraPrivacyDataClass, sublistInt, entryId)
- ULTRALIST -> getFilterListEntryDataClassFromSublist(ultraListDataClass, sublistInt, entryId)
- EASYPRIVACY -> getFilterListEntryDataClassFromSublist(easyPrivacyDataClass, sublistInt, entryId)
- EASYLIST -> getFilterListEntryDataClassFromSublist(easyListDataClass, sublistInt, entryId)
- FANBOYS_ANNOYANCE_LIST -> getFilterListEntryDataClassFromSublist(fanboysAnnoyanceDataClass!!, sublistInt, entryId)
- else -> getFilterListEntryDataClassFromSublist(FilterListDataClass(), sublistInt, entryId)
- }
- }
-
- private fun getFilterListEntryDataClassFromSublist(filterListDataClass: FilterListDataClass, sublistInt: Int, entryId: Int) : FilterListEntryDataClass {
- // Return the filter list entry data class. The list is 0 based, so the entry ID needs to be decremented by 1.
- return when (sublistInt) {
- MAIN_ALLOWLIST -> filterListDataClass.mainAllowList[entryId - 1]
- INITIAL_DOMAIN_ALLOWLIST -> filterListDataClass.initialDomainAllowList[entryId - 1]
- REGULAR_EXPRESSION_ALLOWLIST -> filterListDataClass.regularExpressionAllowList[entryId - 1]
- MAIN_BLOCKLIST -> filterListDataClass.mainBlockList[entryId - 1]
- INITIAL_DOMAIN_BLOCKLIST -> filterListDataClass.initialDomainBlockList[entryId - 1]
- REGULAR_EXPRESSION_BLOCKLIST -> filterListDataClass.regularExpressionBlockList[entryId - 1]
- else -> FilterListEntryDataClass()
- }
- }
-
private fun getFilterListName(filterListInt: Int) : String {
// Return the filter list name.
return when (filterListInt) {
<!--
SPDX-License-Identifier: GPL-3.0-or-later
- SPDX-FileCopyrightText: 2025 Soren Stoutner <soren@stoutner.com>
+ SPDX-FileCopyrightText: 2025-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" />
+
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
<!--
SPDX-License-Identifier: GPL-3.0-or-later
- SPDX-FileCopyrightText: 2025 Soren Stoutner <soren@stoutner.com>
+ SPDX-FileCopyrightText: 2025-2026 Soren Stoutner <soren@stoutner.com>
This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
android:layout_width="wrap_content"
android:layout_marginBottom="15dp" />
</LinearLayout>
+
+ <!-- `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 entries are displayed. -->
<!--
SPDX-License-Identifier: GPL-3.0-or-later
- SPDX-FileCopyrightText: 2018-2024 Soren Stoutner <soren@stoutner.com>
+ SPDX-FileCopyrightText: 2018-2024, 2026 Soren Stoutner <soren@stoutner.com>
This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true" >
android:lines="1"
android:imeOptions="actionDone"
android:inputType="text"
- tools:ignore="Autofill" />
+ android:importantForAutofill="no" />
<TextView
android:id="@+id/search_count_textview"
<!--
SPDX-License-Identifier: GPL-3.0-or-later
- SPDX-FileCopyrightText: 2018-2024 Soren Stoutner <soren@stoutner.com>
+ SPDX-FileCopyrightText: 2018-2024, 2026 Soren Stoutner <soren@stoutner.com>
This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true" >
android:lines="1"
android:imeOptions="actionDone"
android:inputType="text"
- tools:ignore="Autofill" />
+ android:importantForAutofill="no" />
<TextView
android:id="@+id/search_count_textview"
<!--
SPDX-License-Identifier: GPL-3.0-or-later
- SPDX-FileCopyrightText: 2015-2017, 2019-2024 Soren Stoutner <soren@stoutner.com>
+ SPDX-FileCopyrightText: 2015-2017, 2019-2024, 2026 Soren Stoutner <soren@stoutner.com>
This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
android:lines="1"
android:imeOptions="actionDone"
android:inputType="text"
- tools:ignore="Autofill" />
+ android:importantForAutofill="no" />
<TextView
android:id="@+id/find_on_page_count_textview"
<!--
SPDX-License-Identifier: GPL-3.0-or-later
- SPDX-FileCopyrightText: 2015-2017, 2019-2025 Soren Stoutner <soren@stoutner.com>
+ SPDX-FileCopyrightText: 2015-2017, 2019-2026 Soren Stoutner <soren@stoutner.com>
This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
android:lines="1"
android:imeOptions="actionDone"
android:inputType="text"
- tools:ignore="Autofill" />
+ android:importantForAutofill="no" />
<TextView
android:id="@+id/find_on_page_count_textview"
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2015-2019, 2022-2024 Soren Stoutner <soren@stoutner.com>.
+ SPDX-License-Identifier: GPL-3.0-or-later
+ SPDX-FileCopyrightText: 2015-2019, 2022-2024, 2026 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.
+ 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/>. -->
<LinearLayout
android:imeOptions="actionGo"
android:inputType="textUri"
android:selectAllOnFocus="true"
- android:autofillHints="" />
+ android:importantForAutofill="no" />
</RelativeLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright 2015-2020, 2022-2024 Soren Stoutner <soren@stoutner.com>.
+ SPDX-License-Identifier: GPL-3.0-or-later
+ SPDX-FileCopyrightText: 2015-2020, 2022-2024, 2026 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.
+ 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:imeOptions="actionGo"` sets the keyboard to have a `go` key instead of a `new line` key. `android:inputType="textUri"` disables spell check in the `EditText`. -->
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/url_edittext"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:imeOptions="actionGo"
android:inputType="textUri"
android:selectAllOnFocus="true"
- tools:ignore="Autofill" />
+ android:importantForAutofill="no" />
<string name="gui">Benutzer-Oberfläche</string>
<!-- Proxy. -->
+ <string name="orbot_or_tor_services_not_installed_title">Orbot oder Tor-Services nicht installiert</string>
+ <string name="orbot_or_tor_services_not_installed_message">Die Nutzung eines Proxys über Tor steht erst zur Verfügung, wenn die Apps "Orbot" oder "TorServices" auf Ihrem Gerät installiert sind.</string>
<string name="i2p_not_installed_title">I2P ist nicht installiert</string>
<string name="i2p_not_installed_message">Der I2P-Proxy kann erst nach Installation der I2P-App genutzt werden.</string>
<string name="custom_proxy_invalid">Die benutzerdefinierte Proxy-URL ist ungültig.</string>
<string name="gui">Interfaz</string>
<!-- Proxy. -->
+ <string name="orbot_or_tor_services_not_installed_title">Orbot o TorServices No Instalados</string>
+ <string name="orbot_or_tor_services_not_installed_message">El uso de Tor como proxy no funcionará a menos que tenga instalada la aplicación Orbot o TorServices.</string>
<string name="i2p_not_installed_title">I2P No Instalado</string>
<string name="i2p_not_installed_message">El proxy a través de I2P no funcionará a menos que la aplicación I2P esté instalada.</string>
<string name="custom_proxy_invalid">La URL del proxy personalizado no es válida.</string>
<string name="gui">Interfaccia</string>
<!-- Proxy. -->
+ <string name="orbot_or_tor_services_not_installed_title">Orbot or TorServices non installati</string>
+ <string name="orbot_or_tor_services_not_installed_message">Il Proxy attraverso Tor non funziona se non sono installate le app Orbot o TorServices.</string>
<string name="i2p_not_installed_title">I2P Non Installato</string>
<string name="i2p_not_installed_message">Il Proxy con I2P non funziona se non è installata la app I2P.</string>
<string name="custom_proxy_invalid">La URL del proxy personalizzato non è valida.</string>
<string name="gui">Интерфейс</string>
<!-- Proxy. -->
+ <string name="orbot_or_tor_services_not_installed_title">Orbot или TorServices не установлены</string>
+ <string name="orbot_or_tor_services_not_installed_message">Проксирование через Tor работает только если установлено приложение Orbot или TorServices.</string>
<string name="i2p_not_installed_title">I2P не установлен</string>
<string name="i2p_not_installed_message">Прокси через I2P работать не будет, если приложение I2P не установлено.</string>
<string name="custom_proxy_invalid">URL пользовательского прокси недействителен.</string>