2 * Copyright 2016-2024 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
6 * Privacy Browser Android is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser Android is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser Android. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.dialogs
22 import android.app.Dialog
23 import android.content.Context
24 import android.content.DialogInterface
25 import android.graphics.drawable.BitmapDrawable
26 import android.os.Bundle
27 import android.view.View
28 import android.view.WindowManager
29 import android.widget.AdapterView
30 import android.widget.AdapterView.OnItemClickListener
31 import android.widget.ListView
33 import androidx.appcompat.app.AlertDialog
34 import androidx.core.content.ContextCompat
35 import androidx.fragment.app.DialogFragment
36 import androidx.preference.PreferenceManager
38 import com.stoutner.privacybrowser.R
39 import com.stoutner.privacybrowser.activities.MainWebViewActivity
40 import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter
41 import com.stoutner.privacybrowser.dataclasses.HistoryDataClass
42 import com.stoutner.privacybrowser.views.NestedScrollWebView
44 // Define the class constants.
45 private const val WEBVIEW_FRAGMENT_ID = "A"
47 class UrlHistoryDialog : DialogFragment() {
49 fun loadBackForwardList(webViewFragmentId: Long): UrlHistoryDialog {
50 // Create an arguments bundle.
51 val argumentsBundle = Bundle()
53 // Store the WebView fragment ID in the bundle.
54 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
56 // Create a new instance of the URL history dialog.
57 val urlHistoryDialog = UrlHistoryDialog()
59 // Add the arguments bundle to the new dialog.
60 urlHistoryDialog.arguments = argumentsBundle
62 // Return the new dialog.
63 return urlHistoryDialog
67 // Declare the class variables.
68 private lateinit var navigateHistoryListener: NavigateHistoryListener
70 // The public interface is used to send information back to the parent activity.
71 interface NavigateHistoryListener {
72 fun navigateHistory(steps: Int)
75 override fun onAttach(context: Context) {
76 // Run the default commands.
77 super.onAttach(context)
79 // Get a handle for the listener from the launching context.
80 navigateHistoryListener = context as NavigateHistoryListener
83 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
84 // Get the WebView fragment ID from the arguments.
85 val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
87 // Get the current position of this WebView fragment.
88 val webViewPosition = MainWebViewActivity.webViewStateAdapter!!.getPositionForId(webViewFragmentId)
90 // Get the WebView tab fragment.
91 val webViewTabFragment = MainWebViewActivity.webViewStateAdapter!!.getPageFragment(webViewPosition)
93 // Get the fragment view.
94 val fragmentView = webViewTabFragment.requireView()
96 // Get a handle for the current nested scroll WebView.
97 val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
99 // Get the web back forward list from the nested scroll WebView.
100 val webBackForwardList = nestedScrollWebView.copyBackForwardList()
102 // Store the current page index.
103 val currentPageIndex = webBackForwardList.currentIndex
105 // Get the default favorite icon drawable. `ContextCompat` must be used until the minimum API >= 21.
106 val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
108 // Convert the default favorite icon drawable to a bitmap drawable.
109 val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
111 // Extract a bitmap from the default favorite icon bitmap drawable.
112 val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap
114 // Create a history array list.
115 val historyDataClassArrayList = ArrayList<HistoryDataClass>()
117 // Populate the history array list, descending from the end of the list so that the newest entries are at the top. `-1` is needed because the history array list is zero-based.
118 for (i in webBackForwardList.size - 1 downTo 0) {
119 // Store the favorite icon bitmap.
120 val favoriteIconBitmap = if (webBackForwardList.getItemAtIndex(i).favicon == null) {
121 // If the web back forward list does not have a favorite icon, use Privacy Browser's default world icon.
123 } else { // Use the icon from the web back forward list.
124 webBackForwardList.getItemAtIndex(i).favicon
127 // Store the favorite icon and the URL in history entry.
128 val historyDataClassEntry = HistoryDataClass(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
130 // Add this history entry to the history array list.
131 historyDataClassArrayList.add(historyDataClassEntry)
134 // Subtract the original current page ID from the array size because the order of the array is reversed so that the newest entries are at the top. `-1` is needed because the array is zero-based.
135 val currentPageId = webBackForwardList.size - 1 - currentPageIndex
137 // Use an alert dialog builder to create the alert dialog.
138 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
141 dialogBuilder.setTitle(R.string.history)
144 dialogBuilder.setView(R.layout.url_history_dialog)
146 // Setup the clear history button listener.
147 dialogBuilder.setNegativeButton(R.string.clear_history) { _: DialogInterface, _: Int ->
148 // Clear the history.
149 nestedScrollWebView.clearHistory()
152 // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else.
153 dialogBuilder.setPositiveButton(R.string.close, null)
155 // Create an alert dialog from the alert dialog builder.
156 val alertDialog = dialogBuilder.create()
158 // Get a handle for the shared preferences.
159 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
161 // Get the screenshot preference.
162 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
164 // Disable screenshots if not allowed.
165 if (!allowScreenshots) {
166 // Disable screenshots.
167 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
170 //The alert dialog must be shown before the contents can be modified.
173 // Instantiate a history array adapter.
174 val historyArrayAdapter = HistoryArrayAdapter(requireContext(), historyDataClassArrayList, currentPageId)
176 // Get a handle for the list view.
177 val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!!
179 // Set the list view adapter.
180 listView.adapter = historyArrayAdapter
182 // Listen for clicks on entries in the list view.
183 listView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, _: View, _: Int, id: Long ->
184 // Convert the long ID to an int.
185 val itemId = id.toInt()
187 // Only consume the click if it is not on the current page ID.
188 if (itemId != currentPageId) {
189 // Invoke the navigate history listener in the calling activity. Those commands cannot be run here because they need access to `applyDomainSettings()`.
190 navigateHistoryListener.navigateHistory(currentPageId - itemId)
192 // Dismiss the alert dialog.
193 alertDialog.dismiss()
197 // Return the alert dialog.