diff --git a/domains/android/compose-core/src/main/kotlin/app/dapk/st/core/DapkActivity.kt b/domains/android/compose-core/src/main/kotlin/app/dapk/st/core/DapkActivity.kt
index d297ca6..fbcf0ed 100644
--- a/domains/android/compose-core/src/main/kotlin/app/dapk/st/core/DapkActivity.kt
+++ b/domains/android/compose-core/src/main/kotlin/app/dapk/st/core/DapkActivity.kt
@@ -61,11 +61,13 @@ abstract class DapkActivity : ComponentActivity(), EffectScope {
}
}
+ @Suppress("OVERRIDE_DEPRECATION")
override fun onBackPressed() {
if (needsBackLeakWorkaround && !onBackPressedDispatcher.hasEnabledCallbacks()) {
finishAfterTransition()
- } else
+ } else {
super.onBackPressed()
+ }
}
protected suspend fun ensurePermission(permission: String): PermissionResult {
diff --git a/features/home/src/main/AndroidManifest.xml b/features/home/src/main/AndroidManifest.xml
index 3e51f2c..ccee295 100644
--- a/features/home/src/main/AndroidManifest.xml
+++ b/features/home/src/main/AndroidManifest.xml
@@ -1,8 +1,9 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt b/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt
index 6d150f7..1ff20c1 100644
--- a/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt
+++ b/features/home/src/main/kotlin/app/dapk/st/home/MainActivity.kt
@@ -1,46 +1,29 @@
package app.dapk.st.home
import android.os.Bundle
-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 android.widget.Toast
+import androidx.compose.foundation.layout.fillMaxSize
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.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 androidx.lifecycle.viewModelScope
-import app.dapk.st.core.*
-import app.dapk.st.core.components.CenteredLoading
-import app.dapk.st.design.components.GenericError
+import androidx.lifecycle.lifecycleScope
+import app.dapk.st.core.DapkActivity
+import app.dapk.st.core.Lce
+import app.dapk.st.core.module
+import app.dapk.st.core.viewModel
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.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.GetImageFromGallery
import app.dapk.st.home.gallery.Media
import app.dapk.st.login.LoginModule
import app.dapk.st.profile.ProfileModule
-import app.dapk.st.viewmodel.DapkViewModel
-import coil.compose.rememberAsyncImagePainter
-import coil.request.ImageRequest
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
class MainActivity : DapkActivity() {
@@ -52,49 +35,26 @@ class MainActivity : DapkActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val viewModel = ImageGalleryViewModel(
- FetchMediaFoldersUseCase(contentResolver),
- FetchMediaUseCase(contentResolver),
- )
+ homeViewModel.events.onEach {
+ when (it) {
+ 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 {
- Surface {
- ImageGalleryScreen(viewModel) {
- finish()
+ if (homeViewModel.hasVersionChanged()) {
+ BetaUpgradeDialog()
+ } 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
@@ -132,188 +92,3 @@ sealed interface ImageGalleryPage {
sealed interface ImageGalleryEvent
-
-class ImageGalleryViewModel(
- private val foldersUseCase: FetchMediaFoldersUseCase,
- private val fetchMediaUseCase: FetchMediaUseCase,
-) : DapkViewModel(
- 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 { copy(content = Lce.Content(folders)) }
- }
-
- }
-
- fun goTo(page: SpiderPage) {
- 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 {
- copy(content = Lce.Content(media))
- }
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- private inline fun updatePageState(crossinline block: S.() -> S) {
- val page = state.page
- val currentState = page.state
- require(currentState is S)
- updateState { copy(page = (page as SpiderPage).copy(state = block(page.state))) }
- }
-
-}
-
-@Composable
-fun ImageGalleryScreen(viewModel: ImageGalleryViewModel, onTopLevelBack: () -> Unit) {
- LifecycleEffect(onStart = {
- viewModel.start()
- })
-
- val onNavigate: (SpiderPage?) -> 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 { }
- }
-
- }
-
-}
-
-
diff --git a/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryActivity.kt b/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryActivity.kt
new file mode 100644
index 0000000..00275ea
--- /dev/null
+++ b/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryActivity.kt
@@ -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.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>, 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() {
+
+ 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
+ }
+}
\ No newline at end of file
diff --git a/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryScreen.kt b/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryScreen.kt
new file mode 100644
index 0000000..d9fcc2a
--- /dev/null
+++ b/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryScreen.kt
@@ -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?) -> 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 { }
+ }
+
+ }
+
+}
diff --git a/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryViewModel.kt b/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryViewModel.kt
new file mode 100644
index 0000000..a8107b9
--- /dev/null
+++ b/features/home/src/main/kotlin/app/dapk/st/home/gallery/ImageGalleryViewModel.kt
@@ -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(
+ 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 { copy(content = Lce.Content(folders)) }
+ }
+
+ }
+
+ fun goTo(page: SpiderPage) {
+ 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 {
+ copy(content = Lce.Content(media))
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private inline fun updatePageState(crossinline block: S.() -> S) {
+ val page = state.page
+ val currentState = page.state
+ require(currentState is S)
+ updateState { copy(page = (page as SpiderPage).copy(state = block(page.state))) }
+ }
+
+}
\ No newline at end of file
diff --git a/features/navigator/src/main/kotlin/app/dapk/st/navigator/Navigator.kt b/features/navigator/src/main/kotlin/app/dapk/st/navigator/Navigator.kt
index 1e6efea..4fb7281 100644
--- a/features/navigator/src/main/kotlin/app/dapk/st/navigator/Navigator.kt
+++ b/features/navigator/src/main/kotlin/app/dapk/st/navigator/Navigator.kt
@@ -1,6 +1,7 @@
package app.dapk.st.navigator
import android.app.Activity
+import android.app.Instrumentation.ActivityResult
import android.app.PendingIntent
import android.content.Context
import android.content.Intent