From c10cbf373fc1f80b14d9ce611371f7a2aa710e46 Mon Sep 17 00:00:00 2001
From: genki <123@1234.com>
Date: Sat, 20 Dec 2025 17:54:16 -0500
Subject: [PATCH] Working Gallery and Repo - Earlydays!
---
app/src/main/AndroidManifest.xml | 3 +-
.../com/placeholder/sherpai2/MainActivity.kt | 60 +++++--
.../placeholder/sherpai2/data/photos/Photo.kt | 9 +-
.../sherpai2/data/photos/SamplePhoto.kt | 25 ---
.../sherpai2/data/repo/PhotoRepository.kt | 47 ++++-
.../sherpai2/domain/PhotoFunctions.kt | 3 +
.../sherpai2/domain/PhotoScanner.kt | 7 +
.../sherpai2/presentation/MainContentArea.kt | 104 -----------
.../{ => ui}/navigation/AppDestinations.kt | 2 +-
.../{ => ui}/navigation/MainScreen.kt | 8 +-
.../{ => ui}/presentation/AppDrawerContent.kt | 8 +-
.../ui/presentation/MainContentArea.kt | 56 ++++++
.../{ => ui}/presentation/PhotoListScreen.kt | 3 +-
.../sherpai2/ui/tourscreen/GalleryScreen.kt | 170 ++++++------------
.../sherpai2/ui/tourscreen/GalleryUiState.kt | 9 +
.../ui/tourscreen/GalleryViewModel.kt | 104 +++--------
16 files changed, 261 insertions(+), 357 deletions(-)
delete mode 100644 app/src/main/java/com/placeholder/sherpai2/data/photos/SamplePhoto.kt
create mode 100644 app/src/main/java/com/placeholder/sherpai2/domain/PhotoFunctions.kt
create mode 100644 app/src/main/java/com/placeholder/sherpai2/domain/PhotoScanner.kt
delete mode 100644 app/src/main/java/com/placeholder/sherpai2/presentation/MainContentArea.kt
rename app/src/main/java/com/placeholder/sherpai2/{ => ui}/navigation/AppDestinations.kt (96%)
rename app/src/main/java/com/placeholder/sherpai2/{ => ui}/navigation/MainScreen.kt (84%)
rename app/src/main/java/com/placeholder/sherpai2/{ => ui}/presentation/AppDrawerContent.kt (89%)
create mode 100644 app/src/main/java/com/placeholder/sherpai2/ui/presentation/MainContentArea.kt
rename app/src/main/java/com/placeholder/sherpai2/{ => ui}/presentation/PhotoListScreen.kt (89%)
create mode 100644 app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryUiState.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c2a2fd7..f4f001a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,5 +23,6 @@
-
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/placeholder/sherpai2/MainActivity.kt b/app/src/main/java/com/placeholder/sherpai2/MainActivity.kt
index 086bf6d..3a51d89 100644
--- a/app/src/main/java/com/placeholder/sherpai2/MainActivity.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/MainActivity.kt
@@ -1,30 +1,70 @@
package com.placeholder.sherpai2
+import android.Manifest
+import android.content.pm.PackageManager
+import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.LaunchedEffect
+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.core.content.ContextCompat
import com.placeholder.sherpai2.presentation.MainScreen // IMPORT your main screen
+import com.placeholder.sherpai2.ui.theme.SherpAI2Theme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ // 1. Define the permission needed based on API level
+ val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Manifest.permission.READ_MEDIA_IMAGES
+ } else {
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ }
setContent {
- // Assume you have a Theme file named SherpAI2Theme (standard for new projects)
- // Replace with your actual project theme if different
- MaterialTheme {
- Surface(
- modifier = Modifier.fillMaxSize(),
- color = MaterialTheme.colorScheme.background
- ) {
- // Launch the main navigation UI
- MainScreen()
+ SherpAI2Theme {
+ // 2. State to track if permission is granted
+ var hasPermission by remember {
+ mutableStateOf(ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED)
+ }
+
+ // 3. Launcher to ask for permission
+ val launcher = rememberLauncherForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { isGranted ->
+ hasPermission = isGranted
+ }
+
+ // 4. Trigger request on start
+ LaunchedEffect(Unit) {
+ if (!hasPermission) launcher.launch(permission)
+ }
+
+ if (hasPermission) {
+ MainScreen() // Your existing screen that holds MainContentArea
+ } else {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Text("Please grant storage permission to view photos.")
+ }
}
}
+
+
+ }
}
- }
}
+
diff --git a/app/src/main/java/com/placeholder/sherpai2/data/photos/Photo.kt b/app/src/main/java/com/placeholder/sherpai2/data/photos/Photo.kt
index 7c8fc22..7370424 100644
--- a/app/src/main/java/com/placeholder/sherpai2/data/photos/Photo.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/data/photos/Photo.kt
@@ -1,10 +1,13 @@
package com.placeholder.sherpai2.data.photos
+import android.net.Uri
+
data class Photo(
- val id: String,
- val uri: String,
+ val id: Long,
+ val uri: Uri,
val title: String? = null,
- val timestamp: Long
+ val size: Long,
+ val dateModified: Int
)
data class Album(
diff --git a/app/src/main/java/com/placeholder/sherpai2/data/photos/SamplePhoto.kt b/app/src/main/java/com/placeholder/sherpai2/data/photos/SamplePhoto.kt
deleted file mode 100644
index 095bd2d..0000000
--- a/app/src/main/java/com/placeholder/sherpai2/data/photos/SamplePhoto.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.placeholder.sherpai2.data.photos
-
-import com.placeholder.sherpai2.data.photos.Photo
-
-object SamplePhotoSource {
-
- fun loadPhotos(): List {
- return listOf(
- Photo(
- id = "1",
- uri = "file:///sdcard/Pictures/20200115_181335.jpg",
- title = "Sample One",
- timestamp = System.currentTimeMillis()
- ),
- Photo(
- id = "2",
- uri = "file:///sdcard/Pictures/20200115_181417.jpg",
- title = "Sample Two",
- timestamp = System.currentTimeMillis()
- )
- )
- }
-}
- // /sdcard/Pictures/20200115_181335.jpg
-// /sdcard/Pictures/20200115_181417.jpg
\ No newline at end of file
diff --git a/app/src/main/java/com/placeholder/sherpai2/data/repo/PhotoRepository.kt b/app/src/main/java/com/placeholder/sherpai2/data/repo/PhotoRepository.kt
index 41cc9f8..d6286cb 100644
--- a/app/src/main/java/com/placeholder/sherpai2/data/repo/PhotoRepository.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/data/repo/PhotoRepository.kt
@@ -1,11 +1,50 @@
package com.placeholder.sherpai2.data.repo
+import android.content.Context
+import android.provider.MediaStore
+import android.content.ContentUris
+import android.net.Uri
+import android.os.Environment
+import android.util.Log
import com.placeholder.sherpai2.data.photos.Photo
-import com.placeholder.sherpai2.data.photos.SamplePhotoSource
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.io.File
-class PhotoRepository {
+class PhotoRepository(private val context: Context) {
- fun getPhotos(): List {
- return SamplePhotoSource.loadPhotos()
+ fun scanExternalStorage(): Result> {
+ // Best Practice: Use Environment.getExternalStorageDirectory()
+ // only as a fallback or starting point for legacy support.
+ val rootPath = Environment.getExternalStorageDirectory()
+
+ return runCatching {
+ val photos = mutableListOf()
+
+ if (rootPath.exists() && rootPath.isDirectory) {
+ // walkTopDown is efficient but can throw AccessDeniedException
+ rootPath.walkTopDown()
+ .maxDepth(3) // Performance Best Practice: Don't scan the whole phone
+ .filter { it.isFile && isImageFile(it.extension) }
+ .forEach { file ->
+ photos.add(mapFileToPhoto(file))
+ }
+ }
+ photos
+ }.onFailure { e ->
+ Log.e("PhotoRepo", "Failed to scan filesystem", e)
+ }
}
+
+ private fun mapFileToPhoto(file: File): Photo {
+ return Photo(
+ id = file.path.hashCode().toLong(),
+ uri = Uri.fromFile(file),
+ title = file.name,
+ size = file.length(),
+ dateModified = (file.lastModified() / 1000).toInt()
+ )
+ }
+
+ private fun isImageFile(ext: String) = listOf("jpg", "jpeg", "png").contains(ext.lowercase())
}
\ No newline at end of file
diff --git a/app/src/main/java/com/placeholder/sherpai2/domain/PhotoFunctions.kt b/app/src/main/java/com/placeholder/sherpai2/domain/PhotoFunctions.kt
new file mode 100644
index 0000000..a31556f
--- /dev/null
+++ b/app/src/main/java/com/placeholder/sherpai2/domain/PhotoFunctions.kt
@@ -0,0 +1,3 @@
+package com.placeholder.sherpai2.domain
+
+//fun getAllPhotos(context: Context): List {
diff --git a/app/src/main/java/com/placeholder/sherpai2/domain/PhotoScanner.kt b/app/src/main/java/com/placeholder/sherpai2/domain/PhotoScanner.kt
new file mode 100644
index 0000000..18bf55b
--- /dev/null
+++ b/app/src/main/java/com/placeholder/sherpai2/domain/PhotoScanner.kt
@@ -0,0 +1,7 @@
+package com.placeholder.sherpai2.domain
+
+import android.content.Context
+
+class PhotoDuplicateScanner(private val context: Context) {
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/placeholder/sherpai2/presentation/MainContentArea.kt b/app/src/main/java/com/placeholder/sherpai2/presentation/MainContentArea.kt
deleted file mode 100644
index 7602a65..0000000
--- a/app/src/main/java/com/placeholder/sherpai2/presentation/MainContentArea.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-// In presentation/MainContentArea.kt
-package com.placeholder.sherpai2.presentation
-
-import androidx.compose.ui.graphics.Color
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.placeholder.sherpai2.navigation.AppDestinations
-import com.placeholder.sherpai2.ui.theme.SherpAI2Theme
-
-@Composable
-fun MainContentArea(currentScreen: AppDestinations, modifier: Modifier = Modifier) {
- Box(
- modifier = modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.surfaceVariant),
- contentAlignment = Alignment.Center
- ) {
- // Swaps the UI content based on the selected screen from the drawer
- when (currentScreen) {
- AppDestinations.Tour -> TwinAlbumScreen("New Collections." , "Classics")
- AppDestinations.Search -> SimplePlaceholder("Search for your photo.")
- AppDestinations.Models -> SimplePlaceholder("Models Screen: Manage your LoRA/embeddings.")
- AppDestinations.Inventory -> SimplePlaceholder("Inventory Screen: View all collected data.")
- AppDestinations.Train -> SimplePlaceholder("Train Screen: Start the LoRA adaptation process.")
- AppDestinations.Tags -> SimplePlaceholder("Tags Screen: Create and edit custom tags.")
- AppDestinations.Upload -> SimplePlaceholder("Upload Screen: Import new photos/data.")
- AppDestinations.Settings -> SimplePlaceholder("Settings Screen: Configure app behavior.")
- }
- }
-}
-
-@Composable
-private fun SimplePlaceholder(text: String) {
- Text(
- text = text,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(16.dp)
- )
-}
-
-
-@Composable
-fun AlbumPreview(title: String, color: Color) {
- Box(
- modifier = Modifier
- .padding(8.dp)
- .aspectRatio(1f) // Makes it a square
- .background(color, shape = RoundedCornerShape(12.dp)),
- contentAlignment = Alignment.Center
- ) {
- Text(text = title, color = Color.White, fontWeight = FontWeight.Bold)
- }
-}
-
-@Composable
-fun TwinAlbumScreen(albumNameA: String, albumNameB: String) {
- Column(modifier = Modifier.fillMaxSize()) {
- // --- Top 40% Section ---
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .weight(0.30f) // This takes exactly 40% of vertical space
- .padding(16.dp),
- horizontalArrangement = Arrangement.spacedBy(2.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- // These two occupy the 40% area
- Box(modifier = Modifier.weight(1f)) {
- AlbumPreview("Mackenzie Hazer", Color.Blue)
- }
- Box(modifier = Modifier.weight(1f)) {
- AlbumPreview("Winifred", Color.Magenta)
- }
- }
-
- // --- Bottom 60% Section ---
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .weight(0.6f) // This takes the remaining 60%
- .background(Color.LightGray.copy(alpha = 0.2f))
- ) {
- Text("Other content goes here", modifier = Modifier.align(Alignment.Center))
- }
- }
-}
-@Preview
-@Composable
-fun PreviewTwinTopRow() {
- SherpAI2Theme {
- TwinAlbumScreen("Album A", "Album B")
- }
-}
-
-
diff --git a/app/src/main/java/com/placeholder/sherpai2/navigation/AppDestinations.kt b/app/src/main/java/com/placeholder/sherpai2/ui/navigation/AppDestinations.kt
similarity index 96%
rename from app/src/main/java/com/placeholder/sherpai2/navigation/AppDestinations.kt
rename to app/src/main/java/com/placeholder/sherpai2/ui/navigation/AppDestinations.kt
index 992d2dc..ab4baf3 100644
--- a/app/src/main/java/com/placeholder/sherpai2/navigation/AppDestinations.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/navigation/AppDestinations.kt
@@ -1,5 +1,5 @@
// In navigation/AppDestinations.kt
-package com.placeholder.sherpai2.navigation
+package com.placeholder.sherpai2.ui.navigation
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
diff --git a/app/src/main/java/com/placeholder/sherpai2/navigation/MainScreen.kt b/app/src/main/java/com/placeholder/sherpai2/ui/navigation/MainScreen.kt
similarity index 84%
rename from app/src/main/java/com/placeholder/sherpai2/navigation/MainScreen.kt
rename to app/src/main/java/com/placeholder/sherpai2/ui/navigation/MainScreen.kt
index 5baf9c2..50ce1ce 100644
--- a/app/src/main/java/com/placeholder/sherpai2/navigation/MainScreen.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/navigation/MainScreen.kt
@@ -1,13 +1,17 @@
// In presentation/MainScreen.kt
package com.placeholder.sherpai2.presentation
+import GalleryViewModel
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
-import com.placeholder.sherpai2.navigation.AppDestinations
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.placeholder.sherpai2.ui.navigation.AppDestinations
+import com.placeholder.sherpai2.ui.presentation.AppDrawerContent
+import com.placeholder.sherpai2.ui.presentation.MainContentArea
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@@ -18,6 +22,7 @@ fun MainScreen() {
// State to track which screen is currently visible
// var currentScreen by remember { mutableStateOf(AppDestinations.Search) }
var currentScreen: AppDestinations by remember { mutableStateOf(AppDestinations.Search) }
+ val galleryViewModel: GalleryViewModel = viewModel()
// ModalNavigationDrawer provides the left sidebar UI/UX
ModalNavigationDrawer(
@@ -50,6 +55,7 @@ fun MainScreen() {
// Displays the content for the currently selected screen
MainContentArea(
currentScreen = currentScreen,
+ galleryViewModel = galleryViewModel,
modifier = Modifier.padding(paddingValues)
)
}
diff --git a/app/src/main/java/com/placeholder/sherpai2/presentation/AppDrawerContent.kt b/app/src/main/java/com/placeholder/sherpai2/ui/presentation/AppDrawerContent.kt
similarity index 89%
rename from app/src/main/java/com/placeholder/sherpai2/presentation/AppDrawerContent.kt
rename to app/src/main/java/com/placeholder/sherpai2/ui/presentation/AppDrawerContent.kt
index b17fa59..53d2d25 100644
--- a/app/src/main/java/com/placeholder/sherpai2/presentation/AppDrawerContent.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/presentation/AppDrawerContent.kt
@@ -1,14 +1,14 @@
// In presentation/AppDrawerContent.kt
-package com.placeholder.sherpai2.presentation
+package com.placeholder.sherpai2.ui.presentation
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import com.placeholder.sherpai2.navigation.AppDestinations
-import com.placeholder.sherpai2.navigation.mainDrawerItems
-import com.placeholder.sherpai2.navigation.utilityDrawerItems
+import com.placeholder.sherpai2.ui.navigation.AppDestinations
+import com.placeholder.sherpai2.ui.navigation.mainDrawerItems
+import com.placeholder.sherpai2.ui.navigation.utilityDrawerItems
@OptIn(ExperimentalMaterial3Api::class)
@Composable
diff --git a/app/src/main/java/com/placeholder/sherpai2/ui/presentation/MainContentArea.kt b/app/src/main/java/com/placeholder/sherpai2/ui/presentation/MainContentArea.kt
new file mode 100644
index 0000000..51541a3
--- /dev/null
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/presentation/MainContentArea.kt
@@ -0,0 +1,56 @@
+// In presentation/MainContentArea.kt
+package com.placeholder.sherpai2.ui.presentation
+
+import GalleryScreen
+import GalleryViewModel
+import androidx.compose.ui.graphics.Color
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.placeholder.sherpai2.ui.navigation.AppDestinations
+import com.placeholder.sherpai2.ui.theme.SherpAI2Theme
+import androidx.lifecycle.viewmodel.compose.viewModel
+@Composable
+fun MainContentArea(currentScreen: AppDestinations, modifier: Modifier = Modifier, galleryViewModel: GalleryViewModel = viewModel() ) {
+ val uiState by galleryViewModel.uiState.collectAsState()
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ .background(Color.Red),
+ contentAlignment = Alignment.Center
+ ) {
+ // Swaps the UI content based on the selected screen from the drawer
+ when (currentScreen) {
+ AppDestinations.Tour -> GalleryScreen(state = uiState, modifier = Modifier)
+ AppDestinations.Search -> SimplePlaceholder("Find Any Photos.")
+ AppDestinations.Models -> SimplePlaceholder("Models Screen: Manage your LoRA/embeddings.")
+ AppDestinations.Inventory -> SimplePlaceholder("Inventory Screen: View all collected data.")
+ AppDestinations.Train -> SimplePlaceholder("Train Screen: Start the LoRA adaptation process.")
+ AppDestinations.Tags -> SimplePlaceholder("Tags Screen: Create and edit custom tags.")
+ AppDestinations.Upload -> SimplePlaceholder("Upload Screen: Import new photos/data.")
+ AppDestinations.Settings -> SimplePlaceholder("Settings Screen: Configure app behavior.")
+ }
+ }
+}
+
+@Composable
+private fun SimplePlaceholder(text: String) {
+ Text(
+ text = text,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(16.dp).background(color = Color.Magenta)
+ )
+}
+
+
+
diff --git a/app/src/main/java/com/placeholder/sherpai2/presentation/PhotoListScreen.kt b/app/src/main/java/com/placeholder/sherpai2/ui/presentation/PhotoListScreen.kt
similarity index 89%
rename from app/src/main/java/com/placeholder/sherpai2/presentation/PhotoListScreen.kt
rename to app/src/main/java/com/placeholder/sherpai2/ui/presentation/PhotoListScreen.kt
index e911100..18815ed 100644
--- a/app/src/main/java/com/placeholder/sherpai2/presentation/PhotoListScreen.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/presentation/PhotoListScreen.kt
@@ -1,4 +1,4 @@
-package com.placeholder.sherpai2.presentation
+package com.placeholder.sherpai2.ui.presentation
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
@@ -11,7 +11,6 @@ import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
import com.placeholder.sherpai2.data.photos.Photo
-import com.placeholder.sherpai2.data.photos.SamplePhotoSource
@Composable
fun PhotoListScreen(
diff --git a/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryScreen.kt b/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryScreen.kt
index 6165294..a101cff 100644
--- a/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryScreen.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryScreen.kt
@@ -1,141 +1,73 @@
-package com.placeholder.sherpai2.ui.tourscreen
-
-import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+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.*
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.lifecycle.viewmodel.compose.viewModel
-import com.placeholder.sherpai2.data.photos.Album
-import com.placeholder.sherpai2.ui.theme.SherpAI2Theme
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
-import com.placeholder.sherpai2.presentation.AlbumPreview
+import com.placeholder.sherpai2.data.photos.Photo
+import com.placeholder.sherpai2.ui.tourscreen.GalleryUiState
-class GalleryScreen {
-}
-@Composable
-fun GalleryContent(topAlbums: List) {
- Column(modifier = Modifier.fillMaxSize()) {
- // --- Top 40% Section ---
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .weight(0.4f)
- .padding(8.dp)
- ) {
- // We use .getOrNull to safely check if the albums exist in the list
- val firstAlbum = topAlbums.getOrNull(0)
- val secondAlbum = topAlbums.getOrNull(1)
-
- if (firstAlbum != null) {
- AlbumPreviewBoxRandom(album = firstAlbum, modifier = Modifier.weight(1f))
- } else {
- Box(modifier = Modifier.weight(1f)) // Empty placeholder
- }
-
- if (secondAlbum != null) {
- AlbumPreviewBoxRandom(album = secondAlbum, modifier = Modifier.weight(1f))
- } else {
- Box(modifier = Modifier.weight(1f)) // Empty placeholder
- }
- }
-
- // --- Bottom 60% Section ---
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .weight(0.6f)
- ) {
- Text("Lower Content Area", modifier = Modifier.align(Alignment.Center))
- }
- }
-}
-// --- STATEFUL (Use this for Production) ---
-@Composable
-fun GalleryScreen(viewModel: GalleryViewModel = viewModel()) {
- val albumList by viewModel.albums
- GalleryContent(topAlbums = albumList)
-}
-
-// --- PREVIEW ---
-@Preview(showBackground = true)
-@Composable
-fun GalleryPreview() {
- SherpAI2Theme {
- // Feed mock data into the Stateless version
- GalleryContent(topAlbums = listOf(/* Fake Album Objects */))
- }
-}
@Composable
-fun AlbumPreviewBox(
- album: Album,
- modifier: Modifier = Modifier
+fun GalleryScreen(
+ state: GalleryUiState,
+ modifier: Modifier = Modifier // Add default modifier
) {
- Card(
- modifier = modifier
- .padding(8.dp)
- .aspectRatio(1f), // Keeps it square
- shape = RoundedCornerShape(12.dp),
- elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
+ // Note: If this is inside MainContentArea, you might not need a second Scaffold.
+ // Let's use a Column or Box to ensure it fills the space correctly.
+ Column(
+ modifier = modifier.fillMaxSize()
) {
- Box(
- modifier = Modifier
- .fillMaxSize()
- .background(Color.DarkGray), // Background until image is loaded
- contentAlignment = Alignment.BottomStart
- ) {
- // Title Overlay
- Text(
- text = album.title,
- modifier = Modifier
- .fillMaxWidth()
- .background(Color.Black.copy(alpha = 0.5f))
- .padding(8.dp),
- color = Color.White,
- style = MaterialTheme.typography.labelLarge
- )
- }
- }
-}
-
-@Composable
-fun AlbumPreviewBoxRandom(album: Album, modifier: Modifier = Modifier) {
- Card(
- modifier = modifier.padding(8.dp).aspectRatio(1f),
- shape = RoundedCornerShape(12.dp)
- ) {
- Row(modifier = Modifier.fillMaxSize()) {
- // Loop through the 3 random photos in the album
- album.photos.forEach { photo ->
- AsyncImage(
- model = photo.uri,
- contentDescription = null,
- modifier = Modifier
- .weight(1f)
- .fillMaxHeight(),
- contentScale = ContentScale.Crop // Fills the space nicely
- )
+ Text(
+ text = "Photo Gallery",
+ style = MaterialTheme.typography.headlineMedium,
+ modifier = Modifier.padding(16.dp)
+ )
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ when (state) {
+ is GalleryUiState.Loading -> CircularProgressIndicator()
+ is GalleryUiState.Error -> Text(text = state.message)
+ is GalleryUiState.Success -> {
+ if (state.photos.isEmpty()) {
+ Text("No photos found. Try adding some to the emulator!")
+ } else {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(3),
+ modifier = Modifier.fillMaxSize(),
+ horizontalArrangement = Arrangement.spacedBy(2.dp),
+ verticalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ items(state.photos) { photo ->
+ PhotoItem(photo)
+ }
+ }
+ }
+ }
}
}
}
+}
+@Composable
+fun PhotoItem(photo: Photo) {
+ AsyncImage(
+ model = photo.uri,
+ contentDescription = photo.title,
+ modifier = Modifier
+ .aspectRatio(1f) // Makes it a square
+ .fillMaxWidth(),
+ contentScale = ContentScale.Crop
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryUiState.kt b/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryUiState.kt
new file mode 100644
index 0000000..78f8dc2
--- /dev/null
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryUiState.kt
@@ -0,0 +1,9 @@
+package com.placeholder.sherpai2.ui.tourscreen
+
+import com.placeholder.sherpai2.data.photos.Photo
+
+sealed class GalleryUiState {
+ object Loading : GalleryUiState()
+ data class Success(val photos: List) : GalleryUiState()
+ data class Error(val message: String) : GalleryUiState()
+}
diff --git a/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryViewModel.kt b/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryViewModel.kt
index b18fb68..c521a17 100644
--- a/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryViewModel.kt
+++ b/app/src/main/java/com/placeholder/sherpai2/ui/tourscreen/GalleryViewModel.kt
@@ -1,96 +1,34 @@
-package com.placeholder.sherpai2.ui.tourscreen
-
-import android.os.Environment
-import androidx.compose.runtime.mutableStateOf
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
-import com.placeholder.sherpai2.data.photos.AlbumPhoto
-import androidx.compose.runtime.State
-import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.viewModelScope
-import com.placeholder.sherpai2.data.photos.Album // Import your data model
import com.placeholder.sherpai2.data.photos.Photo
-import kotlinx.coroutines.Dispatchers
+import com.placeholder.sherpai2.data.repo.PhotoRepository
+import com.placeholder.sherpai2.ui.tourscreen.GalleryUiState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import java.io.File
-class GalleryViewModel : ViewModel() {
- // This is the 'albums' reference your UI is looking for
- private val _albums = mutableStateOf>(emptyList())
- val albums: State> = _albums
+class GalleryViewModel(application: Application) : AndroidViewModel(application) {
+
+ // Initialize repository with the application context
+ private val repository = PhotoRepository(application)
+
+ private val _uiState = MutableStateFlow(GalleryUiState.Loading)
+ val uiState = _uiState.asStateFlow()
init {
- fetchInitialData()
+ loadPhotos()
}
- private fun fetchInitialData() {
- viewModelScope.launch(Dispatchers.IO) {
- val directory = File("/sdcard/photos") // Or: Environment.getExternalStorageDirectory()
+ fun loadPhotos() {
+ viewModelScope.launch {
+ val result = repository.scanExternalStorage()
- if (directory.exists() && directory.isDirectory) {
- val photoFiles = directory.listFiles { file ->
- file.extension.lowercase() in listOf("jpg", "jpeg", "png")
- } ?: emptyArray()
-
- // Map files to your Photo data class
- val loadedPhotos = photoFiles.map { file ->
- Photo(
- id = file.absolutePath,
- uri = file.toURI().toString(),
- title = file.nameWithoutExtension,
- timestamp = file.lastModified()
- )
- }
-
- // Create an Album object with these photos
- val mainAlbum = Album(
- id = "local_photos",
- title = "Phone Gallery",
- photos = loadedPhotos
- )
-
- // Update the UI State on the Main Thread
- withContext(Dispatchers.Main) {
- _albums.value = listOf(mainAlbum)
- }
- }
- }
- }
-
- private fun fetchInitialDataRandom() {
- viewModelScope.launch(Dispatchers.IO) {
- val root = Environment.getExternalStorageDirectory()
- val directory = File(root, "photos")
-
- if (directory.exists() && directory.isDirectory) {
- // 1. Filter specifically for .jpg files
- val photoFiles = directory.listFiles { file ->
- file.extension.lowercase() == "jpg"
- } ?: emptyArray()
-
- // 2. Shuffle the list and take only the first 3
- val randomPhotos = photoFiles.asSequence()
- .shuffled()
- .take(3)
- .map { file ->
- Photo(
- id = file.absolutePath,
- uri = file.toURI().toString(),
- title = file.nameWithoutExtension,
- timestamp = file.lastModified()
- )
- }.toList()
-
- // 3. Update the state with these 3 random photos
- val mainAlbum = Album(
- id = "random_selection",
- title = "Random Picks",
- photos = randomPhotos
- )
-
- withContext(Dispatchers.Main) {
- _albums.value = listOf(mainAlbum)
- }
+ result.onSuccess { photos ->
+ _uiState.value = GalleryUiState.Success(photos)
+ }.onFailure { error ->
+ _uiState.value = GalleryUiState.Error(error.message ?: "Unknown Error")
}
}
}