From d19a9a13ef2eec0259034d32d5c4ee73084e4ac3 Mon Sep 17 00:00:00 2001 From: Iulia Stana Date: Sun, 31 Jul 2022 15:38:33 +0200 Subject: [PATCH] Handle institution selection and prepare for profile selection --- android/app/build.gradle | 2 + .../app/eduroam/geteduroam/EduTopAppBar.kt | 10 -- .../java/app/eduroam/geteduroam/NavGraph.kt | 21 ++- .../java/app/eduroam/geteduroam/Screens.kt | 17 +++ .../geteduroam/institutions/InstitutionRow.kt | 45 ++++++ .../institutions/InstitutionSearchHeader.kt | 74 ++++++++++ .../institutions/InstitutionState.kt | 58 ++++++++ .../institutions/SelectInstitutionScreen.kt | 115 +++++++++++++++ .../welcome/SelectInstitutionScreen.kt | 134 ------------------ android/app/src/main/res/values/strings.xml | 5 +- .../main/java/app/eduroam/shared/Config.kt | 6 +- .../java/app/eduroam/shared/Dependencies.kt | 5 +- .../kotlin/app/eduroam/shared/Koin.kt | 4 +- .../eduroam/shared/response/Institution.kt | 14 +- .../app/eduroam/shared/response/Profile.kt | 28 ++++ ...epository.kt => InstitutionsRepository.kt} | 32 ++--- .../select/SelectInstitutionViewModel.kt | 51 ++++--- 17 files changed, 428 insertions(+), 193 deletions(-) create mode 100644 android/app/src/main/java/app/eduroam/geteduroam/Screens.kt create mode 100644 android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionRow.kt create mode 100644 android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionSearchHeader.kt create mode 100644 android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionState.kt create mode 100644 android/app/src/main/java/app/eduroam/geteduroam/institutions/SelectInstitutionScreen.kt delete mode 100644 android/app/src/main/java/app/eduroam/geteduroam/welcome/SelectInstitutionScreen.kt create mode 100644 shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Profile.kt rename shared/core/src/commonMain/kotlin/app/eduroam/shared/select/{SelectInstitutionRepository.kt => InstitutionsRepository.kt} (52%) diff --git a/android/app/build.gradle b/android/app/build.gradle index 84260d73..9a754330 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -100,6 +100,8 @@ android { dependencies { implementation project(":shared:core") implementation Libs.Kotlin.stdlibJdk + implementation Libs.Ktor.clientSerializationJvm + implementation Libs.Kotlin.Coroutines.android implementation Libs.AndroidX.Activity.activityCompose implementation Libs.AndroidX.appcompat diff --git a/android/app/src/main/java/app/eduroam/geteduroam/EduTopAppBar.kt b/android/app/src/main/java/app/eduroam/geteduroam/EduTopAppBar.kt index 03aae5f6..693d4124 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/EduTopAppBar.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/EduTopAppBar.kt @@ -1,8 +1,6 @@ package app.eduroam.geteduroam import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Search import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -37,14 +35,6 @@ fun EduTopAppBar( navigationIcon = navigationIcon, scrollBehavior = scrollBehavior, colors = foregroundColors, - actions = { - IconButton(onClick = {}) { - Icon( - imageVector = Icons.Outlined.Search, - contentDescription = "Search" - ) - } - }, modifier = Modifier.windowInsetsPadding( WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) ) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/NavGraph.kt b/android/app/src/main/java/app/eduroam/geteduroam/NavGraph.kt index 9d332575..4805aba3 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/NavGraph.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/NavGraph.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import app.eduroam.geteduroam.welcome.SelectInstitutionScreen +import app.eduroam.geteduroam.institutions.SelectInstitutionScreen import app.eduroam.shared.select.SelectInstitutionViewModel import co.touchlab.kermit.Logger @@ -13,10 +13,19 @@ fun NavGraph(viewModel: SelectInstitutionViewModel, log: Logger) { val navController = rememberNavController() NavHost( navController = navController, - startDestination = SelectInstitution + startDestination = Screens.SelectInstitution.route ) { - composable(SelectInstitution) { SelectInstitutionScreen(viewModel, log) } + composable(Screens.SelectInstitution.route) { + SelectInstitutionScreen( + viewModel = viewModel, + gotToProfileSelection = { it -> navController.navigate("${Screens.SelectProfile.route}/$it") } + ) + } + composable( + route = Screens.SelectProfile.routeWithArgs, + arguments = Screens.SelectProfile.arguments + ) { backStackEntry -> + //TODO: open profile screen + } } -} - -private const val SelectInstitution = "selectInstitution" +} \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/Screens.kt b/android/app/src/main/java/app/eduroam/geteduroam/Screens.kt new file mode 100644 index 00000000..0ceb23be --- /dev/null +++ b/android/app/src/main/java/app/eduroam/geteduroam/Screens.kt @@ -0,0 +1,17 @@ +package app.eduroam.geteduroam + +import androidx.navigation.NavType +import androidx.navigation.navArgument + +sealed class Screens(val route: String) { + object SelectProfile : Screens(route = "select_profile") { + const val urlArg = "url" + val routeWithArgs = "${route}/{${urlArg}}" + val arguments = listOf(navArgument(urlArg) { + type = NavType.StringType + defaultValue = "" + }) + } + + object SelectInstitution : Screens(route = "select_institution") +} \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionRow.kt b/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionRow.kt new file mode 100644 index 00000000..6160bbe4 --- /dev/null +++ b/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionRow.kt @@ -0,0 +1,45 @@ +package app.eduroam.geteduroam.institutions + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import app.eduroam.shared.response.Institution + + +@Composable +fun InstitutionRow( + institution: Institution, + onSelectInstitution: (Institution) -> Unit, + modifier: Modifier = Modifier, +) = Column( + modifier + .fillMaxWidth() + .clickable { onSelectInstitution(institution) }) { + Spacer(Modifier.height(8.dp)) + Text( + text = institution.name, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.primary + ) + Spacer(Modifier.height(4.dp)) + Text( + text = institution.country, + style = MaterialTheme.typography.bodySmall, + ) + Spacer(Modifier.height(8.dp)) + Divider( + Modifier + .height(0.5.dp) + .fillMaxWidth() + ) +} \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionSearchHeader.kt b/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionSearchHeader.kt new file mode 100644 index 00000000..e98180d4 --- /dev/null +++ b/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionSearchHeader.kt @@ -0,0 +1,74 @@ +package app.eduroam.geteduroam.institutions + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import app.eduroam.geteduroam.R +import app.eduroam.geteduroam.ui.theme.AppTheme + +@Composable +fun InstitutionSearchHeader( + searchText: String, onSearchTextChange: (String) -> Unit, modifier: Modifier = Modifier, +) = Column(modifier.fillMaxWidth()) { + val focusManager = LocalFocusManager.current + Text( + text = stringResource(id = R.string.institution_select_title), + style = MaterialTheme.typography.headlineSmall, + ) + Spacer(Modifier.height(8.dp)) + OutlinedTextField( + value = searchText, + onValueChange = onSearchTextChange, + singleLine = true, + placeholder = { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Icon( + imageVector = Icons.Default.Search, + contentDescription = "", + modifier = Modifier.size(ButtonDefaults.IconSize) + ) + Spacer(Modifier.width(8.dp)) + Text( + text = stringResource(id = R.string.institution_search_text), + color = MaterialTheme.colorScheme.secondary, + modifier = Modifier.weight(1f), + fontWeight = FontWeight.Light + ) + } + }, + + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, keyboardType = KeyboardType.Text + ), + keyboardActions = KeyboardActions { + focusManager.clearFocus() + }, + modifier = Modifier.fillMaxWidth(), + ) +} + + +@ExperimentalMaterial3Api +@Preview +@Composable +private fun MarketPlaceHeader_Preview() { + AppTheme { + InstitutionSearchHeader("filterOn", {}) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionState.kt b/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionState.kt new file mode 100644 index 00000000..4173c3cb --- /dev/null +++ b/android/app/src/main/java/app/eduroam/geteduroam/institutions/InstitutionState.kt @@ -0,0 +1,58 @@ +package app.eduroam.geteduroam.institutions + +import android.content.Context +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.flowWithLifecycle +import app.eduroam.shared.response.Institution +import app.eduroam.shared.select.SelectInstitutionViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + + +@Composable +fun rememberSelectInstitutionState( + viewModel: SelectInstitutionViewModel, + goToProfileSelection: (String) -> Unit, + lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, + context: Context = LocalContext.current, + coroutineScope: CoroutineScope = rememberCoroutineScope(), +): SelectInstitutionState { + val currentGoToProfileSelection by rememberUpdatedState(goToProfileSelection) + return remember(viewModel, lifecycleOwner, context, coroutineScope) { + SelectInstitutionState( + viewModel = viewModel, + coroutineScope = coroutineScope, + lifecycleOwner = lifecycleOwner, + goToProfileSelection = currentGoToProfileSelection, + ) + } +} + +@Stable +class SelectInstitutionState( + private val viewModel: SelectInstitutionViewModel, + coroutineScope: CoroutineScope, + goToProfileSelection: (String) -> Unit, + lifecycleOwner: LifecycleOwner, +) { + init { + coroutineScope.launch { + viewModel.currentInstitution.flowWithLifecycle(lifecycleOwner.lifecycle).collectLatest { + if (it != null) { + val institutionArgument = Json.encodeToString(it) + goToProfileSelection(institutionArgument) + } + } + } + } + + fun onSelectInstitution(selectedInstitution: Institution) { + viewModel.onInstitutionSelect(selectedInstitution) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/institutions/SelectInstitutionScreen.kt b/android/app/src/main/java/app/eduroam/geteduroam/institutions/SelectInstitutionScreen.kt new file mode 100644 index 00000000..acfd4bdd --- /dev/null +++ b/android/app/src/main/java/app/eduroam/geteduroam/institutions/SelectInstitutionScreen.kt @@ -0,0 +1,115 @@ +package app.eduroam.geteduroam.institutions + +import android.annotation.SuppressLint +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Scaffold +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.flowWithLifecycle +import app.eduroam.geteduroam.EduTopAppBar +import app.eduroam.geteduroam.R +import app.eduroam.shared.models.DataState +import app.eduroam.shared.models.ItemDataSummary +import app.eduroam.shared.select.SelectInstitutionViewModel + +@Composable +fun SelectInstitutionScreen( + viewModel: SelectInstitutionViewModel, + gotToProfileSelection: (String) -> Unit, + selectInstitutionState: SelectInstitutionState = rememberSelectInstitutionState(viewModel, gotToProfileSelection), +) { + val lifecycleOwner = LocalLifecycleOwner.current + val lifecycleAwareUiDataStateFlow = remember(viewModel.uiDataState, lifecycleOwner) { + viewModel.uiDataState.flowWithLifecycle(lifecycleOwner.lifecycle) + } + + @SuppressLint("StateFlowValueCalledInComposition") // False positive lint check when used inside collectAsState() + val uiDataState: DataState by lifecycleAwareUiDataStateFlow.collectAsState(viewModel.uiDataState.value) + + SelectInstitutionContent( + institutionsState = uiDataState, + selectInstitutionState = selectInstitutionState, + searchText = uiDataState.data?.filterOn.orEmpty(), + onSearchTextChange = { viewModel.onSearchTextChange(it) }, + ) +} + +@Composable +fun SelectInstitutionContent( + institutionsState: DataState, + selectInstitutionState: SelectInstitutionState, + searchText: String, + onSearchTextChange: (String) -> Unit = {}, +) = Scaffold( + topBar = { + EduTopAppBar(stringResource(R.string.name)) + } +) { paddingValues -> + Column( + Modifier + .padding(paddingValues) + .fillMaxSize() + .systemBarsPadding() + .padding(horizontal = 16.dp) + ) { + LazyColumn { + item { + InstitutionSearchHeader( + searchText = searchText, + onSearchTextChange = onSearchTextChange, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(Modifier.height(8.dp)) + } + + if (institutionsState.loading) { + item { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth() + ) + } + } else if (institutionsState.exception != null) { + item { + Text( + text = institutionsState.exception.orEmpty(), + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.bodyLarge, + ) + } + } else { + if (institutionsState.empty) { + item { + Text(stringResource(id = R.string.institutions_no_results)) + } + } else { + + item { + Text( + text = stringResource(id = R.string.institutions_choose_one), + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(Modifier.height(8.dp)) + } + + institutionsState.data?.institutions?.forEach { institution -> + item { + InstitutionRow(institution, { selectInstitutionState.onSelectInstitution(it) }) + } + } + } + } + } + } +} + diff --git a/android/app/src/main/java/app/eduroam/geteduroam/welcome/SelectInstitutionScreen.kt b/android/app/src/main/java/app/eduroam/geteduroam/welcome/SelectInstitutionScreen.kt deleted file mode 100644 index 4c9870d5..00000000 --- a/android/app/src/main/java/app/eduroam/geteduroam/welcome/SelectInstitutionScreen.kt +++ /dev/null @@ -1,134 +0,0 @@ -package app.eduroam.geteduroam.welcome - -import android.annotation.SuppressLint -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Divider -import androidx.compose.material.Scaffold -import androidx.compose.material.Text -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.flowWithLifecycle -import app.eduroam.geteduroam.EduTopAppBar -import app.eduroam.geteduroam.R -import app.eduroam.shared.models.DataState -import app.eduroam.shared.models.ItemDataSummary -import app.eduroam.shared.response.Institution -import app.eduroam.shared.select.SelectInstitutionViewModel -import co.touchlab.kermit.Logger - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SelectInstitutionScreen(viewModel: SelectInstitutionViewModel, log: Logger) { - val lifecycleOwner = LocalLifecycleOwner.current - val lifecycleAwareInstitutionsFlow = remember(viewModel.institutions, lifecycleOwner) { - viewModel.institutions.flowWithLifecycle(lifecycleOwner.lifecycle) - } - - @SuppressLint("StateFlowValueCalledInComposition") // False positive lint check when used inside collectAsState() - val institutionsState: DataState by lifecycleAwareInstitutionsFlow.collectAsState(viewModel.institutions.value) - - SelectInstitutionContent( - institutionsState = institutionsState, - onSelect = viewModel::onInstitutionSelect - ) -} - -@Composable -fun SelectInstitutionContent( - institutionsState: DataState, - onSelect: (Institution) -> Unit = {}, -) = Scaffold( - topBar = { - EduTopAppBar("") - } -) { - Spacer(modifier = Modifier.height(56.dp)) - if (institutionsState.empty) { - Empty(Modifier.padding(it)) - } - val data = institutionsState.data - if (data != null) { - Success(successData = data, onSelect = onSelect, modifier = Modifier.padding(it)) - } - val exception = institutionsState.exception - if (exception != null) { - Error(exception, modifier = Modifier.padding(it)) - } -} - - -@Composable -fun Empty(modifier: Modifier = Modifier) { - Column( - modifier = modifier - .fillMaxSize() - .padding(8.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text(stringResource(R.string.empty_institutions)) - } -} - - -@Composable -fun Error(error: String, modifier: Modifier = Modifier) { - Column( - modifier = modifier - .fillMaxSize() - .padding(8.dp) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text(text = error) - } -} - - -@Composable -fun Success( - successData: ItemDataSummary, - onSelect: (Institution) -> Unit, - modifier: Modifier = Modifier, -) { - InstitutionList(institutions = successData.institutions, onSelect, modifier) -} - -@Composable -fun InstitutionList(institutions: List, onItemClick: (Institution) -> Unit, modifier: Modifier = Modifier) { - LazyColumn(modifier = modifier.padding(8.dp)) { - items(institutions) { institution -> - Column( - Modifier - .clickable { - onItemClick(institution) - } - .padding(16.dp) - ) { - Text( - text = institution.name, - modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.titleLarge - ) - } - Divider() - } - } -} - diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 129954df..50b75435 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -2,5 +2,8 @@ geteduroam Welcome - Could not retrieve any institutions. + Could not retrieve any institutions. + Search for an institution + Select an institution + Select the institution you want to connect to. diff --git a/buildSrc/src/main/java/app/eduroam/shared/Config.kt b/buildSrc/src/main/java/app/eduroam/shared/Config.kt index e9a48b7f..56132b42 100644 --- a/buildSrc/src/main/java/app/eduroam/shared/Config.kt +++ b/buildSrc/src/main/java/app/eduroam/shared/Config.kt @@ -1,7 +1,7 @@ package app.eduroam.shared object Config { - const val compileSdk = 31 - const val targetSdk = 31 - const val minSdk = 23 + const val compileSdk = 32 + const val targetSdk = 32 + const val minSdk = 26 } diff --git a/buildSrc/src/main/java/app/eduroam/shared/Dependencies.kt b/buildSrc/src/main/java/app/eduroam/shared/Dependencies.kt index 4ca3aa77..79d36348 100644 --- a/buildSrc/src/main/java/app/eduroam/shared/Dependencies.kt +++ b/buildSrc/src/main/java/app/eduroam/shared/Dependencies.kt @@ -9,6 +9,7 @@ object Libs { const val desugaring = "com.android.tools:desugar_jdk_libs:1.1.5" object AndroidX { + const val core = "androidx.core:core-ktx:1.8.0" const val appcompat = "androidx.appcompat:appcompat:1.4.1" const val splashCore = "androidx.core:core-splashscreen:1.0.0-beta02" @@ -115,6 +116,7 @@ object Libs { const val stdlibCommon = "org.jetbrains.kotlin:kotlin-stdlib-common:$version" const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$version" const val serialization = "org.jetbrains.kotlin:kotlin-serialization:$version" + const val serializationJson = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2" const val extensions = "org.jetbrains.kotlin:kotlin-android-extensions:$version" object Coroutines { @@ -130,6 +132,7 @@ object Libs { const val clientCore = "io.ktor:ktor-client-core:$version" const val clientJson = "io.ktor:ktor-client-json:$version" const val clientSerialization = "io.ktor:ktor-serialization-kotlinx-json:$version" + const val clientSerializationJvm = "io.ktor:ktor-client-serialization-jvm:$version" const val contentNegotiation = "io.ktor:ktor-client-content-negotiation:$version" const val clientAndroid = "io.ktor:ktor-client-okhttp:$version" const val clientiOS = "io.ktor:ktor-client-ios:$version" @@ -137,7 +140,7 @@ object Libs { } object Material { - private const val version = "1.6.0" + private const val version = "1.6.1" const val design = "com.google.android.material:material:$version" } diff --git a/shared/core/src/commonMain/kotlin/app/eduroam/shared/Koin.kt b/shared/core/src/commonMain/kotlin/app/eduroam/shared/Koin.kt index f1d95661..4c51b2ce 100644 --- a/shared/core/src/commonMain/kotlin/app/eduroam/shared/Koin.kt +++ b/shared/core/src/commonMain/kotlin/app/eduroam/shared/Koin.kt @@ -2,7 +2,7 @@ package app.eduroam.shared import app.eduroam.shared.ktor.InstitutionApi import app.eduroam.shared.ktor.InstitutionApiImpl -import app.eduroam.shared.select.SelectInstitutionRepository +import app.eduroam.shared.select.InstitutionsRepository import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import co.touchlab.kermit.platformLogWriter @@ -54,7 +54,7 @@ private val coreModule = module { factory { (tag: String?) -> if (tag != null) baseLogger.withTag(tag) else baseLogger } single { - SelectInstitutionRepository( + InstitutionsRepository( get(), getWith("SelectInstitutionRepository"), ) diff --git a/shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Institution.kt b/shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Institution.kt index 54784237..ff555b04 100644 --- a/shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Institution.kt +++ b/shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Institution.kt @@ -8,4 +8,16 @@ data class Institution( val country: String, val id: String, val name: String, -) + val profiles: List, +) { + fun hasSingleProfile() = profiles.size == 1 + + fun requiresAuth(profile: Profile? = null) = if (profiles.size == 1 || profile == null) { + profiles[0].oauth + } else if (profile != null) { + profile.oauth + } else { + false + } + +} diff --git a/shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Profile.kt b/shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Profile.kt new file mode 100644 index 00000000..6568a69a --- /dev/null +++ b/shared/core/src/commonMain/kotlin/app/eduroam/shared/response/Profile.kt @@ -0,0 +1,28 @@ +package app.eduroam.shared.response + +import kotlinx.serialization.Serializable + +//with authorization +//authorization_endpoint "https://shlonderwijs.get…roam.nl/oauth/authorize/" +//default true +//eapconfig_endpoint "https://shlonderwijs.geteduroam.nl/api/eap-config/" +//id "letswifi_cat_8149" +//name "geteduroam" +//oauth true +//token_endpoint "https://shlonderwijs.geteduroam.nl/oauth/token/" + +//no token +// +//eapconfig_endpoint "https://cat.eduroam.org/user/API.php?action=downloadInstaller&device=eap-generic&profile=7347" +//id "cat_7347" +//name "(UFV) Universidad Francisco de Vitoria" +//oauth false +@Serializable +data class Profile( + val eapconfig_endpoint: String? = null, + val id: String, + val name: String, + val oauth: Boolean = false, + val authorization_endpoint: String? = null, + val token_endpoint: String? = null, +) \ No newline at end of file diff --git a/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/SelectInstitutionRepository.kt b/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/InstitutionsRepository.kt similarity index 52% rename from shared/core/src/commonMain/kotlin/app/eduroam/shared/select/SelectInstitutionRepository.kt rename to shared/core/src/commonMain/kotlin/app/eduroam/shared/select/InstitutionsRepository.kt index d8f60139..b7353b09 100644 --- a/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/SelectInstitutionRepository.kt +++ b/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/InstitutionsRepository.kt @@ -8,12 +8,12 @@ import co.touchlab.stately.ensureNeverFrozen import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -class SelectInstitutionRepository( +class InstitutionsRepository( private val institutionApi: InstitutionApi, log: Logger, ) { - private val log = log.withTag("BreedRepository") + private val log = log.withTag("InstitutionsRepository") init { ensureNeverFrozen() @@ -25,22 +25,20 @@ class SelectInstitutionRepository( emit(institutionsList) } - private suspend fun getInstitutionsFromNetwork(): DataState { - return try { - val breedResult = institutionApi.getJsonFromApi() - log.v { "Institutions network result: ${breedResult.instances.size}" } - if (breedResult.instances.isEmpty()) { - DataState(empty = true) - } else { - DataState( - ItemDataSummary( - breedResult.instances - ) + private suspend fun getInstitutionsFromNetwork(): DataState = try { + val institutionResult = institutionApi.getJsonFromApi() + log.v { "Institutions network result: ${institutionResult.instances.size}" } + if (institutionResult.instances.isEmpty()) { + DataState(empty = true) + } else { + DataState( + ItemDataSummary( + institutionResult.instances ) - } - } catch (e: Exception) { - log.e(e) { "Error fetching institutions list" } - DataState(exception = "Unable to download institutions list") + ) } + } catch (e: Exception) { + log.e(e) { "Error fetching institutions list" } + DataState(exception = "Unable to download institutions list") } } \ No newline at end of file diff --git a/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/SelectInstitutionViewModel.kt b/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/SelectInstitutionViewModel.kt index 4cf9ddf9..1e2d0072 100644 --- a/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/SelectInstitutionViewModel.kt +++ b/shared/core/src/commonMain/kotlin/app/eduroam/shared/select/SelectInstitutionViewModel.kt @@ -10,43 +10,58 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch class SelectInstitutionViewModel( - private val institutionRepository: SelectInstitutionRepository, + private val institutionRepository: InstitutionsRepository, log: Logger, ) : ViewModel() { private val log = log.withTag("SelectInstitutionViewModel") - val institutions: StateFlow> = MutableStateFlow( + val uiDataState: StateFlow> = MutableStateFlow( DataState(loading = true) ) - init { - observeInstitutions() - } - - override fun onCleared() { - log.v("Clearing SelectInstitutionViewModel") - } - + val currentInstitution: MutableStateFlow = MutableStateFlow(null) - fun onInstitutionSelect(selectedInstitution: Institution) { - //TODO: handle selection + init { + fetchInstitutionsList() } - private fun observeInstitutions() { + private fun fetchInstitutionsList() { viewModelScope.launch { - log.v { "getInstitutionsList: Collecting Things" } institutionRepository.fetchInstitutions().collect { dataState -> if (dataState.loading) { - updateInstitutions(institutions.value.copy(loading = true)) + updateDataState(uiDataState.value.copy(loading = true)) } else { - updateInstitutions(dataState) + updateDataState(dataState) } } } } - private fun updateInstitutions(newValue: DataState) { - (institutions as MutableStateFlow).value = newValue + override fun onCleared() { + log.v("Clearing SelectInstitutionViewModel") + } + + fun onInstitutionSelect(selectedInstitution: Institution) { + if (selectedInstitution.hasSingleProfile()) { + updateDataState(uiDataState.value.copy(loading = true)) + //todo: download EAP file + } else { + currentInstitution.value = selectedInstitution + } + } + + private fun updateDataState(newValue: DataState) { + (uiDataState as MutableStateFlow).value = newValue + } + + fun onSearchTextChange(search: String) { + val listData = uiDataState.value.data ?: return + updateDataState(DataState(listData.copy(filterOn = search))) + if (search.length >= 3) { + val filteredList = listData.institutions.filter { it.name.startsWith(search, true) || it.name.contains(search, true) } + updateDataState(DataState(listData.copy(filterOn = search, + institutions = filteredList))) + } } }