parent
79fcb97ed2
commit
84a7430e57
@ -0,0 +1,78 @@
|
|||||||
|
package org.mozilla.fenix.exceptions
|
||||||
|
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains functionality to manage custom domains for allow-list.
|
||||||
|
*/
|
||||||
|
object ExceptionDomains {
|
||||||
|
private const val PREFERENCE_NAME = "exceptions"
|
||||||
|
private const val KEY_DOMAINS = "exceptions_domains"
|
||||||
|
private const val SEPARATOR = "@<;>@"
|
||||||
|
|
||||||
|
private var exceptions: List<String>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the previously added/saved custom domains from preferences.
|
||||||
|
*
|
||||||
|
* @param context the application context
|
||||||
|
* @return list of custom domains
|
||||||
|
*/
|
||||||
|
fun load(context: Context): List<String> {
|
||||||
|
if (exceptions == null) {
|
||||||
|
exceptions = (preferences(context)
|
||||||
|
.getString(KEY_DOMAINS, "") ?: "")
|
||||||
|
.split(SEPARATOR)
|
||||||
|
.filter { !it.isEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return exceptions ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the provided domains to preferences.
|
||||||
|
*
|
||||||
|
* @param context the application context
|
||||||
|
* @param domains list of domains
|
||||||
|
*/
|
||||||
|
fun save(context: Context, domains: List<String>) {
|
||||||
|
exceptions = domains
|
||||||
|
|
||||||
|
preferences(context)
|
||||||
|
.edit()
|
||||||
|
.putString(KEY_DOMAINS, domains.joinToString(separator = SEPARATOR))
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided domain to preferences.
|
||||||
|
*
|
||||||
|
* @param context the application context
|
||||||
|
* @param domain the domain to add
|
||||||
|
*/
|
||||||
|
fun add(context: Context, domain: String) {
|
||||||
|
val domains = mutableListOf<String>()
|
||||||
|
domains.addAll(load(context))
|
||||||
|
domains.add(domain)
|
||||||
|
|
||||||
|
save(context, domains)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the provided domain from preferences.
|
||||||
|
*
|
||||||
|
* @param context the application context
|
||||||
|
* @param domains the domain to remove
|
||||||
|
*/
|
||||||
|
fun remove(context: Context, domains: List<String>) {
|
||||||
|
save(context, load(context) - domains)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun preferences(context: Context): SharedPreferences =
|
||||||
|
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.exceptions
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
||||||
|
|
||||||
|
private sealed class AdapterItem {
|
||||||
|
object DeleteButton : AdapterItem()
|
||||||
|
object Header : AdapterItem()
|
||||||
|
data class Item(val item: ExceptionsItem) : AdapterItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExceptionsList(val exceptions: List<ExceptionsItem>) {
|
||||||
|
val items: List<AdapterItem>
|
||||||
|
init {
|
||||||
|
val items = mutableListOf<AdapterItem>()
|
||||||
|
items.add(AdapterItem.Header)
|
||||||
|
for (exception in exceptions) {
|
||||||
|
items.add(AdapterItem.Item(exception))
|
||||||
|
}
|
||||||
|
items.add(AdapterItem.DeleteButton)
|
||||||
|
this.items = items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExceptionsAdapter(
|
||||||
|
private val actionEmitter: Observer<ExceptionsAction>
|
||||||
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
private var exceptionsList: ExceptionsList = ExceptionsList(emptyList())
|
||||||
|
private lateinit var job: Job
|
||||||
|
|
||||||
|
fun updateData(items: List<ExceptionsItem>) {
|
||||||
|
this.exceptionsList = ExceptionsList(items)
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = exceptionsList.items.size
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return when (exceptionsList.items[position]) {
|
||||||
|
is AdapterItem.DeleteButton -> ExceptionsDeleteButtonViewHolder.LAYOUT_ID
|
||||||
|
is AdapterItem.Header -> ExceptionsHeaderViewHolder.LAYOUT_ID
|
||||||
|
is AdapterItem.Item -> ExceptionsListItemViewHolder.LAYOUT_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder(view, actionEmitter)
|
||||||
|
ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view)
|
||||||
|
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(view, actionEmitter, job)
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
when (holder) {
|
||||||
|
is ExceptionsListItemViewHolder -> (exceptionsList.items[position] as AdapterItem.Item).also {
|
||||||
|
holder.bind(it.item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
job = Job()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
package org.mozilla.fenix.exceptions
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import org.mozilla.fenix.mvi.Action
|
||||||
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
import org.mozilla.fenix.mvi.Change
|
||||||
|
import org.mozilla.fenix.mvi.UIComponent
|
||||||
|
import org.mozilla.fenix.mvi.ViewState
|
||||||
|
import org.mozilla.fenix.test.Mockable
|
||||||
|
|
||||||
|
data class ExceptionsItem(val url: String)
|
||||||
|
|
||||||
|
@Mockable
|
||||||
|
class ExceptionsComponent(
|
||||||
|
private val container: ViewGroup,
|
||||||
|
bus: ActionBusFactory,
|
||||||
|
override var initialState: ExceptionsState = ExceptionsState(emptyList())
|
||||||
|
) :
|
||||||
|
UIComponent<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
||||||
|
bus.getManagedEmitter(ExceptionsAction::class.java),
|
||||||
|
bus.getSafeManagedObservable(ExceptionsChange::class.java)
|
||||||
|
) {
|
||||||
|
|
||||||
|
override val reducer: (ExceptionsState, ExceptionsChange) -> ExceptionsState = { state, change ->
|
||||||
|
when (change) {
|
||||||
|
is ExceptionsChange.Change -> state.copy(items = change.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable)
|
||||||
|
|
||||||
|
init {
|
||||||
|
render(reducer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ExceptionsState(val items: List<ExceptionsItem>) : ViewState
|
||||||
|
|
||||||
|
sealed class ExceptionsAction : Action {
|
||||||
|
sealed class Delete : ExceptionsAction() {
|
||||||
|
object All : Delete()
|
||||||
|
data class One(val item: ExceptionsItem) : Delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ExceptionsChange : Change {
|
||||||
|
data class Change(val list: List<ExceptionsItem>) : ExceptionsChange()
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package org.mozilla.fenix.exceptions
|
||||||
|
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.navigation.Navigation
|
||||||
|
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||||
|
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||||
|
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class ExceptionsFragment : Fragment(), CoroutineScope {
|
||||||
|
private var job = Job()
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = job + Dispatchers.Main
|
||||||
|
private lateinit var exceptionsComponent: ExceptionsComponent
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
job = Job()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
activity?.title = getString(R.string.preference_exceptions)
|
||||||
|
(activity as AppCompatActivity).supportActionBar?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
||||||
|
exceptionsComponent = ExceptionsComponent(
|
||||||
|
view.exceptions_layout,
|
||||||
|
ActionBusFactory.get(this),
|
||||||
|
ExceptionsState(loadAndMapExceptions())
|
||||||
|
)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
getAutoDisposeObservable<ExceptionsAction>()
|
||||||
|
.subscribe {
|
||||||
|
when (it) {
|
||||||
|
is ExceptionsAction.Delete.All -> launch(Dispatchers.IO) {
|
||||||
|
val domains = ExceptionDomains.load(context!!)
|
||||||
|
ExceptionDomains.remove(context!!, domains)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
view?.let { view: View -> Navigation.findNavController(view).navigateUp() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ExceptionsAction.Delete.One -> launch(Dispatchers.IO) {
|
||||||
|
ExceptionDomains.remove(context!!, listOf(it.item.url))
|
||||||
|
reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAndMapExceptions(): List<ExceptionsItem> {
|
||||||
|
return ExceptionDomains.load(context!!)
|
||||||
|
.map { item ->
|
||||||
|
ExceptionsItem(
|
||||||
|
item
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun reloadData() {
|
||||||
|
val items = loadAndMapExceptions()
|
||||||
|
|
||||||
|
coroutineScope {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
view?.let { view: View -> Navigation.findNavController(view).navigateUp() }
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
getManagedEmitter<ExceptionsChange>().onNext(ExceptionsChange.Change(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.exceptions
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import io.reactivex.functions.Consumer
|
||||||
|
import kotlinx.android.synthetic.main.component_exceptions.view.*
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.mvi.UIView
|
||||||
|
|
||||||
|
class ExceptionsUIView(
|
||||||
|
container: ViewGroup,
|
||||||
|
actionEmitter: Observer<ExceptionsAction>,
|
||||||
|
changesObservable: Observable<ExceptionsChange>
|
||||||
|
) :
|
||||||
|
UIView<ExceptionsState, ExceptionsAction, ExceptionsChange>(container, actionEmitter, changesObservable) {
|
||||||
|
|
||||||
|
override val view: LinearLayout = LayoutInflater.from(container.context)
|
||||||
|
.inflate(R.layout.component_exceptions, container, true)
|
||||||
|
.findViewById(R.id.exceptions_wrapper)
|
||||||
|
|
||||||
|
init {
|
||||||
|
view.exceptions_list.apply {
|
||||||
|
adapter = ExceptionsAdapter(actionEmitter)
|
||||||
|
layoutManager = LinearLayoutManager(container.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateView() = Consumer<ExceptionsState> {
|
||||||
|
(view.exceptions_list.adapter as ExceptionsAdapter).updateData(it.items)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsAction
|
||||||
|
|
||||||
|
class ExceptionsDeleteButtonViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val actionEmitter: Observer<ExceptionsAction>
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
private val deleteButton = view.removeAllExceptions
|
||||||
|
|
||||||
|
init {
|
||||||
|
deleteButton.setOnClickListener {
|
||||||
|
actionEmitter.onNext(ExceptionsAction.Delete.All)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.delete_exceptions_button
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
class ExceptionsHeaderViewHolder(
|
||||||
|
view: View
|
||||||
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.exceptions_description
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import io.reactivex.Observer
|
||||||
|
import kotlinx.android.synthetic.main.exception_item.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.browser.icons.IconRequest
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsAction
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsItem
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class ExceptionsListItemViewHolder(
|
||||||
|
view: View,
|
||||||
|
private val actionEmitter: Observer<ExceptionsAction>,
|
||||||
|
val job: Job
|
||||||
|
) : RecyclerView.ViewHolder(view), CoroutineScope {
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext
|
||||||
|
get() = Dispatchers.IO + job
|
||||||
|
|
||||||
|
private val favicon = view.favicon_image
|
||||||
|
private val url = view.domainView
|
||||||
|
private val deleteButton = view.delete_exception
|
||||||
|
|
||||||
|
private var item: ExceptionsItem? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
deleteButton.setOnClickListener {
|
||||||
|
item?.let {
|
||||||
|
actionEmitter.onNext(ExceptionsAction.Delete.One(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(item: ExceptionsItem) {
|
||||||
|
this.item = item
|
||||||
|
url.text = item.url
|
||||||
|
updateFavIcon(item.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateFavIcon(url: String) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val bitmap = favicon.context.components.utils.icons
|
||||||
|
.loadIcon(IconRequest(url)).await().bitmap
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
favicon.setImageBitmap(bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.exception_item
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/exceptions_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/exceptions_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</LinearLayout>
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<Button android:id="@+id/removeAllExceptions"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:backgroundTint="?attr/neutral"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:text="@string/preferences_tracking_protection_exceptions_turn_on_for_all"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="?attr/accentHighContrast"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" />
|
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/favicon_image"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/domainView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
|
android:textColor="?primaryText"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/delete_exception"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/favicon_image"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="mozilla.org" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/delete_exception"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:src="@drawable/ic_close"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/exceptions_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:text="@string/preferences_tracking_protection_exceptions_description"
|
||||||
|
android:textColor="?primaryText"
|
||||||
|
android:textSize="16sp" />
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/exceptions_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context="org.mozilla.fenix.exceptions.ExceptionsFragment">
|
||||||
|
</LinearLayout>
|
Loading…
Reference in New Issue