/* * Copyright 2016-2024 Soren Stoutner . * * This file is part of Privacy Browser Android . * * Privacy Browser Android is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Privacy Browser Android is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Privacy Browser Android. If not, see . */ package com.stoutner.privacybrowser.dialogs import android.app.Dialog import android.content.Context import android.content.DialogInterface import android.database.Cursor import android.database.MatrixCursor import android.database.MergeCursor import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.KeyEvent import android.view.View import android.view.WindowManager import android.widget.AdapterView import android.widget.AdapterView.OnItemSelectedListener import android.widget.Button import android.widget.EditText import android.widget.ImageView import android.widget.LinearLayout import android.widget.RadioButton import android.widget.Spinner import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.appcompat.content.res.AppCompatResources import androidx.cursoradapter.widget.ResourceCursorAdapter import androidx.fragment.app.DialogFragment import androidx.preference.PreferenceManager import com.google.android.material.snackbar.Snackbar import com.stoutner.privacybrowser.R import com.stoutner.privacybrowser.activities.HOME_FOLDER_DATABASE_ID import com.stoutner.privacybrowser.activities.HOME_FOLDER_ID import com.stoutner.privacybrowser.helpers.BOOKMARK_NAME import com.stoutner.privacybrowser.helpers.BOOKMARK_URL import com.stoutner.privacybrowser.helpers.DISPLAY_ORDER import com.stoutner.privacybrowser.helpers.FAVORITE_ICON import com.stoutner.privacybrowser.helpers.FOLDER_ID import com.stoutner.privacybrowser.helpers.ID import com.stoutner.privacybrowser.helpers.PARENT_FOLDER_ID import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper import java.io.ByteArrayOutputStream // Define the class constants. private const val DATABASE_ID = "A" private const val FAVORITE_ICON_BYTE_ARRAY = "B" class EditBookmarkDatabaseViewDialog : DialogFragment() { companion object { fun bookmarkDatabaseId(databaseId: Int, favoriteIconBitmap: Bitmap): EditBookmarkDatabaseViewDialog { // Create a favorite icon byte array output stream. val favoriteIconByteArrayOutputStream = ByteArrayOutputStream() // 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). favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream) // Convert the byte array output stream to a byte array. val favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray() // Create an arguments bundle. val argumentsBundle = Bundle() // Store the variables in the bundle. argumentsBundle.putInt(DATABASE_ID, databaseId) argumentsBundle.putByteArray(FAVORITE_ICON_BYTE_ARRAY, favoriteIconByteArray) // Create a new instance of the dialog. val editBookmarkDatabaseViewDialog = EditBookmarkDatabaseViewDialog() // Add the arguments bundle to the dialog. editBookmarkDatabaseViewDialog.arguments = argumentsBundle // Return the new dialog. return editBookmarkDatabaseViewDialog } } private val browseActivityResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { imageUri: Uri? -> // Only do something if the user didn't press back from the file picker. if (imageUri != null) { // Get a handle for the content resolver. val contentResolver = requireContext().contentResolver // Get the image MIME type. val mimeType = contentResolver.getType(imageUri) // Decode the image according to the type. if (mimeType == "image/svg+xml") { // The image is an SVG. // Display a snackbar. Snackbar.make(bookmarkNameEditText, getString(R.string.cannot_use_svg), Snackbar.LENGTH_LONG).show() } else { // The image is not an SVG. // Get an input stream for the image URI. val inputStream = contentResolver.openInputStream(imageUri) // Get the bitmap from the URI. // `ImageDecoder.decodeBitmap` can't be used, because when running `Drawable.toBitmap` later the `Software rendering doesn't support hardware bitmaps` error message might be produced. var imageBitmap = BitmapFactory.decodeStream(inputStream) // Scale the image down if it is greater than 128 pixels in either direction. if ((imageBitmap != null) && ((imageBitmap.height > 128) || (imageBitmap.width > 128))) imageBitmap = Bitmap.createScaledBitmap(imageBitmap, 128, 128, true) // Display the new custom favorite icon. customIconImageView.setImageBitmap(imageBitmap) // Select the custom icon radio button. customIconLinearLayout.performClick() } } } // Declare the class views. private lateinit var bookmarkNameEditText: EditText private lateinit var bookmarkUrlEditText: EditText private lateinit var currentIconRadioButton: RadioButton private lateinit var customIconImageView: ImageView private lateinit var customIconLinearLayout: LinearLayout private lateinit var displayOrderEditText: EditText private lateinit var folderSpinner: Spinner private lateinit var saveButton: Button // Declare the class variables. private lateinit var editBookmarkDatabaseViewListener: EditBookmarkDatabaseViewListener // The public interface is used to send information back to the parent activity. interface EditBookmarkDatabaseViewListener { fun saveBookmark(dialogFragment: DialogFragment, selectedBookmarkDatabaseId: Int) } override fun onAttach(context: Context) { // Run the default commands. super.onAttach(context) // Get a handle for edit bookmark database view listener from the launching context. editBookmarkDatabaseViewListener = context as EditBookmarkDatabaseViewListener } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { // Get the arguments. val arguments = requireArguments() // Get the variables from the arguments. val bookmarkDatabaseId = arguments.getInt(DATABASE_ID) val favoriteIconByteArray = arguments.getByteArray(FAVORITE_ICON_BYTE_ARRAY)!! // Convert the favorite icon byte array to a bitmap. val favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.size) // Initialize the database helper. val bookmarksDatabaseHelper = BookmarksDatabaseHelper(requireContext()) // Get a cursor with the selected bookmark. val bookmarkCursor = bookmarksDatabaseHelper.getBookmark(bookmarkDatabaseId) // Move the cursor to the first position. bookmarkCursor.moveToFirst() // Use an alert dialog builder to create the dialog and set the style according to the theme. val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog) // Set the title. dialogBuilder.setTitle(R.string.edit_bookmark) // Set the icon. dialogBuilder.setIcon(R.drawable.bookmark) // Set the view. dialogBuilder.setView(R.layout.edit_bookmark_databaseview_dialog) // Set the cancel button listener. Using `null` as the listener closes the dialog without doing anything else. dialogBuilder.setNegativeButton(R.string.cancel, null) // Set the save button listener. dialogBuilder.setPositiveButton(R.string.save) { _: DialogInterface, _: Int -> // Return the dialog fragment to the parent activity on save. editBookmarkDatabaseViewListener.saveBookmark(this, bookmarkDatabaseId) } // Create an alert dialog from the alert dialog builder. val alertDialog = dialogBuilder.create() // Get a handle for the shared preferences. val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) // Get the screenshot preference. val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false) // Disable screenshots if not allowed. if (!allowScreenshots) { alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } // The alert dialog must be shown before items in the layout can be modified. alertDialog.show() // Get handles for the layout items. val databaseIdTextView = alertDialog.findViewById(R.id.bookmark_database_id_textview)!! val currentIconLinearLayout = alertDialog.findViewById(R.id.current_icon_linearlayout)!! currentIconRadioButton = alertDialog.findViewById(R.id.current_icon_radiobutton)!! val currentIconImageView = alertDialog.findViewById(R.id.current_icon_imageview)!! val webpageFavoriteIconLinearLayout = alertDialog.findViewById(R.id.webpage_favorite_icon_linearlayout)!! val webpageFavoriteIconRadioButton = alertDialog.findViewById(R.id.webpage_favorite_icon_radiobutton)!! val webpageFavoriteIconImageView = alertDialog.findViewById(R.id.webpage_favorite_icon_imageview)!! customIconLinearLayout = alertDialog.findViewById(R.id.custom_icon_linearlayout)!! val customIconRadioButton = alertDialog.findViewById(R.id.custom_icon_radiobutton)!! customIconImageView = alertDialog.findViewById(R.id.custom_icon_imageview)!! val browseButton = alertDialog.findViewById