/* 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.settings import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.Button import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.navigation.Navigation import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList import androidx.paging.PagedListAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job import kotlinx.coroutines.launch import mozilla.components.browser.icons.IconRequest import mozilla.components.feature.sitepermissions.SitePermissions import org.jetbrains.anko.alert import org.jetbrains.anko.noButton import org.jetbrains.anko.yesButton import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import kotlin.coroutines.CoroutineContext import android.graphics.drawable.BitmapDrawable private const val MAX_ITEMS_PER_PAGE = 50 @SuppressWarnings("TooManyFunctions") class SitePermissionsExceptionsFragment : Fragment(), View.OnClickListener, CoroutineScope { private lateinit var emptyContainerMessage: View private lateinit var recyclerView: RecyclerView private lateinit var clearButton: Button private lateinit var job: Job override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (activity as AppCompatActivity).supportActionBar?.show() job = Job() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_site_permissions_exceptions, container, false) } override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) { super.onViewCreated(rootView, savedInstanceState) bindEmptyContainerMess(rootView) bindClearButton(rootView) bindRecyclerView(rootView) } private fun bindRecyclerView(rootView: View) { recyclerView = rootView.findViewById(R.id.exceptions) recyclerView.layoutManager = LinearLayoutManager(requireContext()) val sitePermissionsPaged = requireContext().components.core.permissionStorage.getSitePermissionsPaged() val adapter = ExceptionsAdapter(this) val liveData = LivePagedListBuilder(sitePermissionsPaged, MAX_ITEMS_PER_PAGE).build() liveData.observe(this, Observer> { if (it.isEmpty()) { showEmptyListMessage() } else { hideEmptyListMessage() adapter.submitList(it) recyclerView.adapter = adapter } }) } private fun hideEmptyListMessage() { emptyContainerMessage.visibility = GONE recyclerView.visibility = VISIBLE clearButton.visibility = VISIBLE } private fun showEmptyListMessage() { emptyContainerMessage.visibility = VISIBLE recyclerView.visibility = GONE clearButton.visibility = GONE } private fun bindEmptyContainerMess(rootView: View) { emptyContainerMessage = rootView.findViewById(R.id.empty_exception_container) } private fun bindClearButton(rootView: View) { clearButton = rootView.findViewById(R.id.delete_all_site_permissions_button) clearButton.setOnClickListener { requireContext().alert( R.string.confirm_clear_permissions_on_all_sites, R.string.clear_permissions ) { yesButton { deleteAllSitePermissions() } noButton { } }.show() } } override fun onDestroy() { super.onDestroy() job.cancel() } private fun deleteAllSitePermissions() { launch(IO) { requireContext().components.core.permissionStorage.deleteAllSitePermissions() launch(Main) { showEmptyListMessage() } } } override fun onClick(view: View?) { val sitePermissions = view?.tag as SitePermissions val directions = SitePermissionsExceptionsFragmentDirections .actionSitePermissionsToExceptionsToSitePermissionsDetails(sitePermissions) Navigation.findNavController(requireNotNull(view)).navigate(directions) } } class SitePermissionsViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView) class ExceptionsAdapter(private val clickListener: View.OnClickListener) : PagedListAdapter(diffCallback), CoroutineScope { private lateinit var job: Job override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { super.onAttachedToRecyclerView(recyclerView) job = Job() } override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { super.onDetachedFromRecyclerView(recyclerView) job.cancel() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SitePermissionsViewHolder { val context = parent.context val inflater = LayoutInflater.from(context) val textView = inflater.inflate(R.layout.fragment_site_permissions_exceptions_item, parent, false) as TextView return SitePermissionsViewHolder(textView) } override fun onBindViewHolder(holder: SitePermissionsViewHolder, position: Int) { val sitePermissions = requireNotNull(getItem(position)) val context = holder.textView.context launch(IO) { val bitmap = context.components.core.icons .loadIcon(IconRequest(sitePermissions.origin)).await().bitmap launch(Main) { val drawable = BitmapDrawable(context.resources, bitmap) holder.textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) } } holder.textView.text = sitePermissions.origin holder.textView.tag = sitePermissions holder.textView.setOnClickListener(clickListener) } companion object { private val diffCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(old: SitePermissions, new: SitePermissions) = old.origin == new.origin override fun areContentsTheSame(old: SitePermissions, new: SitePermissions) = old == new } } }