291 lines
10 KiB
Kotlin
291 lines
10 KiB
Kotlin
/* 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.deletebrowsingdata
|
|
|
|
import android.content.DialogInterface
|
|
import android.os.Bundle
|
|
import android.view.View
|
|
import androidx.appcompat.app.AlertDialog
|
|
import androidx.fragment.app.Fragment
|
|
import androidx.lifecycle.lifecycleScope
|
|
import androidx.navigation.fragment.findNavController
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers.IO
|
|
import kotlinx.coroutines.Dispatchers.Main
|
|
import kotlinx.coroutines.cancel
|
|
import kotlinx.coroutines.flow.collect
|
|
import kotlinx.coroutines.flow.map
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.withContext
|
|
import mozilla.components.lib.state.ext.flowScoped
|
|
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
|
|
import org.mozilla.fenix.R
|
|
import org.mozilla.fenix.components.FenixSnackbar
|
|
import org.mozilla.fenix.components.metrics.Event
|
|
import org.mozilla.fenix.databinding.FragmentDeleteBrowsingDataBinding
|
|
import org.mozilla.fenix.ext.requireComponents
|
|
import org.mozilla.fenix.ext.settings
|
|
import org.mozilla.fenix.ext.showToolbar
|
|
import org.mozilla.fenix.utils.Settings
|
|
|
|
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
|
class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_data) {
|
|
|
|
private lateinit var controller: DeleteBrowsingDataController
|
|
private var scope: CoroutineScope? = null
|
|
private lateinit var settings: Settings
|
|
|
|
private var _binding: FragmentDeleteBrowsingDataBinding? = null
|
|
private val binding get() = _binding!!
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
super.onViewCreated(view, savedInstanceState)
|
|
val tabsUseCases = requireComponents.useCases.tabsUseCases
|
|
val downloadUseCases = requireComponents.useCases.downloadUseCases
|
|
|
|
_binding = FragmentDeleteBrowsingDataBinding.bind(view)
|
|
controller = DefaultDeleteBrowsingDataController(
|
|
tabsUseCases.removeAllTabs,
|
|
downloadUseCases.removeAllDownloads,
|
|
requireComponents.core.historyStorage,
|
|
requireComponents.core.permissionStorage,
|
|
requireComponents.core.store,
|
|
requireComponents.core.icons,
|
|
requireComponents.core.engine
|
|
)
|
|
settings = requireContext().settings()
|
|
|
|
getCheckboxes().iterator().forEach {
|
|
it.onCheckListener = { _ ->
|
|
updateDeleteButton()
|
|
updatePreference(it)
|
|
}
|
|
}
|
|
|
|
getCheckboxes().iterator().forEach {
|
|
it.isChecked = when (it.id) {
|
|
R.id.open_tabs_item -> settings.deleteOpenTabs
|
|
R.id.browsing_data_item -> settings.deleteBrowsingHistory
|
|
R.id.cookies_item -> settings.deleteCookies
|
|
R.id.cached_files_item -> settings.deleteCache
|
|
R.id.site_permissions_item -> settings.deleteSitePermissions
|
|
R.id.downloads_item -> settings.deleteDownloads
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
binding.deleteData.setOnClickListener {
|
|
askToDelete()
|
|
}
|
|
updateDeleteButton()
|
|
}
|
|
|
|
private fun updatePreference(it: DeleteBrowsingDataItem) {
|
|
when (it.id) {
|
|
R.id.open_tabs_item -> settings.deleteOpenTabs = it.isChecked
|
|
R.id.browsing_data_item -> settings.deleteBrowsingHistory = it.isChecked
|
|
R.id.cookies_item -> settings.deleteCookies = it.isChecked
|
|
R.id.cached_files_item -> settings.deleteCache = it.isChecked
|
|
R.id.site_permissions_item -> settings.deleteSitePermissions = it.isChecked
|
|
R.id.downloads_item -> settings.deleteDownloads = it.isChecked
|
|
else -> return
|
|
}
|
|
}
|
|
|
|
override fun onStart() {
|
|
super.onStart()
|
|
|
|
scope = requireComponents.core.store.flowScoped(viewLifecycleOwner) { flow ->
|
|
flow.map { state -> state.tabs.size }
|
|
.ifChanged()
|
|
.collect { openTabs -> updateTabCount(openTabs) }
|
|
}
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
showToolbar(getString(R.string.preferences_delete_browsing_data))
|
|
|
|
getCheckboxes().forEach {
|
|
it.visibility = View.VISIBLE
|
|
}
|
|
|
|
updateItemCounts()
|
|
}
|
|
|
|
private fun updateDeleteButton() {
|
|
val enabled = getCheckboxes().any { it.isChecked }
|
|
|
|
binding.deleteData.isEnabled = enabled
|
|
binding.deleteData.alpha = if (enabled) ENABLED_ALPHA else DISABLED_ALPHA
|
|
}
|
|
|
|
private fun askToDelete() {
|
|
context?.let {
|
|
AlertDialog.Builder(it).apply {
|
|
setMessage(
|
|
it.getString(
|
|
R.string.delete_browsing_data_prompt_message_3,
|
|
it.getString(R.string.app_name)
|
|
)
|
|
)
|
|
|
|
setNegativeButton(R.string.delete_browsing_data_prompt_cancel) { it: DialogInterface, _ ->
|
|
it.cancel()
|
|
}
|
|
|
|
setPositiveButton(R.string.delete_browsing_data_prompt_allow) { it: DialogInterface, _ ->
|
|
it.dismiss()
|
|
deleteSelected()
|
|
}
|
|
create()
|
|
}.show()
|
|
}
|
|
}
|
|
|
|
private fun deleteSelected() {
|
|
startDeletion()
|
|
lifecycleScope.launch(IO) {
|
|
getCheckboxes().mapIndexed { i, v ->
|
|
if (v.isChecked) {
|
|
when (i) {
|
|
OPEN_TABS_INDEX -> controller.deleteTabs()
|
|
HISTORY_INDEX -> controller.deleteBrowsingData()
|
|
COOKIES_INDEX -> controller.deleteCookies()
|
|
CACHED_INDEX -> controller.deleteCachedFiles()
|
|
PERMS_INDEX -> controller.deleteSitePermissions()
|
|
DOWNLOADS_INDEX -> controller.deleteDownloads()
|
|
}
|
|
}
|
|
}
|
|
|
|
withContext(Main) {
|
|
finishDeletion()
|
|
requireComponents.analytics.metrics.track(Event.ClearedPrivateData)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun startDeletion() {
|
|
binding.progressBar.visibility = View.VISIBLE
|
|
binding.deleteBrowsingDataWrapper.isEnabled = false
|
|
binding.deleteBrowsingDataWrapper.isClickable = false
|
|
binding.deleteBrowsingDataWrapper.alpha = DISABLED_ALPHA
|
|
}
|
|
|
|
private fun finishDeletion() {
|
|
val popAfter = binding.openTabsItem.isChecked
|
|
binding.progressBar.visibility = View.GONE
|
|
binding.deleteBrowsingDataWrapper.isEnabled = true
|
|
binding.deleteBrowsingDataWrapper.isClickable = true
|
|
binding.deleteBrowsingDataWrapper.alpha = ENABLED_ALPHA
|
|
|
|
updateItemCounts()
|
|
|
|
FenixSnackbar.make(
|
|
view = requireView(),
|
|
duration = FenixSnackbar.LENGTH_SHORT,
|
|
isDisplayedWithBrowserToolbar = true
|
|
)
|
|
.setText(resources.getString(R.string.preferences_delete_browsing_data_snackbar))
|
|
.show()
|
|
|
|
if (popAfter) {
|
|
viewLifecycleOwner.lifecycleScope.launch(Main) {
|
|
findNavController().apply {
|
|
// If the user deletes all open tabs we need to make sure we remove
|
|
// the BrowserFragment from the backstack.
|
|
popBackStack(R.id.homeFragment, false)
|
|
navigate(DeleteBrowsingDataFragmentDirections.actionGlobalSettingsFragment())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onPause() {
|
|
super.onPause()
|
|
binding.progressBar.visibility = View.GONE
|
|
}
|
|
|
|
override fun onStop() {
|
|
super.onStop()
|
|
scope?.cancel()
|
|
}
|
|
|
|
override fun onDestroyView() {
|
|
super.onDestroyView()
|
|
_binding = null
|
|
}
|
|
|
|
private fun updateItemCounts() {
|
|
updateTabCount()
|
|
updateHistoryCount()
|
|
updateCookies()
|
|
updateCachedImagesAndFiles()
|
|
updateSitePermissions()
|
|
}
|
|
|
|
private fun updateTabCount(openTabs: Int = requireComponents.core.store.state.tabs.size) {
|
|
binding.openTabsItem.apply {
|
|
subtitleView.text = resources.getString(
|
|
R.string.preferences_delete_browsing_data_tabs_subtitle,
|
|
openTabs
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun updateHistoryCount() {
|
|
binding.browsingDataItem.subtitleView.text = ""
|
|
|
|
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
|
val historyCount = requireComponents.core.historyStorage.getVisited().size
|
|
launch(Main) {
|
|
binding.browsingDataItem.apply {
|
|
subtitleView.text =
|
|
resources.getString(
|
|
R.string.preferences_delete_browsing_data_browsing_data_subtitle,
|
|
historyCount
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun updateCookies() {
|
|
// NO OP until we have GeckoView methods to count cookies
|
|
}
|
|
|
|
private fun updateCachedImagesAndFiles() {
|
|
// NO OP until we have GeckoView methods to count cached images and files
|
|
}
|
|
|
|
private fun updateSitePermissions() {
|
|
// NO OP until we have GeckoView methods for cookies and cached files, for consistency
|
|
}
|
|
|
|
private fun getCheckboxes(): List<DeleteBrowsingDataItem> {
|
|
return listOf(
|
|
binding.openTabsItem,
|
|
binding.browsingDataItem,
|
|
binding.cookiesItem,
|
|
binding.cachedFilesItem,
|
|
binding.sitePermissionsItem,
|
|
binding.downloadsItem
|
|
)
|
|
}
|
|
|
|
companion object {
|
|
private const val ENABLED_ALPHA = 1f
|
|
private const val DISABLED_ALPHA = 0.6f
|
|
|
|
private const val OPEN_TABS_INDEX = 0
|
|
private const val HISTORY_INDEX = 1
|
|
private const val COOKIES_INDEX = 2
|
|
private const val CACHED_INDEX = 3
|
|
private const val PERMS_INDEX = 4
|
|
private const val DOWNLOADS_INDEX = 5
|
|
}
|
|
}
|