2 * Copyright 2016-2023 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
32 import android.widget.TextView
34 import androidx.appcompat.app.AlertDialog
35 import androidx.core.content.ContextCompat
36 import androidx.fragment.app.DialogFragment
37 import androidx.preference.PreferenceManager
39 import com.stoutner.privacybrowser.R
40 import com.stoutner.privacybrowser.activities.MainWebViewActivity
41 import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter
42 import com.stoutner.privacybrowser.dataclasses.HistoryDataClass
43 import com.stoutner.privacybrowser.views.NestedScrollWebView
45 // Define the class constants.
46 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
48 class UrlHistoryDialog : DialogFragment() {
50 fun loadBackForwardList(webViewFragmentId: Long): UrlHistoryDialog {
51 // Create an arguments bundle.
52 val argumentsBundle = Bundle()
54 // Store the WebView fragment ID in the bundle.
55 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
57 // Create a new instance of the URL history dialog.
58 val urlHistoryDialog = UrlHistoryDialog()
60 // Add the arguments bundle to the new dialog.
61 urlHistoryDialog.arguments = argumentsBundle
63 // Return the new dialog.
64 return urlHistoryDialog
68 // Declare the class variables.
69 private lateinit var navigateHistoryListener: NavigateHistoryListener
71 // The public interface is used to send information back to the parent activity.
72 interface NavigateHistoryListener {
73 fun navigateHistory(url: String, steps: Int)
76 override fun onAttach(context: Context) {
77 // Run the default commands.
78 super.onAttach(context)
80 // Get a handle for the listener from the launching context.
81 navigateHistoryListener = context as NavigateHistoryListener
84 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
85 // Get the WebView fragment ID from the arguments.
86 val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
88 // Get the current position of this WebView fragment.
89 val webViewPosition = MainWebViewActivity.webViewPagerAdapter!!.getPositionForId(webViewFragmentId)
91 // Get the WebView tab fragment.
92 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter!!.getPageFragment(webViewPosition)
94 // Get the fragment view.
95 val fragmentView = webViewTabFragment.requireView()
97 // Get a handle for the current nested scroll WebView.
98 val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
100 // Get the web back forward list from the nested scroll WebView.
101 val webBackForwardList = nestedScrollWebView.copyBackForwardList()
103 // Store the current page index.
104 val currentPageIndex = webBackForwardList.currentIndex
106 // Get the default favorite icon drawable. `ContextCompat` must be used until the minimum API >= 21.
107 val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
109 // Convert the default favorite icon drawable to a bitmap drawable.
110 val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
112 // Extract a bitmap from the default favorite icon bitmap drawable.
113 val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap
115 // Create a history array list.
116 val historyDataClassArrayList = ArrayList<HistoryDataClass>()
118 // 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.
119 for (i in webBackForwardList.size - 1 downTo 0) {
120 // Store the favorite icon bitmap.
121 val favoriteIconBitmap = if (webBackForwardList.getItemAtIndex(i).favicon == null) {
122 // If the web back forward list does not have a favorite icon, use Privacy Browser's default world icon.
124 } else { // Use the icon from the web back forward list.
125 webBackForwardList.getItemAtIndex(i).favicon
128 // Store the favorite icon and the URL in history entry.
129 val historyDataClassEntry = HistoryDataClass(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
131 // Add this history entry to the history array list.
132 historyDataClassArrayList.add(historyDataClassEntry)
135 // 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.
136 val currentPageId = webBackForwardList.size - 1 - currentPageIndex
138 // Use an alert dialog builder to create the alert dialog.
139 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
142 dialogBuilder.setTitle(R.string.history)
145 dialogBuilder.setView(R.layout.url_history_dialog)
147 // Setup the clear history button listener.
148 dialogBuilder.setNegativeButton(R.string.clear_history) { _: DialogInterface, _: Int ->
149 // Clear the history.
150 nestedScrollWebView.clearHistory()
153 // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else.
154 dialogBuilder.setPositiveButton(R.string.close, null)
156 // Create an alert dialog from the alert dialog builder.
157 val alertDialog = dialogBuilder.create()
159 // Get a handle for the shared preferences.
160 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
162 // Get the screenshot preference.
163 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
165 // Disable screenshots if not allowed.
166 if (!allowScreenshots) {
167 // Disable screenshots.
168 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
171 //The alert dialog must be shown before the contents can be modified.
174 // Instantiate a history array adapter.
175 val historyArrayAdapter = HistoryArrayAdapter(requireContext(), historyDataClassArrayList, currentPageId)
177 // Get a handle for the list view.
178 val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!!
180 // Set the list view adapter.
181 listView.adapter = historyArrayAdapter
183 // Listen for clicks on entries in the list view.
184 listView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, id: Long ->
185 // Convert the long ID to an int.
186 val itemId = id.toInt()
188 // Only consume the click if it is not on the current page ID.
189 if (itemId != currentPageId) {
190 // Get a handle for the URL text view.
191 val urlTextView = view.findViewById<TextView>(R.id.history_url_textview)
194 val url = urlTextView.text.toString()
196 // Invoke the navigate history listener in the calling activity. These commands cannot be run here because they need access to `applyDomainSettings()`.
197 navigateHistoryListener.navigateHistory(url, currentPageId - itemId)
199 // Dismiss the alert dialog.
200 alertDialog.dismiss()
204 // Return the alert dialog.