Skip to content
Open
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
10 changes: 10 additions & 0 deletions app/src/main/kotlin/app/grapheneos/info/InfoApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,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,
Expand All @@ -302,6 +303,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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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.fillMaxWidth
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<Pair<String, String>>) {
Row(
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)
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ fun ReleasesScreen(
modifier: Modifier = Modifier,
showSnackbarError: (String) -> Unit,
entries: List<Pair<String, String>>,
releaseStates: List<Pair<String, String>>,
updateChangelog: (useCaches: Boolean, finishedUpdating: () -> Unit) -> Unit,
updateReleaseStates: (useCaches: Boolean, finishedUpdating: () -> Unit) -> Unit,
changelogLazyListState: LazyListState,
additionalContentPadding: PaddingValues = PaddingValues(0.dp)
) {
Expand All @@ -55,6 +57,7 @@ fun ReleasesScreen(
if (event == Lifecycle.Event.ON_START) {
refreshCoroutineScope.launch {
updateChangelog(true) {}
updateReleaseStates(true) {}
}
}
}
Expand All @@ -66,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()
}
}
}
},
Expand All @@ -93,6 +109,15 @@ fun ReleasesScreen(
additionalContentPadding = additionalContentPadding,
verticalArrangement = Arrangement.Top
) {
item {
Row (
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
ReleaseState(releaseStates)
}
}
items(
items = entries,
key = { it.first }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> = mutableStateMapOf()

/** Unsorted release states, use .toSortedMap().toList().asReversible() to get them in the proper order. */
val releaseStates: MutableMap<String, String> = mutableStateMapOf("stable" to "-", "beta" to "-", "alpha" to "-")
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ class ReleasesViewModel(
countAsInitialScroll = false,
onFinishedUpdating = {},
)
updateReleaseStates(
useCaches = true,
showSnackbarError = {},
onFinishedUpdating = {},
)
}

fun updateChangelog(
Expand Down Expand Up @@ -136,4 +141,74 @@ class ReleasesViewModel(
}
}
}

fun updateReleaseStates(
useCaches: Boolean,
showSnackbarError: suspend (message: String) -> Unit,
onFinishedUpdating: () -> Unit = {},
) {
val 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 {
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) {
_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()
}
}
}
}
}
9 changes: 9 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,13 @@
<string name="browser_link_illegal_argument_exception_snackbar_error">Unable to open link. Make sure a browser is installed and enabled on your device.</string>
<string name="releases_top_bar_info_button_content_description">Info about the releases</string>
<string name="releases_see_all_button">See all release notes</string>
<string name="stable">Stable</string>
<string name="beta">Beta</string>
<string name="alpha">Alpha</string>
<string name="update_release_states_socket_timeout_exception_snackbar_message">Socket Timeout Exception</string>
<string name="update_release_states_io_exception_snackbar_message">Failed to retrieve latest release states</string>
<string name="update_release_states_unknown_service_exception_snackbar_message">Unknown Service Exception</string>
<string name="update_release_states_failed_to_create_httpsurlconnection_snackbar_message">Failed to create
HttpsURLConnection
</string>
</resources>