creating dedicated activity for image selection
This commit is contained in:
parent
34e0415892
commit
c61646bbd3
|
@ -61,12 +61,14 @@ abstract class DapkActivity : ComponentActivity(), EffectScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("OVERRIDE_DEPRECATION")
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (needsBackLeakWorkaround && !onBackPressedDispatcher.hasEnabledCallbacks()) {
|
if (needsBackLeakWorkaround && !onBackPressedDispatcher.hasEnabledCallbacks()) {
|
||||||
finishAfterTransition()
|
finishAfterTransition()
|
||||||
} else
|
} else {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected suspend fun ensurePermission(permission: String): PermissionResult {
|
protected suspend fun ensurePermission(permission: String): PermissionResult {
|
||||||
return when {
|
return when {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
<activity android:name="app.dapk.st.home.MainActivity"/>
|
<activity android:name="app.dapk.st.home.MainActivity"/>
|
||||||
|
<activity android:name="app.dapk.st.home.gallery.ImageGalleryActivity"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -1,46 +1,29 @@
|
||||||
package app.dapk.st.home
|
package app.dapk.st.home
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.foundation.Image
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
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.AlertDialog
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.compose.ui.graphics.Color
|
import app.dapk.st.core.DapkActivity
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import app.dapk.st.core.Lce
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import app.dapk.st.core.module
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import app.dapk.st.core.viewModel
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import app.dapk.st.core.*
|
|
||||||
import app.dapk.st.core.components.CenteredLoading
|
|
||||||
import app.dapk.st.design.components.GenericError
|
|
||||||
import app.dapk.st.design.components.Route
|
import app.dapk.st.design.components.Route
|
||||||
import app.dapk.st.design.components.Spider
|
|
||||||
import app.dapk.st.design.components.SpiderPage
|
import app.dapk.st.design.components.SpiderPage
|
||||||
import app.dapk.st.directory.DirectoryModule
|
import app.dapk.st.directory.DirectoryModule
|
||||||
import app.dapk.st.home.gallery.FetchMediaFoldersUseCase
|
|
||||||
import app.dapk.st.home.gallery.FetchMediaUseCase
|
|
||||||
import app.dapk.st.home.gallery.Folder
|
import app.dapk.st.home.gallery.Folder
|
||||||
|
import app.dapk.st.home.gallery.GetImageFromGallery
|
||||||
import app.dapk.st.home.gallery.Media
|
import app.dapk.st.home.gallery.Media
|
||||||
import app.dapk.st.login.LoginModule
|
import app.dapk.st.login.LoginModule
|
||||||
import app.dapk.st.profile.ProfileModule
|
import app.dapk.st.profile.ProfileModule
|
||||||
import app.dapk.st.viewmodel.DapkViewModel
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import kotlinx.coroutines.flow.onEach
|
||||||
import coil.request.ImageRequest
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class MainActivity : DapkActivity() {
|
class MainActivity : DapkActivity() {
|
||||||
|
|
||||||
|
@ -52,49 +35,26 @@ class MainActivity : DapkActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val viewModel = ImageGalleryViewModel(
|
homeViewModel.events.onEach {
|
||||||
FetchMediaFoldersUseCase(contentResolver),
|
when (it) {
|
||||||
FetchMediaUseCase(contentResolver),
|
HomeEvent.Relaunch -> recreate()
|
||||||
)
|
}
|
||||||
|
}.launchIn(lifecycleScope)
|
||||||
|
|
||||||
|
registerForActivityResult(GetImageFromGallery()) {
|
||||||
|
Toast.makeText(this, it.toString(), Toast.LENGTH_SHORT).show()
|
||||||
|
}.launch(null)
|
||||||
|
|
||||||
// lifecycleScope.launch {
|
|
||||||
// when (ensurePermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
|
||||||
// PermissionResult.Denied -> {
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// PermissionResult.Granted -> {
|
|
||||||
// state.value = FetchMediaFoldersUseCase(contentResolver).fetchFolders()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// PermissionResult.ShowRational -> {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
Surface {
|
if (homeViewModel.hasVersionChanged()) {
|
||||||
ImageGalleryScreen(viewModel) {
|
BetaUpgradeDialog()
|
||||||
finish()
|
} else {
|
||||||
|
Surface(Modifier.fillMaxSize()) {
|
||||||
|
HomeScreen(homeViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// homeViewModel.events.onEach {
|
|
||||||
// when (it) {
|
|
||||||
// HomeEvent.Relaunch -> recreate()
|
|
||||||
// }
|
|
||||||
// }.launchIn(lifecycleScope)
|
|
||||||
//
|
|
||||||
// setContent {
|
|
||||||
// if (homeViewModel.hasVersionChanged()) {
|
|
||||||
// BetaUpgradeDialog()
|
|
||||||
// } else {
|
|
||||||
// Surface(Modifier.fillMaxSize()) {
|
|
||||||
// HomeScreen(homeViewModel)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -132,188 +92,3 @@ sealed interface ImageGalleryPage {
|
||||||
|
|
||||||
|
|
||||||
sealed interface ImageGalleryEvent
|
sealed interface ImageGalleryEvent
|
||||||
|
|
||||||
class ImageGalleryViewModel(
|
|
||||||
private val foldersUseCase: FetchMediaFoldersUseCase,
|
|
||||||
private val fetchMediaUseCase: FetchMediaUseCase,
|
|
||||||
) : DapkViewModel<ImageGalleryState, ImageGalleryEvent>(
|
|
||||||
initialState = ImageGalleryState(page = SpiderPage(route = ImageGalleryPage.Routes.folders, "", null, ImageGalleryPage.Folders(Lce.Loading())))
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var currentPageJob: Job? = null
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
currentPageJob?.cancel()
|
|
||||||
currentPageJob = viewModelScope.launch {
|
|
||||||
val folders = foldersUseCase.fetchFolders()
|
|
||||||
updatePageState<ImageGalleryPage.Folders> { copy(content = Lce.Content(folders)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun goTo(page: SpiderPage<out ImageGalleryPage>) {
|
|
||||||
currentPageJob?.cancel()
|
|
||||||
updateState { copy(page = page) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectFolder(folder: Folder) {
|
|
||||||
currentPageJob?.cancel()
|
|
||||||
|
|
||||||
updateState {
|
|
||||||
copy(
|
|
||||||
page = SpiderPage(
|
|
||||||
route = ImageGalleryPage.Routes.files,
|
|
||||||
label = page.label,
|
|
||||||
parent = ImageGalleryPage.Routes.folders,
|
|
||||||
state = ImageGalleryPage.Files(Lce.Loading())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPageJob = viewModelScope.launch {
|
|
||||||
val media = fetchMediaUseCase.getMediaInBucket(folder.bucketId)
|
|
||||||
updatePageState<ImageGalleryPage.Files> {
|
|
||||||
copy(content = Lce.Content(media))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private inline fun <reified S : ImageGalleryPage> updatePageState(crossinline block: S.() -> S) {
|
|
||||||
val page = state.page
|
|
||||||
val currentState = page.state
|
|
||||||
require(currentState is S)
|
|
||||||
updateState { copy(page = (page as SpiderPage<S>).copy(state = block(page.state))) }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ImageGalleryScreen(viewModel: ImageGalleryViewModel, onTopLevelBack: () -> Unit) {
|
|
||||||
LifecycleEffect(onStart = {
|
|
||||||
viewModel.start()
|
|
||||||
})
|
|
||||||
|
|
||||||
val onNavigate: (SpiderPage<out ImageGalleryPage>?) -> Unit = {
|
|
||||||
when (it) {
|
|
||||||
null -> onTopLevelBack()
|
|
||||||
else -> viewModel.goTo(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spider(currentPage = viewModel.state.page, onNavigate = onNavigate) {
|
|
||||||
item(ImageGalleryPage.Routes.folders) {
|
|
||||||
ImageGalleryFolders(it) { folder ->
|
|
||||||
viewModel.selectFolder(folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item(ImageGalleryPage.Routes.files) {
|
|
||||||
ImageGalleryMedia(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ImageGalleryFolders(state: ImageGalleryPage.Folders, onClick: (Folder) -> Unit) {
|
|
||||||
val screenWidth = LocalConfiguration.current.screenWidthDp
|
|
||||||
|
|
||||||
val gradient = Brush.verticalGradient(
|
|
||||||
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.5f)),
|
|
||||||
)
|
|
||||||
|
|
||||||
when (val content = state.content) {
|
|
||||||
is Lce.Loading -> {
|
|
||||||
CenteredLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
is Lce.Content -> {
|
|
||||||
Column {
|
|
||||||
val columns = when {
|
|
||||||
screenWidth > 600 -> 4
|
|
||||||
else -> 2
|
|
||||||
}
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(columns),
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
items(content.value, key = { it.bucketId }) {
|
|
||||||
Box(modifier = Modifier.fillMaxWidth().padding(2.dp).aspectRatio(1f)
|
|
||||||
.clickable { onClick(it) }) {
|
|
||||||
Image(
|
|
||||||
painter = rememberAsyncImagePainter(
|
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data(it.thumbnail.toString())
|
|
||||||
.build(),
|
|
||||||
),
|
|
||||||
contentDescription = "123",
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxWidth().fillMaxHeight(0.6f).background(gradient).align(Alignment.BottomStart))
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is Lce.Error -> GenericError { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ImageGalleryMedia(state: ImageGalleryPage.Files) {
|
|
||||||
val screenWidth = LocalConfiguration.current.screenWidthDp
|
|
||||||
|
|
||||||
Column {
|
|
||||||
val columns = when {
|
|
||||||
screenWidth > 600 -> 4
|
|
||||||
else -> 2
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val content = state.content) {
|
|
||||||
is Lce.Loading -> {
|
|
||||||
CenteredLoading()
|
|
||||||
}
|
|
||||||
|
|
||||||
is Lce.Content -> {
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(columns),
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
) {
|
|
||||||
val modifier = Modifier.fillMaxWidth().padding(2.dp).aspectRatio(1f)
|
|
||||||
items(content.value, key = { it.id }) {
|
|
||||||
Box(modifier = modifier) {
|
|
||||||
Image(
|
|
||||||
painter = rememberAsyncImagePainter(
|
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data(it.uri.toString())
|
|
||||||
.crossfade(true)
|
|
||||||
.build(),
|
|
||||||
),
|
|
||||||
contentDescription = "123",
|
|
||||||
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is Lce.Error -> GenericError { }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package app.dapk.st.home.gallery
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
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.home.ImageGalleryScreen
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ImageGalleryActivity : DapkActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val viewModel = ImageGalleryViewModel(
|
||||||
|
FetchMediaFoldersUseCase(contentResolver),
|
||||||
|
FetchMediaUseCase(contentResolver),
|
||||||
|
)
|
||||||
|
|
||||||
|
val permissionState = mutableStateOf<Lce<PermissionResult>>(Lce.Loading())
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
permissionState.value = runCatching { ensurePermission(Manifest.permission.READ_EXTERNAL_STORAGE) }.fold(
|
||||||
|
onSuccess = { Lce.Content(it) },
|
||||||
|
onFailure = { Lce.Error(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
Surface {
|
||||||
|
PermissionGuard(permissionState) {
|
||||||
|
ImageGalleryScreen(viewModel, onTopLevelBack = { finish() }) { media ->
|
||||||
|
setResult(RESULT_OK, Intent().setData(media.uri))
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Activity.PermissionGuard(state: State<Lce<PermissionResult>>, onGranted: @Composable () -> Unit) {
|
||||||
|
when (val content = state.value) {
|
||||||
|
is Lce.Content -> when (content.value) {
|
||||||
|
PermissionResult.Granted -> onGranted()
|
||||||
|
PermissionResult.Denied -> finish()
|
||||||
|
PermissionResult.ShowRational -> finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
is Lce.Error -> finish()
|
||||||
|
is Lce.Loading -> {
|
||||||
|
// loading should be quick, let's avoid displaying anything
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetImageFromGallery : ActivityResultContract<Void?, Uri?>() {
|
||||||
|
|
||||||
|
override fun createIntent(context: Context, input: Void?): Intent {
|
||||||
|
return Intent(context, ImageGalleryActivity::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
|
||||||
|
return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
package app.dapk.st.home
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
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.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import app.dapk.st.core.Lce
|
||||||
|
import app.dapk.st.core.LifecycleEffect
|
||||||
|
import app.dapk.st.core.components.CenteredLoading
|
||||||
|
import app.dapk.st.design.components.GenericError
|
||||||
|
import app.dapk.st.design.components.Spider
|
||||||
|
import app.dapk.st.design.components.SpiderPage
|
||||||
|
import app.dapk.st.home.gallery.Folder
|
||||||
|
import app.dapk.st.home.gallery.ImageGalleryViewModel
|
||||||
|
import app.dapk.st.home.gallery.Media
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImageGalleryScreen(viewModel: ImageGalleryViewModel, onTopLevelBack: () -> Unit, onImageSelected: (Media) -> Unit) {
|
||||||
|
LifecycleEffect(onStart = {
|
||||||
|
viewModel.start()
|
||||||
|
})
|
||||||
|
|
||||||
|
val onNavigate: (SpiderPage<out ImageGalleryPage>?) -> Unit = {
|
||||||
|
when (it) {
|
||||||
|
null -> onTopLevelBack()
|
||||||
|
else -> viewModel.goTo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spider(currentPage = viewModel.state.page, onNavigate = onNavigate) {
|
||||||
|
item(ImageGalleryPage.Routes.folders) {
|
||||||
|
ImageGalleryFolders(it) { folder ->
|
||||||
|
viewModel.selectFolder(folder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item(ImageGalleryPage.Routes.files) {
|
||||||
|
ImageGalleryMedia(it, onImageSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImageGalleryFolders(state: ImageGalleryPage.Folders, onClick: (Folder) -> Unit) {
|
||||||
|
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||||
|
|
||||||
|
val gradient = Brush.verticalGradient(
|
||||||
|
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.5f)),
|
||||||
|
)
|
||||||
|
|
||||||
|
when (val content = state.content) {
|
||||||
|
is Lce.Loading -> {
|
||||||
|
CenteredLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
is Lce.Content -> {
|
||||||
|
Column {
|
||||||
|
val columns = when {
|
||||||
|
screenWidth > 600 -> 4
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(columns),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
items(content.value, key = { it.bucketId }) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth().padding(2.dp).aspectRatio(1f)
|
||||||
|
.clickable { onClick(it) }) {
|
||||||
|
Image(
|
||||||
|
painter = rememberAsyncImagePainter(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(it.thumbnail.toString())
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
contentDescription = "123",
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxWidth().fillMaxHeight(0.6f).background(gradient).align(Alignment.BottomStart))
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Lce.Error -> GenericError { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImageGalleryMedia(state: ImageGalleryPage.Files, onFileSelected: (Media) -> Unit) {
|
||||||
|
val screenWidth = LocalConfiguration.current.screenWidthDp
|
||||||
|
|
||||||
|
Column {
|
||||||
|
val columns = when {
|
||||||
|
screenWidth > 600 -> 4
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val content = state.content) {
|
||||||
|
is Lce.Loading -> {
|
||||||
|
CenteredLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
is Lce.Content -> {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Fixed(columns),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
val modifier = Modifier.fillMaxWidth().padding(2.dp).aspectRatio(1f)
|
||||||
|
items(content.value, key = { it.id }) {
|
||||||
|
Box(modifier = modifier.clickable { onFileSelected(it) }) {
|
||||||
|
Image(
|
||||||
|
painter = rememberAsyncImagePainter(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(it.uri.toString())
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
contentDescription = "123",
|
||||||
|
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Lce.Error -> GenericError { }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package app.dapk.st.home.gallery
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import app.dapk.st.core.Lce
|
||||||
|
import app.dapk.st.design.components.SpiderPage
|
||||||
|
import app.dapk.st.home.ImageGalleryEvent
|
||||||
|
import app.dapk.st.home.ImageGalleryPage
|
||||||
|
import app.dapk.st.home.ImageGalleryState
|
||||||
|
import app.dapk.st.viewmodel.DapkViewModel
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ImageGalleryViewModel(
|
||||||
|
private val foldersUseCase: FetchMediaFoldersUseCase,
|
||||||
|
private val fetchMediaUseCase: FetchMediaUseCase,
|
||||||
|
) : DapkViewModel<ImageGalleryState, ImageGalleryEvent>(
|
||||||
|
initialState = ImageGalleryState(
|
||||||
|
page = SpiderPage(
|
||||||
|
route = ImageGalleryPage.Routes.folders,
|
||||||
|
label = "",
|
||||||
|
parent = null,
|
||||||
|
state = ImageGalleryPage.Folders(Lce.Loading())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var currentPageJob: Job? = null
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
currentPageJob?.cancel()
|
||||||
|
currentPageJob = viewModelScope.launch {
|
||||||
|
val folders = foldersUseCase.fetchFolders()
|
||||||
|
updatePageState<ImageGalleryPage.Folders> { copy(content = Lce.Content(folders)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun goTo(page: SpiderPage<out ImageGalleryPage>) {
|
||||||
|
currentPageJob?.cancel()
|
||||||
|
updateState { copy(page = page) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectFolder(folder: Folder) {
|
||||||
|
currentPageJob?.cancel()
|
||||||
|
|
||||||
|
updateState {
|
||||||
|
copy(
|
||||||
|
page = SpiderPage(
|
||||||
|
route = ImageGalleryPage.Routes.files,
|
||||||
|
label = page.label,
|
||||||
|
parent = ImageGalleryPage.Routes.folders,
|
||||||
|
state = ImageGalleryPage.Files(Lce.Loading())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPageJob = viewModelScope.launch {
|
||||||
|
val media = fetchMediaUseCase.getMediaInBucket(folder.bucketId)
|
||||||
|
updatePageState<ImageGalleryPage.Files> {
|
||||||
|
copy(content = Lce.Content(media))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private inline fun <reified S : ImageGalleryPage> updatePageState(crossinline block: S.() -> S) {
|
||||||
|
val page = state.page
|
||||||
|
val currentState = page.state
|
||||||
|
require(currentState is S)
|
||||||
|
updateState { copy(page = (page as SpiderPage<S>).copy(state = block(page.state))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package app.dapk.st.navigator
|
package app.dapk.st.navigator
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.Instrumentation.ActivityResult
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
Loading…
Reference in New Issue