Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support batch deletion #70

Merged
merged 7 commits into from
Nov 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ data class GraphicNote(
override var resource: Resource? = null,
override var pendingForDelete: Boolean = false,
var thumb: Bitmap? = null,
override var selected: Boolean = false,
) : Note, Parcelable
1 change: 1 addition & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/models/Note.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ interface Note {
val description: String
var resource: Resource?
var pendingForDelete: Boolean
var selected: Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ data class TextNote(
@IgnoredOnParcel
override var resource: Resource? = null,
override var pendingForDelete: Boolean = false,
override var selected: Boolean = false,
) : Note, Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ class VoiceNote(
var waitToBeResumed: Boolean = false,
var currentPlayingPos: Int = 0,
var currentMaxAmplitude: Int = 0,
override var selected: Boolean = false,
) : Note, Parcelable
2 changes: 2 additions & 0 deletions app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ interface NotesRepo<Note> {
suspend fun read(): List<Note>

suspend fun delete(note: Note)

suspend fun delete(notes: List<Note>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ class NotesRepoHelper
return UserNoteProperties(title, description)
}

suspend fun deleteNotes(notes: List<Note>): Unit =
withContext(Dispatchers.IO) {
notes.forEach { note ->
deleteNote(note)
}
}

suspend fun deleteNote(note: Note): Unit =
withContext(Dispatchers.IO) {
val id = note.resource?.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ class GraphicNotesRepo
helper.deleteNote(note)
}

override suspend fun delete(notes: List<GraphicNote>) {
helper.deleteNotes(notes)
}

override suspend fun read(): List<GraphicNote> =
withContext(iODispatcher) {
readStorage()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ class TextNotesRepo
write(note) { callback(it) }
}

override suspend fun delete(notes: List<TextNote>) {
helper.deleteNotes(notes)
}

override suspend fun delete(note: TextNote) {
helper.deleteNote(note)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class VoiceNotesRepo
readStorage()
}

override suspend fun delete(notes: List<VoiceNote>) {
helper.deleteNotes(notes)
}

override suspend fun delete(note: VoiceNote) {
helper.deleteNote(note)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.RecyclerView
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.shape.CornerFamily
Expand Down Expand Up @@ -35,6 +37,8 @@ class NotesListAdapter(
private val onPlayPauseClick: (path: String, pos: Int?, stopCallback: ((pos: Int) -> Unit)?) -> Unit,
) : RecyclerView.Adapter<NotesListAdapter.NoteViewHolder>() {
private lateinit var activity: MainActivity
private var mActionMode = false
private var checkedByItemClick = false

lateinit var observeItemSideEffect: () -> ArkMediaPlayerSideEffect
lateinit var observeItemState: () -> ArkMediaPlayerState
Expand All @@ -46,6 +50,13 @@ class NotesListAdapter(
activity.resources.getDimension(R.dimen.corner_radius_big)
}

var onItemLongPressed: ((pos: Int, note: Note) -> Unit)? = null
var onItemClicked: (() -> Unit)? = null

private val selectedNotesCount by lazy { MutableLiveData<Int>() }
val observableSelectedNotesCount by lazy { selectedNotesCount }
val selectedNotesForDelete = mutableListOf<Note>()

fun setActivity(activity: AppCompatActivity) {
this.activity = activity as MainActivity
}
Expand Down Expand Up @@ -87,13 +98,19 @@ class NotesListAdapter(
}

holder.btnPlayPause.setOnClickListener {
onPlayPauseClick(note.path.toString(), position) { stopPos ->
onPlayPauseClick(note.path.toString(), holder.bindingAdapterPosition) { stopPos ->
val realPos =
if (holder.bindingAdapterPosition >= 0) {
holder.bindingAdapterPosition
} else {
position
}
showPlaybackIdleState(holder)
(notes[position] as VoiceNote).isPlaying = false
(notes[realPos] as VoiceNote).isPlaying = false
holder.layoutAudioView.animAudioPlaying.resetWave()
holder.layoutAudioView.animAudioPlaying.invalidateWave(0)
holder.tvPlayingPosition.gone()
notifyItemChanged(position)
notifyItemChanged(realPos)
}
handleMediaPlayerSideEffect(observeItemSideEffect(), holder)
note.isPlaying = !note.isPlaying
Expand Down Expand Up @@ -141,6 +158,13 @@ class NotesListAdapter(
} else {
holder.tvDelete.gone()
}

holder.cbDelete.isChecked = note.selected
if (mActionMode) {
holder.cbDelete.visible()
} else {
holder.cbDelete.gone()
}
}

override fun getItemCount() = notes.size
Expand Down Expand Up @@ -212,12 +236,45 @@ class NotesListAdapter(
return notes
}

fun removeNote(noteToRemove: Note) {
notes.remove(noteToRemove)
selectedNotesCount.postValue(notes.size)
}

fun removeNotes(notesToRemove: List<Note>) {
notes.removeAll(notesToRemove)
}

fun setNotes(notes: List<Note>) {
this.notes = notes.toMutableList()
}

fun removeNote(noteToRemove: Note) {
notes.remove(noteToRemove)
fun toggleActionMode(pos: Int) {
mActionMode = !mActionMode
var selectedCount = 0
notes.forEachIndexed { index, note ->
note.selected = mActionMode && index == pos
if (note.selected) {
selectedCount++
selectedNotesForDelete.add(note)
}
}
selectedNotesCount.postValue(selectedCount)
notifyDataSetChanged()
}

fun toggleSelectAllItems(selected: Boolean) {
notes.forEach { it.selected = selected }
selectedNotesForDelete.clear()
selectedNotesCount.postValue(
if (selected) {
selectedNotesForDelete.addAll(notes)
notes.size
} else {
0
},
)
notifyDataSetChanged()
}

inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
Expand All @@ -232,10 +289,17 @@ class NotesListAdapter(
val tvPlayingPosition = binding.layoutAudioView.tvPlayingPosition
val ivGraphicThumb = binding.ivGraphicsThumb
val tvDelete = binding.tvDelete
val cbDelete = binding.cbDelete

var isSwiping: Boolean = false

private val clickNoteToEditListener =
View.OnClickListener {
if (mActionMode) {
checkedByItemClick = true
binding.cbDelete.toggle()
return@OnClickListener
}
var tag = EditTextNotesFragment.TAG
when (val selectedNote = notes[bindingAdapterPosition]) {
is TextNote -> activity.fragment = EditTextNotesFragment.newInstance(selectedNote)
Expand All @@ -248,11 +312,43 @@ class NotesListAdapter(
tag = ArkRecorderFragment.TAG
}
}
onItemClicked?.invoke()
activity.replaceFragment(activity.fragment, tag)
}

private val noteCheckedListener =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (!buttonView.isPressed && !checkedByItemClick) return@OnCheckedChangeListener
checkedByItemClick = false

val selectedNote = notes[bindingAdapterPosition]
selectedNote.selected = isChecked
if (isChecked) {
selectedNotesCount.value?.let { count ->
selectedNotesCount.postValue(count + 1)
}
selectedNotesForDelete.add(selectedNote)
} else {
selectedNotesCount.value?.let { count ->
selectedNotesCount.postValue(count - 1)
}
selectedNotesForDelete.remove(selectedNote)
}

buttonView.post {
notifyItemChanged(bindingAdapterPosition)
}
}

init {
binding.root.setOnClickListener(clickNoteToEditListener)
binding.root.setOnLongClickListener {
onItemLongPressed?.invoke(bindingAdapterPosition, notes[bindingAdapterPosition])
true
}
binding.cbDelete.setOnCheckedChangeListener(noteCheckedListener)
binding.layoutAudioView.root.setBackgroundResource(R.drawable.bg_audio_view_note_item)
binding.viewItemContent.clipToOutline = true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import dev.arkbuilders.arkmemo.databinding.DialogCommonActionBinding
* It's a basic dialog with customizable title, message, one positive button and one negative button
*/
class CommonActionDialog(
@StringRes private val title: Int,
@StringRes private val message: Int,
private val title: String,
private val message: String,
@StringRes private val positiveText: Int,
@StringRes private val negativeText: Int,
private val isAlert: Boolean = false,
Expand Down Expand Up @@ -48,8 +48,8 @@ class CommonActionDialog(
mBinding.tvPositive.setBackgroundResource(R.drawable.bg_red_button)
}

mBinding.tvTitle.setText(title)
mBinding.tvMessage.setText(message)
mBinding.tvTitle.text = title
mBinding.tvMessage.text = message
mBinding.tvPositive.setText(positiveText)
mBinding.tvNegative.setText(negativeText)
mBinding.ivClose.setOnClickListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,16 @@ class ArkRecorderFragment : BaseEditNoteFragment() {
path = arkRecorderViewModel.getRecordingPath(),
)
CommonActionDialog(
title = R.string.delete_note,
message = R.string.ark_memo_delete_warn,
title = getString(R.string.delete_note),
message = resources.getQuantityString(R.plurals.delete_batch_note_message, 1),
positiveText = R.string.action_delete,
negativeText = R.string.ark_memo_cancel,
isAlert = true,
onPositiveClick = {
notesViewModel.onDeleteConfirmed(note) {}
toast(requireContext(), getString(R.string.note_deleted))
activity.onBackPressedDispatcher.onBackPressed()
notesViewModel.onDeleteConfirmed(listOf(note)) {
toast(requireContext(), getString(R.string.note_deleted))
activity.onBackPressedDispatcher.onBackPressed()
}
},
onNegativeClicked = {
},
Expand Down Expand Up @@ -521,8 +522,8 @@ class ArkRecorderFragment : BaseEditNoteFragment() {
)
) {
CommonActionDialog(
title = R.string.dialog_replace_recording_title,
message = R.string.dialog_replace_recording_message,
title = getString(R.string.dialog_replace_recording_title),
message = getString(R.string.dialog_replace_recording_message),
positiveText = R.string.dialog_replace_recording_positive_text,
negativeText = R.string.discard,
onPositiveClick = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ abstract class BaseEditNoteFragment : BaseFragment() {
) {
val saveNoteDialog =
CommonActionDialog(
title = R.string.dialog_save_note_title,
message = R.string.dialog_save_note_message,
title = getString(R.string.dialog_save_note_title),
message = getString(R.string.dialog_save_note_message),
positiveText = R.string.save,
negativeText = R.string.discard,
isAlert = false,
Expand All @@ -138,15 +138,16 @@ abstract class BaseEditNoteFragment : BaseFragment() {

fun showDeleteNoteDialog(note: Note) {
CommonActionDialog(
title = R.string.delete_note,
message = R.string.ark_memo_delete_warn,
title = getString(R.string.delete_note),
message = resources.getQuantityString(R.plurals.delete_batch_note_message, 1),
positiveText = R.string.action_delete,
negativeText = R.string.ark_memo_cancel,
isAlert = true,
onPositiveClick = {
notesViewModel.onDeleteConfirmed(note) {}
hostActivity.onBackPressedDispatcher.onBackPressed()
toast(requireContext(), getString(R.string.note_deleted))
notesViewModel.onDeleteConfirmed(listOf(note)) {
hostActivity.onBackPressedDispatcher.onBackPressed()
toast(requireContext(), getString(R.string.note_deleted))
}
},
onNegativeClicked = {
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class EditTextNotesFragment : BaseEditNoteFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
notesViewModel.init {}
observeSaveResult(notesViewModel.getSaveNoteResultLiveData())

if (arguments != null) {
requireArguments().getParcelableCompat(NOTE_KEY, TextNote::class.java)?.let {
note = it
Expand Down Expand Up @@ -155,6 +155,7 @@ class EditTextNotesFragment : BaseEditNoteFragment() {
?: binding.toolbar.ivRightActionIcon.gone()

view.viewTreeObserver.addOnWindowFocusChangeListener(windowFocusedListener)
observeSaveResult(notesViewModel.getSaveNoteResultLiveData())
}

override fun isContentChanged(): Boolean {
Expand Down
Loading