eliminar imagenes

This commit is contained in:
erik-everardo 2023-12-24 13:18:50 -06:00
parent e9304a7a4e
commit 71fef94f0d
11 changed files with 91 additions and 18 deletions

View File

@ -0,0 +1,5 @@
package com.isolaatti.common
open class Deletable {
var delete = false
}

View File

@ -25,7 +25,7 @@ interface ImagesApi {
@Part setAsProfile: MultipartBody.Part? = null, @Part setAsProfile: MultipartBody.Part? = null,
@Part squadId: MultipartBody.Part? = null): Call<ImageDto> @Part squadId: MultipartBody.Part? = null): Call<ImageDto>
@POST("images/delete_many") @POST("images/delete/delete_many")
fun deleteImages(@Body deleteImagesDto: DeleteImagesDto): Call<Void> fun deleteImages(@Body deleteImagesDto: DeleteImagesDto): Call<Void>
@GET("images/of_squad/{squadId}") @GET("images/of_squad/{squadId}")

View File

@ -46,7 +46,7 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
override fun deleteImages(images: List<Image>): Flow<Resource<Boolean>> = flow { override fun deleteImages(images: List<Image>): Flow<Resource<Boolean>> = flow {
emit(Resource.Loading()) emit(Resource.Loading())
val dto = DeleteImagesDto(images.map { it.imageUrl }) val dto = DeleteImagesDto(images.map { it.id })
try { try {
val response = imagesApi.deleteImages(dto).awaitResponse() val response = imagesApi.deleteImages(dto).awaitResponse()
if(response.isSuccessful) { if(response.isSuccessful) {

View File

@ -1,5 +1,6 @@
package com.isolaatti.images.common.domain.entity package com.isolaatti.images.common.domain.entity
import com.isolaatti.common.Deletable
import com.isolaatti.images.common.data.remote.ImageDto import com.isolaatti.images.common.data.remote.ImageDto
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
import java.io.Serializable import java.io.Serializable
@ -9,7 +10,7 @@ data class Image(
val userId: Int, val userId: Int,
val name: String, val name: String,
val username: String val username: String
): Serializable { ): Deletable(), Serializable {
val imageUrl: String get() = UrlGen.imageUrl(id) val imageUrl: String get() = UrlGen.imageUrl(id)
val smallImageUrl : String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_SMALL) val smallImageUrl : String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_SMALL)
val reducedImageUrl: String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_REDUCED) val reducedImageUrl: String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_REDUCED)

View File

@ -18,10 +18,16 @@ import kotlin.properties.Delegates
@HiltViewModel @HiltViewModel
class ImageListViewModel @Inject constructor(private val imagesRepository: ImagesRepository) : ViewModel() { class ImageListViewModel @Inject constructor(private val imagesRepository: ImagesRepository) : ViewModel() {
val liveList: MutableLiveData<List<Image>> = MutableLiveData() enum class Event {
REMOVED_IMAGE, ADDED_IMAGE_BEGINNING
}
val liveList: MutableLiveData<List<Image>> = MutableLiveData(listOf())
val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData() val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
val loading: MutableLiveData<Boolean> = MutableLiveData() val loading: MutableLiveData<Boolean> = MutableLiveData()
val deleting: MutableLiveData<Boolean> = MutableLiveData()
var noMoreContent = false var noMoreContent = false
var lastEvent: Event? = null
private var loadedFirstTime = false private var loadedFirstTime = false
var userId by Delegates.notNull<Int>() var userId by Delegates.notNull<Int>()
@ -74,10 +80,16 @@ class ImageListViewModel @Inject constructor(private val imagesRepository: Image
viewModelScope.launch { viewModelScope.launch {
imagesRepository.deleteImages(images).onEach { imagesRepository.deleteImages(images).onEach {
when(it) { when(it) {
is Resource.Error -> {} is Resource.Error -> {
is Resource.Loading -> {} deleting.postValue(false)
}
is Resource.Loading -> {
deleting.postValue(true)
}
is Resource.Success -> { is Resource.Success -> {
liveList.value = list.filterNot { image -> images.contains(image) } deleting.postValue(false)
lastEvent = Event.REMOVED_IMAGE
liveList.postValue(list.filterNot { image -> images.contains(image) })
} }
} }
}.flowOn(Dispatchers.IO).launchIn(this) }.flowOn(Dispatchers.IO).launchIn(this)

View File

@ -34,13 +34,11 @@ class ImagesAdapter(
} }
} }
private var selectionState: Array<Boolean> = arrayOf()
var deleteMode: Boolean = false var deleteMode: Boolean = false
set(value) { set(value) {
field = value field = value
if(!value) { if(!value) {
selectionState.forEachIndexed { index, _ -> selectionState[index] = false } currentList.forEach { it.delete = false }
} }
notifyDataSetChanged() notifyDataSetChanged()
} }
@ -48,7 +46,7 @@ class ImagesAdapter(
inner class ImageViewHolder(val imageItemBinding: ImageItemBinding) : RecyclerView.ViewHolder(imageItemBinding.root) inner class ImageViewHolder(val imageItemBinding: ImageItemBinding) : RecyclerView.ViewHolder(imageItemBinding.root)
fun getSelectedImages(): List<Image> { fun getSelectedImages(): List<Image> {
return currentList.filterIndexed { index, _ -> selectionState[index] } return currentList.filter { it.delete }
} }
override fun onCurrentListChanged( override fun onCurrentListChanged(
@ -57,7 +55,6 @@ class ImagesAdapter(
) { ) {
super.onCurrentListChanged(previousList, currentList) super.onCurrentListChanged(previousList, currentList)
noMoreContent = (currentList.size - previousList.size) == 0 noMoreContent = (currentList.size - previousList.size) == 0
selectionState = Array(currentList.size) { false }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
@ -89,11 +86,11 @@ class ImagesAdapter(
holder.imageItemBinding.root.setOnClickListener { holder.imageItemBinding.root.setOnClickListener {
holder.imageItemBinding.imageCheckbox.isChecked = !holder.imageItemBinding.imageCheckbox.isChecked holder.imageItemBinding.imageCheckbox.isChecked = !holder.imageItemBinding.imageCheckbox.isChecked
} }
holder.imageItemBinding.imageCheckbox.isChecked = selectionState[position] holder.imageItemBinding.imageCheckbox.isChecked = image.delete
holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener { buttonView, isChecked -> holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener { buttonView, isChecked ->
selectionState[position] = isChecked image.delete = isChecked
onImageSelectedCountUpdate?.invoke(selectionState.count { it }) onImageSelectedCountUpdate?.invoke(currentList.count { it.delete })
} }
holder.imageItemBinding.root.setOnLongClickListener(null) holder.imageItemBinding.root.setOnLongClickListener(null)
} else { } else {
@ -105,9 +102,9 @@ class ImagesAdapter(
} }
holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener(null) holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener(null)
holder.imageItemBinding.root.setOnLongClickListener { holder.imageItemBinding.root.setOnLongClickListener {
selectionState[position] = true image.delete = true
onDeleteMode?.invoke(true) onDeleteMode?.invoke(true)
onImageSelectedCountUpdate?.invoke(selectionState.count { it }) onImageSelectedCountUpdate?.invoke(currentList.count { it.delete })
true true
} }
} }

View File

@ -1,5 +1,6 @@
package com.isolaatti.images.image_list.ui package com.isolaatti.images.image_list.ui
import android.app.Dialog
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -51,6 +52,7 @@ class ImagesFragment : Fragment() {
private val imageMakerLauncher = registerForActivityResult(ImageMakerContract()) { image -> private val imageMakerLauncher = registerForActivityResult(ImageMakerContract()) { image ->
image?.also { image?.also {
viewModel.lastEvent = ImageListViewModel.Event.ADDED_IMAGE_BEGINNING
viewModel.addImageAtTheBeginning(it) viewModel.addImageAtTheBeginning(it)
} }
} }
@ -73,6 +75,8 @@ class ImagesFragment : Fragment() {
return FileProvider.getUriForFile(requireContext(), "${MyApplication.myApp.packageName}.provider", cacheFile) return FileProvider.getUriForFile(requireContext(), "${MyApplication.myApp.packageName}.provider", cacheFile)
} }
private var deletingImagesDialog: Dialog? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -93,7 +97,6 @@ class ImagesFragment : Fragment() {
viewModel.loadNext() viewModel.loadNext()
} }
} }
setupAdapter() setupAdapter()
setupObservers() setupObservers()
setupListeners() setupListeners()
@ -201,6 +204,11 @@ class ImagesFragment : Fragment() {
private fun setupObservers() { private fun setupObservers() {
viewModel.liveList.observe(viewLifecycleOwner) { list -> viewModel.liveList.observe(viewLifecycleOwner) { list ->
if(viewModel.lastEvent == ImageListViewModel.Event.REMOVED_IMAGE || viewModel.lastEvent == ImageListViewModel.Event.ADDED_IMAGE_BEGINNING) {
actionMode?.finish()
}
viewBinding.noImagesCard.visibility = if(list.isEmpty()) View.VISIBLE else View.GONE
adapter.submitList(list) adapter.submitList(list)
} }
@ -218,6 +226,18 @@ class ImagesFragment : Fragment() {
viewModel.error.observe(viewLifecycleOwner) { viewModel.error.observe(viewLifecycleOwner) {
errorViewModel.error.value = it errorViewModel.error.value = it
} }
viewModel.deleting.observe(viewLifecycleOwner) { deleting ->
if(deleting) {
deletingImagesDialog = MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.deleting_please_wait)
.setCancelable(false)
.show()
} else {
deletingImagesDialog?.dismiss()
deletingImagesDialog = null
}
}
} }
companion object { companion object {

View File

@ -65,14 +65,17 @@ class ImageMakerActivity : IsolaattiBaseActivity() {
is Resource.Error -> { is Resource.Error -> {
errorViewModel.error.value = it.errorType errorViewModel.error.value = it.errorType
binding.progressBarLoading.visibility = View.GONE binding.progressBarLoading.visibility = View.GONE
binding.uploadPhotoFab.visibility = View.VISIBLE
binding.textImageName.isEnabled = true binding.textImageName.isEnabled = true
} }
is Resource.Loading -> { is Resource.Loading -> {
binding.progressBarLoading.visibility = View.VISIBLE binding.progressBarLoading.visibility = View.VISIBLE
binding.uploadPhotoFab.visibility = View.INVISIBLE
binding.textImageName.isEnabled = false binding.textImageName.isEnabled = false
} }
is Resource.Success -> { is Resource.Success -> {
binding.progressBarLoading.visibility = View.GONE binding.progressBarLoading.visibility = View.GONE
binding.uploadPhotoFab.visibility = View.VISIBLE
setResult(Activity.RESULT_OK, Intent().putExtra(EXTRA_IMAGE, it.data)) setResult(Activity.RESULT_OK, Intent().putExtra(EXTRA_IMAGE, it.data))
finish() finish()
} }

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M498,339.7c9.1,-26.2 14,-54.4 14,-83.7C512,114.6 397.4,0 256,0S0,114.6 0,256S114.6,512 256,512c35.4,0 69.1,-7.2 99.7,-20.2c-4.8,-5.5 -8.5,-12.2 -10.4,-19.7l-22.9,-89.3c-10,-39 11.8,-80.9 51.8,-92.1c37.2,-10.4 73.8,10.1 87.5,44c12.7,-1.6 25.1,0.4 36.2,5zM296,332c0,6.9 -3.1,13.2 -7.3,18.3c-4.3,5.2 -10.1,9.7 -16.7,13.4c-2.7,1.5 -5.7,3 -8.7,4.3c3.1,1.3 6,2.7 8.7,4.3c6.6,3.7 12.5,8.2 16.7,13.4c4.3,5.1 7.3,11.4 7.3,18.3s-3.1,13.2 -7.3,18.3c-4.3,5.2 -10.1,9.7 -16.7,13.4C258.7,443.1 241.4,448 224,448c-3.6,0 -6.8,-2.5 -7.7,-6s0.6,-7.2 3.8,-9l0,0 0,0 0,0 0,0 0.2,-0.1c0.2,-0.1 0.5,-0.3 0.9,-0.5c0.8,-0.5 2,-1.2 3.4,-2.1c2.8,-1.9 6.5,-4.5 10.2,-7.6c3.7,-3.1 7.2,-6.6 9.6,-10.1c2.5,-3.5 3.5,-6.4 3.5,-8.6s-1,-5 -3.5,-8.6c-2.5,-3.5 -5.9,-6.9 -9.6,-10.1c-3.7,-3.1 -7.4,-5.7 -10.2,-7.6c-1.4,-0.9 -2.6,-1.6 -3.4,-2.1l-0.6,-0.4 -0.3,-0.2 -0.2,-0.1 0,0 0,0 0,0c-2.5,-1.4 -4.1,-4.1 -4.1,-7s1.6,-5.6 4.1,-7l0,0 0,0 0,0 0,0 0,0 0.2,-0.1c0.2,-0.1 0.5,-0.3 0.9,-0.5c0.8,-0.5 2,-1.2 3.4,-2.1c2.8,-1.9 6.5,-4.5 10.2,-7.6c3.7,-3.1 7.2,-6.6 9.6,-10.1c2.5,-3.5 3.5,-6.4 3.5,-8.6s-1,-5 -3.5,-8.6c-2.5,-3.5 -5.9,-6.9 -9.6,-10.1c-3.7,-3.1 -7.4,-5.7 -10.2,-7.6c-1.4,-0.9 -2.6,-1.6 -3.4,-2.1c-0.4,-0.2 -0.7,-0.4 -0.9,-0.5l-0.2,-0.1 0,0 0,0 0,0c-3.2,-1.8 -4.7,-5.5 -3.8,-9s4.1,-6 7.7,-6c17.4,0 34.7,4.9 47.9,12.3c6.6,3.7 12.5,8.2 16.7,13.4c4.3,5.1 7.3,11.4 7.3,18.3zM176.4,176a32,32 0,1 1,0 64,32 32,0 1,1 0,-64zM371.2,233.6c-17.6,-23.5 -52.8,-23.5 -70.4,0c-5.3,7.1 -15.3,8.5 -22.4,3.2s-8.5,-15.3 -3.2,-22.4c30.4,-40.5 91.2,-40.5 121.6,0c5.3,7.1 3.9,17.1 -3.2,22.4s-17.1,3.9 -22.4,-3.2zM434,352.3c-6,-23.2 -28.8,-37 -51.1,-30.8s-35.4,30.1 -29.5,53.4l22.9,89.3c2.2,8.7 11.2,13.9 19.8,11.4l84.9,-23.8c22.2,-6.2 35.4,-30.1 29.5,-53.4s-28.8,-37 -51.1,-30.8l-20.2,5.6 -5.4,-21z"
android:fillColor="#1E3050"/>
</vector>

View File

@ -43,4 +43,29 @@
android:layout_margin="16dp" android:layout_margin="16dp"
app:srcCompat="@drawable/baseline_add_24" app:srcCompat="@drawable/baseline_add_24"
android:contentDescription="@string/upload_a_picture" /> android:contentDescription="@string/upload_a_picture" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/no_images_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginHorizontal="24dp"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="24dp">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/face_kiss_wink_heart_solid"
android:layout_gravity="center_horizontal" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="No images to show, use the plus button to add a new Image"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -124,4 +124,5 @@
<string name="audio_options">Audio options</string> <string name="audio_options">Audio options</string>
<string name="yes_discard_image">Yes, discard image</string> <string name="yes_discard_image">Yes, discard image</string>
<string name="discard_image">Discard image?</string> <string name="discard_image">Discard image?</string>
<string name="deleting_please_wait">Deleting, please wait…</string>
</resources> </resources>