Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(./gradlew assembleDebug:*)",
"Bash(./gradlew clean assembleDebug:*)",
"Bash(./gradlew kaptDebugKotlin:*)"
]
}
}
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ dependencies {
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'


// Jetpack Compose BOM
implementation platform("androidx.compose:compose-bom:$compose_bom_version")
implementation "androidx.compose.ui:ui"
Expand All @@ -78,6 +79,9 @@ dependencies {
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.material:material-icons-extended"
implementation "androidx.compose.runtime:runtime-livedata"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose"
implementation 'androidx.compose.runtime:runtime-livedata'

debugImplementation "androidx.compose.ui:ui-tooling"
debugImplementation "androidx.compose.ui:ui-test-manifest"

Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<activity
android:name=".activities.LaunchingActivity"
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:label="@string/title_activity_launching">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package com.mohitb117.demo_omdb_api.activities

import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.ui.graphics.vector.ImageVector
import com.mohitb117.demo_omdb_api.R

Expand All @@ -16,6 +14,4 @@ enum class AppDestinations(
) {
HOME(R.string.home, Icons.Default.Home, R.string.home),
FAVORITES(R.string.favorites, Icons.Default.Favorite, R.string.favorites),
SHOPPING(R.string.shopping, Icons.Default.ShoppingCart, R.string.shopping),
PROFILE(R.string.profile, Icons.Default.AccountBox, R.string.profile),
}
Original file line number Diff line number Diff line change
@@ -1,79 +1,226 @@
package com.mohitb117.demo_omdb_api.activities

import android.graphics.Color
import android.os.Bundle
import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.insets.GradientProtection
import androidx.core.view.insets.ProtectionLayout
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.mohitb117.demo_omdb_api.IMDB_ID_KEY
import com.mohitb117.demo_omdb_api.R
import com.mohitb117.demo_omdb_api.databinding.ActivityLaunchingBinding
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.mohitb117.demo_omdb_api.activities.ui.theme.DEMO_OMDB_APITheme
import com.mohitb117.demo_omdb_api.datamodels.SearchResult
import com.mohitb117.demo_omdb_api.datamodels.SearchResultsBody
import com.mohitb117.demo_omdb_api.ui.favourites.FavouritesViewModel
import com.mohitb117.demo_omdb_api.ui.search.FavouritesComposableContent
import com.mohitb117.demo_omdb_api.ui.search.Result
import com.mohitb117.demo_omdb_api.ui.search.SearchComposableContent
import com.mohitb117.demo_omdb_api.ui.search.SearchViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay

@AndroidEntryPoint
class LaunchingActivity : AppCompatActivity() {

private lateinit var binding: ActivityLaunchingBinding
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
class LaunchingActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

enableEdgeToEdge()

binding = ActivityLaunchingBinding.inflate(layoutInflater)
setContentView(binding.root)

val bottomNavigationView: BottomNavigationView = binding.navView
setContent {
DEMO_OMDB_APITheme {
LaunchComposable(modifier = Modifier)
}
}
}

navController = findNavController(R.id.nav_host_fragment_activity_launching)
@Composable
fun LaunchComposable(
modifier: Modifier = Modifier,
searchViewModel: SearchViewModel = viewModel(),
favouritesViewModel: FavouritesViewModel = viewModel(),
) {
val searchUiState by searchViewModel.searchResultsViewState.collectAsStateWithLifecycle()
val favouritesUiState by favouritesViewModel.favouritedIdsFlow.collectAsStateWithLifecycle()

// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(R.id.navigation_search, R.id.navigation_favourites)
LaunchComposableContent(
modifier = modifier,
searchUiState = searchUiState,
favouritesUiState = favouritesUiState,
onSearch = { searchViewModel.loadSearchResult(it) },
isMovieFavourited = { searchViewModel.favouritedIds.value.contains(it) },
onToggleMovieFavorited = { searchViewModel.toggleFavourite(it) },
)
}

setupActionBarWithNavController(navController, appBarConfiguration)
@Composable
fun LaunchComposableContent(
modifier: Modifier = Modifier,
searchUiState: Result<SearchResultsBody>,
favouritesUiState: Set<SearchResult>,
onSearch: (String) -> Unit,
isMovieFavourited: (SearchResult) -> Boolean = { false },
onToggleMovieFavorited: suspend (SearchResult) -> Boolean = { false },
) {
var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }

bottomNavigationView.setupWithNavController(navController)
NavigationSuiteScaffold(
navigationSuiteItems = {
AppDestinations.entries.forEach {
item(
icon = {
Icon(it.icon, stringResource(it.contentDescription))
},
modifier = Modifier,
label = { Text(stringResource(it.label)) },
selected = it == currentDestination,
onClick = { currentDestination = it },
)
}
}
) {
var searchQuery by rememberSaveable { mutableStateOf("") }

ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v: View, insets: WindowInsetsCompat ->
val innerPadding = insets.getInsets(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
)
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
if (currentDestination == AppDestinations.HOME) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.background(color = Color.Gray),
value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text(text = "Search Movie!") }
)
}
}
) { innerPadding ->
when (currentDestination) {
AppDestinations.HOME -> {
SearchMovieInfoLayout(
name = stringResource(currentDestination.label),
modifier = Modifier.padding(innerPadding),
searchQuery = searchQuery,
searchUiState = searchUiState,
onSearch = onSearch,
isMovieFavourited = isMovieFavourited,
onToggleMovieFavorited = onToggleMovieFavorited,
)
}

else -> {
FavouritesComposableLayout(
name = stringResource(currentDestination.label),
modifier = Modifier.padding(innerPadding),
uiState = favouritesUiState,
isMovieFavourited = isMovieFavourited,
onToggleMovieFavorited = onToggleMovieFavorited,
)
}
}
}
}
}

@Composable
fun SearchMovieInfoLayout(
name: String,
modifier: Modifier = Modifier,
searchQuery: String = "",
searchUiState: Result<SearchResultsBody>,
onSearch: (String) -> Unit,
isMovieFavourited: (SearchResult) -> Boolean = { false },
onToggleMovieFavorited: suspend (SearchResult) -> Boolean = { false },
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {

v.setPadding(
innerPadding.left,
innerPadding.top,
innerPadding.right,
innerPadding.bottom
if (searchQuery.isNotEmpty()) {
LaunchedEffect(searchQuery) {
delay(500) // Debounce for 500ms
onSearch(searchQuery)
}

SearchComposableContent(
modifier = Modifier,
searchUiState = searchUiState,
onToggleMovieFavorited = onToggleMovieFavorited,
isMovieFavourited = isMovieFavourited,
)
} else {
Text("Nothing to search right now!")
}
}
}

@Composable
fun FavouritesComposableLayout(
name: String,
uiState: Set<SearchResult>,
modifier: Modifier = Modifier,
isMovieFavourited: (SearchResult) -> Boolean = { false },
onToggleMovieFavorited: suspend (SearchResult) -> Boolean = { false },
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {

FavouritesComposableContent(
modifier = Modifier,
uiState = uiState,
onToggleMovieFavorited = onToggleMovieFavorited,
isMovieFavourited = isMovieFavourited,
)
insets
}
}

fun gotoDetails(imdbId: String) {
navController.navigate(
R.id.navigation_details,
Bundle().apply { putString(IMDB_ID_KEY, imdbId) }
)
@Preview(showBackground = true)
@Composable
fun SearchMovieInfoLayoutPreview() {
DEMO_OMDB_APITheme {
SearchMovieInfoLayout(
name = "Android",
searchUiState = Result.Loading(""),
onSearch = {}
)
}
}

override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration)
@Preview(showBackground = true)
@Composable
fun LaunchComposablePreview() {
DEMO_OMDB_APITheme {
LaunchComposableContent(
searchUiState = Result.Loading(""),
favouritesUiState = emptySet(),
onSearch = {}
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mohitb117.demo_omdb_api.activities.ui.theme

import androidx.compose.ui.graphics.Color

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
Loading