Skip to content

Commit

Permalink
Add a proper error handling if user hit the convert button without en…
Browse files Browse the repository at this point in the history
…tering any amount.

Add Navigation, NavigationGraph.kt that contains splash screen and also main currency rate listing screen for better management of UI.

Refactor MainNavigationActivity.kt name and its package.
  • Loading branch information
asadmukhtar28 committed Apr 30, 2023
1 parent 26525d3 commit 723d848
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 84 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
tools:targetApi="31">
<activity
android:screenOrientation="portrait"
android:name=".ui.currencyrates.CurrencyRatesActivity"
android:name=".ui.MainNavigationActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.CurrencyConverter">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.asad.currencyconverter.ui.currencyrates
package com.asad.currencyconverter.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
Expand All @@ -13,14 +13,21 @@ import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class CurrencyRatesViewModel @Inject constructor(private val repository: CurrencyRatesRepository) :
class MainActivityViewModel @Inject constructor(private val repository: MainRepository) :
ViewModel() {

private val _uiState = MutableStateFlow<UiStates>(LoadingState)
val uiState: StateFlow<UiStates> = _uiState

private val _currencyRatesList = MutableStateFlow<List<CurrencyRatesDbModel>>(arrayListOf())
val currencyRatesList: StateFlow<List<CurrencyRatesDbModel>> = _currencyRatesList
private val _originalCurrencyRatesList =
MutableStateFlow<List<CurrencyRatesDbModel>>(arrayListOf())
val originalCurrencyRatesList: StateFlow<List<CurrencyRatesDbModel>> =
_originalCurrencyRatesList

private val _conversionCurrencyRatesList =
MutableStateFlow<List<CurrencyRatesDbModel>>(arrayListOf())
val conversionCurrencyRatesList: StateFlow<List<CurrencyRatesDbModel>> =
_conversionCurrencyRatesList

private val _selectedCurrency = MutableStateFlow(CurrencyRatesDbModel())
val selectedCurrency: StateFlow<CurrencyRatesDbModel> = _selectedCurrency
Expand All @@ -33,8 +40,8 @@ class CurrencyRatesViewModel @Inject constructor(private val repository: Currenc
repository.fetchCurrencyRatesFromDb().collect {
if (it.isNotEmpty()) {
_uiState.value = ContentState
_currencyRatesList.value = it
_selectedCurrency.value = _currencyRatesList.value.first()
_originalCurrencyRatesList.value = it
_selectedCurrency.value = _originalCurrencyRatesList.value.first()
} else {
if (CurrencyConverterApp.isNetworkConnected) {
_uiState.value = LoadingState
Expand All @@ -46,7 +53,7 @@ class CurrencyRatesViewModel @Inject constructor(private val repository: Currenc
fun performConversion(amount: Double) {
viewModelScope.launch(Dispatchers.Default) {
val list: MutableList<CurrencyRatesDbModel> = ArrayList()
currencyRatesList.value.let { listItems ->
originalCurrencyRatesList.value.let { listItems ->
if (listItems.isNotEmpty()) {
val currencyValue = amount / listItems.filter {
selectedCurrency.value.currencyName == it.currencyName
Expand All @@ -65,7 +72,7 @@ class CurrencyRatesViewModel @Inject constructor(private val repository: Currenc
}
}

_currencyRatesList.value = list
_conversionCurrencyRatesList.value = list
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.asad.currencyconverter.ui

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import com.asad.currencyconverter.ui.navgraph.Navigation
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainNavigationActivity : ComponentActivity() {
private val viewModel: MainActivityViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Navigation(viewModel)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.asad.currencyconverter.ui.currencyrates
package com.asad.currencyconverter.ui

import com.asad.currencyconverter.data.manager.AppDataManager
import javax.inject.Inject

class CurrencyRatesRepository @Inject constructor(private val dataManager: AppDataManager) {
class MainRepository @Inject constructor(private val dataManager: AppDataManager) {

fun fetchCurrencyRatesFromDb() = dataManager.getAppDatabaseHelper().getCurrencyRatesList()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.asad.currencyconverter.ui.currencyrates
package com.asad.currencyconverter.ui


sealed class UiStates
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Scaffold
import androidx.compose.material.Snackbar
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.runtime.*
Expand All @@ -39,60 +44,105 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.asad.currencyconverter.R
import com.asad.currencyconverter.data.local.models.CurrencyRatesDbModel
import com.asad.currencyconverter.ui.ContentState
import com.asad.currencyconverter.ui.ErrorState
import com.asad.currencyconverter.ui.LoadingState
import com.asad.currencyconverter.ui.MainActivityViewModel
import com.asad.currencyconverter.ui.theme.CurrencyConverterTheme
import kotlinx.coroutines.launch

@Composable
fun CurrencyConverter(viewModel: CurrencyRatesViewModel = viewModel()) {
fun CurrencyConverter(viewModel: MainActivityViewModel = viewModel()) {
val uiState = viewModel.uiState.collectAsState()
val currencyRatesList = viewModel.currencyRatesList.collectAsState().value
val currencyRatesList = viewModel.originalCurrencyRatesList.collectAsState().value
val conversionCurrencyRatesList = viewModel.conversionCurrencyRatesList.collectAsState().value
val selectedCurrency = viewModel.selectedCurrency.collectAsState().value

var amount by rememberSaveable {
mutableStateOf(0.toDouble())
}

Column(modifier = Modifier.fillMaxSize()) {
HeaderSection(
modifier = Modifier.fillMaxWidth()
) {
amount = it
viewModel.performConversion(amount)
}
val snackBarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
Scaffold(modifier = Modifier.fillMaxWidth(),
snackbarHost = {
SnackbarHost(
hostState = snackBarHostState,
snackbar = { data ->
Snackbar(
modifier = Modifier.padding(16.dp),
content = { Text(data.message) },
action = {
data.actionLabel?.let { label ->
TextButton(onClick = { data.performAction() }) {
Text(label)
}
}
}
)
}
)
},
topBar = {
HeaderSection(
modifier = Modifier
.fillMaxWidth()
.background(color = Color.Black)
) {
if (it.isNotEmpty()) {
amount = it.toDouble()
viewModel.performConversion(amount)
} else {
coroutineScope.launch {
snackBarHostState.showSnackbar("Please Enter amount")
}
}

when (uiState.value) {
is LoadingState -> {
LoadingState(
modifier = Modifier
.fillMaxSize()
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
)
)
}
}
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
) {
when (uiState.value) {
is LoadingState -> {
LoadingState(
modifier = Modifier
.fillMaxSize()
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
)
)
}

is ContentState -> {
CurrenciesList(
modifier = Modifier
.fillMaxSize()
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
), selectedCurrency, currencyRatesList
) { currencyRatesModel ->
viewModel.updateSelectedCurrency(currencyRatesModel)
is ContentState -> {
CurrenciesList(
modifier = Modifier
.fillMaxSize()
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
),
selectedCurrency,
conversionCurrencyRatesList.ifEmpty { currencyRatesList }
) { currencyRatesModel ->
viewModel.updateSelectedCurrency(currencyRatesModel)
}
}
}

is ErrorState -> {
ErrorState(
modifier = Modifier
.fillMaxSize()
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
)
)
is ErrorState -> {
ErrorState(
modifier = Modifier
.fillMaxSize()
.background(
color = Color.White,
shape = RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp)
)
)
}
}
}

Expand Down Expand Up @@ -207,7 +257,7 @@ fun ItemCurrencyPreview() {
@OptIn(ExperimentalUnitApi::class)
@Composable
fun HeaderSection(
modifier: Modifier = Modifier, onAmountValueChange: (amount: Double) -> Unit
modifier: Modifier = Modifier, onAmountValueChange: (amount: String) -> Unit
) {
var amount by rememberSaveable {
mutableStateOf("")
Expand Down Expand Up @@ -248,7 +298,7 @@ fun HeaderSection(
color = Color.White,
fontSize = TextUnit(25f, TextUnitType.Sp),
modifier = Modifier.clickable {
onAmountValueChange.invoke(amount.toDouble())
onAmountValueChange.invoke(amount)
})
},
textStyle = TextStyle(fontSize = TextUnit(30f, TextUnitType.Sp)),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.asad.currencyconverter.ui.navgraph

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.asad.currencyconverter.ui.MainActivityViewModel
import com.asad.currencyconverter.ui.currencyrates.CurrencyConverter
import com.asad.currencyconverter.ui.splash.SplashScreen
import kotlinx.coroutines.delay

@Composable
fun Navigation(viewModel: MainActivityViewModel) {
val navController = rememberNavController()

LaunchedEffect(Unit) {
delay(500)
navController.navigate(route = Screen.CurrencyRatesListScreen.route) {
popUpTo(Screen.SplashScreen.route) {
inclusive = true
}
}
}

NavHost(navController = navController, startDestination = Screen.SplashScreen.route) {
composable(Screen.SplashScreen.route) {
SplashScreen()
}
composable(route = Screen.CurrencyRatesListScreen.route) {
CurrencyConverter(viewModel)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.asad.currencyconverter.ui.navgraph

sealed class Screen(val route: String) {
object SplashScreen : Screen("SplashScreen")
object CurrencyRatesListScreen : Screen("CurrencyRatesListScreen")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.asad.currencyconverter.ui.splash

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import com.asad.currencyconverter.R

@Composable
fun SplashScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = Color.White),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.ic_currency), contentDescription = null,
)
}
}
4 changes: 3 additions & 1 deletion app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="Theme.CurrencyConverter" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.CurrencyConverter" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:windowDisablePreview">true</item>
</style>
</resources>

0 comments on commit 723d848

Please sign in to comment.