diff --git a/app/build.gradle b/app/build.gradle index 4941d5c..7fc9ea7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "com.bera.collegesearch" minSdk 24 targetSdk 34 - versionCode 7 - versionName "2.0" + versionCode 8 + versionName "3.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -105,4 +105,7 @@ dependencies { //Coil implementation "io.coil-kt:coil-compose:2.4.0" + //Extended Icons + implementation("androidx.compose.material:material-icons-extended:1.5.0") + } \ No newline at end of file diff --git a/app/src/main/java/com/bera/collegesearch/MainActivity.kt b/app/src/main/java/com/bera/collegesearch/MainActivity.kt index 40085b1..71afc12 100644 --- a/app/src/main/java/com/bera/collegesearch/MainActivity.kt +++ b/app/src/main/java/com/bera/collegesearch/MainActivity.kt @@ -3,6 +3,7 @@ package com.bera.collegesearch import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface import androidx.compose.runtime.LaunchedEffect @@ -31,7 +32,10 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) connectivityObserver = ConnectivityObserver(applicationContext) setContent { - CollegeSearchTheme { + + val systemDefault = isSystemInDarkTheme() + var isDarkMode by remember { mutableStateOf(systemDefault) } + CollegeSearchTheme(darkTheme = isDarkMode) { val status by connectivityObserver.observe() .collectAsState(initial = ConnectivityStatus.Unavailable) @@ -39,7 +43,10 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), ) { when(status) { - ConnectivityStatus.Available -> Navigation() + ConnectivityStatus.Available -> Navigation(isDarkMode) { + isDarkMode = !isDarkMode + } + else -> { var showError by remember { mutableStateOf(false) diff --git a/app/src/main/java/com/bera/collegesearch/components/ThemeSwitcher.kt b/app/src/main/java/com/bera/collegesearch/components/ThemeSwitcher.kt new file mode 100644 index 0000000..1a4c3bd --- /dev/null +++ b/app/src/main/java/com/bera/collegesearch/components/ThemeSwitcher.kt @@ -0,0 +1,100 @@ +package com.bera.collegesearch.components + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.LightMode +import androidx.compose.material.icons.filled.Nightlight +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun ThemeSwitcher( + darkTheme: Boolean = false, + size: Dp = 150.dp, + iconSize: Dp = size / 3, + padding: Dp = 10.dp, + borderWidth: Dp = 1.dp, + parentShape: Shape = CircleShape, + toggleShape: Shape = CircleShape, + animationSpec: AnimationSpec = tween(durationMillis = 300), + onClick: () -> Unit +) { + val offset by animateDpAsState( + targetValue = if (darkTheme) 0.dp else size, + animationSpec = animationSpec, label = "" + ) + + Box(modifier = Modifier + .width(size * 2) + .height(size) + .clip(shape = parentShape) + .clickable { onClick() } + .background(MaterialTheme.colorScheme.secondaryContainer) + ) { + Box( + modifier = Modifier + .size(size) + .offset(x = offset) + .padding(all = padding) + .clip(shape = toggleShape) + .background(MaterialTheme.colorScheme.primary) + ) {} + Row( + modifier = Modifier + .border( + border = BorderStroke( + width = borderWidth, + color = MaterialTheme.colorScheme.primary + ), + shape = parentShape + ) + ) { + Box( + modifier = Modifier.size(size), + contentAlignment = Alignment.Center + ) { + Icon( + modifier = Modifier.size(iconSize), + imageVector = Icons.Default.Nightlight, + contentDescription = "Theme Icon", + tint = if (darkTheme) MaterialTheme.colorScheme.secondaryContainer + else MaterialTheme.colorScheme.primary + ) + } + Box( + modifier = Modifier.size(size), + contentAlignment = Alignment.Center + ) { + Icon( + modifier = Modifier.size(iconSize), + imageVector = Icons.Default.LightMode, + contentDescription = "Theme Icon", + tint = if (darkTheme) MaterialTheme.colorScheme.primary + else MaterialTheme.colorScheme.secondaryContainer + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bera/collegesearch/navigation/Navigation.kt b/app/src/main/java/com/bera/collegesearch/navigation/Navigation.kt index e40616b..90327a2 100644 --- a/app/src/main/java/com/bera/collegesearch/navigation/Navigation.kt +++ b/app/src/main/java/com/bera/collegesearch/navigation/Navigation.kt @@ -14,11 +14,15 @@ import com.bera.collegesearch.screens.home.HomeScreen @Composable -fun Navigation() { +fun Navigation(isDarkMode: Boolean, onDarkModeToggle: () -> Unit) { val navController = rememberNavController() NavHost(navController = navController, startDestination = Routes.HomeScreen.route) { composable(Routes.HomeScreen.route) { - HomeScreen(navController = navController) + HomeScreen( + navController = navController, + isDarkMode = isDarkMode, + onDarkModeToggle = onDarkModeToggle + ) } composable(Routes.CollegeScreen.route + "/{category}", listOf(navArgument("category") { type = NavType.StringType diff --git a/app/src/main/java/com/bera/collegesearch/screens/cutoffsbyrank/CBRViewModel.kt b/app/src/main/java/com/bera/collegesearch/screens/cutoffsbyrank/CBRViewModel.kt index 707aef0..047f2bb 100644 --- a/app/src/main/java/com/bera/collegesearch/screens/cutoffsbyrank/CBRViewModel.kt +++ b/app/src/main/java/com/bera/collegesearch/screens/cutoffsbyrank/CBRViewModel.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bera.collegesearch.models.CutoffItem +import com.bera.collegesearch.repository.CutoffRepository import com.bera.collegesearch.use_cases.GetCutoffsUseCase import com.bera.collegesearch.utils.Constants.IIT_STRING import com.bera.collegesearch.utils.Constants.IIT_STRING_1 diff --git a/app/src/main/java/com/bera/collegesearch/screens/home/HomeScreen.kt b/app/src/main/java/com/bera/collegesearch/screens/home/HomeScreen.kt index 3d50b16..338e7b7 100644 --- a/app/src/main/java/com/bera/collegesearch/screens/home/HomeScreen.kt +++ b/app/src/main/java/com/bera/collegesearch/screens/home/HomeScreen.kt @@ -1,5 +1,6 @@ package com.bera.collegesearch.screens.home +import android.graphics.drawable.Icon import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement @@ -19,6 +20,7 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowLeft @@ -38,27 +40,31 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight 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.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import com.bera.collegesearch.R +import com.bera.collegesearch.components.ThemeSwitcher import com.bera.collegesearch.navigation.Routes -import com.bera.collegesearch.ui.theme.CollegeSearchTheme import com.bera.collegesearch.utils.CustomOutlinedCard import com.bera.collegesearch.utils.ShimmerListItem import kotlinx.coroutines.launch +import kotlin.reflect.KSuspendFunction2 @OptIn(ExperimentalFoundationApi::class) @Composable -fun HomeScreen(navController: NavController, viewModel: HomeViewModel = hiltViewModel()) { - +fun HomeScreen( + navController: NavController, + viewModel: HomeViewModel = hiltViewModel(), + isDarkMode: Boolean, + onDarkModeToggle: () -> Unit +) { Surface(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier.fillMaxSize(), @@ -67,23 +73,24 @@ fun HomeScreen(navController: NavController, viewModel: HomeViewModel = hiltView TopHalf( modifier = Modifier .weight(10f) - .padding(start = 6.dp, end = 6.dp, top = 6.dp, bottom = 10.dp), + .padding(6.dp), navController, viewModel.slideImage, - viewModel.pagerState, - viewModel::changeImagePage + viewModel::changeImagePage, + isDarkMode, + onDarkModeToggle ) MainGrid( modifier = Modifier .fillMaxWidth() - .padding(10.dp), + .padding(horizontal = 10.dp, vertical = 6.dp), navController, viewModel.drawableIds ) QuoteBox( modifier = Modifier .weight(3f) - .padding(horizontal = 20.dp, vertical = 10.dp), + .padding(horizontal = 12.dp, vertical = 6.dp), viewModel.quote, viewModel.author, viewModel.isQuoteLoading @@ -104,40 +111,48 @@ fun HomeScreen(navController: NavController, viewModel: HomeViewModel = hiltView fun QuoteBox(modifier: Modifier, quote: String, author: String, isLoading: Boolean) { LazyColumn(modifier = modifier) { item { - ElevatedCard { + ElevatedCard(Modifier.padding(2.dp)) { ShimmerListItem( - modifier = Modifier.fillMaxSize().padding(10.dp), + modifier = Modifier + .fillMaxSize() + .padding(10.dp), isLoading = isLoading, barCount = 2 ) { - Text( - text = "Quote of the day: ", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Justify, - style = MaterialTheme.typography.titleMedium - ) - Spacer( + Column( modifier = Modifier - .fillMaxWidth() - .height(6.dp) - ) - Text( - text = "\" $quote \"", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Justify, - fontStyle = FontStyle.Italic - ) - Spacer( - modifier = Modifier - .fillMaxWidth() - .height(2.dp) - ) - Text( - text = "- $author", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.End, - fontWeight = FontWeight.Bold - ) + .fillMaxSize() + .padding(10.dp) + ) { + Text( + text = "Quote of the day: ", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Justify, + style = MaterialTheme.typography.titleMedium + ) + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(6.dp) + ) + Text( + text = "\" $quote \"", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Justify, + fontStyle = FontStyle.Italic + ) + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + ) + Text( + text = "- $author", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.End, + fontWeight = FontWeight.Bold + ) + } } } } @@ -151,17 +166,24 @@ fun TopHalf( modifier: Modifier = Modifier, navController: NavController, slideImage: Array, - pagerState: PagerState, - changeImage: suspend (Boolean) -> Unit + changeImage: KSuspendFunction2, + isDarkMode: Boolean, + onDarkModeToggle: () -> Unit ) { val scope = rememberCoroutineScope() + val pagerState = rememberPagerState( + initialPage = 0, + initialPageOffsetFraction = 0f + ) { + 4 + } Box( modifier = modifier .clip(RoundedCornerShape(16.dp)) ) { - HorizontalPager(state = pagerState, pageCount = 4, key = { slideImage[it] }) { index -> + HorizontalPager(state = pagerState, key = { slideImage[it] }) { index -> Image( painter = painterResource(id = slideImage[index]), contentDescription = "IIT Bombay", @@ -171,7 +193,7 @@ fun TopHalf( } FilledTonalIconButton( - onClick = { scope.launch { changeImage(false) } }, + onClick = { scope.launch { changeImage(pagerState, false) } }, modifier = Modifier.align(Alignment.CenterStart) ) { Icon( @@ -182,7 +204,7 @@ fun TopHalf( } FilledTonalIconButton( - onClick = { scope.launch { changeImage(true) } }, + onClick = { scope.launch { changeImage(pagerState, true) } }, modifier = Modifier.align(Alignment.CenterEnd), ) { Icon( @@ -204,6 +226,18 @@ fun TopHalf( fontFamily = FontFamily.Monospace ) } + + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .padding(10.dp) + ) { + ThemeSwitcher( + darkTheme = isDarkMode, + onClick = onDarkModeToggle, + size = 50.dp + ) + } } } @@ -247,8 +281,9 @@ fun MainGrid(modifier: Modifier, navController: NavController, drawableIds: Arra verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { - Image( + Icon( painter = painterResource(id = drawableIds[it]), + tint = MaterialTheme.colorScheme.onSecondaryContainer, contentDescription = cardText[it], modifier = Modifier .padding(4.dp) @@ -269,10 +304,10 @@ fun MainGrid(modifier: Modifier, navController: NavController, drawableIds: Arra } } -@Preview(showBackground = true) -@Composable -fun HomePreview() { - CollegeSearchTheme { - HomeScreen(navController = NavController(LocalContext.current)) - } -} \ No newline at end of file +//@Preview(showBackground = true) +//@Composable +//fun HomePreview() { +// CollegeSearchTheme { +// HomeScreen(navController = NavController(LocalContext.current)) +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/bera/collegesearch/screens/home/HomeViewModel.kt b/app/src/main/java/com/bera/collegesearch/screens/home/HomeViewModel.kt index 406862e..653dbe4 100644 --- a/app/src/main/java/com/bera/collegesearch/screens/home/HomeViewModel.kt +++ b/app/src/main/java/com/bera/collegesearch/screens/home/HomeViewModel.kt @@ -2,6 +2,7 @@ package com.bera.collegesearch.screens.home import android.util.Log import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.pager.PagerDefaults import androidx.compose.foundation.pager.PagerState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -22,11 +23,6 @@ class HomeViewModel @Inject constructor( private val getQuotesUseCase: GetQuotesUseCase ) : ViewModel() { - val pagerState = PagerState( - initialPage = 0, - initialPageOffsetFraction = 0f, - ) - val drawableIds = arrayOf( R.drawable.iit_vector, @@ -43,7 +39,7 @@ class HomeViewModel @Inject constructor( R.drawable.ogc ) - suspend fun changeImagePage(next: Boolean) { + suspend fun changeImagePage(pagerState: PagerState, next: Boolean) { pagerState .animateScrollToPage( if (next) pagerState.currentPage + 1 @@ -69,11 +65,13 @@ class HomeViewModel @Inject constructor( } is Resource.Error -> { + isQuoteLoading = false quote = "Oops! Unable to load.." Log.d("error", "error") } is Resource.Success -> { + isQuoteLoading = false quote = resource.data?.get(0)?.quote ?: "" author = resource.data?.get(0)?.author ?: "" Log.d("Success", "Success") diff --git a/app/src/main/java/com/bera/collegesearch/use_cases/GetCutoffsUseCase.kt b/app/src/main/java/com/bera/collegesearch/use_cases/GetCutoffsUseCase.kt index 70b0773..124c5f9 100644 --- a/app/src/main/java/com/bera/collegesearch/use_cases/GetCutoffsUseCase.kt +++ b/app/src/main/java/com/bera/collegesearch/use_cases/GetCutoffsUseCase.kt @@ -3,8 +3,10 @@ package com.bera.collegesearch.use_cases import com.bera.collegesearch.models.CutoffItem import com.bera.collegesearch.repository.CutoffRepository import com.bera.collegesearch.utils.Resource +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject class GetCutoffsUseCase @Inject constructor(