Fix a crash when uploading files to some sites. https://redmine.stoutner.com/issues/556
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / CreateBookmarkDialog.kt
1 /*
2  * Copyright © 2016-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.AlertDialog
24 import android.app.Dialog
25 import android.content.Context
26 import android.content.DialogInterface
27 import android.graphics.Bitmap
28 import android.graphics.BitmapFactory
29 import android.graphics.drawable.BitmapDrawable
30 import android.graphics.drawable.Drawable
31 import android.os.Bundle
32 import android.view.KeyEvent
33 import android.view.View
34 import android.view.WindowManager
35 import android.widget.EditText
36
37 import androidx.fragment.app.DialogFragment
38 import androidx.preference.PreferenceManager
39
40 import com.stoutner.privacybrowser.R
41
42 import java.io.ByteArrayOutputStream
43
44 class CreateBookmarkDialog: DialogFragment() {
45     // The public interface is used to send information back to the parent activity.
46     interface CreateBookmarkListener {
47         fun onCreateBookmark(dialogFragment: DialogFragment, favoriteIconBitmap: Bitmap)
48     }
49
50     // The create bookmark listener is initialized in `onAttach()` and used in `onCreateDialog()`.
51     private lateinit var createBookmarkListener: CreateBookmarkListener
52
53     override fun onAttach(context: Context) {
54         // Run the default commands.
55         super.onAttach(context)
56
57         // Get a handle for the create bookmark listener from the launching context.
58         createBookmarkListener = context as CreateBookmarkListener
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 createBookmark(urlString: String, titleString: String, favoriteIconBitmap: Bitmap): CreateBookmarkDialog {
65             // Create a favorite icon byte array output stream.
66             val favoriteIconByteArrayOutputStream = ByteArrayOutputStream()
67
68             // Convert the favorite icon to a PNG and place it in the byte array output stream.  `0` is for lossless compression (the only option for a PNG).
69             favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream)
70
71             // Convert the byte array output stream to a byte array.
72             val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray()
73
74             // Create an arguments bundle.
75             val argumentsBundle = Bundle()
76
77             // Store the variables in the bundle.
78             argumentsBundle.putString("url_string", urlString)
79             argumentsBundle.putString("title_string", titleString)
80             argumentsBundle.putByteArray("favorite_icon_byte_array", favoriteIconByteArray)
81
82             // Create a new instance of the dialog.
83             val createBookmarkDialog = CreateBookmarkDialog()
84
85             // Add the bundle to the dialog.
86             createBookmarkDialog.arguments = argumentsBundle
87
88             // Return the new dialog.
89             return createBookmarkDialog
90         }
91     }
92
93     // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
94     @SuppressLint("InflateParams")
95     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
96         // Get the arguments.
97         val arguments = arguments!!
98
99         // Get the contents of the arguments.
100         val urlString = arguments.getString("url_string")
101         val titleString = arguments.getString("title_string")
102         val favoriteIconByteArray = arguments.getByteArray("favorite_icon_byte_array")!!
103
104         // Convert the favorite icon byte array to a bitmap.
105         val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size)
106
107         // Get a handle for the shared preferences.
108         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
109
110         // Get the screenshot and theme preferences.
111         val allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false)
112         val darkTheme = sharedPreferences.getBoolean("dark_theme", false)
113
114         // Use an alert dialog builder to create the dialog and set the style according to the theme.
115         val dialogBuilder = if (darkTheme) {
116             AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialogDark)
117         } else {
118             AlertDialog.Builder(context, R.style.PrivacyBrowserAlertDialogLight)
119         }
120
121         // Set the title.
122         dialogBuilder.setTitle(R.string.create_bookmark)
123
124         // Create a drawable version of the favorite icon.
125         val favoriteIconDrawable: Drawable = BitmapDrawable(resources, favoriteIconBitmap)
126
127         // Set the icon.
128         dialogBuilder.setIcon(favoriteIconDrawable)
129
130         // Set the view.  The parent view is `null` because it will be assigned by the alert dialog.
131         dialogBuilder.setView(activity!!.layoutInflater.inflate(R.layout.create_bookmark_dialog, null))
132
133         // Set a listener on the cancel button.  Using `null` as the listener closes the dialog without doing anything else.
134         dialogBuilder.setNegativeButton(R.string.cancel, null)
135
136         // Set a listener on the create button.
137         dialogBuilder.setPositiveButton(R.string.create) { _: DialogInterface, _: Int ->
138             // Return the dialog fragment and the favorite icon bitmap to the parent activity.
139             createBookmarkListener.onCreateBookmark(this, favoriteIconBitmap)
140         }
141
142         // Create an alert dialog from the builder.
143         val alertDialog = dialogBuilder.create()
144
145         // Disable screenshots if not allowed.
146         if (!allowScreenshots) {
147             alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
148         }
149
150         // The alert dialog needs to be shown before the contents can be modified.
151         alertDialog.show()
152
153         // Get a handle for the edit texts.
154         val createBookmarkNameEditText: EditText = alertDialog.findViewById(R.id.create_bookmark_name_edittext)
155         val createBookmarkUrlEditText: EditText = alertDialog.findViewById(R.id.create_bookmark_url_edittext)
156
157         // Set the initial texts for the edit texts.
158         createBookmarkNameEditText.setText(titleString)
159         createBookmarkUrlEditText.setText(urlString)
160
161         // Allow the enter key on the keyboard to create the bookmark from the create bookmark name edit text.
162         createBookmarkNameEditText.setOnKeyListener { _: View, keyCode: Int, keyEvent: KeyEvent ->
163             // Check the key code and event.
164             if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.action == KeyEvent.ACTION_DOWN) {  // The event is a key-down on the enter key.
165                 // Trigger the create bookmark listener and return the dialog fragment and the favorite icon bitmap to the parent activity.
166                 createBookmarkListener.onCreateBookmark(this, favoriteIconBitmap)
167
168                 // Manually dismiss the alert dialog.
169                 alertDialog.dismiss()
170
171                 // Consume the event.
172                 return@setOnKeyListener true
173             } else {  // Some other key was pressed.
174                 // Do not consume the event.
175                 return@setOnKeyListener false
176             }
177         }
178
179         // Allow the enter key on the keyboard to create the bookmark from create bookmark URL edit text.
180         createBookmarkUrlEditText.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 create bookmark listener and return the dialog fragment and the favorite icon bitmap to the parent activity.
184                 createBookmarkListener.onCreateBookmark(this, favoriteIconBitmap)
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 }