2 * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser 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 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. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.dialogs
22 import android.annotation.SuppressLint
23 import android.app.Dialog
24 import android.content.Context
25 import android.content.DialogInterface
26 import android.graphics.drawable.BitmapDrawable
27 import android.os.Bundle
28 import android.view.View
29 import android.view.WindowManager
30 import android.widget.AdapterView
31 import android.widget.AdapterView.OnItemClickListener
32 import android.widget.ListView
33 import android.widget.TextView
35 import androidx.appcompat.app.AlertDialog
36 import androidx.core.content.ContextCompat
37 import androidx.fragment.app.DialogFragment
38 import androidx.preference.PreferenceManager
40 import com.stoutner.privacybrowser.R
41 import com.stoutner.privacybrowser.activities.MainWebViewActivity
42 import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter
43 import com.stoutner.privacybrowser.definitions.History
44 import com.stoutner.privacybrowser.views.NestedScrollWebView
46 // Define the class constants.
47 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
49 class UrlHistoryDialog : DialogFragment() {
50 // Declare the class variables.
51 private lateinit var navigateHistoryListener: NavigateHistoryListener
53 // The public interface is used to send information back to the parent activity.
54 interface NavigateHistoryListener {
55 fun navigateHistory(url: String, steps: Int)
58 override fun onAttach(context: Context) {
59 // Run the default commands.
60 super.onAttach(context)
62 // Get a handle for the listener from the launching context.
63 navigateHistoryListener = context as NavigateHistoryListener
67 // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
69 fun loadBackForwardList(webViewFragmentId: Long): UrlHistoryDialog {
70 // Create an arguments bundle.
71 val argumentsBundle = Bundle()
73 // Store the WebView fragment ID in the bundle.
74 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
76 // Create a new instance of the URL history dialog.
77 val urlHistoryDialog = UrlHistoryDialog()
79 // Add the arguments bundle to the new dialog.
80 urlHistoryDialog.arguments = argumentsBundle
82 // Return the new dialog.
83 return urlHistoryDialog
87 // `@SuppressLint("InflateParams")` removes the warning about using null as the parent view group when inflating the alert dialog.
88 @SuppressLint("InflateParams")
89 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
90 // Get the WebView fragment ID from the arguments.
91 val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
93 // Get the current position of this WebView fragment.
94 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
96 // Get the WebView tab fragment.
97 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
99 // Get the fragment view.
100 val fragmentView = webViewTabFragment.requireView()
102 // Get a handle for the current nested scroll WebView.
103 val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
105 // Get the web back forward list from the nested scroll WebView.
106 val webBackForwardList = nestedScrollWebView.copyBackForwardList()
108 // Store the current page index.
109 val currentPageIndex = webBackForwardList.currentIndex
111 // Get the default favorite icon drawable. `ContextCompat` must be used until the minimum API >= 21.
112 val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
114 // Convert the default favorite icon drawable to a bitmap drawable.
115 val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
117 // Extract a bitmap from the default favorite icon bitmap drawable.
118 val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap
120 // Create a history array list.
121 val historyArrayList = ArrayList<History>()
123 // 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.
124 for (i in webBackForwardList.size - 1 downTo 0) {
125 // Store the favorite icon bitmap.
126 val favoriteIconBitmap = if (webBackForwardList.getItemAtIndex(i).favicon == null) {
127 // If the web back forward list does not have a favorite icon, use Privacy Browser's default world icon.
129 } else { // Use the icon from the web back forward list.
130 webBackForwardList.getItemAtIndex(i).favicon
133 // Store the favorite icon and the URL in history entry.
134 val historyEntry = History(favoriteIconBitmap, webBackForwardList.getItemAtIndex(i).url)
136 // Add this history entry to the history array list.
137 historyArrayList.add(historyEntry)
140 // 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.
141 val currentPageId = webBackForwardList.size - 1 - currentPageIndex
143 // Use an alert dialog builder to create the alert dialog.
144 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
147 dialogBuilder.setTitle(R.string.history)
149 // Set the view. The parent view is `null` because it will be assigned by the alert dialog.
150 dialogBuilder.setView(layoutInflater.inflate(R.layout.url_history_dialog, null))
152 // Setup the clear history button listener.
153 dialogBuilder.setNegativeButton(R.string.clear_history) { _: DialogInterface, _: Int ->
154 // Clear the history.
155 nestedScrollWebView.clearHistory()
158 // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else.
159 dialogBuilder.setPositiveButton(R.string.close, null)
161 // Create an alert dialog from the alert dialog builder.
162 val alertDialog = dialogBuilder.create()
164 // Get a handle for the shared preferences.
165 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
167 // Get the screenshot preference.
168 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
170 // Disable screenshots if not allowed.
171 if (!allowScreenshots) {
172 // Disable screenshots.
173 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
176 //The alert dialog must be shown before the contents can be modified.
179 // Instantiate a history array adapter.
180 val historyArrayAdapter = HistoryArrayAdapter(context, historyArrayList, currentPageId)
182 // Get a handle for the list view.
183 val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!!
185 // Set the list view adapter.
186 listView.adapter = historyArrayAdapter
188 // Listen for clicks on entries in the list view.
189 listView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, id: Long ->
190 // Convert the long ID to an int.
191 val itemId = id.toInt()
193 // Only consume the click if it is not on the current page ID.
194 if (itemId != currentPageId) {
195 // Get a handle for the URL text view.
196 val urlTextView = view.findViewById<TextView>(R.id.history_url_textview)
199 val url = urlTextView.text.toString()
201 // Invoke the navigate history listener in the calling activity. These commands cannot be run here because they need access to `applyDomainSettings()`.
202 navigateHistoryListener.navigateHistory(url, currentPageId - itemId)
204 // Dismiss the alert dialog.
205 alertDialog.dismiss()
209 // Return the alert dialog.