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.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.History
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() {
49 // Declare the class variables.
50 private lateinit var navigateHistoryListener: NavigateHistoryListener
52 // The public interface is used to send information back to the parent activity.
53 interface NavigateHistoryListener {
54 fun navigateHistory(url: String, steps: Int)
57 override fun onAttach(context: Context) {
58 // Run the default commands.
59 super.onAttach(context)
61 // Get a handle for the listener from the launching context.
62 navigateHistoryListener = context as NavigateHistoryListener
66 // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
68 fun loadBackForwardList(webViewFragmentId: Long): UrlHistoryDialog {
69 // Create an arguments bundle.
70 val argumentsBundle = Bundle()
72 // Store the WebView fragment ID in the bundle.
73 argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
75 // Create a new instance of the URL history dialog.
76 val urlHistoryDialog = UrlHistoryDialog()
78 // Add the arguments bundle to the new dialog.
79 urlHistoryDialog.arguments = argumentsBundle
81 // Return the new dialog.
82 return urlHistoryDialog
86 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
87 // Get the WebView fragment ID from the arguments.
88 val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
90 // Get the current position of this WebView fragment.
91 val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
93 // Get the WebView tab fragment.
94 val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
96 // Get the fragment view.
97 val fragmentView = webViewTabFragment.requireView()
99 // Get a handle for the current nested scroll WebView.
100 val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
102 // Get the web back forward list from the nested scroll WebView.
103 val webBackForwardList = nestedScrollWebView.copyBackForwardList()
105 // Store the current page index.
106 val currentPageIndex = webBackForwardList.currentIndex
108 // Get the default favorite icon drawable. `ContextCompat` must be used until the minimum API >= 21.
109 val defaultFavoriteIconDrawable = ContextCompat.getDrawable(requireContext(), R.drawable.world)
111 // Convert the default favorite icon drawable to a bitmap drawable.
112 val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
114 // Extract a bitmap from the default favorite icon bitmap drawable.
115 val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap
117 // Create a history array list.
118 val historyArrayList = ArrayList<History>()
120 // 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.
121 for (i in webBackForwardList.size - 1 downTo 0) {
122 // Store the favorite icon bitmap.
123 val favoriteIconBitmap = if (webBackForwardList.getItemAtIndex(i).favicon == null) {
124 // If the web back forward list does not have a favorite icon, use Privacy Browser's default world icon.
126 } else { // Use the icon from the web back forward list.
127 webBackForwardList.getItemAtIndex(i).favicon
130 // Store the favorite icon and the URL in history entry.
131 val historyEntry = History(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
133 // Add this history entry to the history array list.
134 historyArrayList.add(historyEntry)
137 // 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.
138 val currentPageId = webBackForwardList.size - 1 - currentPageIndex
140 // Use an alert dialog builder to create the alert dialog.
141 val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
144 dialogBuilder.setTitle(R.string.history)
147 dialogBuilder.setView(R.layout.url_history_dialog)
149 // Setup the clear history button listener.
150 dialogBuilder.setNegativeButton(R.string.clear_history) { _: DialogInterface, _: Int ->
151 // Clear the history.
152 nestedScrollWebView.clearHistory()
155 // Set the close button listener. Using `null` as the listener closes the dialog without doing anything else.
156 dialogBuilder.setPositiveButton(R.string.close, null)
158 // Create an alert dialog from the alert dialog builder.
159 val alertDialog = dialogBuilder.create()
161 // Get a handle for the shared preferences.
162 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
164 // Get the screenshot preference.
165 val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
167 // Disable screenshots if not allowed.
168 if (!allowScreenshots) {
169 // Disable screenshots.
170 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
173 //The alert dialog must be shown before the contents can be modified.
176 // Instantiate a history array adapter.
177 val historyArrayAdapter = HistoryArrayAdapter(context, historyArrayList, currentPageId)
179 // Get a handle for the list view.
180 val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!!
182 // Set the list view adapter.
183 listView.adapter = historyArrayAdapter
185 // Listen for clicks on entries in the list view.
186 listView.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, id: Long ->
187 // Convert the long ID to an int.
188 val itemId = id.toInt()
190 // Only consume the click if it is not on the current page ID.
191 if (itemId != currentPageId) {
192 // Get a handle for the URL text view.
193 val urlTextView = view.findViewById<TextView>(R.id.history_url_textview)
196 val url = urlTextView.text.toString()
198 // Invoke the navigate history listener in the calling activity. These commands cannot be run here because they need access to `applyDomainSettings()`.
199 navigateHistoryListener.navigateHistory(url, currentPageId - itemId)
201 // Dismiss the alert dialog.
202 alertDialog.dismiss()
206 // Return the alert dialog.