Added onClick from Albumviewscreen.kt

This commit is contained in:
genki
2026-01-10 22:00:23 -05:00
parent 11a1a33764
commit 52c5643b5b
5 changed files with 356 additions and 108 deletions

View File

@@ -85,4 +85,7 @@ dependencies {
// Gson for storing FloatArrays in Room // Gson for storing FloatArrays in Room
implementation(libs.gson) implementation(libs.gson)
// Zoomable
implementation(libs.zoomable)
} }

View File

@@ -1,6 +1,7 @@
package com.placeholder.sherpai2.ui.album package com.placeholder.sherpai2.ui.album
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.* import androidx.compose.foundation.lazy.grid.*
@@ -23,14 +24,12 @@ import com.placeholder.sherpai2.ui.search.DisplayMode
import com.placeholder.sherpai2.ui.search.components.ImageGridItem import com.placeholder.sherpai2.ui.search.components.ImageGridItem
/** /**
* AlbumViewScreen - Beautiful album detail view * AlbumViewScreen - UPDATED with clickable images
* *
* Features: * Changes:
* - Album stats * - PhotoCard now clickable
* - Search within album * - Passes onImageClick to ImageGridItem
* - Date filtering * - Entire card surface clickable as backup
* - Simple/Verbose toggle
* - Clean person display
*/ */
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -279,6 +278,9 @@ private fun StatItem(icon: androidx.compose.ui.graphics.vector.ImageVector, labe
} }
} }
/**
* PhotoCard - UPDATED: Now fully clickable
*/
@Composable @Composable
private fun PhotoCard( private fun PhotoCard(
photo: AlbumPhoto, photo: AlbumPhoto,
@@ -286,15 +288,19 @@ private fun PhotoCard(
onImageClick: (String) -> Unit onImageClick: (String) -> Unit
) { ) {
Card( Card(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.clickable { onImageClick(photo.image.imageUri) }, // ✅ ENTIRE CARD CLICKABLE
shape = RoundedCornerShape(12.dp) shape = RoundedCornerShape(12.dp)
) { ) {
Column { Column {
// Image (also clickable via ImageGridItem)
ImageGridItem( ImageGridItem(
image = photo.image, image = photo.image,
onClick = { onImageClick(photo.image.imageUri) } onClick = { onImageClick(photo.image.imageUri) } // ✅ IMAGE CLICKABLE
) )
// Person tags
if (photo.persons.isNotEmpty()) { if (photo.persons.isNotEmpty()) {
when (displayMode) { when (displayMode) {
DisplayMode.SIMPLE -> { DisplayMode.SIMPLE -> {

View File

@@ -1,86 +1,322 @@
package com.placeholder.sherpai2.ui.imagedetail package com.placeholder.sherpai2.ui.imagedetail
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.placeholder.sherpai2.data.local.entity.TagEntity
import com.placeholder.sherpai2.ui.imagedetail.viewmodel.ImageDetailViewModel import com.placeholder.sherpai2.ui.imagedetail.viewmodel.ImageDetailViewModel
import net.engawapg.lib.zoomable.rememberZoomState
import net.engawapg.lib.zoomable.zoomable
import java.net.URLEncoder
/** /**
* ImageDetailScreen * ImageDetailScreen - COMPLETE with navigation and tags
* *
* Purpose: * Features:
* - Add tags * - Full-screen zoomable image
* - Remove tags * - Previous/Next navigation buttons
* - Validate write propagation * - Image counter (3/45)
* - Tags button (toggle show/hide)
* - Shows all tags on photo
*/ */
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ImageDetailScreen( fun ImageDetailScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageUri: String, imageUri: String,
onBack: () -> Unit onBack: () -> Unit,
navController: NavController? = null,
allImageUris: List<String> = emptyList(), // Pass from caller
viewModel: ImageDetailViewModel = hiltViewModel() // ✅ FIXED: Use hiltViewModel
) { ) {
val viewModel: ImageDetailViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
LaunchedEffect(imageUri) { LaunchedEffect(imageUri) {
viewModel.loadImage(imageUri) viewModel.loadImage(imageUri)
} }
val tags by viewModel.tags.collectAsStateWithLifecycle() val tags by viewModel.tags.collectAsStateWithLifecycle()
var showTags by remember { mutableStateOf(false) }
var newTag by remember { mutableStateOf("") } // Navigation state
val currentIndex = if (allImageUris.isNotEmpty()) allImageUris.indexOf(imageUri) else -1
val canGoPrevious = currentIndex > 0
val canGoNext = currentIndex in 0 until allImageUris.size - 1
Scaffold(
topBar = {
TopAppBar(
title = {
if (currentIndex >= 0) {
Text(
"${currentIndex + 1} / ${allImageUris.size}",
style = MaterialTheme.typography.titleMedium
)
} else {
Text("Photo")
}
},
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.Default.ArrowBack, "Back")
}
},
actions = {
// Tags toggle button
IconButton(onClick = { showTags = !showTags }) {
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (tags.isNotEmpty()) {
Badge(
containerColor = if (showTags)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.surfaceVariant
) {
Text(
tags.size.toString(),
color = if (showTags)
MaterialTheme.colorScheme.onPrimary
else
MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
Icon(
if (showTags) Icons.Default.Label else Icons.Default.LocalOffer,
"Show Tags",
tint = if (showTags)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
// Previous button
if (navController != null && allImageUris.isNotEmpty()) {
IconButton(
onClick = {
if (canGoPrevious) {
val prevUri = allImageUris[currentIndex - 1]
val encoded = URLEncoder.encode(prevUri, "UTF-8")
navController.navigate("image_detail/$encoded") {
popUpTo("image_detail/${URLEncoder.encode(imageUri, "UTF-8")}") {
inclusive = true
}
}
}
},
enabled = canGoPrevious
) {
Icon(Icons.Default.KeyboardArrowLeft, "Previous")
}
// Next button
IconButton(
onClick = {
if (canGoNext) {
val nextUri = allImageUris[currentIndex + 1]
val encoded = URLEncoder.encode(nextUri, "UTF-8")
navController.navigate("image_detail/$encoded") {
popUpTo("image_detail/${URLEncoder.encode(imageUri, "UTF-8")}") {
inclusive = true
}
}
}
},
enabled = canGoNext
) {
Icon(Icons.Default.KeyboardArrowRight, "Next")
}
}
}
)
}
) { paddingValues ->
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(12.dp) .padding(paddingValues)
) { ) {
// Zoomable image
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.background(Color.Black)
) {
val zoomState = rememberZoomState()
AsyncImage( AsyncImage(
model = imageUri, model = imageUri,
contentDescription = null, contentDescription = "Photo",
modifier = Modifier
.fillMaxSize()
.zoomable(zoomState),
contentScale = ContentScale.Fit
)
}
// Tags panel (slides up when enabled)
if (showTags) {
Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(1f) .heightIn(max = 300.dp),
) color = MaterialTheme.colorScheme.surfaceVariant,
tonalElevation = 3.dp
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = newTag,
onValueChange = { newTag = it },
label = { Text("Add tag") },
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = {
viewModel.addTag(newTag)
newTag = ""
},
modifier = Modifier.padding(top = 8.dp)
) { ) {
Text("Add Tag") LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
item {
Text(
"Tags (${tags.size})",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
} }
Spacer(modifier = Modifier.height(16.dp)) if (tags.isEmpty()) {
item {
Text(
"No tags yet",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
tags.forEach { tag -> items(tags, key = { it.tagId }) { tag ->
TagCard(
tag = tag,
onRemove = { viewModel.removeTag(tag) }
)
}
}
}
}
}
}
}
@Composable
private fun TagCard(
tag: TagEntity,
onRemove: () -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = when (tag.type) {
"PERSON" -> MaterialTheme.colorScheme.primaryContainer
"SYSTEM" -> MaterialTheme.colorScheme.secondaryContainer
else -> MaterialTheme.colorScheme.tertiaryContainer
}
),
shape = RoundedCornerShape(8.dp)
) {
Row( Row(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth() verticalAlignment = Alignment.CenterVertically
) { ) {
Text(tag.value) Column(modifier = Modifier.weight(1f)) {
TextButton(onClick = { viewModel.removeTag(tag) }) { Row(
Text("Remove") horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = when (tag.type) {
"PERSON" -> Icons.Default.Face
"SYSTEM" -> Icons.Default.AutoAwesome
else -> Icons.Default.Label
},
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = when (tag.type) {
"PERSON" -> MaterialTheme.colorScheme.primary
"SYSTEM" -> MaterialTheme.colorScheme.secondary
else -> MaterialTheme.colorScheme.tertiary
}
)
Text(
text = tag.getDisplayValue(), // Uses TagEntity's built-in method
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = tag.type.lowercase().replaceFirstChar { it.uppercase() },
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = "",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = formatTimestamp(tag.createdAt),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
// Remove button (only for user-created tags)
if (tag.isUserTag()) {
IconButton(
onClick = onRemove,
colors = IconButtonDefaults.iconButtonColors(
contentColor = MaterialTheme.colorScheme.error
)
) {
Icon(Icons.Default.Delete, "Remove tag")
} }
} }
} }
} }
} }
/**
* Format timestamp to relative time
*/
private fun formatTimestamp(timestamp: Long): String {
val now = System.currentTimeMillis()
val diff = now - timestamp
return when {
diff < 60_000 -> "Just now"
diff < 3600_000 -> "${diff / 60_000}m ago"
diff < 86400_000 -> "${diff / 3600_000}h ago"
diff < 604800_000 -> "${diff / 86400_000}d ago"
else -> "${diff / 604800_000}w ago"
}
}

View File

@@ -7,6 +7,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@@ -14,6 +15,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.placeholder.sherpai2.ui.devscreens.DummyScreen import com.placeholder.sherpai2.ui.devscreens.DummyScreen
import com.placeholder.sherpai2.ui.album.AlbumViewScreen import com.placeholder.sherpai2.ui.album.AlbumViewScreen
import com.placeholder.sherpai2.ui.album.AlbumViewModel
import com.placeholder.sherpai2.ui.explore.ExploreScreen import com.placeholder.sherpai2.ui.explore.ExploreScreen
import com.placeholder.sherpai2.ui.imagedetail.ImageDetailScreen import com.placeholder.sherpai2.ui.imagedetail.ImageDetailScreen
import com.placeholder.sherpai2.ui.modelinventory.PersonInventoryScreen import com.placeholder.sherpai2.ui.modelinventory.PersonInventoryScreen
@@ -30,20 +32,12 @@ import java.net.URLDecoder
import java.net.URLEncoder import java.net.URLEncoder
/** /**
* AppNavHost - Main navigation graph * AppNavHost - UPDATED with image list navigation
* UPDATED: Added Explore and Tags screens
* *
* Complete flow: * Changes:
* - Photo browsing (Search, Explore, Detail) * - Search/Album screens pass full image list to detail screen
* - Face recognition (Inventory, Train) * - Detail screen can navigate prev/next
* - Organization (Tags, Upload) * - Image URIs stored in SavedStateHandle for navigation
* - Settings
*
* Features:
* - URL encoding for safe navigation
* - Proper back stack management
* - State preservation
* - Beautiful placeholders
*/ */
@Composable @Composable
fun AppNavHost( fun AppNavHost(
@@ -61,19 +55,27 @@ fun AppNavHost(
// ========================================== // ==========================================
/** /**
* SEARCH SCREEN * SEARCH SCREEN - UPDATED: Stores image list for navigation
* Main photo browser with face tag search
*/ */
composable(AppRoutes.SEARCH) { composable(AppRoutes.SEARCH) {
val searchViewModel: SearchViewModel = hiltViewModel() val searchViewModel: SearchViewModel = hiltViewModel()
val images by searchViewModel
.searchImages()
.collectAsStateWithLifecycle(initialValue = emptyList())
SearchScreen( SearchScreen(
searchViewModel = searchViewModel, searchViewModel = searchViewModel,
onImageClick = { imageUri -> onImageClick = { imageUri ->
// Store full image list for prev/next navigation
val allImageUris = images.map { it.image.imageUri }
navController.currentBackStackEntry
?.savedStateHandle
?.set("all_image_uris", allImageUris)
val encodedUri = URLEncoder.encode(imageUri, "UTF-8") val encodedUri = URLEncoder.encode(imageUri, "UTF-8")
navController.navigate("${AppRoutes.IMAGE_DETAIL}/$encodedUri") navController.navigate("${AppRoutes.IMAGE_DETAIL}/$encodedUri")
}, },
onAlbumClick = { tagValue -> onAlbumClick = { tagValue ->
// Navigate to tag-based album
navController.navigate("album/tag/$tagValue") navController.navigate("album/tag/$tagValue")
} }
) )
@@ -81,7 +83,6 @@ fun AppNavHost(
/** /**
* EXPLORE SCREEN * EXPLORE SCREEN
* Browse smart albums (auto-generated from tags)
*/ */
composable(AppRoutes.EXPLORE) { composable(AppRoutes.EXPLORE) {
ExploreScreen( ExploreScreen(
@@ -92,8 +93,7 @@ fun AppNavHost(
} }
/** /**
* IMAGE DETAIL SCREEN * IMAGE DETAIL SCREEN - UPDATED: Receives image list for navigation
* Single photo view with metadata
*/ */
composable( composable(
route = "${AppRoutes.IMAGE_DETAIL}/{imageUri}", route = "${AppRoutes.IMAGE_DETAIL}/{imageUri}",
@@ -107,15 +107,22 @@ fun AppNavHost(
?.let { URLDecoder.decode(it, "UTF-8") } ?.let { URLDecoder.decode(it, "UTF-8") }
?: error("imageUri missing from navigation") ?: error("imageUri missing from navigation")
// Get image list from previous screen
val allImageUris = navController.previousBackStackEntry
?.savedStateHandle
?.get<List<String>>("all_image_uris")
?: emptyList()
ImageDetailScreen( ImageDetailScreen(
imageUri = imageUri, imageUri = imageUri,
onBack = { navController.popBackStack() } onBack = { navController.popBackStack() },
navController = navController,
allImageUris = allImageUris
) )
} }
/** /**
* ALBUM VIEW SCREEN * ALBUM VIEW SCREEN - UPDATED: Stores image list for navigation
* View photos in a specific album (tag, person, or time-based)
*/ */
composable( composable(
route = "album/{albumType}/{albumId}", route = "album/{albumType}/{albumId}",
@@ -128,11 +135,27 @@ fun AppNavHost(
} }
) )
) { ) {
val albumViewModel: AlbumViewModel = hiltViewModel()
val uiState by albumViewModel.uiState.collectAsStateWithLifecycle()
AlbumViewScreen( AlbumViewScreen(
onBack = { onBack = {
navController.popBackStack() navController.popBackStack()
}, },
onImageClick = { imageUri -> onImageClick = { imageUri ->
// Store full album image list
val allImageUris = if (uiState is com.placeholder.sherpai2.ui.album.AlbumUiState.Success) {
(uiState as com.placeholder.sherpai2.ui.album.AlbumUiState.Success)
.photos
.map { it.image.imageUri }
} else {
emptyList()
}
navController.currentBackStackEntry
?.savedStateHandle
?.set("all_image_uris", allImageUris)
val encodedUri = URLEncoder.encode(imageUri, "UTF-8") val encodedUri = URLEncoder.encode(imageUri, "UTF-8")
navController.navigate("${AppRoutes.IMAGE_DETAIL}/$encodedUri") navController.navigate("${AppRoutes.IMAGE_DETAIL}/$encodedUri")
} }
@@ -145,19 +168,10 @@ fun AppNavHost(
/** /**
* PERSON INVENTORY SCREEN * PERSON INVENTORY SCREEN
* View all trained face models
*
* Features:
* - List all trained people
* - Show stats (training count, tagged photos, confidence)
* - Delete models
* - View photos containing each person
*/ */
composable(AppRoutes.INVENTORY) { composable(AppRoutes.INVENTORY) {
PersonInventoryScreen( PersonInventoryScreen(
onViewPersonPhotos = { personId -> onViewPersonPhotos = { personId ->
// Navigate back to search
// TODO: In future, add person filter to search screen
navController.navigate(AppRoutes.SEARCH) navController.navigate(AppRoutes.SEARCH)
} }
) )
@@ -165,22 +179,13 @@ fun AppNavHost(
/** /**
* TRAINING FLOW * TRAINING FLOW
* Train new face recognition model
*
* Flow:
* 1. TrainingScreen (select images button)
* 2. ImageSelectorScreen (pick 15-50 photos)
* 3. ScanResultsScreen (validation + name input)
* 4. Training completes → navigate to Inventory
*/ */
composable(AppRoutes.TRAIN) { entry -> composable(AppRoutes.TRAIN) { entry ->
val trainViewModel: TrainViewModel = hiltViewModel() val trainViewModel: TrainViewModel = hiltViewModel()
val uiState by trainViewModel.uiState.collectAsState() val uiState by trainViewModel.uiState.collectAsState()
// Get images selected from ImageSelector
val selectedUris = entry.savedStateHandle.get<List<Uri>>("selected_image_uris") val selectedUris = entry.savedStateHandle.get<List<Uri>>("selected_image_uris")
// Start scanning when new images are selected
LaunchedEffect(selectedUris) { LaunchedEffect(selectedUris) {
if (selectedUris != null && uiState is ScanningState.Idle) { if (selectedUris != null && uiState is ScanningState.Idle) {
trainViewModel.scanAndTagFaces(selectedUris) trainViewModel.scanAndTagFaces(selectedUris)
@@ -190,7 +195,6 @@ fun AppNavHost(
when (uiState) { when (uiState) {
is ScanningState.Idle -> { is ScanningState.Idle -> {
// Show start screen with "Select Images" button
TrainingScreen( TrainingScreen(
onSelectImages = { onSelectImages = {
navController.navigate(AppRoutes.IMAGE_SELECTOR) navController.navigate(AppRoutes.IMAGE_SELECTOR)
@@ -198,11 +202,9 @@ fun AppNavHost(
) )
} }
else -> { else -> {
// Show validation results and training UI
ScanResultsScreen( ScanResultsScreen(
state = uiState, state = uiState,
onFinish = { onFinish = {
// After training, go to inventory to see new person
navController.navigate(AppRoutes.INVENTORY) { navController.navigate(AppRoutes.INVENTORY) {
popUpTo(AppRoutes.TRAIN) { inclusive = true } popUpTo(AppRoutes.TRAIN) { inclusive = true }
} }
@@ -214,12 +216,10 @@ fun AppNavHost(
/** /**
* IMAGE SELECTOR SCREEN * IMAGE SELECTOR SCREEN
* Pick images for training (internal screen)
*/ */
composable(AppRoutes.IMAGE_SELECTOR) { composable(AppRoutes.IMAGE_SELECTOR) {
ImageSelectorScreen( ImageSelectorScreen(
onImagesSelected = { uris -> onImagesSelected = { uris ->
// Pass selected URIs back to Train screen
navController.previousBackStackEntry navController.previousBackStackEntry
?.savedStateHandle ?.savedStateHandle
?.set("selected_image_uris", uris) ?.set("selected_image_uris", uris)
@@ -230,7 +230,6 @@ fun AppNavHost(
/** /**
* MODELS SCREEN * MODELS SCREEN
* AI model management (placeholder)
*/ */
composable(AppRoutes.MODELS) { composable(AppRoutes.MODELS) {
DummyScreen( DummyScreen(
@@ -245,7 +244,6 @@ fun AppNavHost(
/** /**
* TAGS SCREEN * TAGS SCREEN
* Manage photo tags with auto-tagging features
*/ */
composable(AppRoutes.TAGS) { composable(AppRoutes.TAGS) {
TagManagementScreen() TagManagementScreen()
@@ -253,7 +251,6 @@ fun AppNavHost(
/** /**
* UTILITIES SCREEN * UTILITIES SCREEN
* Photo collection management tools
*/ */
composable(AppRoutes.UTILITIES) { composable(AppRoutes.UTILITIES) {
PhotoUtilitiesScreen() PhotoUtilitiesScreen()
@@ -265,7 +262,6 @@ fun AppNavHost(
/** /**
* SETTINGS SCREEN * SETTINGS SCREEN
* App preferences (placeholder)
*/ */
composable(AppRoutes.SETTINGS) { composable(AppRoutes.SETTINGS) {
DummyScreen( DummyScreen(

View File

@@ -28,6 +28,9 @@ tensorflow-lite = "2.14.0"
tensorflow-lite-support = "0.4.4" tensorflow-lite-support = "0.4.4"
gson = "2.10.1" gson = "2.10.1"
#Album/Image View Tools
zoomable = "1.6.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
@@ -68,6 +71,10 @@ tensorflow-lite-gpu = { group = "org.tensorflow", name = "tensorflow-lite-gpu",
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
#Album/Image View Tools
zoomable = { group = "net.engawapg.lib", name = "zoomable", version.ref = "zoomable" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }