Skip to content

Commit

Permalink
Support batch deletion in Memo app:
Browse files Browse the repository at this point in the history
  • Loading branch information
tuancoltech committed Sep 26, 2024
1 parent 482fe08 commit 524cbce
Show file tree
Hide file tree
Showing 25 changed files with 523 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ data class GraphicNote(
val svg: SVG? = null,
@IgnoredOnParcel
override var resource: Resource? = null,
override var pendingForDelete: Boolean = false
override var pendingForDelete: Boolean = false,
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
}
3 changes: 2 additions & 1 deletion app/src/main/java/dev/arkbuilders/arkmemo/models/TextNote.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ data class TextNote (
val text: String = "",
@IgnoredOnParcel
override var resource: Resource? = null,
override var pendingForDelete: Boolean = false
override var pendingForDelete: Boolean = false,
override var selected: Boolean = false
): Note, Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ class VoiceNote(
override var pendingForDelete: Boolean = false,
var isPlaying: Boolean = false,
var currentPlayingPos: Int = 0,
var currentMaxAmplitude: 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 @@ -11,4 +11,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 @@ -80,6 +80,12 @@ class NotesRepoHelper @Inject constructor(
return UserNoteProperties(title, description)
}

suspend fun deleteNote(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 @@ -47,6 +47,10 @@ class GraphicNotesRepo @Inject constructor(
helper.deleteNote(note)
}

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

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

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

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

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

override suspend fun delete(note: VoiceNote) {
helper.deleteNote(note)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package dev.arkbuilders.arkmemo.ui.adapters
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 dev.arkbuilders.arkmemo.R
Expand All @@ -29,19 +31,27 @@ import dev.arkbuilders.arkmemo.utils.replaceFragment
import dev.arkbuilders.arkmemo.utils.visible

class NotesListAdapter(
private var notes: List<Note>,
private var notes: MutableList<Note>,
private val onPlayPauseClick: (path: String, pos: Int?, stopCallback: ((pos: Int) -> Unit)?) -> Unit,
private val onThumbPrepare : (note: GraphicNote, holder: NotesCanvas) -> Unit
): RecyclerView.Adapter<NotesListAdapter.NoteViewHolder>() {

private lateinit var activity: MainActivity
private var mActionMode = false

lateinit var observeItemSideEffect: () -> ArkMediaPlayerSideEffect
lateinit var observeItemState: () -> ArkMediaPlayerState

private var isFromSearch: Boolean = false
private var searchKeyWord: String = ""

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

private val selectedNoteCount by lazy { MutableLiveData<Int>() }
val observableSelectedNoteCount by lazy { selectedNoteCount }
val selectedNotedForDelete = mutableListOf<Note>()

fun setActivity(activity: AppCompatActivity) {
this.activity = activity as MainActivity
}
Expand All @@ -53,7 +63,7 @@ class NotesListAdapter(
}

override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
val note = notes[position]
val note = notes[holder.bindingAdapterPosition]
holder.title.text = note.getAutoTitle(activity)

if (isFromSearch) {
Expand All @@ -74,13 +84,16 @@ 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 @@ -108,6 +121,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 @@ -159,7 +179,7 @@ class NotesListAdapter(
}

fun updateData(newNotes: List<Note>, fromSearch: Boolean? = null, keyword: String? = null) {
notes = newNotes
notes = newNotes.toMutableList()
isFromSearch = fromSearch ?: false
searchKeyWord = keyword ?: ""
notifyDataSetChanged()
Expand All @@ -169,8 +189,33 @@ class NotesListAdapter(
return notes
}

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

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

fun toggleActionMode() {
mActionMode = !mActionMode
notes.forEach { it.selected = false }
selectedNoteCount.postValue(0)
notifyDataSetChanged()
}

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

inner class NoteViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
Expand All @@ -185,6 +230,9 @@ class NotesListAdapter(
val tvPlayingPosition = binding.layoutAudioView.tvPlayingPosition
val canvasGraphicThumb = binding.canvasGraphicThumb
val tvDelete = binding.tvDelete
val cbDelete = binding.cbDelete

var isSwiping: Boolean = false

private val clickNoteToEditListener = View.OnClickListener {
var tag = EditTextNotesFragment.TAG
Expand All @@ -199,11 +247,39 @@ class NotesListAdapter(
tag = ArkRecorderFragment.TAG
}
}
onItemClicked?.invoke()
activity.replaceFragment(activity.fragment, tag)
}

private val noteCheckedListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (!buttonView.isPressed) return@OnCheckedChangeListener
val selectedNote = notes[bindingAdapterPosition]
selectedNote.selected = isChecked
if (isChecked) {
selectedNoteCount.value?.let { count ->
selectedNoteCount.postValue(count + 1)
}
selectedNotedForDelete.add(selectedNote)
} else {
selectedNoteCount.value?.let { count ->
selectedNoteCount.postValue(count - 1)
}
selectedNotedForDelete.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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import dev.arkbuilders.arkmemo.databinding.DialogCommonActionBinding
* This is a common action dialog that can be used inside app.
* 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,
class CommonActionDialog(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 @@ -46,8 +46,8 @@ class CommonActionDialog(@StringRes private val title: Int,
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 @@ -151,8 +151,8 @@ class ArkRecorderFragment: BaseEditNoteFragment() {
&& (arkRecorderViewModel.isRecordExisting() ||
File(getCurrentRecordingPath()).length() > 0L)) {

CommonActionDialog(title = R.string.dialog_replace_recording_title,
message = R.string.dialog_replace_recording_message,
CommonActionDialog(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 Expand Up @@ -200,15 +200,16 @@ class ArkRecorderFragment: BaseEditNoteFragment() {
title = title.ifEmpty { defaultNoteTitle },
path = arkRecorderViewModel.getRecordingPath()
)
CommonActionDialog(title = R.string.delete_note,
message = R.string.ark_memo_delete_warn,
CommonActionDialog(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 = {
}).show(parentFragmentManager, CommonActionDialog.TAG)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ abstract class BaseEditNoteFragment: Fragment() {

private fun showSaveNoteDialog() {
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 @@ -119,15 +119,16 @@ abstract class BaseEditNoteFragment: Fragment() {

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 = {
}).show(parentFragmentManager, CommonActionDialog.TAG)
}
Expand Down
Loading

0 comments on commit 524cbce

Please sign in to comment.