providing image gallery via DI graph
This commit is contained in:
parent
bd885823bd
commit
415ea4b150
|
@ -15,6 +15,7 @@ import app.dapk.st.graph.AppModule
|
||||||
import app.dapk.st.home.HomeModule
|
import app.dapk.st.home.HomeModule
|
||||||
import app.dapk.st.login.LoginModule
|
import app.dapk.st.login.LoginModule
|
||||||
import app.dapk.st.messenger.MessengerModule
|
import app.dapk.st.messenger.MessengerModule
|
||||||
|
import app.dapk.st.messenger.gallery.ImageGalleryModule
|
||||||
import app.dapk.st.notifications.NotificationsModule
|
import app.dapk.st.notifications.NotificationsModule
|
||||||
import app.dapk.st.profile.ProfileModule
|
import app.dapk.st.profile.ProfileModule
|
||||||
import app.dapk.st.push.PushModule
|
import app.dapk.st.push.PushModule
|
||||||
|
@ -81,6 +82,7 @@ class SmallTalkApplication : Application(), ModuleProvider {
|
||||||
TaskRunnerModule::class -> appModule.domainModules.taskRunnerModule
|
TaskRunnerModule::class -> appModule.domainModules.taskRunnerModule
|
||||||
CoreAndroidModule::class -> appModule.coreAndroidModule
|
CoreAndroidModule::class -> appModule.coreAndroidModule
|
||||||
ShareEntryModule::class -> featureModules.shareEntryModule
|
ShareEntryModule::class -> featureModules.shareEntryModule
|
||||||
|
ImageGalleryModule::class -> featureModules.imageGalleryModule
|
||||||
else -> throw IllegalArgumentException("Unknown: $klass")
|
else -> throw IllegalArgumentException("Unknown: $klass")
|
||||||
} as T
|
} as T
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import app.dapk.st.matrix.sync.internal.request.ApiToDeviceEvent
|
||||||
import app.dapk.st.matrix.sync.internal.room.MessageDecrypter
|
import app.dapk.st.matrix.sync.internal.room.MessageDecrypter
|
||||||
import app.dapk.st.messenger.MessengerActivity
|
import app.dapk.st.messenger.MessengerActivity
|
||||||
import app.dapk.st.messenger.MessengerModule
|
import app.dapk.st.messenger.MessengerModule
|
||||||
|
import app.dapk.st.messenger.gallery.ImageGalleryModule
|
||||||
import app.dapk.st.navigator.IntentFactory
|
import app.dapk.st.navigator.IntentFactory
|
||||||
import app.dapk.st.navigator.MessageAttachment
|
import app.dapk.st.navigator.MessageAttachment
|
||||||
import app.dapk.st.notifications.MatrixPushHandler
|
import app.dapk.st.notifications.MatrixPushHandler
|
||||||
|
@ -217,6 +218,10 @@ internal class FeatureModules internal constructor(
|
||||||
ShareEntryModule(matrixModules.sync, matrixModules.room)
|
ShareEntryModule(matrixModules.sync, matrixModules.room)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val imageGalleryModule by unsafeLazy {
|
||||||
|
ImageGalleryModule(context.contentResolver, coroutineDispatchers)
|
||||||
|
}
|
||||||
|
|
||||||
val pushModule by unsafeLazy {
|
val pushModule by unsafeLazy {
|
||||||
domainModules.pushModule
|
domainModules.pushModule
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,21 +3,17 @@ package app.dapk.st.messenger.gallery
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.provider.MediaStore
|
|
||||||
import android.provider.MediaStore.Images
|
import android.provider.MediaStore.Images
|
||||||
import kotlinx.coroutines.Dispatchers
|
import app.dapk.st.core.CoroutineDispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import app.dapk.st.core.withIoContext
|
||||||
|
|
||||||
|
|
||||||
// https://github.com/signalapp/Signal-Android/blob/e22ddb8f96f8801f0abe622b5261abc6cb396d94/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java
|
|
||||||
|
|
||||||
class FetchMediaFoldersUseCase(
|
class FetchMediaFoldersUseCase(
|
||||||
private val contentResolver: ContentResolver,
|
private val contentResolver: ContentResolver,
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun fetchFolders(): List<Folder> {
|
suspend fun fetchFolders(): List<Folder> {
|
||||||
return withContext(Dispatchers.IO) {
|
return dispatchers.withIoContext {
|
||||||
val projection = arrayOf(Images.Media._ID, Images.Media.BUCKET_ID, Images.Media.BUCKET_DISPLAY_NAME, Images.Media.DATE_MODIFIED)
|
val projection = arrayOf(Images.Media._ID, Images.Media.BUCKET_ID, Images.Media.BUCKET_DISPLAY_NAME, Images.Media.DATE_MODIFIED)
|
||||||
val selection = "${isNotPending()} AND ${Images.Media.BUCKET_ID} AND ${Images.Media.MIME_TYPE} NOT LIKE ?"
|
val selection = "${isNotPending()} AND ${Images.Media.BUCKET_ID} AND ${Images.Media.MIME_TYPE} NOT LIKE ?"
|
||||||
val sortBy = "${Images.Media.BUCKET_DISPLAY_NAME} COLLATE NOCASE ASC, ${Images.Media.DATE_MODIFIED} DESC"
|
val sortBy = "${Images.Media.BUCKET_DISPLAY_NAME} COLLATE NOCASE ASC, ${Images.Media.DATE_MODIFIED} DESC"
|
||||||
|
@ -39,7 +35,6 @@ class FetchMediaFoldersUseCase(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Folder(
|
data class Folder(
|
||||||
|
@ -56,59 +51,3 @@ data class Folder(
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FetchMediaUseCase(private val contentResolver: ContentResolver) {
|
|
||||||
|
|
||||||
private val projection = arrayOf(
|
|
||||||
Images.Media._ID,
|
|
||||||
Images.Media.MIME_TYPE,
|
|
||||||
Images.Media.DATE_MODIFIED,
|
|
||||||
Images.Media.ORIENTATION,
|
|
||||||
Images.Media.WIDTH,
|
|
||||||
Images.Media.HEIGHT,
|
|
||||||
Images.Media.SIZE
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun getMediaInBucket(bucketId: String): List<Media> {
|
|
||||||
return withContext(Dispatchers.IO) {
|
|
||||||
|
|
||||||
val media = mutableListOf<Media>()
|
|
||||||
val selection = Images.Media.BUCKET_ID + " = ? AND " + isNotPending() + " AND " + Images.Media.MIME_TYPE + " NOT LIKE ?"
|
|
||||||
val selectionArgs = arrayOf(bucketId, "%image/svg%")
|
|
||||||
val sortBy = Images.Media.DATE_MODIFIED + " DESC"
|
|
||||||
val contentUri = Images.Media.EXTERNAL_CONTENT_URI
|
|
||||||
contentResolver.query(contentUri, projection, selection, selectionArgs, sortBy).use { cursor ->
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
val rowId = cursor.getLong(cursor.getColumnIndexOrThrow(projection[0]))
|
|
||||||
val uri = ContentUris.withAppendedId(contentUri, rowId)
|
|
||||||
val mimetype = cursor.getString(cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE))
|
|
||||||
val date = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.DATE_MODIFIED))
|
|
||||||
val orientation = cursor.getInt(cursor.getColumnIndexOrThrow(Images.Media.ORIENTATION))
|
|
||||||
val width = cursor.getInt(cursor.getColumnIndexOrThrow(getWidthColumn(orientation)))
|
|
||||||
val height = cursor.getInt(cursor.getColumnIndexOrThrow(getHeightColumn(orientation)))
|
|
||||||
val size = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.SIZE))
|
|
||||||
media.add(Media(rowId, uri, mimetype, width, height, size, date))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
media
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getWidthColumn(orientation: Int) = if (orientation == 0 || orientation == 180) Images.Media.WIDTH else Images.Media.HEIGHT
|
|
||||||
|
|
||||||
private fun getHeightColumn(orientation: Int) = if (orientation == 0 || orientation == 180) Images.Media.HEIGHT else Images.Media.WIDTH
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Media(
|
|
||||||
val id: Long,
|
|
||||||
val uri: Uri,
|
|
||||||
val mimeType: String,
|
|
||||||
val width: Int,
|
|
||||||
val height: Int,
|
|
||||||
val size: Long,
|
|
||||||
val dateModifiedEpochMillis: Long,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun isNotPending() = if (Build.VERSION.SDK_INT <= 28) Images.Media.DATA + " NOT NULL" else MediaStore.MediaColumns.IS_PENDING + " != 1"
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package app.dapk.st.messenger.gallery
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.ContentUris
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import app.dapk.st.core.CoroutineDispatchers
|
||||||
|
import app.dapk.st.core.withIoContext
|
||||||
|
|
||||||
|
class FetchMediaUseCase(private val contentResolver: ContentResolver, private val dispatchers: CoroutineDispatchers) {
|
||||||
|
|
||||||
|
private val projection = arrayOf(
|
||||||
|
MediaStore.Images.Media._ID,
|
||||||
|
MediaStore.Images.Media.MIME_TYPE,
|
||||||
|
MediaStore.Images.Media.DATE_MODIFIED,
|
||||||
|
MediaStore.Images.Media.ORIENTATION,
|
||||||
|
MediaStore.Images.Media.WIDTH,
|
||||||
|
MediaStore.Images.Media.HEIGHT,
|
||||||
|
MediaStore.Images.Media.SIZE
|
||||||
|
)
|
||||||
|
|
||||||
|
private val selection = MediaStore.Images.Media.BUCKET_ID + " = ? AND " + isNotPending() + " AND " + MediaStore.Images.Media.MIME_TYPE + " NOT LIKE ?"
|
||||||
|
|
||||||
|
suspend fun getMediaInBucket(bucketId: String): List<Media> {
|
||||||
|
|
||||||
|
return dispatchers.withIoContext {
|
||||||
|
val media = mutableListOf<Media>()
|
||||||
|
val selectionArgs = arrayOf(bucketId, "%image/svg%")
|
||||||
|
val sortBy = MediaStore.Images.Media.DATE_MODIFIED + " DESC"
|
||||||
|
val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
|
contentResolver.query(contentUri, projection, selection, selectionArgs, sortBy).use { cursor ->
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
val rowId = cursor.getLong(cursor.getColumnIndexOrThrow(projection[0]))
|
||||||
|
val uri = ContentUris.withAppendedId(contentUri, rowId)
|
||||||
|
val mimetype = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE))
|
||||||
|
val date = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED))
|
||||||
|
val orientation = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.ORIENTATION))
|
||||||
|
val width = cursor.getInt(cursor.getColumnIndexOrThrow(getWidthColumn(orientation)))
|
||||||
|
val height = cursor.getInt(cursor.getColumnIndexOrThrow(getHeightColumn(orientation)))
|
||||||
|
val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))
|
||||||
|
media.add(Media(rowId, uri, mimetype, width, height, size, date))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
media
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getWidthColumn(orientation: Int) = if (orientation == 0 || orientation == 180) MediaStore.Images.Media.WIDTH else MediaStore.Images.Media.HEIGHT
|
||||||
|
|
||||||
|
private fun getHeightColumn(orientation: Int) =
|
||||||
|
if (orientation == 0 || orientation == 180) MediaStore.Images.Media.HEIGHT else MediaStore.Images.Media.WIDTH
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Media(
|
||||||
|
val id: Long,
|
||||||
|
val uri: Uri,
|
||||||
|
val mimeType: String,
|
||||||
|
val width: Int,
|
||||||
|
val height: Int,
|
||||||
|
val size: Long,
|
||||||
|
val dateModifiedEpochMillis: Long,
|
||||||
|
)
|
||||||
|
|
|
@ -12,21 +12,18 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import app.dapk.st.core.DapkActivity
|
import app.dapk.st.core.*
|
||||||
import app.dapk.st.core.Lce
|
import app.dapk.st.core.extensions.unsafeLazy
|
||||||
import app.dapk.st.core.PermissionResult
|
import app.dapk.st.messenger.MessengerModule
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ImageGalleryActivity : DapkActivity() {
|
class ImageGalleryActivity : DapkActivity() {
|
||||||
|
|
||||||
|
private val module by unsafeLazy { module<ImageGalleryModule>() }
|
||||||
|
private val viewModel by viewModel { module.imageGalleryViewModel() }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val viewModel = ImageGalleryViewModel(
|
|
||||||
FetchMediaFoldersUseCase(contentResolver),
|
|
||||||
FetchMediaUseCase(contentResolver),
|
|
||||||
)
|
|
||||||
|
|
||||||
val permissionState = mutableStateOf<Lce<PermissionResult>>(Lce.Loading())
|
val permissionState = mutableStateOf<Lce<PermissionResult>>(Lce.Loading())
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package app.dapk.st.messenger.gallery
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import app.dapk.st.core.CoroutineDispatchers
|
||||||
|
import app.dapk.st.core.ProvidableModule
|
||||||
|
|
||||||
|
class ImageGalleryModule(
|
||||||
|
private val contentResolver: ContentResolver,
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
|
) : ProvidableModule {
|
||||||
|
|
||||||
|
fun imageGalleryViewModel() = ImageGalleryViewModel(
|
||||||
|
FetchMediaFoldersUseCase(contentResolver, dispatchers),
|
||||||
|
FetchMediaUseCase(contentResolver, dispatchers),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package app.dapk.st.messenger.gallery
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.MediaStore
|
||||||
|
|
||||||
|
fun isNotPending() = if (Build.VERSION.SDK_INT <= 28) MediaStore.Images.Media.DATA + " NOT NULL" else MediaStore.MediaColumns.IS_PENDING + " != 1"
|
Loading…
Reference in New Issue