WIP eliminar fotos

This commit is contained in:
Erik Everardo 2023-11-22 22:02:39 -06:00
parent dacba723b0
commit 1cca08df0b
18 changed files with 291 additions and 11 deletions

View File

@ -0,0 +1,5 @@
package com.isolaatti.images.image_list.data.remote
data class DeleteImagesDto(
val imageIds: List<String>
)

View File

@ -0,0 +1,6 @@
package com.isolaatti.images.image_list.data.remote
data class DeleteImagesResultDto(
val success: Boolean,
val unSuccessIds: List<String>
)

View File

@ -27,6 +27,9 @@ interface ImagesApi {
@DELETE("images/{imageId}")
fun deleteImage(@Path("imageId") imageId: String): Call<Any>
@DELETE("images/delete_many")
fun deleteImages(@Body deleteImagesDto: DeleteImagesDto): Call<DeleteImagesResultDto>
@GET("images/of_squad/{squadId}")
fun getImagesOfSquad(@Path("squadId") squadId: String,
@Query("lastId") lastId: String?): Call<List<ImageDto>>

View File

@ -26,4 +26,8 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi)
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
override fun deleteImages(images: List<Image>): Flow<Resource<Boolean>> = flow {
}
}

View File

@ -6,4 +6,5 @@ import kotlinx.coroutines.flow.Flow
interface ImagesRepository {
fun getImagesOfUser(userId: Int, lastId: String? = null): Flow<Resource<List<Image>>>
fun deleteImages(images: List<Image>): Flow<Resource<Boolean>>
}

View File

@ -26,4 +26,8 @@ class ImageListViewModel @Inject constructor(private val imagesRepository: Image
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
fun removeImages(images: List<Image>) {
}
}

View File

@ -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<Image>, position: Int) -> Unit), private val itemWidth: Int) : Adapter<ImagesAdapter.ImageViewHolder>(){
class ImagesAdapter(
private val imageOnClick: ((images: List<Image>, position: Int) -> Unit),
private val itemWidth: Int,
private val onImageSelectedCountUpdate: ((count: Int) -> Unit)? = null,
private val onDeleteMode: ((enabled: Boolean) -> Unit)? = null) : Adapter<ImagesAdapter.ImageViewHolder>(){
private var data: List<Image> = listOf()
private var selectionState: Array<Boolean> = 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<Image>) {
this.data = data
selectionState = Array(data.size) { false }
notifyDataSetChanged()
}
fun getSelectedImages(): List<Image> {
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<Image>, 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
}
}
}
}

View File

@ -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

View File

@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000"
<vector android:height="24dp" android:tint="@color/on_surface"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:translationZ="10dp"
android:visibility="gone" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
android:layout_width="400dp"
android:layout_height="wrap_content"
android:layout_gravity="center">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:text="@string/app_name"
android:textAlignment="center"
style="@style/toolbar_text" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:layout_margin="24dp"
android:text="@string/sign_in"/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textFieldEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
android:hint="@string/email">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textFieldPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
app:endIconMode="password_toggle"
android:hint="@string/password">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/sign_in_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sign_in"
android:layout_margin="8dp"/>
<Button
android:id="@+id/forgot_password_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_gravity="center_horizontal"
android:text="@string/forgot_password"
style="@style/Widget.Material3.Button.TextButton"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/or_you_can_sign_up"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"/>
<Button
android:id="@+id/sign_up_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:text="@string/sign_up"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</ScrollView>
</FrameLayout>

View File

@ -4,6 +4,7 @@
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar

View File

@ -1,10 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
android:src="@drawable/baseline_image_24"/>
<CheckBox
android:id="@+id/image_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"/>
<FrameLayout
android:id="@+id/image_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="@color/translucent_purple"
tools:visibility="visible"/>
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:showAsAction="always"
android:icon="@drawable/baseline_delete_24"
android:title="@string/delete" />
</menu>

View File

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_item"
android:id="@+id/delete_mode_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@drawable/baseline_delete_24"

View File

@ -5,6 +5,8 @@
<item name="android:colorPrimary">@color/purple</item>
<item name="android:statusBarColor">@color/purple</item>
<item name="colorOnSurface">@color/on_surface</item>
<item name="windowActionModeOverlay">true</item>
<item name="actionModeCloseDrawable">@drawable/baseline_close_24</item>
<item name="actionBarTheme">@style/ThemeOverlay.Material3.Dark.ActionBar</item>
</style>
</resources>

View File

@ -7,4 +7,5 @@
<color name="danger">#BA0606</color>
<color name="black">#000000</color>
<color name="translucent_black">#9A000000</color>
<color name="translucent_purple">#704D3B68</color>
</resources>

View File

@ -118,4 +118,6 @@
<string name="upload_a_picture">Upload a picture</string>
<string name="take_a_photo">Take a photo</string>
<string name="upload_photo">Upload picture</string>
<string name="delete_images_dialog_message">Remove %d images?</string>
<string name="selected_images_count">Images selected: %d</string>
</resources>

View File

@ -5,6 +5,9 @@
<item name="colorSurface">@color/surface</item>
<item name="colorOnSurface">@color/on_surface</item>
<item name="android:statusBarColor">@color/purple</item>
<item name="windowActionModeOverlay">true</item>
<item name="actionModeCloseDrawable">@drawable/baseline_close_24</item>
<item name="actionBarTheme">@style/ThemeOverlay.Material3.Dark.ActionBar</item>
</style>
<style name="Theme.Isolaatti.Splash" parent="Theme.SplashScreen"/>