mirror of
https://github.com/ouchadam/small-talk.git
synced 2025-03-23 23:40:12 +01:00
adding first pass at a image gallery component with folder fetching
This commit is contained in:
parent
846cf66fa1
commit
debfc5e5f0
@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="app.dapk.st">
|
||||
package="app.dapk.st">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:name="app.dapk.st.SmallTalkApplication"
|
||||
@ -17,13 +19,13 @@
|
||||
android:exported="true"
|
||||
android:targetActivity="app.dapk.st.home.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
android:resource="@xml/shortcuts"/>
|
||||
|
||||
</activity-alias>
|
||||
|
||||
|
@ -13,4 +13,5 @@ dependencies {
|
||||
implementation project(':domains:store')
|
||||
implementation project(":core")
|
||||
implementation project(":design-library")
|
||||
implementation Dependencies.mavenCentral.coil
|
||||
}
|
@ -1,22 +1,49 @@
|
||||
package app.dapk.st.home
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Bundle
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import app.dapk.st.core.DapkActivity
|
||||
import app.dapk.st.core.PermissionResult
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.core.viewModel
|
||||
import app.dapk.st.design.components.Toolbar
|
||||
import app.dapk.st.directory.DirectoryModule
|
||||
import app.dapk.st.home.gallery.FetchMediaFoldersUseCase
|
||||
import app.dapk.st.home.gallery.Folder
|
||||
import app.dapk.st.login.LoginModule
|
||||
import app.dapk.st.profile.ProfileModule
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity : DapkActivity() {
|
||||
|
||||
@ -27,21 +54,46 @@ class MainActivity : DapkActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
homeViewModel.events.onEach {
|
||||
when (it) {
|
||||
HomeEvent.Relaunch -> recreate()
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
|
||||
setContent {
|
||||
if (homeViewModel.hasVersionChanged()) {
|
||||
BetaUpgradeDialog()
|
||||
} else {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
HomeScreen(homeViewModel)
|
||||
val state = mutableStateOf(emptyList<Folder>())
|
||||
|
||||
lifecycleScope.launch {
|
||||
when (ensurePermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
PermissionResult.Denied -> {
|
||||
}
|
||||
|
||||
PermissionResult.Granted -> {
|
||||
state.value = FetchMediaFoldersUseCase(contentResolver).fetchFolders()
|
||||
}
|
||||
|
||||
PermissionResult.ShowRational -> {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setContent {
|
||||
Surface {
|
||||
ImageGallery(state)
|
||||
}
|
||||
}
|
||||
|
||||
// homeViewModel.events.onEach {
|
||||
// when (it) {
|
||||
// HomeEvent.Relaunch -> recreate()
|
||||
// }
|
||||
// }.launchIn(lifecycleScope)
|
||||
//
|
||||
// setContent {
|
||||
// if (homeViewModel.hasVersionChanged()) {
|
||||
// BetaUpgradeDialog()
|
||||
// } else {
|
||||
// Surface(Modifier.fillMaxSize()) {
|
||||
// HomeScreen(homeViewModel)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -60,3 +112,57 @@ class MainActivity : DapkActivity() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ImageGallery(state: State<List<Folder>>) {
|
||||
var boxWidth by remember { mutableStateOf(IntSize.Zero) }
|
||||
val localDensity = LocalDensity.current
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||
|
||||
Column {
|
||||
Toolbar(title = "Send to Awesome Room", onNavigate = {})
|
||||
val columns = when {
|
||||
screenWidth > 600 -> 4
|
||||
else -> 2
|
||||
}
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(columns),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
items(state.value, key = { it.bucketId }) {
|
||||
Box(modifier = Modifier.fillMaxWidth().padding(2.dp).onGloballyPositioned {
|
||||
boxWidth = it.size
|
||||
}) {
|
||||
Image(
|
||||
painter = rememberAsyncImagePainter(
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(it.thumbnail.toString())
|
||||
.build(),
|
||||
),
|
||||
contentDescription = "123",
|
||||
modifier = Modifier.fillMaxWidth().height(with(localDensity) { boxWidth.width.toDp() }),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
|
||||
val gradient = Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.5f)),
|
||||
startY = boxWidth.width.toFloat() * 0.5f,
|
||||
endY = boxWidth.width.toFloat()
|
||||
)
|
||||
|
||||
Box(modifier = Modifier.matchParentSize().background(gradient))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().align(Alignment.BottomStart).padding(4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(it.title, fontSize = 13.sp, color = Color.White)
|
||||
Text(it.itemCount.toString(), fontSize = 11.sp, color = Color.White)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
package app.dapk.st.home.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
|
||||
|
||||
class FetchMediaFoldersUseCase(
|
||||
private val contentResolver: ContentResolver,
|
||||
) {
|
||||
|
||||
|
||||
suspend fun fetchFolders(): List<Folder> {
|
||||
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"
|
||||
|
||||
val folders = mutableMapOf<String, Folder>()
|
||||
val contentUri = Images.Media.EXTERNAL_CONTENT_URI
|
||||
withContext(Dispatchers.IO) {
|
||||
contentResolver.query(contentUri, projection, selection, arrayOf("%image/svg%"), sortBy).use { cursor ->
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
val rowId = cursor.getLong(cursor.getColumnIndexOrThrow(projection[0]))
|
||||
val thumbnail = ContentUris.withAppendedId(contentUri, rowId)
|
||||
val bucketId = cursor.getString(cursor.getColumnIndexOrThrow(projection[1]))
|
||||
val title = cursor.getString(cursor.getColumnIndexOrThrow(projection[2])) ?: ""
|
||||
val timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(projection[3]))
|
||||
|
||||
val folder = folders.getOrPut(bucketId) { Folder(bucketId, title, thumbnail) }
|
||||
folder.incrementItemCount()
|
||||
|
||||
// val folder: FolderData = Util.getOrDefault(folders, bucketId, FolderData(thumbnail, localizeTitle(context, title), bucketId))
|
||||
// folder.incrementCount()
|
||||
// folders.put(bucketId, folder)
|
||||
// if (cameraBucketId == null && title == "Camera") {
|
||||
// cameraBucketId = bucketId
|
||||
// }
|
||||
// if (timestamp > thumbnailTimestamp) {
|
||||
// globalThumbnail = thumbnail
|
||||
// thumbnailTimestamp = timestamp
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
return folders.values.toList()
|
||||
}
|
||||
|
||||
private fun isNotPending() = if (Build.VERSION.SDK_INT <= 28) Images.Media.DATA + " NOT NULL" else MediaStore.MediaColumns.IS_PENDING + " != 1"
|
||||
|
||||
}
|
||||
|
||||
data class Folder(
|
||||
val bucketId: String,
|
||||
val title: String,
|
||||
val thumbnail: Uri,
|
||||
) {
|
||||
private var _itemCount: Long = 0L
|
||||
val itemCount: Long
|
||||
get() = _itemCount
|
||||
|
||||
fun incrementItemCount() {
|
||||
_itemCount++
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user