Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GETEDUROAM-54: Type safe navigation #97

Merged
merged 1 commit into from
Jul 29, 2024
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
2 changes: 2 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
alias(libs.plugins.hilt)
alias(libs.plugins.serialization)
id("kotlin-parcelize")
alias(libs.plugins.protobuf)
id("com.google.gms.google-services")
Expand Down Expand Up @@ -151,6 +152,7 @@ dependencies {

implementation(libs.moshi.moshi)
implementation(libs.moshi.adapters)
implementation(libs.kotlin.serialization)
ksp(libs.moshi.codegen)

//OkHttp client
Expand Down
16 changes: 11 additions & 5 deletions android/app/src/main/java/app/eduroam/geteduroam/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.core.view.WindowCompat
import androidx.navigation.NavController
import app.eduroam.geteduroam.di.repository.NotificationRepository
import app.eduroam.geteduroam.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
Expand All @@ -25,10 +26,10 @@ class MainActivity : ComponentActivity() {
private var navController: NavController? = null

private val job = Job()
val coroutineContext: CoroutineContext
private val coroutineContext: CoroutineContext
get() = job + Dispatchers.IO

val coroutineScope = CoroutineScope(coroutineContext)
private val coroutineScope = CoroutineScope(coroutineContext)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -79,17 +80,22 @@ class MainActivity : ComponentActivity() {
}
}
}
} else if (intent?.hasExtra(NotificationRepository.KEY_EXTRA_PAYLOAD) == true) {
@Suppress("DEPRECATION")
intent.getParcelableExtra<Route.SelectProfile>(NotificationRepository.KEY_EXTRA_PAYLOAD)?.let { payload ->
navController?.navigate(payload)
}
} else {
navController?.handleDeepLink(intent)
}
}
}

private suspend fun openFileUri(fileUri: Uri): Boolean {
Route.ConfigureWifi.buildDeepLink(this@MainActivity, fileUri)?.let {
intent.data = Uri.parse(it)
Route.ConfigureWifi.buildDeepLink(this@MainActivity, fileUri)?.let { entry ->
return withContext(Dispatchers.Main) {
return@withContext navController?.handleDeepLink(intent) ?: false
navController?.navigate(entry)
return@withContext true
}
}
return false
Expand Down
83 changes: 44 additions & 39 deletions android/app/src/main/java/app/eduroam/geteduroam/MainGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,73 @@ import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalFocusManager
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navDeepLink
import androidx.navigation.toRoute
import app.eduroam.geteduroam.config.WifiConfigScreen
import app.eduroam.geteduroam.config.WifiConfigViewModel
import app.eduroam.geteduroam.config.model.EAPIdentityProviderList
import app.eduroam.geteduroam.models.Configuration
import app.eduroam.geteduroam.oauth.OAuthScreen
import app.eduroam.geteduroam.oauth.OAuthViewModel
import app.eduroam.geteduroam.organizations.SelectOrganizationScreen
import app.eduroam.geteduroam.organizations.SelectOrganizationViewModel
import app.eduroam.geteduroam.profile.SelectProfileScreen
import app.eduroam.geteduroam.profile.SelectProfileViewModel
import app.eduroam.geteduroam.status.ConfigSource
import app.eduroam.geteduroam.status.StatusScreen
import app.eduroam.geteduroam.status.StatusScreenViewModel
import app.eduroam.geteduroam.webview_fallback.WebViewFallbackScreen
import app.eduroam.geteduroam.webview_fallback.WebViewFallbackViewModel
import kotlin.reflect.typeOf

const val BASE_URI = "https://eduroam.org"
@Composable
fun MainGraph(
navController: NavHostController = rememberNavController(),
openFileUri: (Uri) -> Unit,
closeApp: () -> Unit
) : NavController {
NavHost(
navController = navController, startDestination = Route.StatusScreen.route
navController = navController, startDestination = Route.StatusScreen
) {
composable(Route.StatusScreen.route) { entry ->
composable<Route.StatusScreen> { _ ->
val viewModel = hiltViewModel<StatusScreenViewModel>()
StatusScreen(
viewModel = viewModel,
goToInstitutionSelection = {
navController.navigate(Route.SelectInstitution.route)
navController.navigate(Route.SelectInstitution)
},
renewAccount = { organizationId ->
navController.navigate(Route.SelectProfile.encodeInstitutionIdArgument(organizationId))
navController.navigate(Route.SelectProfile(institutionId = organizationId, customHostUri = null))
},
repairConfig = { source, organizationId, organizationName, eapIdentityProviderList ->
navController.navigate(Route.ConfigureWifi.encodeArguments(source, organizationId, organizationName, eapIdentityProviderList))
navController.navigate(Route.ConfigureWifi(
source = source,
organizationId = organizationId,
organizationName = organizationName,
eapIdentityProviderList = eapIdentityProviderList
))
})

}
composable(Route.SelectInstitution.route) { entry ->
composable<Route.SelectInstitution> { entry ->
val viewModel = hiltViewModel<SelectOrganizationViewModel>(entry)
val focusManager = LocalFocusManager.current
SelectOrganizationScreen(
viewModel = viewModel,
openProfileModal = { institutionId ->
// Remove the focus from the search field (if it was there)
focusManager.clearFocus(force = true)
navController.navigate(Route.SelectProfile.encodeInstitutionIdArgument(institutionId))
navController.navigate(Route.SelectProfile(institutionId = institutionId, customHostUri = null))
},
goToOAuth = { configuration ->
navController.navigate(
Route.OAuth.encodeArguments(
Route.OAuth(
configuration = configuration,
redirectUri = null
)
Expand All @@ -69,33 +79,27 @@ fun MainGraph(
goToConfigScreen = { source, organizationId, organizationName, wifiConfigData ->
navController.popBackStack()
navController.navigate(
Route.ConfigureWifi.encodeArguments(
Route.ConfigureWifi(
source, organizationId, organizationName, wifiConfigData
)
)
},
openFileUri = openFileUri,
discoverUrl = {
navController.navigate(Route.SelectProfile.encodeCustomHostArgument(it))
navController.navigate(Route.SelectProfile(institutionId = null, customHostUri = it.toString()))
}
)
}
composable(
route = Route.SelectProfile.routeWithArgs,
arguments = Route.SelectProfile.arguments,
deepLinks = listOf(navDeepLink {
uriPattern = Route.SelectProfile.deepLinkUrl
})
) { entry ->
composable<Route.SelectProfile> { entry ->
val viewModel = hiltViewModel<SelectProfileViewModel>(entry)
SelectProfileScreen(
viewModel = viewModel,
goToOAuth = { configuration ->
navController.navigate(Route.OAuth.encodeArguments(configuration, null))
navController.navigate(Route.OAuth(configuration, null))
},
goToConfigScreen = { source, organizationId, organizationName, provider ->
navController.navigate(
Route.ConfigureWifi.encodeArguments(
Route.ConfigureWifi(
source,
organizationId,
organizationName,
Expand All @@ -107,8 +111,10 @@ fun MainGraph(
navController.popBackStack()
})
}
composable(
route = Route.OAuth.routeWithArgs, arguments = Route.OAuth.arguments
composable<Route.OAuth>(
typeMap = mapOf(
typeOf<Configuration>() to NavTypes.ConfigurationNavType
)
) { _ ->
val viewModel = hiltViewModel<OAuthViewModel>()
OAuthScreen(
Expand All @@ -118,21 +124,23 @@ fun MainGraph(
},
goToWebViewFallback = { configuration, navigationUri ->
navController.navigate(
Route.WebViewFallback.encodeArguments(configuration, navigationUri)
Route.WebViewFallback(configuration, navigationUri.toString())
)
}
)
}
composable(
route = Route.WebViewFallback.routeWithArgs, arguments = Route.WebViewFallback.arguments
composable<Route.WebViewFallback>(
typeMap = mapOf(
typeOf<Configuration>() to NavTypes.ConfigurationNavType
)
) { _ ->
val viewModel = hiltViewModel<WebViewFallbackViewModel>()
WebViewFallbackScreen(
viewModel = viewModel,
onRedirectUriFound = { configuration, uri ->
navController.popBackStack() // this screen
navController.popBackStack() // OAuth screen
navController.navigate(Route.OAuth.encodeArguments(configuration, uri)) // OAuth screen again
navController.navigate(Route.OAuth(configuration, uri.toString())) // OAuth screen again
},
onCancel = {
navController.navigateUp() // OAuth screen
Expand All @@ -141,21 +149,18 @@ fun MainGraph(
)
}

composable(
route = Route.ConfigureWifi.routeWithArgs, arguments = Route.ConfigureWifi.arguments,
deepLinks = listOf(navDeepLink {
uriPattern = Route.ConfigureWifi.deepLinkUrl
})
composable<Route.ConfigureWifi>(
typeMap = mapOf(
typeOf<ConfigSource>() to NavTypes.ConfigSourceNavType,
typeOf<EAPIdentityProviderList>() to NavTypes.EAPIdentityProviderListNavType
)
) { backStackEntry ->
val wifiConfigData = Route.ConfigureWifi.decodeUrlArgument(backStackEntry.arguments)
val organizationId = Route.ConfigureWifi.decodeOrganizationIdArgument(backStackEntry.arguments)
val organizationName = Route.ConfigureWifi.decodeOrganizationNameArgument(backStackEntry.arguments)
val source = Route.ConfigureWifi.decodeSourceArgument(backStackEntry.arguments)
val data: Route.ConfigureWifi = backStackEntry.toRoute()
val viewModel = hiltViewModel<WifiConfigViewModel>()
viewModel.eapIdentityProviderList = wifiConfigData
viewModel.organizationId = organizationId
viewModel.source = source
viewModel.organizationName = organizationName
viewModel.eapIdentityProviderList = data.eapIdentityProviderList
viewModel.organizationId = data.organizationId
viewModel.source = data.source
viewModel.organizationName = data.organizationName
WifiConfigScreen(
viewModel,
closeApp = closeApp,
Expand Down
Loading
Loading