Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e3866a8
Switched back to Image from AsyncImage in ImageRenderer.kt for displa…
srikrishnasakunia Sep 9, 2025
3563e39
Merge branch 'main' into krishna/nav3_resultScreenImp
srikrishnasakunia Sep 9, 2025
3285cfa
Moved ResultsScreen.kt & CustomizeExportScreen.kt to open from MainNa…
srikrishnasakunia Aug 11, 2025
92c7774
Refactor image saving and loading in Creation and Customize features
srikrishnasakunia Aug 11, 2025
887aac4
Fixed the PR Comments
srikrishnasakunia Aug 17, 2025
43760ac
Handling the process death scenario for Bitmap
srikrishnasakunia Aug 18, 2025
f4f7c4d
Test Cases handling
srikrishnasakunia Aug 18, 2025
b01f805
Test Cases handling and comments resolved.
srikrishnasakunia Sep 4, 2025
453f8e8
./gradlew spotlessApply
srikrishnasakunia Sep 4, 2025
1eccbae
Fixed the failed TestCase
srikrishnasakunia Sep 4, 2025
3ceec41
Merge remote-tracking branch 'origin/main' into krishna/nav3_resultSc…
srikrishnasakunia Sep 9, 2025
b13fca1
Test Cases handling and animation bug resolved.
srikrishnasakunia Sep 10, 2025
dda5cda
Test Cases handling and animation bug resolved.
srikrishnasakunia Sep 10, 2025
10a6433
Test Cases handling
srikrishnasakunia Sep 10, 2025
6378aac
Used imageBitmap of ExportImageCanvas to load Image in ImageRenderer.…
srikrishnasakunia Sep 12, 2025
c886f0d
Used imageBitmap of ExportImageCanvas to load Image in ImageRenderer.…
srikrishnasakunia Sep 12, 2025
7cfd2b2
Removed Commented Code
srikrishnasakunia Sep 12, 2025
ad0e930
Merge remote-tracking branch 'origin/main' into krishna/nav3_resultSc…
srikrishnasakunia Sep 12, 2025
09bb009
Added Timber exception
srikrishnasakunia Sep 12, 2025
944c054
Added Timber exception
srikrishnasakunia Sep 12, 2025
c7077be
Merge remote-tracking branch 'origin/main' into krishna/nav3_resultSc…
srikrishnasakunia Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,21 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.IntOffset
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entry
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.android.developers.androidify.camera.CameraPreviewScreen
import com.android.developers.androidify.creation.CreationScreen
import com.android.developers.androidify.creation.CreationViewModel
import com.android.developers.androidify.customize.CustomizeAndExportScreen
import com.android.developers.androidify.customize.CustomizeExportViewModel
import com.android.developers.androidify.home.AboutScreen
import com.android.developers.androidify.home.HomeScreen
import com.android.developers.androidify.results.ResultsScreen
import com.android.developers.androidify.results.ResultsViewModel
import com.android.developers.androidify.theme.transitions.ColorSplashTransitionScreen
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity

Expand Down Expand Up @@ -92,14 +98,20 @@ fun MainNavigation() {
CameraPreviewScreen(
onImageCaptured = { uri ->
backStack.removeAll { it is Create }
backStack.add(Create(uri.toString()))
backStack.add(Create(uri))
backStack.removeAll { it is Camera }
},
)
}
entry<Create> { createKey ->
val creationViewModel = hiltViewModel<CreationViewModel, CreationViewModel.Factory>(
creationCallback = { factory ->
factory.create(
originalImageUrl = createKey.fileName,
)
},
)
CreationScreen(
createKey.fileName,
onCameraPressed = {
backStack.removeAll { it is Camera }
backStack.add(Camera)
Expand All @@ -110,6 +122,64 @@ fun MainNavigation() {
onAboutPressed = {
backStack.add(About)
},
onImageCreated = { resultImageUri, prompt, originalImageUri ->
backStack.removeAll { it is Result }
backStack.add(
Result(
resultImageUri = resultImageUri,
prompt = prompt,
originalImageUri = originalImageUri,
),
)
},
creationViewModel = creationViewModel,
)
}
entry<Result> { resultKey ->
val resultsViewModel = hiltViewModel<ResultsViewModel, ResultsViewModel.Factory>(
creationCallback = { factory ->
factory.create(
resultImageUrl = resultKey.resultImageUri,
originalImageUrl = resultKey.originalImageUri,
promptText = resultKey.prompt,
)
},
)
ResultsScreen(
onNextPress = { resultImageUri, originalImageUri ->
backStack.add(
CustomizeExport(
resultImageUri = resultImageUri,
originalImageUri = originalImageUri,
),
)
},
onAboutPress = {
backStack.add(About)
},
onBackPress = {
backStack.removeLastOrNull()
},
viewModel = resultsViewModel,
)
}
entry<CustomizeExport> { shareKey ->
val customizeExportViewModel = hiltViewModel<CustomizeExportViewModel, CustomizeExportViewModel.Factory>(
creationCallback = { factory ->
factory.create(
resultImageUrl = shareKey.resultImageUri,
originalImageUrl = shareKey.originalImageUri,
)
},
)
CustomizeAndExportScreen(
onBackPress = {
backStack.removeLastOrNull()
},
onInfoPress = {
backStack.add(About)
},
viewModel = customizeExportViewModel,
)
}
entry<About> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package com.android.developers.androidify.navigation

import android.net.Uri
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable

Expand All @@ -26,10 +27,39 @@ sealed interface NavigationRoute
data object Home : NavigationRoute

@Serializable
data class Create(val fileName: String? = null, val prompt: String? = null) : NavigationRoute
data class Create(
@Serializable(with = UriSerializer::class) val fileName: Uri? = null,
val prompt: String? = null,
) : NavigationRoute

@Serializable
object Camera : NavigationRoute

@Serializable
object About : NavigationRoute

/**
* Represents the result of an image generation process, used for navigation.
*
* @param resultImageUri The URI of the generated image.
* @param originalImageUri The URI of the original image used as a base for generation, if any.
* @param prompt The text prompt used to generate the image, if any.
*/
@Serializable
data class Result(
@Serializable(with = UriSerializer::class) val resultImageUri: Uri,
@Serializable(with = UriSerializer::class) val originalImageUri: Uri? = null,
val prompt: String? = null,
) : NavigationRoute

/**
* Represents the navigation route to the screen for customizing and exporting a generated image.
*
* @param resultImageUri The URI of the generated image to be customized.
* @param originalImageUri The URI of the original image, passed along for context.
*/
@Serializable
data class CustomizeExport(
@Serializable(with = UriSerializer::class) val resultImageUri: Uri,
@Serializable(with = UriSerializer::class) val originalImageUri: Uri?,
) : NavigationRoute
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.developers.androidify.navigation

import android.net.Uri
import androidx.core.net.toUri
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

object UriSerializer : KSerializer<Uri> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: Uri) {
encoder.encodeString(value.toString())
}

override fun deserialize(decoder: Decoder): Uri = decoder.decodeString().toUri()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.developers.testing.data

import android.graphics.Bitmap

val bitmapSample = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,8 @@ class TestFileProvider : LocalFileProvider {
): Uri {
TODO("Not yet implemented")
}

override suspend fun loadBitmapFromUri(uri: Uri): Bitmap? {
return bitmapSample
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.android.developers.androidify.util
import android.app.Application
import android.content.ContentValues
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Environment
Expand Down Expand Up @@ -53,6 +54,9 @@ interface LocalFileProvider {

@WorkerThread
suspend fun saveUriToSharedStorage(inputUri: Uri, fileName: String, mimeType: String): Uri

@WorkerThread
suspend fun loadBitmapFromUri(uri: Uri): Bitmap?
}

@Singleton
Expand Down Expand Up @@ -120,6 +124,20 @@ class LocalFileProviderImpl @Inject constructor(
return@withContext newUri
}

override suspend fun loadBitmapFromUri(uri: Uri): Bitmap? {
return withContext(ioDispatcher) {
try {
application.contentResolver.openInputStream(uri)?.use {
return@withContext BitmapFactory.decodeStream(it)
}
null
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}

@Throws(IOException::class)
@WorkerThread
private fun saveFileToUri(file: File, uri: Uri) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,13 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.rectangle
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
import com.android.developers.androidify.customize.CustomizeAndExportScreen
import com.android.developers.androidify.customize.CustomizeExportViewModel
import com.android.developers.androidify.data.DropBehaviourFactory
import com.android.developers.androidify.results.ResultsScreen
import com.android.developers.androidify.theme.AndroidifyTheme
import com.android.developers.androidify.theme.LimeGreen
import com.android.developers.androidify.theme.LocalSharedTransitionScope
Expand Down Expand Up @@ -160,32 +155,41 @@ import com.android.developers.androidify.creation.R as CreationR

@Composable
fun CreationScreen(
fileName: String? = null,
creationViewModel: CreationViewModel = hiltViewModel(),
creationViewModel: CreationViewModel,
isMedium: Boolean = isAtLeastMedium(),
onCameraPressed: () -> Unit = {},
onBackPressed: () -> Unit,
onAboutPressed: () -> Unit,
onImageCreated: (resultImageUri: Uri, prompt: String?, originalImageUri: Uri?) -> Unit,
) {
val uiState by creationViewModel.uiState.collectAsStateWithLifecycle()
BackHandler(
enabled = uiState.screenState != ScreenState.EDIT,
) {
creationViewModel.onBackPress()
}
LaunchedEffect(Unit) {
if (fileName != null) {
creationViewModel.onImageSelected(fileName.toUri())
} else {
creationViewModel.onImageSelected(null)
}
}
val pickMedia = rememberLauncherForActivityResult(PickVisualMedia()) { uri ->
if (uri != null) {
creationViewModel.onImageSelected(uri)
}
}
val snackbarHostState by creationViewModel.snackbarHostState.collectAsStateWithLifecycle()

LaunchedEffect(uiState.resultBitmapUri) {
uiState.resultBitmapUri?.let { resultBitmapUri ->
onImageCreated(
resultBitmapUri,
uiState.descriptionText.text.toString(),
if (uiState.selectedPromptOption == PromptType.PHOTO) {
uiState.imageUri
} else {
null
},
)
creationViewModel.onResultDisplayed()
}
}

when (uiState.screenState) {
ScreenState.EDIT -> {
EditScreen(
Expand Down Expand Up @@ -213,46 +217,6 @@ fun CreationScreen(
},
)
}

ScreenState.RESULT -> {
val prompt = uiState.descriptionText.text.toString()
val key = if (uiState.descriptionText.text.isBlank()) {
uiState.imageUri.toString()
} else {
prompt
}
ResultsScreen(
uiState.resultBitmap!!,
if (uiState.selectedPromptOption == PromptType.PHOTO) {
uiState.imageUri
} else {
null
},
promptText = prompt,
viewModel = hiltViewModel(key = key),
onAboutPress = onAboutPressed,
onBackPress = onBackPressed,
onNextPress = creationViewModel::customizeExportClicked,
)
}

ScreenState.CUSTOMIZE -> {
val prompt = uiState.descriptionText.text.toString()
val key = if (uiState.descriptionText.text.isBlank()) {
uiState.imageUri.toString()
} else {
prompt
}
uiState.resultBitmap?.let { bitmap ->
CustomizeAndExportScreen(
resultImage = bitmap,
originalImageUri = uiState.imageUri,
onBackPress = onBackPressed,
onInfoPress = onAboutPressed,
viewModel = hiltViewModel<CustomizeExportViewModel>(key = key),
)
}
}
}
}

Expand Down
Loading