diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 26ed3c1..fa935dd 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -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") @@ -151,6 +152,7 @@ dependencies { implementation(libs.moshi.moshi) implementation(libs.moshi.adapters) + implementation(libs.kotlin.serialization) ksp(libs.moshi.codegen) //OkHttp client diff --git a/android/app/src/main/java/app/eduroam/geteduroam/MainActivity.kt b/android/app/src/main/java/app/eduroam/geteduroam/MainActivity.kt index 5232ea6..75a4ddb 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/MainActivity.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/MainActivity.kt @@ -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 @@ -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) @@ -79,6 +80,11 @@ class MainActivity : ComponentActivity() { } } } + } else if (intent?.hasExtra(NotificationRepository.KEY_EXTRA_PAYLOAD) == true) { + @Suppress("DEPRECATION") + intent.getParcelableExtra(NotificationRepository.KEY_EXTRA_PAYLOAD)?.let { payload -> + navController?.navigate(payload) + } } else { navController?.handleDeepLink(intent) } @@ -86,10 +92,10 @@ class MainActivity : ComponentActivity() { } 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 diff --git a/android/app/src/main/java/app/eduroam/geteduroam/MainGraph.kt b/android/app/src/main/java/app/eduroam/geteduroam/MainGraph.kt index 0d3d10a..4ac146c 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/MainGraph.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/MainGraph.kt @@ -4,26 +4,31 @@ 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(), @@ -31,24 +36,29 @@ fun MainGraph( closeApp: () -> Unit ) : NavController { NavHost( - navController = navController, startDestination = Route.StatusScreen.route + navController = navController, startDestination = Route.StatusScreen ) { - composable(Route.StatusScreen.route) { entry -> + composable { _ -> val viewModel = hiltViewModel() 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 { entry -> val viewModel = hiltViewModel(entry) val focusManager = LocalFocusManager.current SelectOrganizationScreen( @@ -56,11 +66,11 @@ fun MainGraph( 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 ) @@ -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 { entry -> val viewModel = hiltViewModel(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, @@ -107,8 +111,10 @@ fun MainGraph( navController.popBackStack() }) } - composable( - route = Route.OAuth.routeWithArgs, arguments = Route.OAuth.arguments + composable( + typeMap = mapOf( + typeOf() to NavTypes.ConfigurationNavType + ) ) { _ -> val viewModel = hiltViewModel() OAuthScreen( @@ -118,13 +124,15 @@ 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( + typeMap = mapOf( + typeOf() to NavTypes.ConfigurationNavType + ) ) { _ -> val viewModel = hiltViewModel() WebViewFallbackScreen( @@ -132,7 +140,7 @@ fun MainGraph( 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 @@ -141,21 +149,18 @@ fun MainGraph( ) } - composable( - route = Route.ConfigureWifi.routeWithArgs, arguments = Route.ConfigureWifi.arguments, - deepLinks = listOf(navDeepLink { - uriPattern = Route.ConfigureWifi.deepLinkUrl - }) + composable( + typeMap = mapOf( + typeOf() to NavTypes.ConfigSourceNavType, + typeOf() 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() - 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, diff --git a/android/app/src/main/java/app/eduroam/geteduroam/Route.kt b/android/app/src/main/java/app/eduroam/geteduroam/Route.kt index 334f423..60c6b6f 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/Route.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/Route.kt @@ -1,224 +1,136 @@ package app.eduroam.geteduroam -import android.content.ContentResolver import android.content.Context -import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.Parcelable import androidx.navigation.NavType -import androidx.navigation.navArgument import app.eduroam.geteduroam.config.AndroidConfigParser -import app.eduroam.geteduroam.config.model.EAPIdentityProvider import app.eduroam.geteduroam.config.model.EAPIdentityProviderList -import app.eduroam.geteduroam.extensions.DateJsonAdapter import app.eduroam.geteduroam.models.Configuration import app.eduroam.geteduroam.status.ConfigSource -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import timber.log.Timber -import java.io.StringReader -import java.net.URL -import java.util.Date - - -sealed class Route(val route: String) { - object SelectInstitution : Route(route = "select_institution") - object StatusScreen : Route(route = "status_screen") - object SelectProfile : Route(route = "select_profile") { - const val institutionIdArg = "institutionIdArg" - const val customHostArg = "customHostArg" - val routeWithArgs = "$route/?institutionId={$institutionIdArg}&customHost={$customHostArg}" - val arguments = listOf( - navArgument(institutionIdArg) { - type = NavType.StringType - nullable = true - defaultValue = "" - }, - navArgument(customHostArg) { - type = NavType.StringType - nullable = true - defaultValue = "" - } - ) - val deepLinkUrl = "$BASE_URI/${route}/?institutionId={${institutionIdArg}}&customHost={$customHostArg}" - fun buildDeepLink(institutionId: String) = "$BASE_URI/${route}/?institutionId=${Uri.encode(institutionId)}" - fun buildDeepLink(customHost: Uri) = "$BASE_URI/${route}/?customHost=${Uri.encode(customHost.toString())}" - fun encodeInstitutionIdArgument(id: String) = "$route/?institutionId=${Uri.encode(id)}" - fun encodeCustomHostArgument(customHost: Uri) = "$route/?customHost=${Uri.encode(customHost.toString())}" - } - - object OAuth : Route(route = "oauth_prompt") { - const val configurationArg = "configurationArg" - const val redirectUriArg = "redirectUriArg" - - val routeWithArgs = "$route?config={$configurationArg}&redirectUri={$redirectUriArg}" - val arguments = listOf( - navArgument(configurationArg) { - type = NavType.StringType - nullable = false - defaultValue = "" - }, - navArgument(redirectUriArg) { - type = NavType.StringType - nullable = true - defaultValue = "" - } - ) - - fun encodeArguments(configuration: Configuration, redirectUri: Uri?): String { - val moshi = Moshi.Builder().build() - val adapter: JsonAdapter = moshi.adapter(Configuration::class.java) - val serializedConfig = adapter.toJson(configuration) - val encodedConfig = Uri.encode(serializedConfig) - if (redirectUri == null) { - return "$route?config=$encodedConfig" +import java.net.URLDecoder +import java.net.URLEncoder + +object NavTypes { + val ConfigurationNavType = object: NavType(isNullableAllowed = false) { + override fun get(bundle: Bundle, key: String): Configuration? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + bundle.getParcelable(key, Configuration::class.java) } else { - val encodedUri = Uri.encode(redirectUri.toString()) - return "$route?config=$encodedConfig&redirectUri=$encodedUri" + @Suppress("DEPRECATION") + bundle.getParcelable(key) } } - fun decodeConfigurationArgument(encodedConfiguration: String): Configuration { - val moshi = Moshi.Builder().build() - val adapter: JsonAdapter = moshi.adapter(Configuration::class.java) - val decodedConfiguration = Uri.decode(encodedConfiguration) - return adapter.fromJson(decodedConfiguration)!! + override fun parseValue(value: String): Configuration { + val decoded = URLDecoder.decode(value, Charsets.UTF_8.name()) + return Json.decodeFromString(decoded) } - } - - object WebViewFallback : Route(route = "webview_fallback") { - const val urlArg = "urlArg" - const val configurationArg = "configurationArg" - - val routeWithArgs = "$route/{$configurationArg}?url={$urlArg}" - val arguments = listOf( - navArgument(configurationArg) { - type = NavType.StringType - nullable = false - defaultValue = "" - }, - navArgument(urlArg) { - type = NavType.StringType - nullable = false - defaultValue = "" - } - ) - fun decodeConfigurationArgument(encodedConfiguration: String): Configuration { - val moshi = Moshi.Builder().build() - val adapter: JsonAdapter = moshi.adapter(Configuration::class.java) - val decodedConfiguration = Uri.decode(encodedConfiguration) - return adapter.fromJson(decodedConfiguration)!! + override fun serializeAsValue(value: Configuration): String { + val string = Json.encodeToString(value) + return URLEncoder.encode(string, Charsets.UTF_8.name()) } - fun encodeArguments(configuration: Configuration, uri: Uri): String { - val moshi = Moshi.Builder().build() - val adapter: JsonAdapter = moshi.adapter(Configuration::class.java) - val serializedConfig = adapter.toJson(configuration) - val encodedConfig = Uri.encode(serializedConfig) - val encodedUri = Uri.encode(uri.toString()) - return "$route/$encodedConfig?url=$encodedUri" + override fun put(bundle: Bundle, key: String, value: Configuration) { + bundle.putParcelable(key, value) } } - - - object ConfigureWifi : Route(route = "configure_wifi") { - const val organizationIdArg = "organizationId" - const val organizationNameArg = "organizationName" - const val wifiConfigDataArg = "wificonfigdata" - const val sourceArg = "source" - const val emptyOrganization = "no_organization_id" - val routeWithArgs = "$route/{$wifiConfigDataArg}?organizationId={$organizationIdArg}&source=${sourceArg}&organizationName={$organizationNameArg}" - val arguments = listOf( - navArgument(organizationIdArg) { - type = NavType.StringType - nullable = true - }, - navArgument(wifiConfigDataArg) { - type = NavType.StringType - defaultValue = "" - }, - navArgument(sourceArg) { - type = NavType.StringType - defaultValue = "" - }, - navArgument(organizationNameArg) { - type = NavType.StringType - nullable = true + val ConfigSourceNavType = object: NavType(isNullableAllowed = false) { + override fun get(bundle: Bundle, key: String): ConfigSource? { + val string = bundle.getString(key) + return if (string.isNullOrEmpty()) { + null + } else { + ConfigSource.valueOf(string) } - ) + } - val deepLinkUrl = - "$BASE_URI/$route/{$wifiConfigDataArg}?organizationId={$organizationIdArg}&source=${sourceArg}&organizationName={$organizationNameArg}" + override fun parseValue(value: String): ConfigSource { + return Json.decodeFromString(value) + } - suspend fun buildDeepLink(context: Context, fileUri: Uri): String? { - // Read the contents of the file as XML - val inputStream = context.contentResolver.openInputStream(fileUri) ?: return null - val bytes = inputStream.readBytes() - val configParser = AndroidConfigParser() - return try { - val provider = configParser.parse(bytes) - inputStream.close() - "${BASE_URI}/${encodeArguments(ConfigSource.File, null, null, provider)}" - } catch (ex: Exception) { - Timber.w(ex, "Could not parse file opened!") - null - } + override fun serializeAsValue(value: ConfigSource): String { + return Json.encodeToString(value) } - fun encodeArguments( - source: ConfigSource, - organizationId: String?, - organizationName: String?, - eapIdentityProviderList: EAPIdentityProviderList - ): String { - // TODO use type-safe navigation because this is something crashing, and we don't know why - val moshi = Moshi.Builder() - .add(Date::class.java, DateJsonAdapter()) - .build() - // Here we remove all the embedded images. This is required because some profiles embed images of several megabytes, - // which makes the app slow or even crash - val listWithoutLogos = eapIdentityProviderList.copy( - eapIdentityProvider = eapIdentityProviderList.eapIdentityProvider?.map { provider -> - provider.copy(providerInfo = provider.providerInfo?.copy(providerLogo = null)) - } - ) - val adapter: JsonAdapter = moshi.adapter(EAPIdentityProviderList::class.java) - val wifiConfigDataJson = adapter.toJson(listWithoutLogos) - val encodedWifiConfig = Uri.encode(wifiConfigDataJson) - val baseRoute = if (organizationId.isNullOrEmpty()) { - "$route/$encodedWifiConfig?source=${source.name}&organizationName=" + override fun put(bundle: Bundle, key: String, value: ConfigSource) { + bundle.putString(key, value.name) + } + } + val EAPIdentityProviderListNavType = object: NavType(isNullableAllowed = false) { + override fun get(bundle: Bundle, key: String): EAPIdentityProviderList? { + val string = bundle.getString(key) + return if (string.isNullOrEmpty()) { + null } else { - "$route/$encodedWifiConfig?organizationId=$organizationId&source=${source.name}&organizationName=" + val decodedString = URLDecoder.decode(string, Charsets.UTF_8.name()) + Json.decodeFromString(decodedString) } - return baseRoute + Uri.encode(organizationName ?: eapIdentityProviderList.eapIdentityProvider?.firstOrNull()?.providerInfo?.displayName) } - fun decodeOrganizationIdArgument(arguments: Bundle?): String { - return arguments?.getString(organizationIdArg).orEmpty() + override fun parseValue(value: String): EAPIdentityProviderList { + val decoded = URLDecoder.decode(value, Charsets.UTF_8.name()) + return Json.decodeFromString(decoded) } - fun decodeOrganizationNameArgument(arguments: Bundle?): String { - return Uri.decode(arguments?.getString(organizationNameArg).orEmpty()) - } + override fun serializeAsValue(value: EAPIdentityProviderList): String { + val string = Json.encodeToString(value) + return URLEncoder.encode(string, Charsets.UTF_8.name()) - fun decodeSourceArgument(arguments: Bundle?): ConfigSource { - val sourceString = arguments?.getString(sourceArg).orEmpty() - return try { - ConfigSource.valueOf(sourceString) - } catch (ex: Exception) { - ConfigSource.Unknown - } } - fun decodeUrlArgument(arguments: Bundle?): EAPIdentityProviderList { - val encodedEAPIdentityProviderList = arguments?.getString(wifiConfigDataArg).orEmpty() - val moshi = Moshi.Builder() - .add(Date::class.java, DateJsonAdapter()) - .build() - val adapter: JsonAdapter = moshi.adapter(EAPIdentityProviderList::class.java) - val decodedWifiConfigDataJson = Uri.decode(encodedEAPIdentityProviderList) - return adapter.fromJson(decodedWifiConfigDataJson)!! + override fun put(bundle: Bundle, key: String, value: EAPIdentityProviderList) { + val string = Json.encodeToString(value) + val encodedString = URLEncoder.encode(string, Charsets.UTF_8.name()) + bundle.putString(key, encodedString) + } + } +} + +sealed class Route { + @Serializable + data object SelectInstitution : Route() + @Serializable + data object StatusScreen : Route() + @Serializable + @Parcelize + data class SelectProfile(val institutionId: String?, val customHostUri: String?) : Route(), Parcelable + @Serializable + data class OAuth(val configuration: Configuration, val redirectUri: String?): Route() + @Serializable + data class WebViewFallback(val configuration: Configuration, val urlToLoad: String) : Route() + @Serializable + data class ConfigureWifi( + val source: ConfigSource, + val organizationId: String, + val organizationName: String?, + val eapIdentityProviderList: EAPIdentityProviderList + ): Route() { + companion object { + suspend fun buildDeepLink(context: Context, fileUri: Uri): ConfigureWifi? = withContext(Dispatchers.IO) { + // Read the contents of the file as XML + val inputStream = context.contentResolver.openInputStream(fileUri) ?: return@withContext null + val bytes = inputStream.readBytes() + val configParser = AndroidConfigParser() + return@withContext try { + val provider = configParser.parse(bytes) + inputStream.close() + ConfigureWifi(ConfigSource.File, "", null, provider) + } catch (ex: Exception) { + Timber.w(ex, "Could not parse file opened!") + null + } + } } } } \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/AuthenticationMethod.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/AuthenticationMethod.kt index c262d48..7d7715e 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/AuthenticationMethod.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/AuthenticationMethod.kt @@ -4,11 +4,13 @@ import android.net.wifi.WifiEnterpriseConfig import app.eduroam.geteduroam.config.convertEAPMethod import app.eduroam.geteduroam.config.getClientCertificate import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element import org.simpleframework.xml.Root @Root(name = "AuthenticationMethod") @JsonClass(generateAdapter = true) +@Serializable class AuthenticationMethod { @field:Element(name = "EAPMethod", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/CertData.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/CertData.kt index 1be24e4..8455b1d 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/CertData.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/CertData.kt @@ -1,12 +1,14 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Attribute import org.simpleframework.xml.Root import org.simpleframework.xml.Text @Root(name = "CA") @JsonClass(generateAdapter = true) +@Serializable class CertData { @field:Text diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ClientSideCredential.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ClientSideCredential.kt index 8df215d..e9ce402 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ClientSideCredential.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ClientSideCredential.kt @@ -2,9 +2,11 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element @JsonClass(generateAdapter = true) +@Serializable class ClientSideCredential { @field:Element(name = "InnerIdentitySuffix", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProvider.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProvider.kt index aadb9f0..13eb24d 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProvider.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProvider.kt @@ -1,6 +1,8 @@ package app.eduroam.geteduroam.config.model +import app.eduroam.geteduroam.util.serializer.DateSerializer import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Attribute import org.simpleframework.xml.Element import org.simpleframework.xml.ElementList @@ -10,6 +12,7 @@ import java.util.Date @Root(name = "EAPIdentityProvider", strict = false) @JsonClass(generateAdapter = true) +@Serializable data class EAPIdentityProvider( @field:ElementList(name = "AuthenticationMethods", entry = "AuthenticationMethod") var authenticationMethod: List? = null, @@ -18,6 +21,7 @@ data class EAPIdentityProvider( var credentialApplicability: List? = null, @field:Element(name = "ValidUntil", required = false) + @Serializable(with = DateSerializer::class) var validUntil: Date? = null, @field:Element(name = "ProviderInfo") diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProviderList.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProviderList.kt index 35d3b70..31bffd4 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProviderList.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPIdentityProviderList.kt @@ -1,11 +1,13 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.ElementList import org.simpleframework.xml.Root @Root(name = "EAPIdentityProviderList", strict = false) @JsonClass(generateAdapter = true) +@Serializable data class EAPIdentityProviderList( @field:ElementList(name = "EAPIdentityProvider", inline = true) var eapIdentityProvider: List? = null diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPMethod.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPMethod.kt index fd7d51a..03b5d74 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPMethod.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/EAPMethod.kt @@ -1,11 +1,13 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element import org.simpleframework.xml.Root @Root(name = "EAPMethod") @JsonClass(generateAdapter = true) +@Serializable class EAPMethod { @field:Element(name = "Type", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/Helpdesk.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/Helpdesk.kt index 1757874..fe4d708 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/Helpdesk.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/Helpdesk.kt @@ -1,9 +1,11 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element @JsonClass(generateAdapter = true) +@Serializable class Helpdesk { @field:Element(name = "EmailAddress", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/IEEE80211.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/IEEE80211.kt index 1b39f60..31c2ee1 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/IEEE80211.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/IEEE80211.kt @@ -1,9 +1,11 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element @JsonClass(generateAdapter = true) +@Serializable class IEEE80211 { @field:Element(name = "SSID", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/InnerAuthenticationMethod.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/InnerAuthenticationMethod.kt index fa8b1f3..6a78daf 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/InnerAuthenticationMethod.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/InnerAuthenticationMethod.kt @@ -1,11 +1,13 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element import org.simpleframework.xml.Root @Root(name = "InnerAuthenticationMethod") @JsonClass(generateAdapter = true) +@Serializable class InnerAuthenticationMethod { @field:Element(name = "NonEAPAuthMethod", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/NonEAPAuthMethod.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/NonEAPAuthMethod.kt index f756e22..49c4dc5 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/NonEAPAuthMethod.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/NonEAPAuthMethod.kt @@ -1,11 +1,13 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element import org.simpleframework.xml.Root @Root(name = "NonEAPAuthMethod") @JsonClass(generateAdapter = true) +@Serializable class NonEAPAuthMethod { @field:Element(name = "Type", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderInfo.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderInfo.kt index 5461e05..4a30aa3 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderInfo.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderInfo.kt @@ -1,12 +1,14 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element import org.simpleframework.xml.ElementList import org.simpleframework.xml.Root @Root(name = "ProviderInfo") @JsonClass(generateAdapter = true) +@Serializable data class ProviderInfo( @field:Element(name = "DisplayName", required = false) var displayName: String? = null, diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLocation.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLocation.kt index cbebc24..2b4fba5 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLocation.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLocation.kt @@ -1,11 +1,13 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element import org.simpleframework.xml.Root @Root(name = "ProviderLocation") @JsonClass(generateAdapter = true) +@Serializable class ProviderLocation { @field:Element(name = "Longitude", required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLogo.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLogo.kt index cc68b35..25337a9 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLogo.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ProviderLogo.kt @@ -4,11 +4,13 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.Base64 import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Attribute import org.simpleframework.xml.Text @JsonClass(generateAdapter = true) +@Serializable class ProviderLogo { @field:Text diff --git a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ServerSideCredential.kt b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ServerSideCredential.kt index fe9006d..b42e5f8 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/config/model/ServerSideCredential.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/config/model/ServerSideCredential.kt @@ -1,12 +1,14 @@ package app.eduroam.geteduroam.config.model import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable import org.simpleframework.xml.Element import org.simpleframework.xml.ElementList import org.simpleframework.xml.Root @Root @JsonClass(generateAdapter = true) +@Serializable class ServerSideCredential { @field:ElementList(entry = "ServerID", inline = true, required = false) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/di/repository/NotificationRepository.kt b/android/app/src/main/java/app/eduroam/geteduroam/di/repository/NotificationRepository.kt index af23e9b..075678b 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/di/repository/NotificationRepository.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/di/repository/NotificationRepository.kt @@ -12,6 +12,7 @@ import android.content.Context.ALARM_SERVICE import android.content.Intent import android.content.pm.PackageManager import android.net.Uri +import android.os.Bundle import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -35,6 +36,7 @@ class NotificationRepository( const val NOTIFICATION_CHANNEL_ID = "reconfiguration_reminders" const val REMIND_DAYS_BEFORE_EXPIRY = 5 const val NOTIFICATION_ID = 100 + const val KEY_EXTRA_PAYLOAD = "extra_payload" } fun shouldRequestPushPermission(provider: EAPIdentityProvider, organizationId: String): Boolean { @@ -99,7 +101,7 @@ class NotificationAlarmReceiver : BroadcastReceiver() { } val tapResultIntent = Intent(context, MainActivity::class.java) intent?.getStringExtra(NotificationRepository.NOTIFICATION_KEY_PROVIDER_ID)?.let { - tapResultIntent.data = Uri.parse(Route.SelectProfile.buildDeepLink(it)) + tapResultIntent.putExtra(NotificationRepository.KEY_EXTRA_PAYLOAD, Route.SelectProfile(institutionId = it, customHostUri = null)) } tapResultIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP val pendingIntent: PendingIntent = getActivity( context, 0, tapResultIntent,PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/models/Configuration.kt b/android/app/src/main/java/app/eduroam/geteduroam/models/Configuration.kt index 6314823..ab74194 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/models/Configuration.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/models/Configuration.kt @@ -2,14 +2,17 @@ package app.eduroam.geteduroam.models import android.net.Uri import android.os.Parcelable +import app.eduroam.geteduroam.util.serializer.UriSerializer import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable @Parcelize @JsonClass(generateAdapter = true) +@Serializable data class Configuration( @Json(name = "client_id") val clientId: String?, @Json(name = "authorization_scope") val scope: String, @@ -23,24 +26,31 @@ data class Configuration( @Json(name = "https_required") val isHttpsRequired: Boolean = true, ) : Parcelable { @IgnoredOnParcel + @Serializable(with = UriSerializer::class) val redirectUri: Uri = Uri.parse(redirect) @IgnoredOnParcel + @Serializable(with = UriSerializer::class) val endSessionRedirectUri: Uri? = endSessionRedirect?.let { Uri.parse(it) } @IgnoredOnParcel + @Serializable(with = UriSerializer::class) val discoveryUri: Uri? = discovery?.let { Uri.parse(it) } @IgnoredOnParcel + @Serializable(with = UriSerializer::class) val authEndpointUri: Uri = Uri.parse(authEndpoint) @IgnoredOnParcel + @Serializable(with = UriSerializer::class) val tokenEndpointUri: Uri = Uri.parse(tokenEndpoint) @IgnoredOnParcel + @Serializable(with = UriSerializer::class) val endSessionEndpointUri: Uri? = endSessionEndpoint?.let { Uri.parse(it) } @IgnoredOnParcel + @Serializable(with = UriSerializer::class) val registrationEndpointUri: Uri? = registrationEndpoint?.let { Uri.parse(it) } companion object { diff --git a/android/app/src/main/java/app/eduroam/geteduroam/oauth/OAuthViewModel.kt b/android/app/src/main/java/app/eduroam/geteduroam/oauth/OAuthViewModel.kt index dfa5d88..86cc4ea 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/oauth/OAuthViewModel.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/oauth/OAuthViewModel.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute import app.eduroam.geteduroam.R import app.eduroam.geteduroam.Route import app.eduroam.geteduroam.di.assist.AuthenticationAssistant @@ -58,14 +59,13 @@ class OAuthViewModel @Inject constructor( private var configuration: Configuration = Configuration.EMPTY init { - val configurationArg = savedStateHandle.get(Route.OAuth.configurationArg) ?: "" - configuration = Route.OAuth.decodeConfigurationArgument(configurationArg) - val redirectUriArg = savedStateHandle.get(Route.OAuth.redirectUriArg) ?: "" - val redirectUri: Uri? - if (redirectUriArg.isNotEmpty()) { - redirectUri = Uri.parse(Uri.decode(redirectUriArg)) + val data = savedStateHandle.toRoute() + configuration = data.configuration + val redirectUriArg = data.redirectUri ?: "" + val redirectUri = if (redirectUriArg.isNotEmpty()) { + Uri.parse(Uri.decode(redirectUriArg)) } else { - redirectUri = null + null } prepareAppAuth(context, redirectUri) } diff --git a/android/app/src/main/java/app/eduroam/geteduroam/profile/SelectProfileViewModel.kt b/android/app/src/main/java/app/eduroam/geteduroam/profile/SelectProfileViewModel.kt index 1d2a349..8a1edfd 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/profile/SelectProfileViewModel.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/profile/SelectProfileViewModel.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.toRoute import app.eduroam.geteduroam.R import app.eduroam.geteduroam.Route import app.eduroam.geteduroam.config.AndroidConfigParser @@ -48,8 +49,9 @@ class SelectProfileViewModel @Inject constructor( private var didAgreeToTerms = false init { - institutionId = savedStateHandle.get(Route.SelectProfile.institutionIdArg) ?: "" - customHost = savedStateHandle.get(Route.SelectProfile.customHostArg)?.let { Uri.parse(it) } + val data = savedStateHandle.toRoute() + institutionId = data.institutionId ?: "" + customHost = data.customHostUri?.let { Uri.parse(it) } if (institutionId.isNotBlank()) { loadDataFromInstitution() } else if (customHost != null) { diff --git a/android/app/src/main/java/app/eduroam/geteduroam/status/StatusScreen.kt b/android/app/src/main/java/app/eduroam/geteduroam/status/StatusScreen.kt index c180769..c263eb7 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/status/StatusScreen.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/status/StatusScreen.kt @@ -55,7 +55,7 @@ fun StatusScreen( viewModel: StatusScreenViewModel, goToInstitutionSelection: () -> Unit, renewAccount: (String) -> Unit, - repairConfig: (ConfigSource, String?, String?, EAPIdentityProviderList) -> Unit + repairConfig: (ConfigSource, String, String?, EAPIdentityProviderList) -> Unit ) = EduTopAppBar( withBackIcon = false ) { paddingValues -> @@ -87,7 +87,7 @@ fun StatusScreen( repairConfig = { config -> repairConfig( configSource ?: ConfigSource.Unknown, - organizationId, + organizationId.orEmpty(), organizationName, config ) diff --git a/android/app/src/main/java/app/eduroam/geteduroam/util/serializer/DateSerializer.kt b/android/app/src/main/java/app/eduroam/geteduroam/util/serializer/DateSerializer.kt new file mode 100644 index 0000000..4a53702 --- /dev/null +++ b/android/app/src/main/java/app/eduroam/geteduroam/util/serializer/DateSerializer.kt @@ -0,0 +1,23 @@ +package app.eduroam.geteduroam.util.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.util.Date + +class DateSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Date { + val long = decoder.decodeLong() + return Date(long) + } + + override fun serialize(encoder: Encoder, value: Date) { + val long = value.time + encoder.encodeLong(long) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/util/serializer/UriSerializer.kt b/android/app/src/main/java/app/eduroam/geteduroam/util/serializer/UriSerializer.kt new file mode 100644 index 0000000..5d6bda6 --- /dev/null +++ b/android/app/src/main/java/app/eduroam/geteduroam/util/serializer/UriSerializer.kt @@ -0,0 +1,23 @@ +package app.eduroam.geteduroam.util.serializer + +import android.net.Uri +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +class UriSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Uri { + val string = decoder.decodeString() + return Uri.parse(string) + } + + override fun serialize(encoder: Encoder, value: Uri) { + val string = value.toString() + encoder.encodeString(string) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/eduroam/geteduroam/webview_fallback/WebViewFallbackViewModel.kt b/android/app/src/main/java/app/eduroam/geteduroam/webview_fallback/WebViewFallbackViewModel.kt index d58542d..acef5ce 100644 --- a/android/app/src/main/java/app/eduroam/geteduroam/webview_fallback/WebViewFallbackViewModel.kt +++ b/android/app/src/main/java/app/eduroam/geteduroam/webview_fallback/WebViewFallbackViewModel.kt @@ -7,9 +7,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.navigation.toRoute import app.eduroam.geteduroam.Route import app.eduroam.geteduroam.di.api.GetEduroamApi import app.eduroam.geteduroam.di.repository.StorageRepository +import app.eduroam.geteduroam.models.Configuration import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first @@ -24,11 +26,12 @@ class WebViewFallbackViewModel @Inject constructor( ): ViewModel() { var uiState by mutableStateOf(UiState()) + val configuration: Configuration - val configuration = Route.WebViewFallback.decodeConfigurationArgument(savedStateHandle.get(Route.WebViewFallback.configurationArg) ?: "") init { - val requestUriArg = savedStateHandle.get(Route.WebViewFallback.urlArg)!! - uiState = UiState(startUri = Uri.parse(requestUriArg)) + val data = savedStateHandle.toRoute() + configuration = data.configuration + uiState = UiState(startUri = Uri.parse(data.urlToLoad)) } fun getAuthRequest(): Flow { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b2f69f2..0338dbd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,6 +7,7 @@ android-gradle-plugin = "8.2.2" androidx-junit = "1.1.5" espresso-core = "3.5.1" google-services-plugin = "4.3.15" +serialization-plugin = "2.0.0" junit = "4.13.2" protobuf = "3.23.0" kotlin-gradle-plugin = "1.9.22" @@ -16,6 +17,7 @@ firebase-app-distribution-plugin = "4.0.0" firebase-crashlytics-gradle = "2.9.9" protobuf-plugin = "0.9.3" play-publisher-plugin = "3.8.6" +compose-navigation = "2.8.0-beta05" # Using beta for type safew navigation arguments hilt-android = "2.48.1" hilt-navigation-compose = "1.1.0" @@ -31,11 +33,10 @@ accompanist = "0.31.3-beta" retrofit = "2.11.0" moshi = "1.15.1" okhttp = "4.12.0" +kotlin-serialization = "1.6.3" [libraries] androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" } -androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso-core" } -androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" } androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3-version" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" } @@ -53,16 +54,14 @@ simpleframework-xml-parser = "org.simpleframework:simple-xml:2.7.1" hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt-android" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt-android" } -androidx-compose-navigation = "androidx.navigation:navigation-compose:2.6.0" +androidx-compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "compose-navigation" } androidx-compose-hilt-navigation = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hilt-navigation-compose" } androidx-splash-core = "androidx.core:core-splashscreen:1.0.1" android-material = { module = "com.google.android.material:material", version.ref = "android-material-version" } androidx-compose-debug-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-compose-debug-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -androidx-compose-ui-test-junit = { group = "androidx.compose.ui", name = "ui-test-junit4" } -junit = { module = "junit:junit", version.ref = "junit" } -androidx-navigation-runtime-ktx = { group = "androidx.navigation", name = "navigation-runtime-ktx", version = "2.6.0" } +androidx-navigation-runtime-ktx = { module = "androidx.navigation:navigation-runtime-ktx", version.ref = "compose-navigation"} timber = "com.jakewharton.timber:timber:5.0.1" @@ -79,18 +78,21 @@ moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.re okhttp-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } -androidx-datastore = "androidx.datastore:datastore-preferences:1.1.0-alpha06" +androidx-datastore = "androidx.datastore:datastore-preferences:1.1.1" google-protobuf = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" } firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" } firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics"} +kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serialization" } + [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle-plugin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp-plugin" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt-android" } +serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "serialization-plugin" } google-gms-gradle = { id = "com.google.gms.google-services", version.ref = "google-services-plugin" } protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" } play-publisher = { id = "com.github.triplet.play", version.ref = "play-publisher-plugin"}