[GSoC] Saved Image Fragment Scroll State (#4528)

* Saved Image Fragment Scroll State

* Fix delete image

* Fixed Delete bug

* Changed custom selector icon
This commit is contained in:
Aditya-Srivastav 2021-08-08 18:38:11 +05:30 committed by Aditya Srivastava
parent e8f7d0f03b
commit d78e6deb4e
11 changed files with 186 additions and 83 deletions

View file

@ -1,19 +1,7 @@
package fr.free.nrw.commons.customselector.helper package fr.free.nrw.commons.customselector.helper
import android.content.Context
import com.mapbox.android.core.FileUtils
import fr.free.nrw.commons.customselector.model.Folder import fr.free.nrw.commons.customselector.model.Folder
import fr.free.nrw.commons.customselector.model.Image import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.filepicker.Constants
import timber.log.Timber
import java.io.*
import java.math.BigInteger
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.LinkedHashMap
/** /**
* Image Helper object, includes all the static functions required by custom selector. * Image Helper object, includes all the static functions required by custom selector.
@ -24,7 +12,7 @@ object ImageHelper {
/** /**
* Returns the list of folders from given image list. * Returns the list of folders from given image list.
*/ */
fun folderListFromImages(images: List<Image>): List<Folder> { fun folderListFromImages(images: List<Image>): ArrayList<Folder> {
val folderMap: MutableMap<Long, Folder> = LinkedHashMap() val folderMap: MutableMap<Long, Folder> = LinkedHashMap()
for (image in images) { for (image in images) {
val bucketId = image.bucketId val bucketId = image.bucketId
@ -61,6 +49,17 @@ object ImageHelper {
return list.indexOf(image) return list.indexOf(image)
} }
/**
* getIndex: Returns the index of image in given list.
*/
fun getIndexFromId(list: ArrayList<Image>, imageId: Long): Int {
for(i in list){
if(i.id == imageId)
return list.indexOf(i)
}
return 0;
}
/** /**
* Gets the list of indices from the master list. * Gets the list of indices from the master list.
*/ */

View file

@ -1,5 +1,5 @@
package fr.free.nrw.commons.customselector.listeners package fr.free.nrw.commons.customselector.listeners
interface FolderClickListener { interface FolderClickListener {
fun onFolderClick(folderId: Long, folderName: String) fun onFolderClick(folderId: Long, folderName: String, lastItemId: Long)
} }

View file

@ -11,6 +11,7 @@ import com.bumptech.glide.Glide
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.Folder import fr.free.nrw.commons.customselector.model.Folder
import fr.free.nrw.commons.customselector.model.Image
class FolderAdapter( class FolderAdapter(
/** /**
@ -43,16 +44,38 @@ class FolderAdapter(
*/ */
override fun onBindViewHolder(holder: FolderViewHolder, position: Int) { override fun onBindViewHolder(holder: FolderViewHolder, position: Int) {
val folder = folders[position] val folder = folders[position]
val toBeRemoved = ArrayList<Image>()
for(image in folder.images) {
// Remove all the top images that do not exist anymore
if(context.contentResolver.getType(image.uri) == null){
// File not found
toBeRemoved.add(image)
} else {
break
}
}
holder.image.setImageDrawable (null)
folder.images.removeAll(toBeRemoved)
val count = folder.images.size val count = folder.images.size
if(count == 0) {
// Folder is empty, remove folder from the adapter.
holder.itemView.post{
val updatePosition = folders.indexOf(folder)
folders.removeAt(updatePosition)
notifyItemRemoved(updatePosition)
notifyItemRangeChanged(updatePosition, folders.size)
}
} else {
val previewImage = folder.images[0] val previewImage = folder.images[0]
Glide.with(context).load(previewImage.uri).into(holder.image) Glide.with(context).load(previewImage.uri).into(holder.image)
holder.name.text = folder.name holder.name.text = folder.name
holder.count.text = count.toString() holder.count.text = count.toString()
holder.itemView.setOnClickListener{ holder.itemView.setOnClickListener {
itemClickListener.onFolderClick(folder.bucketId, folder.name) itemClickListener.onFolderClick(folder.bucketId, folder.name, 0)
}
} }
//todo load image thumbnail.
} }
/** /**

View file

@ -1,9 +1,8 @@
package fr.free.nrw.commons.customselector.ui.adapter package fr.free.nrw.commons.customselector.ui.adapter
import android.content.Context import android.content.Context
import android.view.ViewGroup
import fr.free.nrw.commons.R
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
@ -11,6 +10,7 @@ import androidx.constraintlayout.widget.Group
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.helper.ImageHelper import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.Image import fr.free.nrw.commons.customselector.model.Image
@ -59,7 +59,7 @@ class ImageAdapter(
* Create View holder. * Create View holder.
*/ */
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
val itemView = inflater.inflate(R.layout.item_custom_selector_image,parent, false) val itemView = inflater.inflate(R.layout.item_custom_selector_image, parent, false)
return ImageViewHolder(itemView) return ImageViewHolder(itemView)
} }
@ -68,36 +68,46 @@ class ImageAdapter(
*/ */
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val image=images[position] val image=images[position]
val selectedIndex = ImageHelper.getIndex(selectedImages,image) holder.image.setImageDrawable (null)
val isSelected = selectedIndex != -1 if (context.contentResolver.getType(image.uri) == null) {
if(isSelected){ // Image does not exist anymore, update adapter.
holder.itemSelected(selectedIndex+1) holder.itemView.post {
val updatedPosition = images.indexOf(image)
images.remove(image)
notifyItemRemoved(updatedPosition)
notifyItemRangeChanged(updatedPosition, images.size)
} }
else { } else {
val selectedIndex = ImageHelper.getIndex(selectedImages, image)
val isSelected = selectedIndex != -1
if (isSelected) {
holder.itemSelected(selectedIndex + 1)
} else {
holder.itemUnselected(); holder.itemUnselected();
} }
Glide.with(context).load(image.uri).thumbnail(0.3f).into(holder.image) Glide.with(context).load(image.uri).thumbnail(0.3f).into(holder.image)
imageLoader.queryAndSetView(holder,image) imageLoader.queryAndSetView(holder, image)
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
selectOrRemoveImage(holder, position) selectOrRemoveImage(holder, position)
} }
} }
}
/** /**
* Handle click event on an image, update counter on images. * Handle click event on an image, update counter on images.
*/ */
private fun selectOrRemoveImage(holder:ImageViewHolder, position:Int){ private fun selectOrRemoveImage(holder: ImageViewHolder, position: Int){
val clickedIndex = ImageHelper.getIndex(selectedImages,images[position]) val clickedIndex = ImageHelper.getIndex(selectedImages, images[position])
if (clickedIndex != -1) { if (clickedIndex != -1) {
selectedImages.removeAt(clickedIndex) selectedImages.removeAt(clickedIndex)
notifyItemChanged(position,ImageUnselected()) notifyItemChanged(position, ImageUnselected())
val indexes = ImageHelper.getIndexList(selectedImages, images) val indexes = ImageHelper.getIndexList(selectedImages, images)
for (index in indexes) { for (index in indexes) {
notifyItemChanged(index, ImageSelectedOrUpdated()) notifyItemChanged(index, ImageSelectedOrUpdated())
} }
} else { } else {
if(holder.isItemUploaded()){ if(holder.isItemUploaded()){
Toast.makeText(context,"Already Uploaded image", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Already Uploaded image", Toast.LENGTH_SHORT).show()
} else { } else {
selectedImages.add(images[position]) selectedImages.add(images[position])
notifyItemChanged(position, ImageSelectedOrUpdated()) notifyItemChanged(position, ImageSelectedOrUpdated())
@ -109,7 +119,7 @@ class ImageAdapter(
/** /**
* Initialize the data set. * Initialize the data set.
*/ */
fun init(newImages:List<Image>) { fun init(newImages: List<Image>) {
val oldImageList:ArrayList<Image> = images val oldImageList:ArrayList<Image> = images
val newImageList:ArrayList<Image> = ArrayList(newImages) val newImageList:ArrayList<Image> = ArrayList(newImages)
val diffResult = DiffUtil.calculateDiff( val diffResult = DiffUtil.calculateDiff(
@ -128,6 +138,10 @@ class ImageAdapter(
return images.size return images.size
} }
fun getImageIdAt(position: Int): Long {
return images.get(position).id
}
/** /**
* Image view holder. * Image view holder.
*/ */

View file

@ -7,7 +7,6 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.TextView import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.FolderClickListener
@ -17,7 +16,7 @@ import fr.free.nrw.commons.theme.BaseActivity
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectListener, FragmentManager.OnBackStackChangedListener { class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectListener {
/** /**
* View model. * View model.
@ -58,10 +57,11 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
setupViews() setupViews()
// Open folder if saved in prefs. // Open folder if saved in prefs.
if(prefs.contains("FolderId")){ if(prefs.contains(FOLDER_ID)){
val lastOpenFolderId: Long = prefs.getLong("FolderId", 0L) val lastOpenFolderId: Long = prefs.getLong(FOLDER_ID, 0L)
val lastOpenFolderName: String? = prefs.getString("FolderName", null) val lastOpenFolderName: String? = prefs.getString(FOLDER_NAME, null)
lastOpenFolderName?.let { onFolderClick(lastOpenFolderId, it) } val lastItemId: Long = prefs.getLong(ITEM_ID, 0)
lastOpenFolderName?.let { onFolderClick(lastOpenFolderId, it, lastItemId) }
} }
} }
@ -107,9 +107,9 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
/** /**
* override on folder click, change the toolbar title on folder click. * override on folder click, change the toolbar title on folder click.
*/ */
override fun onFolderClick(folderId: Long, folderName: String) { override fun onFolderClick(folderId: Long, folderName: String, lastItemId: Long) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, ImageFragment.newInstance(folderId)) .add(R.id.fragment_container, ImageFragment.newInstance(folderId, lastItemId))
.addToBackStack(null) .addToBackStack(null)
.commit() .commit()
@ -172,25 +172,27 @@ class CustomSelectorActivity: BaseActivity(), FolderClickListener, ImageSelectLi
super.onBackPressed() super.onBackPressed()
val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container) val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
if(fragment != null && fragment is FolderFragment){ if(fragment != null && fragment is FolderFragment){
isImageFragmentOpen = false
changeTitle(getString(R.string.custom_selector_title)) changeTitle(getString(R.string.custom_selector_title))
} }
} }
/**
* On activity destroy
* If image fragment is open, overwrite its attributes otherwise discard the values.
*/
override fun onDestroy() { override fun onDestroy() {
if(isImageFragmentOpen){ if(isImageFragmentOpen){
prefs.edit().putLong("FolderId", bucketId).putString("FolderName", bucketName).apply() prefs.edit().putLong(FOLDER_ID, bucketId).putString(FOLDER_NAME, bucketName).apply()
} else { } else {
prefs.edit().remove("FolderId").remove("FolderName").apply() prefs.edit().remove(FOLDER_ID).remove(FOLDER_NAME).apply()
} }
super.onDestroy() super.onDestroy()
} }
/** companion object {
* Called whenever the contents of the back stack change. const val FOLDER_ID : String = "FolderId"
*/ const val FOLDER_NAME : String = "FolderName"
override fun onBackStackChanged() { const val ITEM_ID : String = "ItemId"
if(supportFragmentManager.backStackEntryCount == 0) {
isImageFragmentOpen = false
}
} }
} }

View file

@ -14,6 +14,7 @@ import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.model.Result import fr.free.nrw.commons.customselector.model.Result
import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.model.CallbackStatus import fr.free.nrw.commons.customselector.model.CallbackStatus
import fr.free.nrw.commons.customselector.model.Folder
import fr.free.nrw.commons.customselector.ui.adapter.FolderAdapter import fr.free.nrw.commons.customselector.ui.adapter.FolderAdapter
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.media.MediaClient import fr.free.nrw.commons.media.MediaClient
@ -55,6 +56,11 @@ class FolderFragment : CommonsDaggerSupportFragment() {
*/ */
private lateinit var gridLayoutManager: GridLayoutManager private lateinit var gridLayoutManager: GridLayoutManager
/**
* Folder List.
*/
private lateinit var folders : ArrayList<Folder>
/** /**
* Companion newInstance. * Companion newInstance.
*/ */
@ -102,7 +108,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
*/ */
private fun handleResult(result: Result) { private fun handleResult(result: Result) {
if(result.status is CallbackStatus.SUCCESS){ if(result.status is CallbackStatus.SUCCESS){
val folders = ImageHelper.folderListFromImages(result.images) folders = ImageHelper.folderListFromImages(result.images)
folderAdapter.init(folders) folderAdapter.init(folders)
folderAdapter.notifyDataSetChanged() folderAdapter.notifyDataSetChanged()
selectorRV?.let { selectorRV?.let {
@ -114,6 +120,11 @@ class FolderFragment : CommonsDaggerSupportFragment() {
} }
} }
override fun onResume() {
folderAdapter.notifyDataSetChanged()
super.onResume()
}
/** /**
* Return Column count ie span count for grid view adapter. * Return Column count ie span count for grid view adapter.
*/ */

View file

@ -1,6 +1,8 @@
package fr.free.nrw.commons.customselector.ui.selector package fr.free.nrw.commons.customselector.ui.selector
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -13,11 +15,15 @@ import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.helper.ImageHelper import fr.free.nrw.commons.customselector.helper.ImageHelper
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.model.CallbackStatus import fr.free.nrw.commons.customselector.model.CallbackStatus
import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.customselector.model.Result import fr.free.nrw.commons.customselector.model.Result
import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter import fr.free.nrw.commons.customselector.ui.adapter.ImageAdapter
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import kotlinx.android.synthetic.main.fragment_custom_selector.* import fr.free.nrw.commons.theme.BaseActivity
import kotlinx.android.synthetic.main.fragment_custom_selector.view.* import kotlinx.android.synthetic.main.fragment_custom_selector.view.*
import java.io.File
import java.io.FileInputStream
import java.net.URI
import javax.inject.Inject import javax.inject.Inject
class ImageFragment: CommonsDaggerSupportFragment() { class ImageFragment: CommonsDaggerSupportFragment() {
@ -27,13 +33,18 @@ class ImageFragment: CommonsDaggerSupportFragment() {
*/ */
private var bucketId: Long? = null private var bucketId: Long? = null
/**
* Last ImageItem Id.
*/
private var lastItemId: Long? = null
/** /**
* View model for images. * View model for images.
*/ */
private var viewModel: CustomSelectorViewModel? = null private var viewModel: CustomSelectorViewModel? = null
/** /**
* View Elements * View Elements.
*/ */
private var selectorRV: RecyclerView? = null private var selectorRV: RecyclerView? = null
private var loader: ProgressBar? = null private var loader: ProgressBar? = null
@ -67,14 +78,16 @@ class ImageFragment: CommonsDaggerSupportFragment() {
* BucketId args name * BucketId args name
*/ */
const val BUCKET_ID = "BucketId" const val BUCKET_ID = "BucketId"
const val LAST_ITEM_ID = "LastItemId"
/** /**
* newInstance from bucketId. * newInstance from bucketId.
*/ */
fun newInstance(bucketId: Long): ImageFragment { fun newInstance(bucketId: Long, lastItemId: Long): ImageFragment {
val fragment = ImageFragment() val fragment = ImageFragment()
val args = Bundle() val args = Bundle()
args.putLong(BUCKET_ID, bucketId) args.putLong(BUCKET_ID, bucketId)
args.putLong(LAST_ITEM_ID, lastItemId)
fragment.arguments = args fragment.arguments = args
return fragment return fragment
} }
@ -87,6 +100,7 @@ class ImageFragment: CommonsDaggerSupportFragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
bucketId = arguments?.getLong(BUCKET_ID) bucketId = arguments?.getLong(BUCKET_ID)
lastItemId = arguments?.getLong(LAST_ITEM_ID, 0)
viewModel = ViewModelProvider(requireActivity(),customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java) viewModel = ViewModelProvider(requireActivity(),customSelectorViewModelFactory).get(CustomSelectorViewModel::class.java)
} }
@ -116,6 +130,8 @@ class ImageFragment: CommonsDaggerSupportFragment() {
return root return root
} }
lateinit var filteredImages: ArrayList<Image>;
/** /**
* Handle view model result. * Handle view model result.
*/ */
@ -123,9 +139,14 @@ class ImageFragment: CommonsDaggerSupportFragment() {
if(result.status is CallbackStatus.SUCCESS){ if(result.status is CallbackStatus.SUCCESS){
val images = result.images val images = result.images
if(images.isNotEmpty()) { if(images.isNotEmpty()) {
imageAdapter.init(ImageHelper.filterImages(images,bucketId)) filteredImages = ImageHelper.filterImages(images, bucketId)
selectorRV?.let{ imageAdapter.init(filteredImages)
selectorRV?.let {
it.visibility = View.VISIBLE it.visibility = View.VISIBLE
lastItemId?.let { pos ->
(it.layoutManager as GridLayoutManager)
.scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos))
}
} }
} }
else{ else{
@ -149,11 +170,35 @@ class ImageFragment: CommonsDaggerSupportFragment() {
// todo change span count depending on the device orientation and other factos. // todo change span count depending on the device orientation and other factos.
} }
override fun onResume() {
imageAdapter.notifyDataSetChanged()
super.onResume()
}
/** /**
* OnDestroy Cleanup the imageLoader coroutine. * OnDestroy
* Cleanup the imageLoader coroutine.
* Save the Image Fragment state.
*/ */
override fun onDestroy() { override fun onDestroy() {
imageLoader?.cleanUP() imageLoader?.cleanUP()
val position = (selectorRV?.layoutManager as GridLayoutManager)
.findFirstVisibleItemPosition()
// Check for empty RecyclerView.
if (position != -1) {
context?.let { context ->
context.getSharedPreferences(
"CustomSelector",
BaseActivity.MODE_PRIVATE
)?.let { prefs ->
prefs.edit()?.let { editor ->
editor.putLong("ItemId", imageAdapter.getImageIdAt(position))?.apply()
}
}
}
}
super.onDestroy() super.onDestroy()
} }
} }

View file

@ -13,6 +13,7 @@ import fr.free.nrw.commons.upload.FileProcessor
import fr.free.nrw.commons.upload.FileUtilsWrapper import fr.free.nrw.commons.upload.FileUtilsWrapper
import kotlinx.coroutines.* import kotlinx.coroutines.*
import timber.log.Timber import timber.log.Timber
import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.* import java.util.*
@ -86,6 +87,8 @@ class ImageLoader @Inject constructor(
} }
val imageSHA1 = getImageSHA1(image.uri) val imageSHA1 = getImageSHA1(image.uri)
if(imageSHA1.isEmpty())
return@launch
val uploadedStatus = getFromUploaded(imageSHA1) val uploadedStatus = getFromUploaded(imageSHA1)
val sha1 = uploadedStatus?.let { val sha1 = uploadedStatus?.let {
@ -195,9 +198,14 @@ class ImageLoader @Inject constructor(
mapImageSHA1[uri]?.let{ mapImageSHA1[uri]?.let{
return@withContext it return@withContext it
} }
try {
val result = fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri)) val result = fileUtilsWrapper.getSHA1(context.contentResolver.openInputStream(uri))
mapImageSHA1[uri] = result mapImageSHA1[uri] = result
result result
} catch (e: FileNotFoundException){
e.printStackTrace()
""
}
} }
} }

File diff suppressed because one or more lines are too long

View file

@ -79,7 +79,7 @@
app:useCompatPadding="true" app:useCompatPadding="true"
app:elevation="@dimen/tiny_margin" app:elevation="@dimen/tiny_margin"
app:fabSize="mini" app:fabSize="mini"
app:srcCompat="@drawable/commons_logo" app:srcCompat="@drawable/ic_custom_image_picker"
android:background="@drawable/commons"/> android:background="@drawable/commons"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton

View file

@ -16,7 +16,6 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:background="@color/white"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
@ -29,7 +28,6 @@
android:id="@+id/album_overlay" android:id="@+id/album_overlay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black"
android:alpha="0.15" /> android:alpha="0.15" />
<LinearLayout <LinearLayout