From 47a090420d7a30c7e0b7c0d8956681f19122a20c Mon Sep 17 00:00:00 2001 From: Dirk Baumeister Date: Tue, 22 Oct 2024 21:44:53 +0200 Subject: [PATCH 1/2] added release states display --- .../info/ui/releases/ReleaseState.kt | 70 ++++++++++++++++ .../info/ui/releases/ReleasesScreen.kt | 10 +++ .../info/ui/releases/ReleasesUiState.kt | 3 + .../info/ui/releases/ReleasesViewModel.kt | 84 ++++++++++++++++++- app/src/main/res/values/strings.xml | 10 +++ 5 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt new file mode 100644 index 0000000..64000a9 --- /dev/null +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt @@ -0,0 +1,70 @@ +package app.grapheneos.info.ui.releasenotes + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme.typography +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import app.grapheneos.info.R + +@Composable +fun ReleaseState(releaseStates: List>, modifier: Modifier = Modifier) { + + Row( + modifier = Modifier + .fillMaxSize(), horizontalArrangement = Arrangement.SpaceEvenly) { + Column( + ) { + ElevatedCard() { + Text( + stringResource(R.string.stable), + style = typography.titleMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + Text( + text = releaseStates.find { it.first == "stable" }?.second.toString(), + style = typography.bodyMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + } + } + Column( + ) { + ElevatedCard() { + Text( + stringResource(R.string.beta), + style = typography.titleMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + Text( + text = releaseStates.find { it.first == "beta" }?.second.toString(), + style = typography.bodyMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + } + } + Column( + ) { + ElevatedCard() { + Text( + stringResource(R.string.alpha), + style = typography.titleMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + Text( + text = releaseStates.find { it.first == "alpha" }?.second.toString(), + style = typography.bodyMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + } + } + } +} diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt index 5805453..883a107 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt @@ -37,7 +37,9 @@ fun ReleasesScreen( modifier: Modifier = Modifier, showSnackbarError: (String) -> Unit, entries: List>, + releaseStates: List>, updateChangelog: (useCaches: Boolean, finishedUpdating: () -> Unit) -> Unit, + updateReleaseStates: (useCaches: Boolean, finishedUpdating: () -> Unit) -> Unit, changelogLazyListState: LazyListState, additionalContentPadding: PaddingValues = PaddingValues(0.dp) ) { @@ -55,6 +57,7 @@ fun ReleasesScreen( if (event == Lifecycle.Event.ON_START) { refreshCoroutineScope.launch { updateChangelog(true) {} + updateReleaseStates(true) {} } } } @@ -93,6 +96,13 @@ fun ReleasesScreen( additionalContentPadding = additionalContentPadding, verticalArrangement = Arrangement.Top ) { + item { + ReleaseState( + releaseStates, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp)) + } items( items = entries, key = { it.first }) { diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesUiState.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesUiState.kt index ff32841..20faa2e 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesUiState.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesUiState.kt @@ -13,4 +13,7 @@ class ReleasesUiState(savedStateHandle: SavedStateHandle) { } /** Unsorted release notes, use .toSortedMap().toList().asReversible() to get them in the proper order. */ val entries: MutableMap = mutableStateMapOf() + + /** Unsorted release states, use .toSortedMap().toList().asReversible() to get them in the proper order. */ + val releaseStates: MutableMap = mutableStateMapOf("stable" to "-", "beta" to "-", "alpha" to "-") } \ No newline at end of file diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt index dfa6647..eee8ce2 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt @@ -38,6 +38,11 @@ class ReleasesViewModel( countAsInitialScroll = false, onFinishedUpdating = {}, ) + updateReleaseStates( + useCaches = true, + showSnackbarError = {}, + onFinishedUpdating = {}, + ) } fun updateChangelog( @@ -139,6 +144,8 @@ class ReleasesViewModel( } finally { connection.disconnect() } + + } catch (e: IOException) { val errorMessage = application.getString(R.string.update_changelog_failed_to_create_httpsurlconnection_snackbar_message) @@ -151,4 +158,79 @@ class ReleasesViewModel( } } } -} + + fun updateReleaseStates( + useCaches: Boolean, + showSnackbarError: suspend (message: String) -> Unit, + onFinishedUpdating: () -> Unit = {}, + ) { + var board = android.os.Build.BOARD + val releasePhases = arrayOf("stable", "beta", "alpha") + for (releasePhase in releasePhases) { + viewModelScope.launch(Dispatchers.IO) { + + try { + val url = URL("https://releases.grapheneos.org/$board-$releasePhase") + val connection = url.openConnection() as HttpsURLConnection + + connection.apply { + connectTimeout = 10_000 + readTimeout = 30_000 + } + + try { + + connection.useCaches = useCaches + + connection.connect() + + val responseText = String(connection.inputStream.readBytes()) + + Log.e(TAG, responseText); + + withContext(Dispatchers.Main) { + _uiState.value.releaseStates[releasePhase] = responseText.split(" ")[0] + } + + connection.disconnect() + + } catch (e: SocketTimeoutException) { + val errorMessage = + application.getString(R.string.update_release_states_socket_timeout_exception_snackbar_message) + Log.e(TAG, errorMessage, e) + viewModelScope.launch { + showSnackbarError("$errorMessage: $e") + } + } catch (e: IOException) { + val errorMessage = + application.getString(R.string.update_release_states_io_exception_snackbar_message) + Log.e(TAG, errorMessage, e) + viewModelScope.launch { + showSnackbarError("$errorMessage: $e") + } + } catch (e: UnknownServiceException) { + val errorMessage = + application.getString(R.string.update_release_states_unknown_service_exception_snackbar_message) + Log.e(TAG, errorMessage, e) + viewModelScope.launch { + showSnackbarError("$errorMessage: $e") + } + } finally { + connection.disconnect() + } + + + } catch (e: IOException) { + val errorMessage = + application.getString(R.string.update_release_states_failed_to_create_httpsurlconnection_snackbar_message) + Log.e(TAG, errorMessage, e) + viewModelScope.launch { + showSnackbarError("$errorMessage: $e") + } + } finally { + onFinishedUpdating() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cdc937e..c37cef7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -143,4 +143,14 @@ our Wise Quick Pay link " rather than using the bank account details. This enables sending donations from anywhere Wise is available. If the currency you're using isn't listed due to not being one of the currencies where we accept local bank transfers, please select CAD or USD so we can avoid another conversion." Wise + Info about the releases + Stable + Beta + Alpha + Socket Timeout Exception + Failed to retrieve latest release states + Unknown Service Exception + Failed to create + HttpsURLConnection + From e738b031a97d4a9730edcf0a5f5f42af0a94bb29 Mon Sep 17 00:00:00 2001 From: Dirk Baumeister Date: Tue, 22 Oct 2024 21:49:45 +0200 Subject: [PATCH 2/2] implemented release state display to info app --- .../kotlin/app/grapheneos/info/InfoApp.kt | 10 +++ .../info/ui/releases/ReleaseState.kt | 80 +++++++------------ .../info/ui/releases/ReleasesScreen.kt | 33 +++++--- .../info/ui/releases/ReleasesViewModel.kt | 11 +-- app/src/main/res/values/strings.xml | 1 - 5 files changed, 66 insertions(+), 69 deletions(-) diff --git a/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt b/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt index e89478a..72a100f 100644 --- a/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt +++ b/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt @@ -288,6 +288,7 @@ fun InfoApp() { .consumeWindowInsets(innerPadding), entries = releasesUiState.value.entries.toSortedMap().toList().asReversed(), + releaseStates = releasesUiState.value.releaseStates.toSortedMap().toList(), updateChangelog = { useCaches, onFinishedUpdating -> releasesViewModel.updateChangelog( useCaches = useCaches, @@ -304,6 +305,15 @@ fun InfoApp() { onFinishedUpdating = onFinishedUpdating, ) }, + updateReleaseStates = { useCaches, onFinishedUpdating -> + releasesViewModel.updateReleaseStates( + useCaches = useCaches, + showSnackbarError = { + snackbarHostState.showSnackbar(it) + }, + onFinishedUpdating = onFinishedUpdating, + ) + }, changelogLazyListState = changelogLazyListState, additionalContentPadding = PaddingValues( start = innerPadding.calculateStartPadding(layoutDirection), diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt index 64000a9..0837967 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleaseState.kt @@ -1,9 +1,9 @@ -package app.grapheneos.info.ui.releasenotes +package app.grapheneos.info.ui.releases import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.ElevatedCard import androidx.compose.material3.MaterialTheme.typography @@ -16,55 +16,35 @@ import androidx.compose.ui.unit.dp import app.grapheneos.info.R @Composable -fun ReleaseState(releaseStates: List>, modifier: Modifier = Modifier) { - +fun ReleaseState(releaseStates: List>) { Row( - modifier = Modifier - .fillMaxSize(), horizontalArrangement = Arrangement.SpaceEvenly) { - Column( - ) { - ElevatedCard() { - Text( - stringResource(R.string.stable), - style = typography.titleMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) - ) - Text( - text = releaseStates.find { it.first == "stable" }?.second.toString(), - style = typography.bodyMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) - ) - } - } - Column( - ) { - ElevatedCard() { - Text( - stringResource(R.string.beta), - style = typography.titleMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) - ) - Text( - text = releaseStates.find { it.first == "beta" }?.second.toString(), - style = typography.bodyMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) - ) - } - } - Column( - ) { - ElevatedCard() { - Text( - stringResource(R.string.alpha), - style = typography.titleMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) - ) - Text( - text = releaseStates.find { it.first == "alpha" }?.second.toString(), - style = typography.bodyMedium, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) - ) + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + val releasePhases = mapOf( + "stable" to R.string.stable, + "beta" to R.string.beta, + "alpha" to R.string.alpha, + ) + for ((releasePhase, resourceId) in releasePhases) { + Column ( + modifier = Modifier.weight(1f), + ) { + ElevatedCard ( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = stringResource(id = resourceId), + style = typography.titleMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + Text( + text = releaseStates.find { it.first == releasePhase }?.second.toString(), + style = typography.bodyMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 5.dp).align(Alignment.CenterHorizontally) + ) + } } } } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt index 883a107..c833e7d 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesScreen.kt @@ -69,19 +69,32 @@ fun ReleasesScreen( } } - var isRefreshing by rememberSaveable { mutableStateOf(false) } + var isChangelogRefreshing by rememberSaveable { mutableStateOf(false) } + var isReleaseStatesRefreshing by rememberSaveable { mutableStateOf(false) } val state = rememberPullToRefreshState() PullToRefreshBox( - isRefreshing = isRefreshing, + isRefreshing = isChangelogRefreshing || isReleaseStatesRefreshing, onRefresh = { - isRefreshing = true + isChangelogRefreshing = true + isReleaseStatesRefreshing = true updateChangelog(false) { - isRefreshing = false + isChangelogRefreshing = false - refreshCoroutineScope.launch { - state.animateToHidden() + if (!isReleaseStatesRefreshing) { + refreshCoroutineScope.launch { + state.animateToHidden() + } + } + } + updateReleaseStates(false) { + isReleaseStatesRefreshing = false + + if (!isChangelogRefreshing) { + refreshCoroutineScope.launch { + state.animateToHidden() + } } } }, @@ -97,11 +110,13 @@ fun ReleasesScreen( verticalArrangement = Arrangement.Top ) { item { - ReleaseState( - releaseStates, + Row ( modifier = Modifier .fillMaxWidth() - .padding(vertical = 16.dp)) + .padding(vertical = 16.dp) + ) { + ReleaseState(releaseStates) + } } items( items = entries, diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt index eee8ce2..e6a0590 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releases/ReleasesViewModel.kt @@ -144,8 +144,6 @@ class ReleasesViewModel( } finally { connection.disconnect() } - - } catch (e: IOException) { val errorMessage = application.getString(R.string.update_changelog_failed_to_create_httpsurlconnection_snackbar_message) @@ -158,7 +156,7 @@ class ReleasesViewModel( } } } - + fun updateReleaseStates( useCaches: Boolean, showSnackbarError: suspend (message: String) -> Unit, @@ -168,24 +166,22 @@ class ReleasesViewModel( val releasePhases = arrayOf("stable", "beta", "alpha") for (releasePhase in releasePhases) { viewModelScope.launch(Dispatchers.IO) { - try { val url = URL("https://releases.grapheneos.org/$board-$releasePhase") val connection = url.openConnection() as HttpsURLConnection connection.apply { + sslSocketFactory = tlsSocketFactory connectTimeout = 10_000 readTimeout = 30_000 } try { - connection.useCaches = useCaches connection.connect() val responseText = String(connection.inputStream.readBytes()) - Log.e(TAG, responseText); withContext(Dispatchers.Main) { @@ -193,7 +189,6 @@ class ReleasesViewModel( } connection.disconnect() - } catch (e: SocketTimeoutException) { val errorMessage = application.getString(R.string.update_release_states_socket_timeout_exception_snackbar_message) @@ -218,8 +213,6 @@ class ReleasesViewModel( } finally { connection.disconnect() } - - } catch (e: IOException) { val errorMessage = application.getString(R.string.update_release_states_failed_to_create_httpsurlconnection_snackbar_message) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c37cef7..52e7e28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -143,7 +143,6 @@ our Wise Quick Pay link " rather than using the bank account details. This enables sending donations from anywhere Wise is available. If the currency you're using isn't listed due to not being one of the currencies where we accept local bank transfers, please select CAD or USD so we can avoid another conversion." Wise - Info about the releases Stable Beta Alpha