update UI states and refactor code

Also, add repository as Single Source of Truth for performing operations on images
This commit is contained in:
Rohit Verma 2025-01-03 23:10:04 +05:30
parent 443e713955
commit e611cbc86f
14 changed files with 262 additions and 49 deletions

View file

@ -0,0 +1,29 @@
package fr.free.nrw.commons.customselector.data
import fr.free.nrw.commons.customselector.database.NotForUploadStatus
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
import fr.free.nrw.commons.customselector.domain.ImageRepository
import fr.free.nrw.commons.customselector.domain.model.Image
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class ImageRepositoryImpl @Inject constructor(
private val mediaReader: MediaReader,
private val notForUploadStatusDao: NotForUploadStatusDao
): ImageRepository {
override suspend fun getImagesFromDevice(): Flow<Image> {
return mediaReader.getImages()
}
override suspend fun markAsNotForUpload(imageSHA: String) {
notForUploadStatusDao.insert(NotForUploadStatus(imageSHA))
}
override suspend fun unmarkAsNotForUpload(imageSHA: String) {
notForUploadStatusDao.deleteWithImageSHA1(imageSHA)
}
override suspend fun isNotForUpload(imageSHA: String): Boolean {
return notForUploadStatusDao.find(imageSHA) > 0
}
}

View file

@ -0,0 +1,15 @@
package fr.free.nrw.commons.customselector.domain
import fr.free.nrw.commons.customselector.domain.model.Image
import kotlinx.coroutines.flow.Flow
interface ImageRepository {
suspend fun getImagesFromDevice(): Flow<Image>
suspend fun markAsNotForUpload(imageSHA: String)
suspend fun unmarkAsNotForUpload(imageSHA: String)
suspend fun isNotForUpload(imageSHA: String): Boolean
}

View file

@ -0,0 +1,89 @@
package fr.free.nrw.commons.customselector.domain.use_case
import android.content.Context
import android.net.Uri
import androidx.exifinterface.media.ExifInterface
import fr.free.nrw.commons.customselector.ui.selector.ImageLoader
import fr.free.nrw.commons.filepicker.PickedFiles
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.upload.FileProcessor
import fr.free.nrw.commons.upload.FileUtilsWrapper
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.FileNotFoundException
import timber.log.Timber
import java.io.IOException
import java.net.UnknownHostException
import javax.inject.Inject
class ImageUseCase @Inject constructor(
private val fileUtilsWrapper: FileUtilsWrapper,
private val fileProcessor: FileProcessor,
private val mediaClient: MediaClient,
private val context: Context
) {
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
/**
* Retrieves the SHA1 hash of an image from its URI.
*
* @param uri The URI of the image.
* @return The SHA1 hash of the image, or an empty string if the image is not found.
*/
suspend fun getImageSHA1(uri: Uri): String = withContext(ioDispatcher) {
try {
val inputStream = context.contentResolver.openInputStream(uri)
fileUtilsWrapper.getSHA1(inputStream)
} catch (e: FileNotFoundException) {
Timber.e(e)
""
}
}
/**
* Generates a modified SHA1 hash of an image after redacting sensitive EXIF tags.
*
* @param imageUri The URI of the image to process.
* @return The modified SHA1 hash of the image.
*/
suspend fun generateModifiedSHA1(imageUri: Uri): String = withContext(ioDispatcher) {
val uploadableFile = PickedFiles.pickedExistingPicture(context, imageUri)
val exifInterface: ExifInterface? = try {
ExifInterface(uploadableFile.file!!)
} catch (e: IOException) {
Timber.e(e)
null
}
fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact())
val sha1 = fileUtilsWrapper.getSHA1(
fileUtilsWrapper.getFileInputStream(uploadableFile.filePath))
uploadableFile.file.delete()
sha1
}
/**
* Checks whether a file with the given SHA1 hash exists on Wikimedia Commons.
*
* @param sha1 The SHA1 hash of the file to check.
* @return An ImageLoader.Result indicating the existence of the file on Commons.
*/
suspend fun checkWhetherFileExistsOnCommonsUsingSHA1(
sha1: String
): ImageLoader.Result = withContext(ioDispatcher) {
return@withContext try {
if (mediaClient.checkFileExistsUsingSha(sha1).blockingGet()) {
ImageLoader.Result.TRUE
} else {
ImageLoader.Result.FALSE
}
} catch (e: UnknownHostException) {
Timber.e(e, "Network Connection Error")
ImageLoader.Result.ERROR
} catch (e: Exception) {
e.printStackTrace()
ImageLoader.Result.ERROR
}
}
}

View file

@ -56,12 +56,14 @@ import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.ui.components.CustomSelectorBottomBar import fr.free.nrw.commons.customselector.ui.components.CustomSelectorBottomBar
import fr.free.nrw.commons.customselector.ui.components.CustomSelectorTopBar import fr.free.nrw.commons.customselector.ui.components.CustomSelectorTopBar
import fr.free.nrw.commons.customselector.ui.components.PartialStorageAccessDialog import fr.free.nrw.commons.customselector.ui.components.PartialStorageAccessDialog
import fr.free.nrw.commons.customselector.ui.states.CustomSelectorUiState
import fr.free.nrw.commons.customselector.ui.states.ImageUiState
import fr.free.nrw.commons.ui.theme.CommonsTheme import fr.free.nrw.commons.ui.theme.CommonsTheme
@OptIn(ExperimentalMaterial3AdaptiveApi::class) @OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable @Composable
fun CustomSelectorScreen( fun CustomSelectorScreen(
uiState: CustomSelectorState, uiState: CustomSelectorUiState,
onEvent: (CustomSelectorEvent)-> Unit, onEvent: (CustomSelectorEvent)-> Unit,
selectedImageIds: ()-> Set<Long>, selectedImageIds: ()-> Set<Long>,
onViewImage: (id: Long)-> Unit, onViewImage: (id: Long)-> Unit,
@ -112,7 +114,7 @@ fun CustomSelectorScreen(
@Composable @Composable
fun FoldersPane( fun FoldersPane(
uiState: CustomSelectorState, uiState: CustomSelectorUiState,
onFolderClick: (Folder)-> Unit, onFolderClick: (Folder)-> Unit,
onUnselectAll: ()-> Unit, onUnselectAll: ()-> Unit,
adaptiveInfo: WindowAdaptiveInfo, adaptiveInfo: WindowAdaptiveInfo,
@ -270,7 +272,7 @@ private fun FolderItemPreview() {
private fun CustomSelectorScreenPreview() { private fun CustomSelectorScreenPreview() {
CommonsTheme { CommonsTheme {
CustomSelectorScreen( CustomSelectorScreen(
uiState = CustomSelectorState(), uiState = CustomSelectorUiState(),
onViewImage = { }, onViewImage = { },
onEvent = { }, onEvent = { },
selectedImageIds = { emptySet() }, selectedImageIds = { emptySet() },

View file

@ -1,13 +0,0 @@
package fr.free.nrw.commons.customselector.ui.screens
import fr.free.nrw.commons.customselector.model.Image
data class CustomSelectorState(
val isLoading: Boolean = false,
val folders: List<Folder> = emptyList(),
val filteredImages: List<Image> = emptyList(),
val selectedImageIds: Set<Long> = emptySet()
) {
val inSelectionMode: Boolean
get() = selectedImageIds.isNotEmpty()
}

View file

@ -1,36 +1,55 @@
package fr.free.nrw.commons.customselector.ui.screens package fr.free.nrw.commons.customselector.ui.screens
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import fr.free.nrw.commons.customselector.data.MediaReader import fr.free.nrw.commons.customselector.domain.ImageRepository
import fr.free.nrw.commons.customselector.model.Image import fr.free.nrw.commons.customselector.domain.model.Image
import fr.free.nrw.commons.customselector.domain.use_case.ImageUseCase
import fr.free.nrw.commons.customselector.ui.states.CustomSelectorUiState
import fr.free.nrw.commons.customselector.ui.states.ImageUiState
import fr.free.nrw.commons.customselector.ui.states.toImageUiState
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
class CustomSelectorViewModel(private val mediaReader: MediaReader): ViewModel() { typealias imageId = Long
typealias imageSHA = String
private val _uiState = MutableStateFlow(CustomSelectorState()) class CustomSelectorViewModel @Inject constructor(
private val imageRepository: ImageRepository,
private val imageUseCase: ImageUseCase
): ViewModel() {
private val _uiState = MutableStateFlow(CustomSelectorUiState())
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val cacheSHA1 = mutableMapOf<imageId, imageSHA>()
private val allImages = mutableListOf<ImageUiState>()
private val foldersMap = mutableMapOf<Long, MutableList<Image>>() private val foldersMap = mutableMapOf<Long, MutableList<Image>>()
init { init {
_uiState.update { it.copy(isLoading = true) }
viewModelScope.launch { viewModelScope.launch {
mediaReader.getImages().collect { image-> imageRepository.getImagesFromDevice().collect { image ->
val bucketId = image.bucketId val bucketId = image.bucketId
allImages.add(image.toImageUiState())
foldersMap.getOrPut(bucketId) { mutableListOf() }.add(image) foldersMap.getOrPut(bucketId) { mutableListOf() }.add(image)
} }
val foldersList = foldersMap.map { (bucketId, images)-> val folders = foldersMap.map { (bucketId, images)->
val firstImage = images.first() val firstImage = images.first()
Folder( Folder(
bucketId = bucketId, bucketName = firstImage.bucketName, bucketId = bucketId,
preview = firstImage.uri, itemsCount = images.size bucketName = firstImage.bucketName,
preview = firstImage.uri,
itemsCount = images.size,
images = images
) )
} }
_uiState.update { it.copy(isLoading = false, folders = foldersList) } _uiState.update { it.copy(isLoading = false, folders = folders) }
} }
} }
@ -38,7 +57,11 @@ class CustomSelectorViewModel(private val mediaReader: MediaReader): ViewModel()
when(e) { when(e) {
is CustomSelectorEvent.OnFolderClick -> { is CustomSelectorEvent.OnFolderClick -> {
_uiState.update { _uiState.update {
it.copy(filteredImages = foldersMap[e.bucketId]?.toList() ?: emptyList()) it.copy(
filteredImages = foldersMap[e.bucketId]?.map {
img -> img.toImageUiState()
} ?: emptyList()
)
} }
} }

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.customselector.ui.screens
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import fr.free.nrw.commons.customselector.domain.model.Image
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
@ -9,5 +10,6 @@ data class Folder(
val bucketId: Long, val bucketId: Long,
val bucketName: String, val bucketName: String,
val preview: Uri, val preview: Uri,
val images: List<Image>,
val itemsCount: Int val itemsCount: Int
): Parcelable ): Parcelable

View file

@ -39,6 +39,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
@ -54,18 +55,20 @@ import androidx.compose.ui.unit.toIntRect
import androidx.window.core.layout.WindowWidthSizeClass import androidx.window.core.layout.WindowWidthSizeClass
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.domain.model.Image
import fr.free.nrw.commons.customselector.ui.components.CustomSelectorBottomBar import fr.free.nrw.commons.customselector.ui.components.CustomSelectorBottomBar
import fr.free.nrw.commons.customselector.ui.components.CustomSelectorTopBar import fr.free.nrw.commons.customselector.ui.components.CustomSelectorTopBar
import fr.free.nrw.commons.customselector.ui.components.PartialStorageAccessDialog import fr.free.nrw.commons.customselector.ui.components.PartialStorageAccessDialog
import fr.free.nrw.commons.customselector.ui.states.CustomSelectorUiState
import fr.free.nrw.commons.customselector.ui.states.ImageUiState
import fr.free.nrw.commons.ui.theme.CommonsTheme import fr.free.nrw.commons.ui.theme.CommonsTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun ImagesPane( fun ImagesPane(
uiState: CustomSelectorState, uiState: CustomSelectorUiState,
selectedFolder: Folder, selectedFolder: Folder,
selectedImages: () -> Set<Long>, selectedImages: () -> Set<Long>,
onNavigateBack: () -> Unit, onNavigateBack: () -> Unit,
@ -74,7 +77,6 @@ fun ImagesPane(
adaptiveInfo: WindowAdaptiveInfo, adaptiveInfo: WindowAdaptiveInfo,
hasPartialAccess: Boolean = false hasPartialAccess: Boolean = false
) { ) {
// val inSelectionMode by remember { derivedStateOf { selectedImages().isNotEmpty() } }
val lazyGridState = rememberLazyGridState() val lazyGridState = rememberLazyGridState()
var autoScrollSpeed by remember { mutableFloatStateOf(0f) } var autoScrollSpeed by remember { mutableFloatStateOf(0f) }
val isCompatWidth by remember(adaptiveInfo.windowSizeClass) { val isCompatWidth by remember(adaptiveInfo.windowSizeClass) {
@ -255,7 +257,7 @@ private fun ImageItemPreview() {
*/ */
fun Modifier.imageGridDragHandler( fun Modifier.imageGridDragHandler(
gridState: LazyGridState, gridState: LazyGridState,
imageList: List<Image>, imageList: List<ImageUiState>,
selectedImageIds: () -> Set<Long>, selectedImageIds: () -> Set<Long>,
autoScrollThreshold: Float, autoScrollThreshold: Float,
setSelectedImageIds: (Set<Long>) -> Unit, setSelectedImageIds: (Set<Long>) -> Unit,
@ -337,7 +339,7 @@ fun Modifier.imageGridDragHandler(
* @param pointerKey The ending index of the range. * @param pointerKey The ending index of the range.
* @return A set of image IDs within the specified range. * @return A set of image IDs within the specified range.
*/ */
fun List<Image>.getImageIdsInRange(initialKey: Int, pointerKey: Int): Set<Long> { fun List<ImageUiState>.getImageIdsInRange(initialKey: Int, pointerKey: Int): Set<Long> {
val setOfKeys = mutableSetOf<Long>() val setOfKeys = mutableSetOf<Long>()
if (initialKey < pointerKey) { if (initialKey < pointerKey) {
(initialKey..pointerKey).forEach { (initialKey..pointerKey).forEach {

View file

@ -27,13 +27,13 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import fr.free.nrw.commons.customselector.domain.model.Image import fr.free.nrw.commons.customselector.ui.states.ImageUiState
import kotlin.math.abs import kotlin.math.abs
@Composable @Composable
fun ViewImageScreen( fun ViewImageScreen(
currentImageIndex: Int, currentImageIndex: Int,
imageList: List<Image>, imageList: List<ImageUiState>,
) { ) {
var imageScale by remember { mutableFloatStateOf(1f) } var imageScale by remember { mutableFloatStateOf(1f) }
var imageOffset by remember { mutableStateOf(Offset.Zero) } var imageOffset by remember { mutableStateOf(Offset.Zero) }

View file

@ -27,21 +27,20 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.data.MediaReader
import fr.free.nrw.commons.customselector.database.NotForUploadStatus import fr.free.nrw.commons.customselector.database.NotForUploadStatus
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
import fr.free.nrw.commons.customselector.domain.model.Image
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants
import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper
import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
import fr.free.nrw.commons.customselector.domain.model.Image
import fr.free.nrw.commons.customselector.ui.screens.CustomSelectorScreen import fr.free.nrw.commons.customselector.ui.screens.CustomSelectorScreen
import fr.free.nrw.commons.customselector.ui.screens.ViewImageScreen import fr.free.nrw.commons.customselector.ui.screens.ViewImageScreen
import fr.free.nrw.commons.customselector.utils.CustomSelectorViewModelFactory
import fr.free.nrw.commons.databinding.ActivityCustomSelectorBinding import fr.free.nrw.commons.databinding.ActivityCustomSelectorBinding
import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding
import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding
@ -191,17 +190,11 @@ class CustomSelectorActivity :
// setContentView(view) // setContentView(view)
prefs = applicationContext.getSharedPreferences("CustomSelector", MODE_PRIVATE) prefs = applicationContext.getSharedPreferences("CustomSelector", MODE_PRIVATE)
viewModel =
ViewModelProvider(this, customSelectorViewModelFactory).get(
CustomSelectorViewModel::class.java,
)
val mediaReader = MediaReader(this)
setContent { setContent {
val csViewModel = viewModel<fr.free.nrw.commons.customselector.ui.screens.CustomSelectorViewModel> { val csViewModel = ViewModelProvider(this, customSelectorViewModelFactory).get(
fr.free.nrw.commons.customselector.ui.screens.CustomSelectorViewModel(mediaReader) fr.free.nrw.commons.customselector.ui.screens.CustomSelectorViewModel::class.java
} )
val uiState by csViewModel.uiState.collectAsStateWithLifecycle() val uiState by csViewModel.uiState.collectAsStateWithLifecycle()
@ -265,7 +258,7 @@ class CustomSelectorActivity :
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
fetchData() // fetchData()
} }
/** /**

View file

@ -0,0 +1,17 @@
package fr.free.nrw.commons.customselector.ui.states
import fr.free.nrw.commons.customselector.ui.screens.Folder
import fr.free.nrw.commons.customselector.ui.screens.imageId
typealias isNotForUpload = Boolean
data class CustomSelectorUiState(
val isLoading: Boolean = true,
val folders: List<Folder> = emptyList(),
val filteredImages: List<ImageUiState> = emptyList(),
val selectedImageIds: Set<Long> = emptySet(),
val imagesNotForUpload: Map<imageId, isNotForUpload> = emptyMap()
) {
val inSelectionMode: Boolean
get() = selectedImageIds.isNotEmpty()
}

View file

@ -0,0 +1,20 @@
package fr.free.nrw.commons.customselector.ui.states
import android.net.Uri
import fr.free.nrw.commons.customselector.domain.model.Image
data class ImageUiState(
val id: Long,
val name: String,
val uri: Uri,
val bucketId: Long,
val isNotForUpload: Boolean = false,
val isUploaded: Boolean = false
)
fun Image.toImageUiState() = ImageUiState(
id = id,
name = name,
uri = uri,
bucketId = bucketId
)

View file

@ -0,0 +1,21 @@
package fr.free.nrw.commons.customselector.utils
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import fr.free.nrw.commons.customselector.domain.ImageRepository
import fr.free.nrw.commons.customselector.domain.use_case.ImageUseCase
import fr.free.nrw.commons.customselector.ui.screens.CustomSelectorViewModel
import javax.inject.Inject
class CustomSelectorViewModelFactory @Inject constructor(
private val imageRepository: ImageRepository,
private val imageUseCase: ImageUseCase
): ViewModelProvider.Factory {
override fun <CustomSelectorViewModel : ViewModel> create(
modelClass: Class<CustomSelectorViewModel>
): CustomSelectorViewModel {
return CustomSelectorViewModel(
imageRepository, imageUseCase
) as CustomSelectorViewModel
}
}

View file

@ -17,8 +17,12 @@ import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AccountUtil; import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.ContributionDao; import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.customselector.data.ImageRepositoryImpl;
import fr.free.nrw.commons.customselector.data.MediaReader;
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao; import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao;
import fr.free.nrw.commons.customselector.database.UploadedStatusDao; import fr.free.nrw.commons.customselector.database.UploadedStatusDao;
import fr.free.nrw.commons.customselector.domain.ImageRepository;
import fr.free.nrw.commons.customselector.domain.use_case.ImageUseCase;
import fr.free.nrw.commons.customselector.ui.selector.ImageFileLoader; import fr.free.nrw.commons.customselector.ui.selector.ImageFileLoader;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.db.AppDatabase; import fr.free.nrw.commons.db.AppDatabase;
@ -317,4 +321,13 @@ public class CommonsApplicationModule {
public ContentResolver providesContentResolver(Context context){ public ContentResolver providesContentResolver(Context context){
return context.getContentResolver(); return context.getContentResolver();
} }
@Provides
public ImageRepository providesImageRepository(
MediaReader mediaReader,
NotForUploadStatusDao notForUploadStatusDao,
ImageUseCase imageUseCase
) {
return new ImageRepositoryImpl(mediaReader, notForUploadStatusDao);
}
} }