Fix the ViewPager not always moving to new tabs. https://redmine.stoutner.com/issues/798
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / UrlHistoryDialog.kt
1 /*
2  * Copyright © 2016-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
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
33
34 import androidx.appcompat.app.AlertDialog
35 import androidx.core.content.ContextCompat
36 import androidx.fragment.app.DialogFragment
37 import androidx.preference.PreferenceManager
38
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
44
45 // Define the class constants.
46 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
47
48 class UrlHistoryDialog : DialogFragment() {
49     // Declare the class variables.
50     private lateinit var navigateHistoryListener: NavigateHistoryListener
51
52     // The public interface is used to send information back to the parent activity.
53     interface NavigateHistoryListener {
54         fun navigateHistory(url: String, steps: Int)
55     }
56
57     override fun onAttach(context: Context) {
58         // Run the default commands.
59         super.onAttach(context)
60
61         // Get a handle for the listener from the launching context.
62         navigateHistoryListener = context as NavigateHistoryListener
63     }
64
65     companion object {
66         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
67         @JvmStatic
68         fun loadBackForwardList(webViewFragmentId: Long): UrlHistoryDialog {
69             // Create an arguments bundle.
70             val argumentsBundle = Bundle()
71
72             // Store the WebView fragment ID in the bundle.
73             argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
74
75             // Create a new instance of the URL history dialog.
76             val urlHistoryDialog = UrlHistoryDialog()
77
78             // Add the arguments bundle to the new dialog.
79             urlHistoryDialog.arguments = argumentsBundle
80
81             // Return the new dialog.
82             return urlHistoryDialog
83         }
84     }
85
86     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
87         // Get the WebView fragment ID from the arguments.
88         val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
89
90         // Get the current position of this WebView fragment.
91         val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
92
93         // Get the WebView tab fragment.
94         val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
95
96         // Get the fragment view.
97         val fragmentView = webViewTabFragment.requireView()
98
99         // Get a handle for the current nested scroll WebView.
100         val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
101
102         // Get the web back forward list from the nested scroll WebView.
103         val webBackForwardList = nestedScrollWebView.copyBackForwardList()
104
105         // Store the current page index.
106         val currentPageIndex = webBackForwardList.currentIndex
107
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)
110
111         // Convert the default favorite icon drawable to a bitmap drawable.
112         val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable)
113
114         // Extract a bitmap from the default favorite icon bitmap drawable.
115         val defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.bitmap
116
117         // Create a history array list.
118         val historyArrayList = ArrayList<History>()
119
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.
125                 defaultFavoriteIcon
126             } else {  // Use the icon from the web back forward list.
127                 webBackForwardList.getItemAtIndex(i).favicon
128             }
129
130             // Store the favorite icon and the URL in history entry.
131             val historyEntry = History(favoriteIconBitmap!!, webBackForwardList.getItemAtIndex(i).url)
132
133             // Add this history entry to the history array list.
134             historyArrayList.add(historyEntry)
135         }
136
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
139
140         // Use an alert dialog builder to create the alert dialog.
141         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
142
143         // Set the title.
144         dialogBuilder.setTitle(R.string.history)
145
146         // Set the view.
147         dialogBuilder.setView(R.layout.url_history_dialog)
148
149         // Setup the clear history button listener.
150         dialogBuilder.setNegativeButton(R.string.clear_history) { _: DialogInterface, _: Int ->
151             // Clear the history.
152             nestedScrollWebView.clearHistory()
153         }
154
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)
157
158         // Create an alert dialog from the alert dialog builder.
159         val alertDialog = dialogBuilder.create()
160
161         // Get a handle for the shared preferences.
162         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
163
164         // Get the screenshot preference.
165         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
166
167         // Disable screenshots if not allowed.
168         if (!allowScreenshots) {
169             // Disable screenshots.
170             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
171         }
172
173         //The alert dialog must be shown before the contents can be modified.
174         alertDialog.show()
175
176         // Instantiate a history array adapter.
177         val historyArrayAdapter = HistoryArrayAdapter(context, historyArrayList, currentPageId)
178
179         // Get a handle for the list view.
180         val listView = alertDialog.findViewById<ListView>(R.id.history_listview)!!
181
182         // Set the list view adapter.
183         listView.adapter = historyArrayAdapter
184
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()
189
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)
194
195                 // Get the URL.
196                 val url = urlTextView.text.toString()
197
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)
200
201                 // Dismiss the alert dialog.
202                 alertDialog.dismiss()
203             }
204         }
205
206         // Return the alert dialog.
207         return alertDialog
208     }
209 }