forked from geode-sdk/android-launcher
Implement built in updater (#6)
* implement release check api * add options for updater * fix settings scroll * minor preference reorganization * implement initial release properties * add downloading code * change download api * add extraction code * fix modifier padding * update geode status after update * set stored release after update * disable updater by default for armv8 * add settings toasts * use snackbar instead of toast * small cleanup * remove unused constant * implement stable channel * child doesn't require viewmodel * update release format * enable updater by default for all archs * modify updater to be global * fix copyfile reference * correct state ordering issue * only allow one update at a time * show message in settings on second update * allow update cancellation and retry * disable start until update finished * fix countdown lifecycle * always sync release state to main activity * allow scrolling main activity * use a library for internal downloads mat allowed me to * show download progress in settings * no internet messages * enable api request cache * delete old library before extracting * oops....
This commit is contained in:
1 parent
c421e37e1a
commit
4c555881b5
17 files changed
+1184
-209
No files matched your search
@@ -9,26 +9,36 @@ import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.geode.launcher.api.ReleaseViewModel
|
||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||
import com.geode.launcher.ui.theme.Typography
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import java.net.ConnectException
|
||||
import java.net.UnknownHostException
|
||||
|
||||
class SettingsActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -78,12 +88,89 @@ fun onOpenFolder(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
fun UpdateIndicator(
|
||||
snackbarHostState: SnackbarHostState,
|
||||
updateStatus: ReleaseViewModel.ReleaseUIState
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var enablePopup by remember { mutableStateOf(false) }
|
||||
|
||||
when (updateStatus) {
|
||||
is ReleaseViewModel.ReleaseUIState.InUpdateCheck -> {
|
||||
enablePopup = true
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
is ReleaseViewModel.ReleaseUIState.InDownload -> {
|
||||
// is this the ideal design? idk
|
||||
enablePopup = true
|
||||
val progress = updateStatus.downloaded / updateStatus.outOf.toDouble()
|
||||
|
||||
CircularProgressIndicator(progress.toFloat())
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
LaunchedEffect(updateStatus) {
|
||||
// only show popup if some progress was shown
|
||||
if (!enablePopup) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
when (updateStatus) {
|
||||
is ReleaseViewModel.ReleaseUIState.Failure -> {
|
||||
val message = when (updateStatus.exception) {
|
||||
is UnknownHostException, is ConnectException ->
|
||||
context.getString(R.string.release_fetch_no_internet)
|
||||
else -> context.getString(R.string.preference_check_for_updates_failed)
|
||||
}
|
||||
|
||||
snackbarHostState.showSnackbar(message)
|
||||
}
|
||||
is ReleaseViewModel.ReleaseUIState.Finished -> {
|
||||
if (updateStatus.hasUpdated) {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.preference_check_for_updates_success)
|
||||
)
|
||||
} else {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.preference_check_for_updates_none_found)
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||
releaseViewModel: ReleaseViewModel = viewModel(factory = ReleaseViewModel.Factory)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val currentRelease by PreferenceUtils.useStringPreference(PreferenceUtils.Key.CURRENT_VERSION_TAG)
|
||||
val updateStatus by releaseViewModel.uiState.collectAsState()
|
||||
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
var showUpdateInProgress by remember { mutableStateOf(false) }
|
||||
|
||||
if (showUpdateInProgress) {
|
||||
LaunchedEffect(snackbarHostState) {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(R.string.preference_check_for_updates_already_updating)
|
||||
)
|
||||
showUpdateInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
snackbarHost = {
|
||||
SnackbarHost(hostState = snackbarHostState)
|
||||
},
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
@@ -107,18 +194,19 @@ fun SettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
OptionsGroup(context.getString(R.string.preference_category_testing)) {
|
||||
SettingsCard(
|
||||
title = context.getString(R.string.preference_load_testing_name),
|
||||
preferenceId = R.string.preference_load_testing
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_TESTING,
|
||||
)
|
||||
SettingsCard(
|
||||
title = context.getString(R.string.preference_load_automatically_name),
|
||||
description = context.getString(R.string.preference_load_automatically_description),
|
||||
preferenceId = R.string.preference_load_automatically
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_AUTOMATICALLY,
|
||||
)
|
||||
OptionsButton(
|
||||
title = context.getString(R.string.preferences_copy_external_button),
|
||||
@@ -126,6 +214,41 @@ fun SettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
onClick = { onOpenFolder(context) }
|
||||
)
|
||||
}
|
||||
|
||||
OptionsGroup(context.getString(R.string.preference_category_updater)) {
|
||||
SettingsCard(
|
||||
title = context.getString(R.string.preference_update_automatically_name),
|
||||
preferenceKey = PreferenceUtils.Key.UPDATE_AUTOMATICALLY,
|
||||
)
|
||||
SettingsCard(
|
||||
title = context.getString(R.string.preference_release_channel_name),
|
||||
preferenceKey = PreferenceUtils.Key.RELEASE_CHANNEL,
|
||||
)
|
||||
OptionsCard(
|
||||
title = {
|
||||
OptionsTitle(
|
||||
title = stringResource(R.string.preference_check_for_updates_button),
|
||||
description = stringResource(
|
||||
R.string.preference_check_for_updates_description,
|
||||
currentRelease ?: "unknown"
|
||||
)
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = {
|
||||
if (releaseViewModel.isInUpdate) {
|
||||
showUpdateInProgress = true
|
||||
} else {
|
||||
releaseViewModel.runReleaseCheck()
|
||||
}
|
||||
},
|
||||
role = Role.Button
|
||||
)
|
||||
) {
|
||||
UpdateIndicator(snackbarHostState, updateStatus)
|
||||
}
|
||||
}
|
||||
/*
|
||||
OptionsGroup("Data") {
|
||||
OptionsButton(
|
||||
@@ -139,28 +262,16 @@ fun SettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
)
|
||||
}
|
||||
|
||||
fun toggleSetting(context: Context, @StringRes preferenceId: Int): Boolean {
|
||||
val preferences = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key), Context.MODE_PRIVATE
|
||||
)
|
||||
fun toggleSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
||||
val preferences = PreferenceUtils.get(context)
|
||||
|
||||
val current = preferences.getBoolean(
|
||||
context.getString(preferenceId), false
|
||||
)
|
||||
preferences.edit {
|
||||
putBoolean(context.getString(preferenceId), !current)
|
||||
commit()
|
||||
}
|
||||
|
||||
return preferences.getBoolean(context.getString(preferenceId), false)
|
||||
return preferences.toggleBoolean(preferenceKey)
|
||||
}
|
||||
|
||||
fun getSetting(context: Context, @StringRes preferenceId: Int): Boolean {
|
||||
val preferences = context.getSharedPreferences(
|
||||
context.getString(R.string.preference_file_key), Context.MODE_PRIVATE
|
||||
)
|
||||
fun getSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
||||
val preferences = PreferenceUtils.get(context)
|
||||
|
||||
return preferences.getBoolean(context.getString(preferenceId), false)
|
||||
return preferences.getBoolean(preferenceKey)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -190,10 +301,11 @@ fun OptionsButton(title: String, description: String? = null, onClick: () -> Uni
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsCard(title: String, description: String? = null, @StringRes preferenceId: Int) {
|
||||
fun SettingsCard(title: String, description: String? = null, preferenceKey: PreferenceUtils.Key) {
|
||||
val context = LocalContext.current
|
||||
val settingEnabled = remember {
|
||||
mutableStateOf(getSetting(context, preferenceId)) }
|
||||
mutableStateOf(getSetting(context, preferenceKey))
|
||||
}
|
||||
|
||||
OptionsCard(
|
||||
title = {
|
||||
@@ -205,7 +317,7 @@ fun SettingsCard(title: String, description: String? = null, @StringRes preferen
|
||||
},
|
||||
modifier = Modifier.toggleable(
|
||||
value = settingEnabled.value,
|
||||
onValueChange = { settingEnabled.value = toggleSetting(context, preferenceId) },
|
||||
onValueChange = { settingEnabled.value = toggleSetting(context, preferenceKey) },
|
||||
role = Role.Switch,
|
||||
)
|
||||
) {
|
||||
@@ -253,11 +365,11 @@ fun OptionsCardPreview() {
|
||||
SettingsCard(
|
||||
title = "Load files from /test",
|
||||
description = "Very long testing description goes here. It is incredibly long, it should wrap onto a new line.",
|
||||
preferenceId = R.string.preference_load_testing
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_TESTING
|
||||
)
|
||||
SettingsCard(
|
||||
title = "Testing option 2",
|
||||
preferenceId = R.string.preference_load_automatically
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_AUTOMATICALLY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user