mirror of
https://github.com/AdrianKuta/android-challange-adrian-kuta.git
synced 2025-07-01 14:58:03 +02:00
feat: Implement stations screen with grouped airports
This commit introduces a new "Stations" screen that displays airports grouped by country. Key changes: - Added a new Gradle module: `ui:stations`. - Created `StationsScreen.kt` to display a list of airports grouped by country using `LazyColumn`. Airports within each country are displayed as `AirportInfoItem` and countries as `CountryItem`. - Implemented `StationsScreenViewModel.kt` to fetch and manage the state of airports grouped by country. It uses `ObserveAirportsGroupedByCountry` use case. - Defined `ObserveAirportsGroupedByCountry.kt` use case in `domain:stations` module to provide a flow of airports grouped by country. - Implemented `ObserveAirportsGroupedByCountryImpl.kt` in the repository layer, which fetches data using `AirportService`, stores it in `AirportsDatasource`, and maps it to the domain model. - Added Hilt module `ObserveAirportsGroupedByCountryModule.kt` to provide the use case implementation. - Added `stationsScreen()` and `navigateToStations()` to `FlightsNavGraph.kt` and `TopLevelDestination.kt` for navigation. - Updated `settings.gradle.kts` and `app/build.gradle.kts` to include the new `ui:stations` and `domain:stations` modules. - Updated `CacheObservers.kt` to make `mapToDomain` a suspend function. - Added string resources for the stations screen title.
This commit is contained in:
@ -60,6 +60,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(projects.ui.designsystem)
|
implementation(projects.ui.designsystem)
|
||||||
implementation(projects.ui.home)
|
implementation(projects.ui.home)
|
||||||
|
implementation(projects.ui.stations)
|
||||||
|
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.core.splashscreen)
|
implementation(libs.androidx.core.splashscreen)
|
||||||
|
@ -12,6 +12,8 @@ import androidx.navigation.navOptions
|
|||||||
import dev.adriankuta.flights.ui.home.navigation.HomeRoute
|
import dev.adriankuta.flights.ui.home.navigation.HomeRoute
|
||||||
import dev.adriankuta.flights.ui.home.navigation.homeScreen
|
import dev.adriankuta.flights.ui.home.navigation.homeScreen
|
||||||
import dev.adriankuta.flights.ui.home.navigation.navigateToHome
|
import dev.adriankuta.flights.ui.home.navigation.navigateToHome
|
||||||
|
import dev.adriankuta.flights.ui.home.navigation.navigateToStations
|
||||||
|
import dev.adriankuta.flights.ui.home.navigation.stationsScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FlightsNavGraph(
|
fun FlightsNavGraph(
|
||||||
@ -24,6 +26,7 @@ fun FlightsNavGraph(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
homeScreen()
|
homeScreen()
|
||||||
|
stationsScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +48,7 @@ fun NavController.navigateToTopLevelDestination(topLevelDestination: TopLevelDes
|
|||||||
|
|
||||||
when (topLevelDestination) {
|
when (topLevelDestination) {
|
||||||
TopLevelDestination.HOME -> navigateToHome(topLevelNavOptions)
|
TopLevelDestination.HOME -> navigateToHome(topLevelNavOptions)
|
||||||
TopLevelDestination.STATIONS -> navigateToHome(topLevelNavOptions)
|
TopLevelDestination.STATIONS -> navigateToStations(topLevelNavOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ import androidx.compose.material.icons.outlined.Place
|
|||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import dev.adriankuta.flights.ui.home.navigation.HomeRoute
|
import dev.adriankuta.flights.ui.home.navigation.HomeRoute
|
||||||
|
import dev.adriankuta.flights.ui.home.navigation.StationsRoute
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import dev.adriankuta.flights.ui.home.R as homeR
|
import dev.adriankuta.flights.ui.home.R as homeR
|
||||||
|
import dev.adriankuta.flights.ui.stations.R as stationsR
|
||||||
|
|
||||||
enum class TopLevelDestination(
|
enum class TopLevelDestination(
|
||||||
val icon: ImageVector,
|
val icon: ImageVector,
|
||||||
@ -22,7 +24,7 @@ enum class TopLevelDestination(
|
|||||||
),
|
),
|
||||||
STATIONS(
|
STATIONS(
|
||||||
icon = Icons.Outlined.Place,
|
icon = Icons.Outlined.Place,
|
||||||
titleTextId = homeR.string.home_screen_title,
|
titleTextId = stationsR.string.stations_screen_title,
|
||||||
route = HomeRoute::class,
|
route = StationsRoute::class,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ internal fun FlightsBottomBar(
|
|||||||
selected = selectedDestination == index,
|
selected = selectedDestination == index,
|
||||||
onClick = {
|
onClick = {
|
||||||
selectedDestination = index
|
selectedDestination = index
|
||||||
navController.navigateToTopLevelDestination(TopLevelDestination.HOME)
|
navController.navigateToTopLevelDestination(destination)
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
|
12
domain/stations/build.gradle.kts
Normal file
12
domain/stations/build.gradle.kts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.flights.android.library)
|
||||||
|
alias(libs.plugins.flights.android.library.hilt)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "dev.adriankuta.flights.domain.stations"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(projects.domain.types)
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package dev.adriankuta.flights.domain.stations
|
||||||
|
|
||||||
|
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||||
|
import dev.adriankuta.flights.domain.types.Country
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
fun interface ObserveAirportsGroupedByCountry {
|
||||||
|
operator fun invoke(): Flow<Map<Country, List<AirportInfo>>?>
|
||||||
|
}
|
@ -8,7 +8,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(projects.core.util)
|
||||||
implementation(projects.domain.search)
|
implementation(projects.domain.search)
|
||||||
|
implementation(projects.domain.stations)
|
||||||
implementation(projects.model.data.api)
|
implementation(projects.model.data.api)
|
||||||
implementation(projects.model.datasource.airports)
|
implementation(projects.model.datasource.airports)
|
||||||
|
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.adriankuta.flights.model.repository.di
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import dev.adriankuta.flights.domain.stations.ObserveAirportsGroupedByCountry
|
||||||
|
import dev.adriankuta.flights.model.repository.usecases.ObserveAirportsGroupedByCountryImpl
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@Suppress("UnnecessaryAbstractClass")
|
||||||
|
internal abstract class ObserveAirportsGroupedByCountryModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bind(
|
||||||
|
observeAirportsGroupedByCountryImpl: ObserveAirportsGroupedByCountryImpl,
|
||||||
|
): ObserveAirportsGroupedByCountry
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package dev.adriankuta.flights.model.repository.usecases
|
||||||
|
|
||||||
|
import dev.adriankuta.flights.core.util.DefaultDispatcher
|
||||||
|
import dev.adriankuta.flights.domain.stations.ObserveAirportsGroupedByCountry
|
||||||
|
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||||
|
import dev.adriankuta.flights.domain.types.Country
|
||||||
|
import dev.adriankuta.flights.model.datasource.airports.AirportsDatasource
|
||||||
|
import dev.adriankuta.flights.model.datasource.airports.entities.AirportInfoModel
|
||||||
|
import dev.adriankuta.flights.model.repository.mappers.toDomain
|
||||||
|
import dev.adriankuta.flights.model.repository.utilities.loadData
|
||||||
|
import dev.adriankuta.model.data.api.AirportService
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class ObserveAirportsGroupedByCountryImpl @Inject constructor(
|
||||||
|
private val airportService: AirportService,
|
||||||
|
private val airportsDatasource: AirportsDatasource,
|
||||||
|
@DefaultDispatcher private val dispatcher: CoroutineDispatcher,
|
||||||
|
) : ObserveAirportsGroupedByCountry {
|
||||||
|
override fun invoke(): Flow<Map<Country, List<AirportInfo>>?> = loadData(
|
||||||
|
onCacheInvalidated = { cacheKey ->
|
||||||
|
val response = airportService.getAirports("pl")
|
||||||
|
airportsDatasource.setAirportsInfo(response, cacheKey)
|
||||||
|
},
|
||||||
|
observeCache = {
|
||||||
|
airportsDatasource.airports
|
||||||
|
},
|
||||||
|
mapToDomain = { model ->
|
||||||
|
withContext(dispatcher) {
|
||||||
|
model.orEmpty().map(AirportInfoModel::toDomain).groupBy { airportInfo ->
|
||||||
|
airportInfo.country
|
||||||
|
}.toSortedMap(compareBy { it.name })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -9,7 +9,7 @@ import kotlinx.datetime.Clock
|
|||||||
internal fun <T, R> loadData(
|
internal fun <T, R> loadData(
|
||||||
onCacheInvalidated: suspend (cacheKey: String) -> Unit,
|
onCacheInvalidated: suspend (cacheKey: String) -> Unit,
|
||||||
observeCache: () -> Flow<Cache<T>>,
|
observeCache: () -> Flow<Cache<T>>,
|
||||||
mapToDomain: (T?) -> R?,
|
mapToDomain: suspend (T?) -> R?,
|
||||||
): Flow<R?> {
|
): Flow<R?> {
|
||||||
return observeCache().distinctUntilChanged().map {
|
return observeCache().distinctUntilChanged().map {
|
||||||
if (it.cacheKey == null) {
|
if (it.cacheKey == null) {
|
||||||
|
@ -25,6 +25,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
|||||||
include(":app")
|
include(":app")
|
||||||
include(":core:util")
|
include(":core:util")
|
||||||
include(":domain:search")
|
include(":domain:search")
|
||||||
|
include(":domain:stations")
|
||||||
include(":domain:types")
|
include(":domain:types")
|
||||||
include(":model:data:api")
|
include(":model:data:api")
|
||||||
include(":model:data:room")
|
include(":model:data:room")
|
||||||
@ -36,3 +37,4 @@ include(":model:repository")
|
|||||||
include(":ui:designsystem")
|
include(":ui:designsystem")
|
||||||
include(":ui:home")
|
include(":ui:home")
|
||||||
include(":ui:sharedui")
|
include(":ui:sharedui")
|
||||||
|
include(":ui:stations")
|
||||||
|
18
ui/stations/build.gradle.kts
Normal file
18
ui/stations/build.gradle.kts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.flights.android.library.compose)
|
||||||
|
alias(libs.plugins.flights.android.library.hilt)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "dev.adriankuta.flights.ui.stations"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.core.util)
|
||||||
|
implementation(projects.ui.designsystem)
|
||||||
|
implementation(projects.ui.sharedui)
|
||||||
|
implementation(projects.domain.stations)
|
||||||
|
|
||||||
|
implementation(libs.androidx.hilt.navigation.compose)
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package dev.adriankuta.flights.ui.home
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||||
|
import dev.adriankuta.flights.domain.types.Country
|
||||||
|
import dev.adriankuta.flights.ui.home.components.AirportInfoItem
|
||||||
|
import dev.adriankuta.flights.ui.home.components.CountryItem
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun StationsScreen(
|
||||||
|
stationScreenViewModel: StationsScreenViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val originAirports by stationScreenViewModel.originAirports.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
StationsScreen(
|
||||||
|
originAirports = originAirports,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StationsScreen(
|
||||||
|
originAirports: OriginAirportsUiState,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
when (originAirports) {
|
||||||
|
is OriginAirportsUiState.Error -> item { Text("Error") }
|
||||||
|
is OriginAirportsUiState.Loading -> item { Text("Loading") }
|
||||||
|
is OriginAirportsUiState.Success -> groupedAirports(originAirports.groupedAirports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LazyListScope.groupedAirports(
|
||||||
|
data: Map<Country, List<AirportInfo>>,
|
||||||
|
) {
|
||||||
|
data.keys.forEach { country ->
|
||||||
|
stickyHeader { CountryItem(country) }
|
||||||
|
items(data[country]!!) { airport ->
|
||||||
|
AirportInfoItem(airport)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package dev.adriankuta.flights.ui.home
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dev.adriankuta.flights.core.util.Result
|
||||||
|
import dev.adriankuta.flights.core.util.asResult
|
||||||
|
import dev.adriankuta.flights.domain.stations.ObserveAirportsGroupedByCountry
|
||||||
|
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||||
|
import dev.adriankuta.flights.domain.types.Country
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
internal class StationsScreenViewModel @Inject constructor(
|
||||||
|
observeAirportsGroupedByCountry: ObserveAirportsGroupedByCountry,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val originAirports = originAirportsUiState(
|
||||||
|
useCase = observeAirportsGroupedByCountry,
|
||||||
|
).stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = OriginAirportsUiState.Loading,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun originAirportsUiState(
|
||||||
|
useCase: ObserveAirportsGroupedByCountry,
|
||||||
|
): Flow<OriginAirportsUiState> {
|
||||||
|
return useCase()
|
||||||
|
.asResult()
|
||||||
|
.map { result ->
|
||||||
|
when (result) {
|
||||||
|
is Result.Error -> OriginAirportsUiState.Error(result.exception)
|
||||||
|
is Result.Loading -> OriginAirportsUiState.Loading
|
||||||
|
is Result.Success -> OriginAirportsUiState.Success(result.data.orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed interface OriginAirportsUiState {
|
||||||
|
data object Loading : OriginAirportsUiState
|
||||||
|
data class Success(val groupedAirports: Map<Country, List<AirportInfo>>) : OriginAirportsUiState
|
||||||
|
data class Error(val exception: Throwable) : OriginAirportsUiState
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed interface DestinationAirportsUiState {
|
||||||
|
data object Loading : DestinationAirportsUiState
|
||||||
|
data class Success(val airports: List<AirportInfo>) : DestinationAirportsUiState
|
||||||
|
data class Error(val exception: Throwable) : DestinationAirportsUiState
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package dev.adriankuta.flights.ui.home.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.adriankuta.flights.domain.types.AirportInfo
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun AirportInfoItem(
|
||||||
|
data: AirportInfo,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 4.dp),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = data.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = data.code,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (data.isBase) {
|
||||||
|
Text(
|
||||||
|
text = "BASE",
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "City: ${data.city.name}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Region: ${data.region.name}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package dev.adriankuta.flights.ui.home.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.adriankuta.flights.domain.types.Country
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun CountryItem(
|
||||||
|
data: Country,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = data.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Currency: ${data.currencyCode}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
@file:Suppress("MatchingDeclarationName")
|
||||||
|
|
||||||
|
package dev.adriankuta.flights.ui.home.navigation
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavOptions
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import dev.adriankuta.flights.ui.home.StationsScreen
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object StationsRoute
|
||||||
|
|
||||||
|
fun NavController.navigateToStations(
|
||||||
|
navOptions: NavOptions,
|
||||||
|
) {
|
||||||
|
navigate(route = StationsRoute, navOptions = navOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.stationsScreen() {
|
||||||
|
composable<StationsRoute> {
|
||||||
|
StationsScreen()
|
||||||
|
}
|
||||||
|
}
|
4
ui/stations/src/main/res/values/strings.xml
Normal file
4
ui/stations/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="stations_screen_title">Stations</string>
|
||||||
|
</resources>
|
Reference in New Issue
Block a user