Switch to the new Day/Night theme. https://redmine.stoutner.com/issues/522
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / AddDomainDialog.kt
1 /*
2  * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
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.privacybrowser.dialogs
21
22 import android.annotation.SuppressLint
23 import android.app.Dialog
24 import android.content.Context
25 import android.content.DialogInterface
26 import android.content.res.Configuration
27 import android.net.Uri
28 import android.os.Bundle
29 import android.text.Editable
30 import android.text.TextWatcher
31 import android.view.KeyEvent
32 import android.view.View
33 import android.view.WindowManager
34 import android.widget.EditText
35 import android.widget.TextView
36
37 import androidx.appcompat.app.AlertDialog
38 import androidx.fragment.app.DialogFragment
39 import androidx.preference.PreferenceManager
40
41 import com.stoutner.privacybrowser.R
42 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper
43
44 class AddDomainDialog: DialogFragment() {
45     // The public interface is used to send information back to the parent activity.
46     interface AddDomainListener {
47         fun onAddDomain(dialogFragment: DialogFragment)
48     }
49
50     // The add domain listener is initialized in `onAttach()` and used in `onCreateDialog()`.
51     private lateinit var addDomainListener: AddDomainListener
52
53     override fun onAttach(context: Context) {
54         // Run the default commands.
55         super.onAttach(context)
56
57         // Get a handle for the listener from the launching context.
58         addDomainListener = context as AddDomainListener
59     }
60
61     companion object {
62         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.  Also, the function can then be moved out of a companion object and just become a package-level function.
63         @JvmStatic
64         fun addDomain(urlString: String): AddDomainDialog {
65             // Create an arguments bundle.
66             val argumentsBundle = Bundle()
67
68             // Store the URL in the bundle.
69             argumentsBundle.putString("url_string", urlString)
70
71             // Create a new instance of the dialog.
72             val addDomainDialog = AddDomainDialog()
73
74             // Add the arguments bundle to the dialog.
75             addDomainDialog.arguments = argumentsBundle
76
77             // Return the new dialog.
78             return addDomainDialog
79         }
80     }
81
82     // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
83     @SuppressLint("InflateParams")
84     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
85         // Get the arguments.
86         val arguments = requireArguments()
87
88         // Get the URL from the bundle.
89         val urlString = arguments.getString("url_string")
90
91         // Use an alert dialog builder to create the alert dialog.
92         val dialogBuilder: AlertDialog.Builder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
93
94         // Get the current theme status.
95         val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
96
97         // Set the icon according to the theme.
98         if (currentThemeStatus == Configuration.UI_MODE_NIGHT_YES) {
99             // Set the dark icon.
100             dialogBuilder.setIcon(R.drawable.domains_night)
101         } else {
102             // Set the light icon.
103             dialogBuilder.setIcon(R.drawable.domains_light)
104         }
105
106         // Set the title.
107         dialogBuilder.setTitle(R.string.add_domain)
108
109         // Set the view.  The parent view is `null` because it will be assigned by the alert dialog.
110         dialogBuilder.setView(requireActivity().layoutInflater.inflate(R.layout.add_domain_dialog, null))
111
112         // Set a listener on the cancel button.  Using `null` as the listener closes the dialog without doing anything else.
113         dialogBuilder.setNegativeButton(R.string.cancel, null)
114
115         // Set a listener on the add button.
116         dialogBuilder.setPositiveButton(R.string.add) { _: DialogInterface, _: Int ->
117             // Return the dialog fragment to the parent activity on add.
118             addDomainListener.onAddDomain(this)
119         }
120
121         // Create an alert dialog from the builder.
122         val alertDialog = dialogBuilder.create()
123
124         // Get a handle for the shared preferences.
125         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
126
127         // Get the screenshot preference.
128         val allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false)
129
130         // Disable screenshots if not allowed.
131         if (!allowScreenshots) {
132             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
133         }
134
135         // The alert dialog must be shown before the contents can be modified.
136         alertDialog.show()
137
138         // Initialize the domains database helper.  The `0` specifies the database version, but that is ignored and set instead using a constant in domains database helper.
139         val domainsDatabaseHelper = DomainsDatabaseHelper(context, null, null, 0)
140
141         // Get handles for the views in the alert dialog.
142         val addDomainEditText = alertDialog.findViewById<EditText>(R.id.domain_name_edittext)!!
143         val domainNameAlreadyExistsTextView = alertDialog.findViewById<TextView>(R.id.domain_name_already_exists_textview)!!
144         val addButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
145
146         //  Update the status of the warning text and the add button when the domain name changes.
147         addDomainEditText.addTextChangedListener(object: TextWatcher {
148             override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
149                 // Do nothing.
150             }
151
152             override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
153                 // Do nothing.
154             }
155
156             override fun afterTextChanged(s: Editable) {
157                 if (domainsDatabaseHelper.getCursorForDomainName(addDomainEditText.text.toString()).count > 0) {  // The domain already exists.
158                     // Show the warning text.
159                     domainNameAlreadyExistsTextView.visibility = View.VISIBLE
160
161                     // Disable the add button.
162                     addButton.isEnabled = false
163                 } else {  // The domain do not yet exist.
164                     // Hide the warning text.
165                     domainNameAlreadyExistsTextView.visibility = View.GONE
166
167                     // Enable the add button.
168                     addButton.isEnabled = true
169                 }
170             }
171         })
172
173         // Convert the URL string to a URI.
174         val currentUri = Uri.parse(urlString)
175
176         // Display the host in the add domain edit text.
177         addDomainEditText.setText(currentUri.host)
178
179         // Allow the enter key on the keyboard to create the domain from the add domain edit text.
180         addDomainEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
181             // Check the key code and event.
182             if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.action == KeyEvent.ACTION_DOWN) {  // The event is a key-down on the enter key.
183                 // Trigger the add domain listener and return the dialog fragment to the parent activity.
184                 addDomainListener.onAddDomain(this)
185
186                 // Manually dismiss the alert dialog.
187                 alertDialog.dismiss()
188
189                 // Consume the event.
190                 return@setOnKeyListener true
191             } else {  // Some other key was pressed.
192                 // Do not consume the event.
193                 return@setOnKeyListener false
194             }
195         }
196
197         // Return the alert dialog.
198         return alertDialog
199     }
200 }