diff --git a/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt b/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt index 960de00..9a1a00c 100644 --- a/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt +++ b/app/src/main/kotlin/app/grapheneos/info/InfoApp.kt @@ -252,6 +252,7 @@ fun InfoApp() { ) { ReleaseNotesScreen( releaseNotesUiState.value.entries.toSortedMap().toList().asReversed(), + releaseNotesUiState.value.releaseStates.toSortedMap().toList(), { useCaches, onFinishedUpdating -> releaseNotesViewModel.updateReleaseNotes( useCaches = useCaches, @@ -268,6 +269,15 @@ fun InfoApp() { onFinishedUpdating = onFinishedUpdating, ) }, + { useCaches, onFinishedUpdating -> + releaseNotesViewModel.updateReleaseStates( + useCaches = useCaches, + showSnackbarError = { + snackbarHostState.showSnackbar(it) + }, + onFinishedUpdating = onFinishedUpdating, + ) + }, releaseNotesLazyListState, ) } diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesScreen.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesScreen.kt index d896f92..6416741 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesScreen.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesScreen.kt @@ -35,7 +35,9 @@ import kotlinx.coroutines.launch @Composable fun ReleaseNotesScreen( entries: List>, + releaseStates: List>, updateReleaseNotes: (useCaches: Boolean, finishedUpdating: () -> Unit) -> Unit, + updateReleaseStates: (useCaches: Boolean, finishedUpdating: () -> Unit) -> Unit, lazyListState: LazyListState, ) { val lifecycleOwner = LocalLifecycleOwner.current @@ -49,6 +51,7 @@ fun ReleaseNotesScreen( if (event == Lifecycle.Event.ON_START) { refreshCoroutineScope.launch { updateReleaseNotes(true) {} + updateReleaseStates(true) {} } } } @@ -105,6 +108,9 @@ fun ReleaseNotesScreen( state = lazyListState, verticalArrangement = Arrangement.Top ) { + item { + ReleaseState(releaseStates) + } items( items = entries, key = { it.first }) { diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesUiState.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesUiState.kt index abc2ccf..4d3db4b 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesUiState.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesUiState.kt @@ -13,4 +13,7 @@ class ReleaseNotesUiState(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/releasenotes/ReleaseNotesViewModel.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesViewModel.kt index 90972b6..c68c7e0 100644 --- a/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesViewModel.kt +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseNotesViewModel.kt @@ -36,6 +36,11 @@ class ReleaseNotesViewModel( countAsInitialScroll = false, onFinishedUpdating = {}, ) + updateReleaseStates( + useCaches = true, + showSnackbarError = {}, + onFinishedUpdating = {}, + ) } fun updateReleaseNotes( @@ -121,6 +126,8 @@ class ReleaseNotesViewModel( } finally { connection.disconnect() } + + } catch (e: IOException) { val errorMessage = application.getString(R.string.update_release_notes_failed_to_create_httpsurlconnection_snackbar_message) @@ -133,4 +140,79 @@ class ReleaseNotesViewModel( } } } + + fun updateReleaseStates( + useCaches: Boolean, + showSnackbarError: suspend (message: String) -> Unit, + onFinishedUpdating: () -> Unit = {}, + ) { + var board = android.os.Build.DEVICE + 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() + } + } + } + } } diff --git a/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseState.kt b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/ReleaseState.kt new file mode 100644 index 0000000..07bf974 --- /dev/null +++ b/app/src/main/kotlin/app/grapheneos/info/ui/releasenotes/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>) { + + 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/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1bdbe92..6465b14 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -147,4 +147,13 @@ Copy %1$s Unable to open link. Make sure a browser is installed and enabled on your device. Info about the releases + Stable + Beta + Alpha + Socket Timeout Exception + Failed to retrieve latest release states + Unknown Service Exception + Failed to create + HttpsURLConnection +