diff --git a/app/src/main/kotlin/app/dapk/st/SmallTalkApplication.kt b/app/src/main/kotlin/app/dapk/st/SmallTalkApplication.kt index d2e2f97..1b28443 100644 --- a/app/src/main/kotlin/app/dapk/st/SmallTalkApplication.kt +++ b/app/src/main/kotlin/app/dapk/st/SmallTalkApplication.kt @@ -15,6 +15,7 @@ import app.dapk.st.graph.AppModule import app.dapk.st.home.HomeModule import app.dapk.st.login.LoginModule import app.dapk.st.messenger.MessengerModule +import app.dapk.st.messenger.gallery.ImageGalleryModule import app.dapk.st.notifications.NotificationsModule import app.dapk.st.profile.ProfileModule import app.dapk.st.push.PushModule @@ -81,6 +82,7 @@ class SmallTalkApplication : Application(), ModuleProvider { TaskRunnerModule::class -> appModule.domainModules.taskRunnerModule CoreAndroidModule::class -> appModule.coreAndroidModule ShareEntryModule::class -> featureModules.shareEntryModule + ImageGalleryModule::class -> featureModules.imageGalleryModule else -> throw IllegalArgumentException("Unknown: $klass") } as T } diff --git a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt index 296ee1f..f424c2b 100644 --- a/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt +++ b/app/src/main/kotlin/app/dapk/st/graph/AppModule.kt @@ -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.messenger.MessengerActivity import app.dapk.st.messenger.MessengerModule +import app.dapk.st.messenger.gallery.ImageGalleryModule import app.dapk.st.navigator.IntentFactory import app.dapk.st.navigator.MessageAttachment import app.dapk.st.notifications.MatrixPushHandler @@ -217,6 +218,10 @@ internal class FeatureModules internal constructor( ShareEntryModule(matrixModules.sync, matrixModules.room) } + val imageGalleryModule by unsafeLazy { + ImageGalleryModule(context.contentResolver, coroutineDispatchers) + } + val pushModule by unsafeLazy { domainModules.pushModule } diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/FetchMediaFoldersUseCase.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/FetchMediaFoldersUseCase.kt index 8d08c97..1f17102 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/FetchMediaFoldersUseCase.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/FetchMediaFoldersUseCase.kt @@ -3,21 +3,17 @@ package app.dapk.st.messenger.gallery import android.content.ContentResolver import android.content.ContentUris import android.net.Uri -import android.os.Build -import android.provider.MediaStore import android.provider.MediaStore.Images -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - - -// https://github.com/signalapp/Signal-Android/blob/e22ddb8f96f8801f0abe622b5261abc6cb396d94/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java +import app.dapk.st.core.CoroutineDispatchers +import app.dapk.st.core.withIoContext class FetchMediaFoldersUseCase( private val contentResolver: ContentResolver, + private val dispatchers: CoroutineDispatchers, ) { suspend fun fetchFolders(): List { - 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 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" @@ -39,7 +35,6 @@ class FetchMediaFoldersUseCase( } } - } 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 { - return withContext(Dispatchers.IO) { - - val media = mutableListOf() - 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" diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/FetchMediaUseCase.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/FetchMediaUseCase.kt new file mode 100644 index 0000000..7ea73f7 --- /dev/null +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/FetchMediaUseCase.kt @@ -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 { + + return dispatchers.withIoContext { + val media = mutableListOf() + 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, +) + diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/ImageGalleryActivity.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/ImageGalleryActivity.kt index 53430dc..0917df0 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/ImageGalleryActivity.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/ImageGalleryActivity.kt @@ -12,21 +12,18 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.lifecycleScope -import app.dapk.st.core.DapkActivity -import app.dapk.st.core.Lce -import app.dapk.st.core.PermissionResult +import app.dapk.st.core.* +import app.dapk.st.core.extensions.unsafeLazy +import app.dapk.st.messenger.MessengerModule import kotlinx.coroutines.launch class ImageGalleryActivity : DapkActivity() { + private val module by unsafeLazy { module() } + private val viewModel by viewModel { module.imageGalleryViewModel() } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - val viewModel = ImageGalleryViewModel( - FetchMediaFoldersUseCase(contentResolver), - FetchMediaUseCase(contentResolver), - ) - val permissionState = mutableStateOf>(Lce.Loading()) lifecycleScope.launch { diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/ImageGalleryModule.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/ImageGalleryModule.kt new file mode 100644 index 0000000..b3076b8 --- /dev/null +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/ImageGalleryModule.kt @@ -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), + ) + +} \ No newline at end of file diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/MediaStoreExtensions.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/MediaStoreExtensions.kt new file mode 100644 index 0000000..dd0679f --- /dev/null +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/MediaStoreExtensions.kt @@ -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"