Finish the navigation menu entries with WebView dialogs.
[PrivacyCell.git] / app / src / main / java / com / stoutner / privacycell / dialogs / WebViewDialog.kt
1 /*
2  * Copyright © 2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Cell <https://www.stoutner.com/privacy-cell>.
5  *
6  * Privacy Cell 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 Cell 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.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacycell.dialogs
21
22 import android.app.Dialog
23 import android.content.Intent
24 import android.content.res.Configuration
25 import android.os.Bundle
26 import android.webkit.WebResourceRequest
27 import android.webkit.WebResourceResponse
28 import android.webkit.WebView
29 import android.webkit.WebViewClient
30
31 import androidx.appcompat.app.AlertDialog
32 import androidx.fragment.app.DialogFragment
33 import androidx.webkit.WebSettingsCompat
34 import androidx.webkit.WebViewAssetLoader
35 import androidx.webkit.WebViewFeature
36
37 import com.stoutner.privacycell.R
38
39 // Define the class constants.
40 private const val DIALOG_TYPE = "dialog_type"
41 private const val SCROLL_Y = "scroll_y"
42
43 class WebViewDialog : DialogFragment() {
44     companion object {
45         // Define the public constants.
46         const val PERMISSIONS = 0
47         const val PRIVACY_POLICY = 1
48         const val CHANGELOG = 2
49         const val LICENSES = 3
50         const val CONTRIBUTORS = 4
51     }
52
53     // Define the class views.
54     private lateinit var webView: WebView
55
56     // Populate the WebView dialog type.
57     fun type(dialogType: Int): WebViewDialog {
58         // Create an arguments bundle.
59         val argumentsBundle = Bundle()
60
61         // Add the dialog type to the bundle.
62         argumentsBundle.putInt(DIALOG_TYPE, dialogType)
63
64         // Create a new instance of the WebView dialog.
65         val webViewDialog = WebViewDialog()
66
67         // Add the arguments bundle to the new dialog.
68         webViewDialog.arguments = argumentsBundle
69
70         // Return the new dialog.
71         return webViewDialog
72     }
73
74     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
75         // Get the dialog type from the arguments bundle.
76         val dialogType = requireArguments().getInt(DIALOG_TYPE)
77
78         // Use a builder to create the alert dialog.
79         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.Theme_PrivacyCellAlertDialog)
80
81         // Set the icon and the title according to the dialog type.
82         when (dialogType) {
83             PERMISSIONS -> {
84                 // Set the icon.
85                 dialogBuilder.setIcon(R.drawable.permissions)
86
87                 // Set the title.
88                 dialogBuilder.setTitle(R.string.permissions)
89             }
90
91             PRIVACY_POLICY -> {
92                 // Set the icon.
93                 dialogBuilder.setIcon(R.drawable.privacy_policy)
94
95                 // Set the title.
96                 dialogBuilder.setTitle(R.string.privacy_policy)
97             }
98
99             CHANGELOG -> {
100                 // Set the icon.
101                 dialogBuilder.setIcon(R.drawable.changelog)
102
103                 // Set the title.
104                 dialogBuilder.setTitle(R.string.changelog)
105             }
106
107             LICENSES -> {
108                 // Set the icon.
109                 dialogBuilder.setIcon(R.drawable.licenses)
110
111                 // Set the title.
112                 dialogBuilder.setTitle(R.string.licenses)
113             }
114
115             CONTRIBUTORS -> {
116                 // Set the icon.
117                 dialogBuilder.setIcon(R.drawable.contributors)
118
119                 // Set the tile.
120                 dialogBuilder.setTitle(R.string.contributors)
121             }
122         }
123
124         // Set the view.
125         dialogBuilder.setView(R.layout.webview_dialog)
126
127         // Set a listener on the close button.  Using `null` as the listener closes the dialog without doing anything else.
128         dialogBuilder.setNegativeButton(R.string.close, null)
129
130         // Create an alert dialog from the builder.
131         val alertDialog = dialogBuilder.create()
132
133         // The alert dialog needs to be shown before the contents can be modified.
134         alertDialog.show()
135
136         // Get a handle for the WebView.
137         webView = alertDialog.findViewById(R.id.webview)!!
138
139         // Get the current theme status.
140         val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
141
142         // Check to see if the app is in night mode.
143         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES && WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {  // The app is in night mode.
144             // Apply the dark WebView theme.
145             WebSettingsCompat.setForceDark(webView.settings, WebSettingsCompat.FORCE_DARK_ON)
146         }
147
148         // Create a WebView asset loader.
149         val webViewAssetLoader = WebViewAssetLoader.Builder().addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(requireContext())).build()
150
151         // Set a WebView client.
152         webView.webViewClient = object : WebViewClient() {
153             // Send external links to a web browser.
154             override fun shouldOverrideUrlLoading(view: WebView, webResourceRequest: WebResourceRequest): Boolean {
155                 // Create an intent to view the URL.
156                 val urlIntent = Intent(Intent.ACTION_VIEW)
157
158                 // Add the URL to the intent.
159                 urlIntent.data = webResourceRequest.url
160
161                 // Make it so.
162                 startActivity(urlIntent)
163
164                 // Consume the click.
165                 return true
166             }
167
168             // Process asset requests with the asset loader.
169             override fun shouldInterceptRequest(webView: WebView, webResourceRequest: WebResourceRequest): WebResourceResponse? {
170                 // This allows using the `appassets.androidplatform.net` URL, which handles the loading of SVG files, which otherwise is prevented by the CORS policy.
171                 return webViewAssetLoader.shouldInterceptRequest(webResourceRequest.url)
172             }
173         }
174
175         // Load the WebView data according to the dialog type.
176         when (dialogType) {
177             PERMISSIONS -> webView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.asset_directory) + "/permissions.html")
178             PRIVACY_POLICY -> webView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.asset_directory) + "/privacy_policy.html")
179             CHANGELOG -> webView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.asset_directory) + "/changelog.html")
180             LICENSES -> webView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.asset_directory) + "/licenses.html")
181             CONTRIBUTORS -> webView.loadUrl("https://appassets.androidplatform.net/assets/" + getString(R.string.asset_directory) + "/contributors.html")
182         }
183
184         // Scroll the WebView if the saved instance state is not null.
185         if (savedInstanceState != null) {
186             webView.post {
187                 webView.scrollY = savedInstanceState.getInt(SCROLL_Y)
188             }
189         }
190
191         // Return the alert dialog.
192         return alertDialog
193     }
194
195     override fun onSaveInstanceState(savedInstanceState: Bundle) {
196         // Run the default commands.
197         super.onSaveInstanceState(savedInstanceState)
198
199         // Save the scroll position.
200         savedInstanceState.putInt(SCROLL_Y, webView.scrollY)
201     }
202 }