diff --git a/app/src/main/java/com/isolaatti/images/image_list/data/remote/DeleteImagesDto.kt b/app/src/main/java/com/isolaatti/images/image_list/data/remote/DeleteImagesDto.kt new file mode 100644 index 0000000..1bb9251 --- /dev/null +++ b/app/src/main/java/com/isolaatti/images/image_list/data/remote/DeleteImagesDto.kt @@ -0,0 +1,5 @@ +package com.isolaatti.images.image_list.data.remote + +data class DeleteImagesDto( + val imageIds: List +) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/images/image_list/data/remote/DeleteImagesResultDto.kt b/app/src/main/java/com/isolaatti/images/image_list/data/remote/DeleteImagesResultDto.kt new file mode 100644 index 0000000..04b377d --- /dev/null +++ b/app/src/main/java/com/isolaatti/images/image_list/data/remote/DeleteImagesResultDto.kt @@ -0,0 +1,6 @@ +package com.isolaatti.images.image_list.data.remote + +data class DeleteImagesResultDto( + val success: Boolean, + val unSuccessIds: List +) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/images/image_list/data/remote/ImagesApi.kt b/app/src/main/java/com/isolaatti/images/image_list/data/remote/ImagesApi.kt index e3d0430..27ec940 100644 --- a/app/src/main/java/com/isolaatti/images/image_list/data/remote/ImagesApi.kt +++ b/app/src/main/java/com/isolaatti/images/image_list/data/remote/ImagesApi.kt @@ -27,6 +27,9 @@ interface ImagesApi { @DELETE("images/{imageId}") fun deleteImage(@Path("imageId") imageId: String): Call + @DELETE("images/delete_many") + fun deleteImages(@Body deleteImagesDto: DeleteImagesDto): Call + @GET("images/of_squad/{squadId}") fun getImagesOfSquad(@Path("squadId") squadId: String, @Query("lastId") lastId: String?): Call> diff --git a/app/src/main/java/com/isolaatti/images/image_list/data/repository/ImagesRepositoryImpl.kt b/app/src/main/java/com/isolaatti/images/image_list/data/repository/ImagesRepositoryImpl.kt index a53228c..66af6e4 100644 --- a/app/src/main/java/com/isolaatti/images/image_list/data/repository/ImagesRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/images/image_list/data/repository/ImagesRepositoryImpl.kt @@ -26,4 +26,8 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi) emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } + + override fun deleteImages(images: List): Flow> = flow { + + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/images/image_list/domain/repository/ImagesRepository.kt b/app/src/main/java/com/isolaatti/images/image_list/domain/repository/ImagesRepository.kt index c7d224d..0af9920 100644 --- a/app/src/main/java/com/isolaatti/images/image_list/domain/repository/ImagesRepository.kt +++ b/app/src/main/java/com/isolaatti/images/image_list/domain/repository/ImagesRepository.kt @@ -6,4 +6,5 @@ import kotlinx.coroutines.flow.Flow interface ImagesRepository { fun getImagesOfUser(userId: Int, lastId: String? = null): Flow>> + fun deleteImages(images: List): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/images/image_list/presentation/ImageListViewModel.kt b/app/src/main/java/com/isolaatti/images/image_list/presentation/ImageListViewModel.kt index 921ae6c..c8f30f4 100644 --- a/app/src/main/java/com/isolaatti/images/image_list/presentation/ImageListViewModel.kt +++ b/app/src/main/java/com/isolaatti/images/image_list/presentation/ImageListViewModel.kt @@ -26,4 +26,8 @@ class ImageListViewModel @Inject constructor(private val imagesRepository: Image }.flowOn(Dispatchers.IO).launchIn(this) } } + + fun removeImages(images: List) { + + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/images/image_list/presentation/ImagesAdapter.kt b/app/src/main/java/com/isolaatti/images/image_list/presentation/ImagesAdapter.kt index deed3d4..a5b8c0a 100644 --- a/app/src/main/java/com/isolaatti/images/image_list/presentation/ImagesAdapter.kt +++ b/app/src/main/java/com/isolaatti/images/image_list/presentation/ImagesAdapter.kt @@ -1,6 +1,7 @@ package com.isolaatti.images.image_list.presentation import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import androidx.recyclerview.widget.RecyclerView @@ -10,17 +11,36 @@ import com.isolaatti.common.CoilImageLoader.imageLoader import com.isolaatti.databinding.ImageItemBinding import com.isolaatti.images.image_list.domain.entity.Image -class ImagesAdapter(private val imageOnClick: ((images: List, position: Int) -> Unit), private val itemWidth: Int) : Adapter(){ +class ImagesAdapter( + private val imageOnClick: ((images: List, position: Int) -> Unit), + private val itemWidth: Int, + private val onImageSelectedCountUpdate: ((count: Int) -> Unit)? = null, + private val onDeleteMode: ((enabled: Boolean) -> Unit)? = null) : Adapter(){ private var data: List = listOf() + private var selectionState: Array = arrayOf() + + var deleteMode: Boolean = false + set(value) { + field = value + if(!value) { + selectionState.forEachIndexed { index, _ -> selectionState[index] = false } + } + notifyDataSetChanged() + } inner class ImageViewHolder(val imageItemBinding: ImageItemBinding) : RecyclerView.ViewHolder(imageItemBinding.root) fun setData(data: List) { this.data = data + selectionState = Array(data.size) { false } notifyDataSetChanged() } + fun getSelectedImages(): List { + return data.filterIndexed { index, _ -> selectionState[index] } + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ImageItemBinding.inflate(inflater) @@ -36,9 +56,35 @@ class ImagesAdapter(private val imageOnClick: ((images: List, position: I override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { val image = data[position] - holder.imageItemBinding.image.load(image.smallImageUrl, imageLoader) - holder.imageItemBinding.root.setOnClickListener { - imageOnClick(data, position) + holder.imageItemBinding.image.load(image.reducedImageUrl, imageLoader) + + if(deleteMode) { + holder.imageItemBinding.imageCheckbox.visibility = View.VISIBLE + holder.imageItemBinding.imageOverlay.visibility = View.VISIBLE + holder.imageItemBinding.root.setOnClickListener { + holder.imageItemBinding.imageCheckbox.isChecked = !holder.imageItemBinding.imageCheckbox.isChecked + } + holder.imageItemBinding.imageCheckbox.isChecked = selectionState[position] + holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener { buttonView, isChecked -> + selectionState[position] = isChecked + + onImageSelectedCountUpdate?.invoke(selectionState.count { it }) + } + holder.imageItemBinding.root.setOnLongClickListener(null) + } else { + holder.imageItemBinding.imageCheckbox.visibility = View.GONE + holder.imageItemBinding.imageCheckbox.isChecked = false + holder.imageItemBinding.imageOverlay.visibility = View.GONE + holder.imageItemBinding.root.setOnClickListener { + imageOnClick(data, position) + } + holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener(null) + holder.imageItemBinding.root.setOnLongClickListener { + selectionState[position] = true + onDeleteMode?.invoke(true) + onImageSelectedCountUpdate?.invoke(selectionState.count { it }) + true + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/images/image_list/ui/ImagesFragment.kt b/app/src/main/java/com/isolaatti/images/image_list/ui/ImagesFragment.kt index 52b2cf2..5fb7cab 100644 --- a/app/src/main/java/com/isolaatti/images/image_list/ui/ImagesFragment.kt +++ b/app/src/main/java/com/isolaatti/images/image_list/ui/ImagesFragment.kt @@ -3,7 +3,10 @@ package com.isolaatti.images.image_list.ui import android.content.res.Resources import android.net.Uri import android.os.Bundle +import android.view.ActionMode import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.activity.result.PickVisualMediaRequest @@ -15,6 +18,7 @@ import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.isolaatti.MyApplication import com.isolaatti.R import com.isolaatti.databinding.FragmentImagesBinding @@ -88,10 +92,60 @@ class ImagesFragment : Fragment() { viewBinding.topAppBar.inflateMenu(R.menu.images_menu) } + private fun showDeleteDialog() { + val imagesToDelete = adapter.getSelectedImages() + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.delete) + .setMessage(getString(R.string.delete_images_dialog_message, imagesToDelete.size)) + .setPositiveButton(R.string.yes_continue) { _, _ -> + viewModel.removeImages(imagesToDelete) + } + .setNegativeButton(R.string.no, null) + .show() + } + + private var actionMode: ActionMode? = null + + private val contextBarCallback: ActionMode.Callback = object: ActionMode.Callback { + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + requireActivity().menuInflater.inflate(R.menu.images_context_menu, menu) + return true + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return true + } + + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + return when(item?.itemId) { + R.id.delete_item -> { + showDeleteDialog() + true + } + else -> false + } + } + + override fun onDestroyActionMode(mode: ActionMode?) { + adapter.deleteMode = false + } + + } + private fun setupListeners() { viewBinding.topAppBar.setNavigationOnClickListener { findNavController().popBackStack() } + viewBinding.topAppBar.setOnMenuItemClickListener { + when(it.itemId) { + R.id.delete_mode_item -> { + adapter.deleteMode = true + actionMode = requireActivity().startActionMode(contextBarCallback) + true + } + else -> false + } + } viewBinding.newPictureButton.setOnClickListener { val popup = PopupMenu(requireContext(), it) popup.menuInflater.inflate(R.menu.add_picture_menu, popup.menu) @@ -116,7 +170,17 @@ class ImagesFragment : Fragment() { } private fun setupAdapter() { - adapter = ImagesAdapter(imageOnClick, Resources.getSystem().displayMetrics.widthPixels/3) + adapter = ImagesAdapter( + imageOnClick = imageOnClick, + itemWidth = Resources.getSystem().displayMetrics.widthPixels/3, + onImageSelectedCountUpdate = { + actionMode?.title = getString(R.string.selected_images_count, it) + actionMode?.menu?.findItem(R.id.delete_item)?.isEnabled = it > 0 + }, + onDeleteMode = { + adapter.deleteMode = it + actionMode = requireActivity().startActionMode(contextBarCallback) + }) viewBinding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 3, GridLayoutManager.VERTICAL, false) viewBinding.recyclerView.adapter = adapter diff --git a/app/src/main/res/drawable/baseline_close_24.xml b/app/src/main/res/drawable/baseline_close_24.xml index 844b6b6..b74d709 100644 --- a/app/src/main/res/drawable/baseline_close_24.xml +++ b/app/src/main/res/drawable/baseline_close_24.xml @@ -1,4 +1,4 @@ - diff --git a/app/src/main/res/layout-sw600dp/activity_login.xml b/app/src/main/res/layout-sw600dp/activity_login.xml new file mode 100644 index 0000000..7e82bb1 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/activity_login.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + +