X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FDomainsActivity.kt;h=4996568e1e79084b865a75b4098f670a89a2a53a;hb=HEAD;hp=a4e3505aa19f18b80c710f0a8526770fee77cdde;hpb=e065315a36c804626a7dba38d3edad05e9fdb473;p=PrivacyBrowserAndroid.git diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt index a4e3505a..f3b854e4 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.kt @@ -1,7 +1,7 @@ /* * Copyright 2017-2024 Soren Stoutner . * - * This file is part of Privacy Browser Android . + * This file is part of 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 @@ -14,7 +14,7 @@ * 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 . + * along with Privacy Browser Android. If not, see . */ package com.stoutner.privacybrowser.activities @@ -24,7 +24,9 @@ import android.content.Context import android.database.Cursor import android.os.Bundle import android.os.Handler +import android.os.Looper import android.view.Menu +import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup @@ -41,8 +43,11 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.Toolbar import androidx.core.app.NavUtils +import androidx.core.view.MenuProvider import androidx.cursoradapter.widget.CursorAdapter import androidx.fragment.app.DialogFragment +import androidx.fragment.app.commitNow +import androidx.lifecycle.Lifecycle import androidx.preference.PreferenceManager import com.google.android.material.floatingactionbutton.FloatingActionButton @@ -50,11 +55,9 @@ import com.google.android.material.snackbar.Snackbar import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.dialogs.AddDomainDialog -import com.stoutner.privacybrowser.dialogs.AddDomainDialog.AddDomainListener +import com.stoutner.privacybrowser.dialogs.DeleteAllDomainsDialog import com.stoutner.privacybrowser.fragments.DomainSettingsFragment import com.stoutner.privacybrowser.fragments.DomainsListFragment -import com.stoutner.privacybrowser.fragments.DomainsListFragment.DismissSnackbarInterface -import com.stoutner.privacybrowser.fragments.DomainsListFragment.SaveDomainSettingsInterface import com.stoutner.privacybrowser.helpers.DOMAIN_NAME import com.stoutner.privacybrowser.helpers.ID import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper @@ -62,6 +65,7 @@ import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper // Define the public constants. const val CLOSE_ON_BACK = "close_on_back" const val CURRENT_IP_ADDRESSES = "current_ip_addresses" +const val DOMAIN_SETTINGS_FRAGMENT_TAG = "domain_settings_fragment" const val LOAD_DOMAIN = "load_domain" const val SSL_END_DATE = "ssl_end_date" const val SSL_ISSUED_BY_CNAME = "ssl_issued_by_cname" @@ -78,7 +82,9 @@ private const val DOMAIN_SETTINGS_DISPLAYED = "domain_settings_displayed" private const val DOMAIN_SETTINGS_SCROLL_Y = "domain_settings_scroll_y" private const val LISTVIEW_POSITION = "listview_position" -class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarInterface, SaveDomainSettingsInterface { +class DomainsActivity : AppCompatActivity(), AddDomainDialog.AddDomainListener, DeleteAllDomainsDialog.DeleteAllDomainSettingsListener, DomainsListFragment.DismissSnackbarInterface, + DomainsListFragment.SaveDomainSettingsInterface { + companion object { // Define the public variables. var currentDomainDatabaseId = 0 // Used in `DomainsListFragment`. @@ -88,9 +94,6 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI var sslStartDateLong: Long = 0 // Used in `DomainSettingsFragment`. var twoPanedMode = false // Used in `DomainsListFragment`. - // Declare the public views. They are used in `DomainsListFragment`. - lateinit var deleteMenuItem: MenuItem - // Declare the SSL certificate and IP address strings. var currentIpAddresses: String? = null // Used in `DomainSettingsFragment`. var sslIssuedToCName: String? = null // Used in `DomainSettingsFragment`. @@ -104,6 +107,8 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI // Declare the class views. private lateinit var addDomainFAB: FloatingActionButton private lateinit var coordinatorLayout: View + + // Define the class views. private var domainsListView: ListView? = null private var undoDeleteSnackbar: Snackbar? = null @@ -115,6 +120,8 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI private var closeActivityAfterDismissingSnackbar = false private var closeOnBack = false private var deletedDomainPosition = 0 + private var deleteMenuItemEnabled = true + private var deleteAllMenuItemEnabled = true private var domainSettingsDatabaseIdBeforeRestart = 0 private var domainSettingsDisplayedBeforeRestart = false private var domainSettingsScrollY = 0 @@ -236,16 +243,15 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI val domainsListFragment = DomainsListFragment() // Display the domains list fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainsListFragment) + } // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode. populateDomainsListView(-1, domainsListViewPosition) // Show the add domain floating action button. addDomainFAB.show() - - // Hide the delete menu item. - deleteMenuItem.isVisible = false } else { // The device is in single-paned mode and the domain list fragment is displayed. // Dismiss the undo delete SnackBar if it is shown. if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { @@ -262,22 +268,271 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI } } - // Register the on back pressed callback. - onBackPressedDispatcher.addCallback(this, onBackPressedCallback) - } + // Get a handle for the activity (used in an inner class below). + val activity: Activity = this + + // Add the menu provider. This runs each time a fragment is replaced. + addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + // Inflate the menu. + menuInflater.inflate(R.menu.domains_options_menu, menu) + + // Get a handle for the menu items. + val deleteMenuItem = menu.findItem(R.id.delete_domain) + val deleteAllMenuItem = menu.findItem(R.id.delete_all) + + // Get the domain settings fragment. + val domainSettingsFragment = supportFragmentManager.findFragmentByTag(DOMAIN_SETTINGS_FRAGMENT_TAG) + + // Update the visibility of the delete menu item. + if (twoPanedMode) { // The device is in two-paned mode. + // Show both menu items. + deleteMenuItem.isVisible = true + deleteAllMenuItem.isVisible = true + } else if ((domainSettingsFragment != null) && domainSettingsFragment.isVisible) { // The device is in single-paned mode and the domain settings fragment is visible. + // Show the delete menu item. + deleteMenuItem.isVisible = true + + // Hide the delete all menu item. + deleteAllMenuItem.isVisible = false + } else { // The device is in single-paned mode and the domains list is visible. + // Hide the delete menu item. + deleteMenuItem.isVisible = false + + // Show the delete all menu item. + deleteAllMenuItem.isVisible = true + } + + // Update the status of the delete menu item. + deleteMenuItem.isEnabled = deleteMenuItemEnabled + deleteAllMenuItem.isEnabled = deleteAllMenuItemEnabled + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + // Run the command according to the selected menu item. + when (menuItem.itemId) { + android.R.id.home -> { // The home arrow is identified as `android.R.id.home`, not just `R.id.home`. + // Check if the device is in two-paned mode. + if (twoPanedMode) { // The device is in two-paned mode. + // Save the current domain settings if the domain settings fragment is displayed. + if (findViewById(R.id.domain_settings_scrollview) != null) + saveDomainSettings(coordinatorLayout) + + // Dismiss the undo delete snackbar if it is shown. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { + // Set the close flag. + closeActivityAfterDismissingSnackbar = true + + // Dismiss the snackbar. + undoDeleteSnackbar!!.dismiss() + } else { + // Go home. + finish() + } + } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu. + // Save the current domain settings. + saveDomainSettings(coordinatorLayout) + + // Go home. + finish() + } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed. + // Save the current domain settings. + saveDomainSettings(coordinatorLayout) + + // Instantiate a new domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domains list fragment. + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainsListFragment) + } + + // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode. + populateDomainsListView(-1, domainsListViewPosition) + + // Show the add domain floating action button. + addDomainFAB.show() + } else { // The device is in single-paned mode and domains list fragment is displayed. + // Dismiss the undo delete snackbar if it is shown. + if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { + // Set the close flag. + closeActivityAfterDismissingSnackbar = true + + // Dismiss the snackbar. + undoDeleteSnackbar!!.dismiss() + } else { + // Go home. + finish() + } + } + } + + R.id.delete_domain -> { // Delete. + // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode. + if (closeOnBack && !twoPanedMode) { // The activity should delete the domain settings and exit straight to the the main WebView activity. + // Delete the selected domain. + domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId) + + // Go home. + NavUtils.navigateUpFromSameTask(activity) + } else { // A snackbar should be shown before deleting the domain settings. + // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists. + closeOnBack = false + + // Store a copy of the current domain database ID because it could change while the snackbar is displayed. + val databaseIdToDelete = currentDomainDatabaseId + + // Update the fragments and menu items. + if (twoPanedMode) { // Two-paned mode. + // Store the deleted domain position, which is needed if undo is selected in the snackbar. + deletedDomainPosition = domainsListView!!.checkedItemPosition + + // Get a handle for the domain settings fragment. + val domainSettingsFragment = supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!! + + // Get a handle for the domain settings fragment view. + val domainSettingsFragmentView = domainSettingsFragment.requireView() + + // Hide the domain settings fragment. + domainSettingsFragmentView.visibility = View.INVISIBLE + + // Disable the delete menu item. + deleteMenuItemEnabled = false + } else { // Single-paned mode. + // Instantiate a new domains list fragment. + val domainsListFragment = DomainsListFragment() + + // Display the domains list fragment. + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainsListFragment) + } + + // Show the add domain floating action button. + addDomainFAB.show() + } + + // Get a cursor that does not show the domain to be deleted. + val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete) + + // Populate the domains pending delete cursor adapter. + val domainsPendingDeleteCursorAdapter = populateDomainsCursorAdapter(domainsPendingDeleteCursor) + + // Update the handle for the current domains list view. + domainsListView = findViewById(R.id.domains_listview) + + // Update the list view. + domainsListView!!.adapter = domainsPendingDeleteCursorAdapter + + // Disable the delete all menu item if no domains are displayed. + deleteAllMenuItemEnabled = (domainsPendingDeleteCursor.count > 0) + + // Invalidate the options menu. + invalidateMenu() + + // Display a snackbar. + undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.domain_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo) {} // Do nothing because everything will be handled by `onDismissed()` below. + .addCallback(object : Snackbar.Callback() { + override fun onDismissed(snackbar: Snackbar, event: Int) { + // Run commands based on the event. + if (event == DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button. + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the domain settings in the arguments bundle. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete) + argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY) + + // Instantiate a new domain settings fragment. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain settings fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Display the correct fragments. + if (twoPanedMode) { // The device is in two-paned mode. + // Get a cursor with the current contents of the domains database. + val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain + + // Populate the undo delete domains cursor adapter. + val undoDeleteDomainsCursorAdapter = populateDomainsCursorAdapter(undoDeleteDomainsCursor) + + // Update the domains list view. + domainsListView!!.adapter = undoDeleteDomainsCursorAdapter + + // Select the previously deleted domain in the list view. + domainsListView!!.setItemChecked(deletedDomainPosition, true) + + // Enable the menu items. + deleteMenuItemEnabled = true + deleteAllMenuItemEnabled = true + + // Display the domain settings fragment. + supportFragmentManager.commitNow { + replace(R.id.domain_settings_fragment_container, domainSettingsFragment, DOMAIN_SETTINGS_FRAGMENT_TAG) + } + } else { // The device is in one-paned mode. + // Hide the add domain floating action button. + addDomainFAB.hide() + + // Display the domain settings fragment. + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainSettingsFragment, DOMAIN_SETTINGS_FRAGMENT_TAG) + } + } + } else { // The snackbar was dismissed without the undo button being pushed. + // Delete the selected domain. + val rowsDeleted = domainsDatabaseHelper.deleteDomain(databaseIdToDelete) + + // Enable the delete menu item. + // The rows deleted should always be greater than 0, but in all cases they should be greater than -1. + // This has the effect of tricking the compiler into waiting until after the delete finishes to reenable the delete menu item, + // because the compiler (probably) can't tell that the response will never be less than -1, so it doesn't compile out the delay. + if (rowsDeleted > -1) { + // Enable the delete menu item if in two-paned mode. + if (twoPanedMode) { + // Enable the delete menu item. + deleteMenuItemEnabled = true + + // Invalidate the options menu. + invalidateMenu() + } + + // Reset the dismissing snackbar tracker. + dismissingSnackbar = false + } + + // Close the activity if back was pressed. + if (closeActivityAfterDismissingSnackbar) + NavUtils.navigateUpFromSameTask(activity) + } + } + }) + + // Show the Snackbar. + undoDeleteSnackbar!!.show() + } + } + + R.id.delete_all -> { // Delete all. + // Instantiate the delete all domains dialog fragment. + val deleteAllDomainsDialogFragment = DeleteAllDomainsDialog() - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the menu. - menuInflater.inflate(R.menu.domains_options_menu, menu) + // Show the delete all domains alert dialog. + deleteAllDomainsDialogFragment.show(supportFragmentManager, getString(R.string.delete_all)) - // Get a handle for the delete menu item. - deleteMenuItem = menu.findItem(R.id.delete_domain) + // Consume the event. + return true + } + } - // Only display the delete menu item (initially) in two-paned mode. - deleteMenuItem.isVisible = twoPanedMode + // Consume the event. + return true + } + }, this, Lifecycle.State.RESUMED) - // Display the fragments. This must be done from `onCreateOptionsMenu()` instead of `onCreate()` because `populateDomainsListView()` needs `deleteMenuItem` to be inflated. - if (appRestarted && domainSettingsDisplayedBeforeRestart) { // The app was restarted, possibly because the device was rotated, and domain settings were displayed previously. + // Display the fragments. + if (appRestarted && domainSettingsDisplayedBeforeRestart) { // The app was restarted and domain settings were displayed previously. // Reset the app restarted flag. appRestarted = false @@ -286,10 +541,20 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI val domainsListFragment = DomainsListFragment() // Display the domains list fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainsListFragment) + } - // Populate the list of domains and highlight the domain that was highlighted before the restart. - populateDomainsListView(domainSettingsDatabaseIdBeforeRestart, domainsListViewPosition) + // Create a populate domains list view handler. + val populateDomainsListViewHandler = Handler(Looper.getMainLooper()) + + // Create a populate domains list view runnable. + val populateDomainsListViewRunnable = Runnable { + populateDomainsListView(domainSettingsDatabaseIdBeforeRestart, domainsListViewPosition) + } + + // Populate the domains list view. For some reason, beginning with appcompat 1.7.0, this needs to be in a runnable instead of being called directly, or the system crashes. + populateDomainsListViewHandler.post(populateDomainsListViewRunnable) } else { // The device is in single-paned mode. // Store the current domain database ID. currentDomainDatabaseId = domainSettingsDatabaseIdBeforeRestart @@ -307,14 +572,13 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI // Add the arguments bundle to the domain settings fragment. domainSettingsFragment.arguments = argumentsBundle - // Show the delete menu item. - deleteMenuItem.isVisible = true - // Hide the add domain floating action button. addDomainFAB.hide() // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainSettingsFragment, DOMAIN_SETTINGS_FRAGMENT_TAG) + } } } else { // The device was not restarted or, if it was, domain settings were not displayed previously. if (goDirectlyToDatabaseId >= 0) { // Load the indicated domain settings. @@ -327,10 +591,20 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI val domainsListFragment = DomainsListFragment() // Display the domains list fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainsListFragment) + } + + // Create a populate domains list view handler. + val populateDomainsListViewHandler = Handler(Looper.getMainLooper()) + + // Create a populate domains list view runnable. + val populateDomainsListViewRunnable = Runnable { + populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition) + } - // Populate the list of domains. - populateDomainsListView(goDirectlyToDatabaseId, domainsListViewPosition) + // Populate the domains list view. For some reason, beginning with appcompat 1.7.0, this needs to be in a runnable instead of being called directly, or the system crashes. + populateDomainsListViewHandler.post(populateDomainsListViewRunnable) } else { // The device is in single-paned mode. // Create an arguments bundle. val argumentsBundle = Bundle() @@ -345,274 +619,43 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI // Add the arguments bundle to the domain settings fragment. domainSettingsFragment.arguments = argumentsBundle - // Show the delete menu item. - deleteMenuItem.isVisible = true - // Hide the add domain floating action button. addDomainFAB.hide() // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainSettingsFragment, DOMAIN_SETTINGS_FRAGMENT_TAG) + } } - } else { // Highlight the first domain. - // Instantiate a new domains list fragment. + } else { // Display the domains list view. + // Instantiate a new domain settings fragment. val domainsListFragment = DomainsListFragment() - // Display the domain list fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() - - // Populate the list of domains. `-1` highlights the first domain. - populateDomainsListView(-1, domainsListViewPosition) - } - } - - // Success! - return true - } - - override fun onOptionsItemSelected(menuItem: MenuItem): Boolean { - // Run the command according to the selected menu item. - when (menuItem.itemId) { - android.R.id.home -> { // The home arrow is identified as `android.R.id.home`, not just `R.id.home`. - // Check if the device is in two-paned mode. - if (twoPanedMode) { // The device is in two-paned mode. - // Save the current domain settings if the domain settings fragment is displayed. - if (findViewById(R.id.domain_settings_scrollview) != null) - saveDomainSettings(coordinatorLayout) - - // Dismiss the undo delete snackbar if it is shown. - if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { - // Set the close flag. - closeActivityAfterDismissingSnackbar = true - - // Dismiss the snackbar. - undoDeleteSnackbar!!.dismiss() - } else { - // Go home. - finish() - } - } else if (closeOnBack) { // Go directly back to the main WebView activity because the domains activity was launched from the options menu. - // Save the current domain settings. - saveDomainSettings(coordinatorLayout) - - // Go home. - finish() - } else if (findViewById(R.id.domain_settings_scrollview) != null) { // The device is in single-paned mode and the domain settings fragment is displayed. - // Save the current domain settings. - saveDomainSettings(coordinatorLayout) + // Display the domains list fragment. + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainsListFragment) + } - // Instantiate a new domains list fragment. - val domainsListFragment = DomainsListFragment() + // Create a populate domains list view handler. + val populateDomainsListViewHandler = Handler(Looper.getMainLooper()) - // Display the domains list fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() - - // Populate the list of domains. `-1` highlights the first domain if in two-paned mode. It has no effect in single-paned mode. + // Create a populate domains list view runnable. + val populateDomainsListViewRunnable = Runnable { populateDomainsListView(-1, domainsListViewPosition) - - // Show the add domain floating action button. - addDomainFAB.show() - - // Hide the delete menu item. - deleteMenuItem.isVisible = false - } else { // The device is in single-paned mode and domains list fragment is displayed. - // Dismiss the undo delete snackbar if it is shown. - if (undoDeleteSnackbar != null && undoDeleteSnackbar!!.isShown) { - // Set the close flag. - closeActivityAfterDismissingSnackbar = true - - // Dismiss the snackbar. - undoDeleteSnackbar!!.dismiss() - } else { - // Go home. - finish() - } } - } - - R.id.delete_domain -> { // Delete. - // Get a handle for the activity (used in an inner class below). - val activity: Activity = this - - // Check to see if the domain settings were loaded directly for editing of this app in single-paned mode. - if (closeOnBack && !twoPanedMode) { // The activity should delete the domain settings and exit straight to the the main WebView activity. - // Delete the selected domain. - domainsDatabaseHelper.deleteDomain(currentDomainDatabaseId) - - // Go home. - NavUtils.navigateUpFromSameTask(activity) - } else { // A snackbar should be shown before deleting the domain settings. - // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists. - closeOnBack = false - - // Store a copy of the current domain database ID because it could change while the snackbar is displayed. - val databaseIdToDelete = currentDomainDatabaseId - - // Update the fragments and menu items. - if (twoPanedMode) { // Two-paned mode. - // Store the deleted domain position, which is needed if undo is selected in the snackbar. - deletedDomainPosition = domainsListView!!.checkedItemPosition - - // Disable the delete menu item. - deleteMenuItem.isEnabled = false - - // Remove the domain settings fragment. - supportFragmentManager.beginTransaction().remove(supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!!).commitNow() - } else { // Single-paned mode. - // Instantiate a new domains list fragment. - val domainsListFragment = DomainsListFragment() - - // Display the domains list fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainsListFragment).commitNow() - - // Show the add domain floating action button. - addDomainFAB.show() - - // Hide the delete menu item. - deleteMenuItem.isVisible = false - } - - // Get a cursor that does not show the domain to be deleted. - val domainsPendingDeleteCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomainExcept(databaseIdToDelete) - - // Setup the domains pending delete cursor adapter. - val domainsPendingDeleteCursorAdapter: CursorAdapter = object : CursorAdapter(this, domainsPendingDeleteCursor, false) { - override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { - // Inflate the individual item layout. - return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false) - } - - override fun bindView(view: View, context: Context, cursor: Cursor) { - // Get the domain name string. - val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME)) - - // Get a handle for the domain name text view. - val domainNameTextView = view.findViewById(R.id.domain_name_textview) - - // Display the domain name. - domainNameTextView.text = domainNameString - } - } - - // Update the handle for the current domains list view. - domainsListView = findViewById(R.id.domains_listview) - - // Update the list view. - domainsListView!!.adapter = domainsPendingDeleteCursorAdapter - - // Display a snackbar. - undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.domain_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.undo) {} // Do nothing because everything will be handled by `onDismissed()` below. - .addCallback(object : Snackbar.Callback() { - override fun onDismissed(snackbar: Snackbar, event: Int) { - // Run commands based on the event. - if (event == DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button. - // Create an arguments bundle. - val argumentsBundle = Bundle() - - // Store the domains settings in the arguments bundle. - argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseIdToDelete) - argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY) - - // Instantiate a new domain settings fragment. - val domainSettingsFragment = DomainSettingsFragment() - - // Add the arguments bundle to the domain settings fragment. - domainSettingsFragment.arguments = argumentsBundle - - // Display the correct fragments. - if (twoPanedMode) { // The device in in two-paned mode. - // Get a cursor with the current contents of the domains database. - val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain - - // Setup the domains cursor adapter. - val undoDeleteDomainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, undoDeleteDomainsCursor, false) { - override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { - // Inflate the individual item layout. - return layoutInflater.inflate(R.layout.domain_name_linearlayout, parent, false) - } - - override fun bindView(view: View, context: Context, cursor: Cursor) { - /// Get the domain name string. - val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME)) - - // Get a handle for the domain name text view. - val domainNameTextView = view.findViewById(R.id.domain_name_textview) - - // Display the domain name. - domainNameTextView.text = domainNameString - } - } - - // Update the domains list view. - domainsListView!!.adapter = undoDeleteDomainsCursorAdapter - - // Select the previously deleted domain in the list view. - domainsListView!!.setItemChecked(deletedDomainPosition, true) - - // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commitNow() - - // Enable the delete menu item. - deleteMenuItem.isEnabled = true - } else { // The device in in one-paned mode. - // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() - - // Hide the add domain floating action button. - addDomainFAB.hide() - - // Show and enable the delete menu item. - deleteMenuItem.isVisible = true - - // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() - } - } else { // The snackbar was dismissed without the undo button being pushed. - // Delete the selected domain. - domainsDatabaseHelper.deleteDomain(databaseIdToDelete) - - // Enable the delete menu item if the system was waiting for a snackbar to be dismissed. - if (dismissingSnackbar) { - // Create a runnable to enable the delete menu item. - val enableDeleteMenuItemRunnable = Runnable { - // Enable or show the delete menu item according to the display mode. - if (twoPanedMode) - deleteMenuItem.isEnabled = true - else - deleteMenuItem.isVisible = true - - // Reset the dismissing snackbar tracker. - dismissingSnackbar = false - } - - // Instantiate a handler running the main looper. - val handler = Handler(mainLooper) - - // Enable or show the delete menu icon after 100 milliseconds to make sure that the previous domain has been deleted from the database. - handler.postDelayed(enableDeleteMenuItemRunnable, 100) - } - - // Close the activity if back was pressed. - if (closeActivityAfterDismissingSnackbar) - NavUtils.navigateUpFromSameTask(activity) - } - } - }) - // Show the Snackbar. - undoDeleteSnackbar!!.show() - } + // Populate the domains list view. For some reason, beginning with appcompat 1.7.0, this needs to be in a runnable instead of being called directly, or the system crashes. + populateDomainsListViewHandler.post(populateDomainsListViewRunnable) } } - // Consume the event. - return true + // Register the on back pressed callback. + onBackPressedDispatcher.addCallback(this, onBackPressedCallback) } - override fun onSaveInstanceState(savedInstanceState: Bundle) { + override fun onSaveInstanceState(outState: Bundle) { // Run the default commands. - super.onSaveInstanceState(savedInstanceState) + super.onSaveInstanceState(outState) // Get a handle for the domain settings scrollview. val domainSettingsScrollView = findViewById(R.id.domain_settings_scrollview) @@ -620,9 +663,9 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI // Check to see if the domain settings scrollview exists. if (domainSettingsScrollView == null) { // The domain settings are not displayed. // Store the domain settings status in the bundle. - savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false) - savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1) - savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0) + outState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, false) + outState.putInt(DOMAIN_SETTINGS_DATABASE_ID, -1) + outState.putInt(DOMAIN_SETTINGS_SCROLL_Y, 0) } else { // The domain settings are displayed. // Save any changes that have been made to the domain settings. saveDomainSettings(coordinatorLayout) @@ -631,9 +674,9 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI val domainSettingsScrollY = domainSettingsScrollView.scrollY // Store the domain settings status in the bundle. - savedInstanceState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true) - savedInstanceState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId) - savedInstanceState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY) + outState.putBoolean(DOMAIN_SETTINGS_DISPLAYED, true) + outState.putInt(DOMAIN_SETTINGS_DATABASE_ID, DomainSettingsFragment.databaseId) + outState.putInt(DOMAIN_SETTINGS_SCROLL_Y, domainSettingsScrollY) } // Check to see if the domains listview exists. @@ -642,7 +685,7 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI val domainsListViewPosition = domainsListView!!.firstVisiblePosition // Store the listview position in the bundle. - savedInstanceState.putInt(LISTVIEW_POSITION, domainsListViewPosition) + outState.putInt(LISTVIEW_POSITION, domainsListViewPosition) } } @@ -667,9 +710,6 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI // Hide the add domain floating action button. addDomainFAB.hide() - // Show and enable the delete menu item. - deleteMenuItem.isVisible = true - // Create an arguments bundle. val argumentsBundle = Bundle() @@ -684,10 +724,136 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI domainSettingsFragment.arguments = argumentsBundle // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domains_listview_fragment_container, domainSettingsFragment).commitNow() + supportFragmentManager.commitNow { + replace(R.id.domains_listview_fragment_container, domainSettingsFragment, DOMAIN_SETTINGS_FRAGMENT_TAG) + } } } + override fun deleteAllDomainSettings() { + // Reset close-on-back, which otherwise can cause errors if the system attempts to save settings for a domain that no longer exists. + closeOnBack = false + + // Store a copy of the currently selected domain database ID because it could change while the snackbar is displayed. + val currentlySelectedDatabaseId = currentDomainDatabaseId + + // Store the domains list view scroll position. + val domainsListViewFirstVisiblePosition = domainsListView!!.firstVisiblePosition + + // Update the fragments and menu items. + if (twoPanedMode) { // Two-paned mode. + // Store the deleted domain position, which is needed if undo is selected in the snackbar. + deletedDomainPosition = domainsListView!!.checkedItemPosition + + // Get a handle for the domain settings fragment. + val domainSettingsFragment = supportFragmentManager.findFragmentById(R.id.domain_settings_fragment_container)!! + + // Get a handle for the domain settings fragment view. + val domainSettingsFragmentView = domainSettingsFragment.requireView() + + // Hide the domain settings fragment. + domainSettingsFragmentView.visibility = View.INVISIBLE + + // Disable the delete menu item. + deleteMenuItemEnabled = false + } + + // Disable the delete all menu item. + deleteAllMenuItemEnabled = false + + // Invalidate the options menu. + invalidateMenu() + + // Create an empty cursor + val emptyCursor = domainsDatabaseHelper.getCursorForId(-1) + + // Populate the empty cursor adapter. + val emptyCursorAdapter = populateDomainsCursorAdapter(emptyCursor) + + // Update the handle for the current domains list view. + domainsListView = findViewById(R.id.domains_listview) + + // Update the list view. + domainsListView!!.adapter = emptyCursorAdapter + + // Get a handle for the activity (used in an inner class below). + val activity: Activity = this + + // Display a snackbar. + undoDeleteSnackbar = Snackbar.make(domainsListView!!, R.string.all_domains_deleted, Snackbar.LENGTH_LONG) + .setAction(R.string.undo) {} // Do nothing because everything will be handled by `onDismissed()` below. + .addCallback(object : Snackbar.Callback() { + override fun onDismissed(snackbar: Snackbar, event: Int) { + //Run commands based on the event. + if (event == DISMISS_EVENT_ACTION) { // The user pushed the `Undo` button. + // Get a cursor with the current contents of the domains database. + val undoDeleteDomainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain + + // Populate the undo delete domains cursor adapter. + val undoDeleteDomainsCursorAdapter = populateDomainsCursorAdapter(undoDeleteDomainsCursor) + + // Update the domains list view. + domainsListView!!.adapter = undoDeleteDomainsCursorAdapter + + // Redisplay the domain settings in two-paned mode. + if (twoPanedMode) { + // Create an arguments bundle. + val argumentsBundle = Bundle() + + // Store the domain settings in the arguments bundle. + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, currentlySelectedDatabaseId) + argumentsBundle.putInt(DomainSettingsFragment.SCROLL_Y, domainSettingsScrollY) + + // Instantiate a new domain settings fragment. + val domainSettingsFragment = DomainSettingsFragment() + + // Add the arguments bundle to the domain settings fragment. + domainSettingsFragment.arguments = argumentsBundle + + // Select the previously selected domain in the list view. + domainsListView!!.setItemChecked(deletedDomainPosition, true) + + // Display the domain settings fragment. + supportFragmentManager.commitNow { + replace(R.id.domain_settings_fragment_container, domainSettingsFragment, DOMAIN_SETTINGS_FRAGMENT_TAG) + } + + // Enable the delete menu item. + deleteMenuItemEnabled = true + } + + // Restore the domains list view scroll position. + domainsListView!!.setSelection(domainsListViewFirstVisiblePosition) + + // Enable the delete all menu item. + deleteAllMenuItemEnabled = true + + // Update the options menu. + invalidateMenu() + } else { // The snackbar was dismissed without the undo button being pushed. + // Delete all the domains. + val rowsDeleted = domainsDatabaseHelper.deleteAllDomains() + + // Reset the dismissing snackbar option. + // The rows deleted should always be greater than 0, but in all cases they should be greater than -1. + // This has the effect of tricking the compiler into waiting until after the delete finishes to reenable the delete menu item, + // because the compiler (probably) can't tell that the response will never be less than -1, so it doesn't compile out the delay. + if (rowsDeleted > -1) { + // Reset the dismissing snackbar tracker. + dismissingSnackbar = false + } + + // Close the activity if back was pressed. + if (closeActivityAfterDismissingSnackbar) + NavUtils.navigateUpFromSameTask(activity) + } + } + }) + + // Show the Snackbar. + undoDeleteSnackbar!!.show() + } + override fun saveDomainSettings(view: View) { // Get handles for the domain settings. val domainNameEditText = view.findViewById(R.id.domain_settings_name_edittext) @@ -774,11 +940,8 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI domainsDatabaseHelper.updatePinnedIpAddresses(currentDomainDatabaseId, currentIpAddresses!!) } - private fun populateDomainsListView(highlightedDomainDatabaseId: Int, domainsListViewPosition: Int) { - // Get a cursor with the current contents of the domains database. - val domainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain - - // Setup the domains cursor adapter. + private fun populateDomainsCursorAdapter(domainsCursor: Cursor) :CursorAdapter { + // Populate the domains cursor adapter. val domainsCursorAdapter: CursorAdapter = object : CursorAdapter(applicationContext, domainsCursor, false) { override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View { // Inflate the individual item layout. @@ -786,17 +949,28 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI } override fun bindView(view: View, context: Context, cursor: Cursor) { + /// Get the domain name string. + val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME)) + // Get a handle for the domain name text view. val domainNameTextView = view.findViewById(R.id.domain_name_textview) - // Get the domain name string. - val domainNameString = cursor.getString(cursor.getColumnIndexOrThrow(DOMAIN_NAME)) - - // Set the domain name. + // Display the domain name. domainNameTextView.text = domainNameString } } + // Return the domains cursor adapter. + return domainsCursorAdapter + } + + private fun populateDomainsListView(highlightedDomainDatabaseId: Int, domainsListViewPosition: Int) { + // Get a cursor with the current contents of the domains database. + val domainsCursor = domainsDatabaseHelper.domainNameCursorOrderedByDomain + + // Populate the domains cursor adapter. + val domainsCursorAdapter = populateDomainsCursorAdapter(domainsCursor) + // get a handle for the current domains listview. domainsListView = findViewById(R.id.domains_listview) @@ -807,7 +981,7 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI domainsListView!!.setSelection(domainsListViewPosition) // Display the domain settings in the second pane if operating in two pane mode and the database contains at least one domain. - if (twoPanedMode && domainsCursor.count > 0) { // Two-paned mode is enabled and there is at least one domain. + if (twoPanedMode && (domainsCursor.count > 0)) { // Two-paned mode is enabled and there is at least one domain. // Initialize the highlighted domain position tracker. var highlightedDomainPosition = 0 @@ -847,14 +1021,24 @@ class DomainsActivity : AppCompatActivity(), AddDomainListener, DismissSnackbarI domainSettingsFragment.arguments = argumentsBundle // Display the domain settings fragment. - supportFragmentManager.beginTransaction().replace(R.id.domain_settings_fragment_container, domainSettingsFragment).commit() + supportFragmentManager.commitNow { + replace(R.id.domain_settings_fragment_container, domainSettingsFragment, DOMAIN_SETTINGS_FRAGMENT_TAG) + } - // Enable the delete options menu items. - deleteMenuItem.isEnabled = true + // Enable the menu items. + deleteMenuItemEnabled = true + deleteAllMenuItemEnabled = true } else if (twoPanedMode) { // Two-paned mode is enabled but there are no domains. - // Disable the delete menu item. - deleteMenuItem.isEnabled = false + // Disable the menu items. + deleteMenuItemEnabled = false + deleteAllMenuItemEnabled = false + } else { // Single-paned mode is enabled and the domains list is displayed. + // Set the delete all menu item status according to the number of domains. + deleteAllMenuItemEnabled = (domainsCursor.count > 0) } + + // Update the options menu. + invalidateMenu() } override fun dismissSnackbar() {