Skip to content
77 changes: 61 additions & 16 deletions app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.widget.CheckBox
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputEditText
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
Expand Down Expand Up @@ -206,7 +212,6 @@ class DeleteHelper @Inject constructor(
alert.setCancelable(false)
alert.setTitle(question)

val checkedItems = booleanArrayOf(false, false, false, false)
val mUserReason = arrayListOf<Int>()

val reasonList: Array<String>
Expand All @@ -217,15 +222,18 @@ class DeleteHelper @Inject constructor(
reasonList = arrayOf(
context.getString(R.string.delete_helper_ask_spam_selfie),
context.getString(R.string.delete_helper_ask_spam_blurry),
context.getString(R.string.delete_helper_ask_spam_nonsense)
context.getString(R.string.delete_helper_ask_spam_nonsense),
context.getString(R.string.delete_helper_ask_spam_other)
)
reasonListEnglish = arrayOf(
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_spam_selfie),
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_spam_blurry),
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_spam_nonsense)
.getString(R.string.delete_helper_ask_spam_nonsense),
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_spam_other)
)
}
ReviewController.DeleteReason.COPYRIGHT_VIOLATION -> {
Expand Down Expand Up @@ -256,21 +264,49 @@ class DeleteHelper @Inject constructor(
}
}

alert.setMultiChoiceItems(
reasonList,
checkedItems
) { dialogInterface, position, isChecked ->
if (isChecked) {
mUserReason.add(position)
} else {
mUserReason.remove(position)
}
// Inflate custom layout
val inflater = LayoutInflater.from(context)
val customView = inflater.inflate(R.layout.dialog_deletion_reason, null)
val checkboxContainer = customView.findViewById<LinearLayout>(R.id.checkboxContainer)
val otherNotesEditText = customView.findViewById<TextInputEditText>(R.id.otherNotesEditText)

// Function to update OK button state
fun updateOkButtonState() {
val hasSelection = mUserReason.isNotEmpty()
val hasText = !otherNotesEditText?.text.isNullOrBlank()

d?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = hasSelection && hasText
}

// Safely enable or disable the OK button based on selection
val dialog = dialogInterface as? AlertDialog
dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = mUserReason.isNotEmpty()
// Create checkboxes dynamically
val checkBoxes = mutableListOf<CheckBox>()
reasonList.forEachIndexed { index, reason ->
val checkBox = CheckBox(context)
checkBox.text = reason
checkBox.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
mUserReason.add(index)
} else {
mUserReason.remove(index)
}
updateOkButtonState()
}
checkBoxes.add(checkBox)
checkboxContainer.addView(checkBox)
}

// Add text watcher for other notes field
otherNotesEditText?.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
updateOkButtonState()
}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})

alert.setView(customView)

alert.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
reviewCallback.disableButtons()

Expand All @@ -281,12 +317,21 @@ class DeleteHelper @Inject constructor(
)
append(" ")

mUserReason.forEachIndexed { index, position ->
mUserReason.sorted().forEachIndexed { index, position ->
append(reasonListEnglish[position])
if (index != mUserReason.lastIndex) {
append(", ")
}
}

// Add other notes if provided
val otherNotes = otherNotesEditText?.text?.toString()?.trim()
if (!otherNotes.isNullOrBlank()) {
if (mUserReason.isNotEmpty()) {
append(". ")
}
append(otherNotes)
}
}

Timber.d("thread is askReasonAndExecute %s", Thread.currentThread().name)
Expand Down
40 changes: 40 additions & 0 deletions app/src/main/res/layout/dialog_deletion_reason.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/dimen_6">

<LinearLayout
android:id="@+id/checkboxContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/otherNotesInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen_6"
android:layout_marginStart="@dimen/dimen_6"
android:layout_marginEnd="@dimen/dimen_6">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/otherNotesEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/delete_helper_other_notes_hint"
android:imeOptions="actionDone|flagNoExtractUi"
android:lines="3"
android:inputType="text|textMultiLine"
android:minLines="2"/>

</com.google.android.material.textfield.TextInputLayout>

</LinearLayout>
</ScrollView>
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ Upload your first media by tapping on the add button.</string>
<string name="delete_helper_ask_spam_selfie">a selfie which is not used in any article</string>
<string name="delete_helper_ask_spam_blurry">totally blurry</string>
<string name="delete_helper_ask_spam_nonsense">nonsense, absolutely unusable in any article</string>
<string name="delete_helper_ask_spam_other">Other: specify below</string>
<string name="delete_helper_other_notes_hint">Justification</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Press photo</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Random photo from internet</string>
<string name="delete_helper_ask_reason_copyright_logo">Logo</string>
Expand Down
42 changes: 28 additions & 14 deletions app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package fr.free.nrw.commons.delete

import android.app.AlertDialog
import android.content.Context
import android.widget.CheckBox
import android.widget.LinearLayout
import com.google.android.material.textfield.TextInputEditText
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
Expand Down Expand Up @@ -181,7 +184,7 @@ class DeleteHelperTest {
}

@Test
fun alertDialogPositiveButtonDisableTest() {
fun buttonRemainsDisabledWhenCheckboxCheckedButNoText() {
val mContext = RuntimeEnvironment.getApplication().applicationContext
deleteHelper.askReasonAndExecute(
media,
Expand All @@ -190,23 +193,34 @@ class DeleteHelperTest {
ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback
)

deleteHelper.getListener()?.onClick(
deleteHelper.getDialog(),
1,
true
)
assertEquals(
true,
deleteHelper.getDialog()?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled
)
val dialog = deleteHelper.getDialog()
val container = dialog?.findViewById<LinearLayout>(R.id.checkboxContainer)
val checkbox = container?.getChildAt(0) as? CheckBox
checkbox?.isChecked = true

assertEquals(false, dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled)
}

@Test
fun alertDialogPositiveButtonEnableTest() {
fun buttonEnabledWhenCheckboxCheckedAndTextEntered() {
val mContext = RuntimeEnvironment.getApplication().applicationContext
deleteHelper.askReasonAndExecute(media, mContext, "My Question", ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback)
deleteHelper.getListener()?.onClick(deleteHelper.getDialog(), 1, true)
assertEquals(true, deleteHelper.getDialog()?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled)
deleteHelper.askReasonAndExecute(
media,
mContext,
"My Question",
ReviewController.DeleteReason.SPAM,
callback
)

val dialog = deleteHelper.getDialog()
val container = dialog?.findViewById<LinearLayout>(R.id.checkboxContainer)
val checkbox = container?.getChildAt(0) as? CheckBox
checkbox?.isChecked = true

val editText = dialog?.findViewById<TextInputEditText>(R.id.otherNotesEditText)
editText?.setText("some justification")

assertEquals(true, dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled)
}

@Test(expected = RuntimeException::class)
Expand Down