fix(HomeScreen): immediately show installations regardless of remote data fetching
This changes state handling to update the up-to-date status after the remote has completed. This ensures that the already-fetched installations are immediately shown in the UI regardless of up-to-date status.
This commit is contained in:
parent
a9d894eae2
commit
fbc6fbe2ab
6 changed files with 117 additions and 76 deletions
|
|
@ -24,11 +24,14 @@ import com.aliucord.manager.patcher.InstallMetadata
|
|||
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
|
||||
import com.aliucord.manager.ui.screens.patchopts.PatchOptionsScreen
|
||||
import com.aliucord.manager.ui.util.DiscordVersion
|
||||
import com.aliucord.manager.ui.util.toUnsafeImmutable
|
||||
import com.aliucord.manager.util.launchBlock
|
||||
import com.aliucord.manager.util.showToast
|
||||
import com.github.diamondminer88.zip.ZipReader
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
|
|
@ -42,18 +45,33 @@ class HomeModel(
|
|||
var supportedVersion by mutableStateOf<DiscordVersion>(DiscordVersion.None)
|
||||
private set
|
||||
|
||||
var state by mutableStateOf<InstallsState>(InstallsState.Fetching)
|
||||
var installsState by mutableStateOf<InstallsState>(InstallsState.Fetching)
|
||||
private set
|
||||
|
||||
private val refreshingLock = Mutex()
|
||||
private var remoteDataJson: BuildInfo? = null
|
||||
private var latestAliuhookVersion: SemVer? = null
|
||||
|
||||
init {
|
||||
// fetchInstallations() is also called from UI first to ensure fast TTI
|
||||
refresh()
|
||||
}
|
||||
|
||||
screenModelScope.launch {
|
||||
fetchRemoteData()
|
||||
fetchInstallations() // Re-fetch installations to set the up-to-date statuses
|
||||
fun refresh() = screenModelScope.launchBlock {
|
||||
if (refreshingLock.isLocked) return@launchBlock
|
||||
|
||||
refreshingLock.withLock {
|
||||
val packages = fetchAliucordPackages()
|
||||
|
||||
val jobs = listOf(
|
||||
screenModelScope.launch { fetchInstallations(packages) },
|
||||
screenModelScope.launch {
|
||||
if (remoteDataJson == null || latestAliuhookVersion == null)
|
||||
fetchRemoteData()
|
||||
}
|
||||
)
|
||||
|
||||
jobs.joinAll()
|
||||
refreshInstallationsUpToDate(packages)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,60 +94,6 @@ class HomeModel(
|
|||
application.startActivity(launchIntent)
|
||||
}
|
||||
|
||||
fun fetchInstallations() = screenModelScope.launchBlock {
|
||||
state = InstallsState.Fetching
|
||||
|
||||
try {
|
||||
val packageManager = application.packageManager
|
||||
|
||||
val installedPackages = packageManager
|
||||
.getInstalledPackages(PackageManager.GET_META_DATA)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?: throw IllegalStateException("Failed to fetch installed packages (returned none)")
|
||||
|
||||
val aliucordPackages = installedPackages
|
||||
.asSequence()
|
||||
.filter {
|
||||
val isAliucordPkg = it.packageName == "com.aliucord" // Legacy installer builds do not have isAliucord metadata marker
|
||||
val hasAliucordMeta = it.applicationInfo?.metaData?.containsKey("isAliucord") == true
|
||||
isAliucordPkg || hasAliucordMeta
|
||||
}
|
||||
|
||||
val aliucordInstallations = aliucordPackages.mapNotNull {
|
||||
// `longVersionCode` is unnecessary since Discord doesn't use `versionCodeMajor`
|
||||
@Suppress("DEPRECATION")
|
||||
val versionCode = it.versionCode
|
||||
val versionName = it.versionName ?: return@mapNotNull null
|
||||
val applicationInfo = it.applicationInfo ?: return@mapNotNull null
|
||||
|
||||
InstallData(
|
||||
name = packageManager.getApplicationLabel(applicationInfo).toString(),
|
||||
packageName = it.packageName,
|
||||
isUpToDate = isInstallationUpToDate(it),
|
||||
icon = packageManager
|
||||
.getApplicationIcon(applicationInfo)
|
||||
.toBitmap()
|
||||
.asImageBitmap()
|
||||
.let(::BitmapPainter),
|
||||
version = DiscordVersion.Existing(
|
||||
type = DiscordVersion.parseVersionType(versionCode),
|
||||
name = versionName.split("-")[0].trim(),
|
||||
code = versionCode,
|
||||
),
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
state = if (aliucordInstallations.isNotEmpty()) {
|
||||
InstallsState.Fetched(data = aliucordInstallations)
|
||||
} else {
|
||||
InstallsState.None
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to query Aliucord installations", t)
|
||||
state = InstallsState.Error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [PatchOptionsScreen] that can be navigated to,
|
||||
* with prefilled options from an existing installation.
|
||||
|
|
@ -153,7 +117,68 @@ class HomeModel(
|
|||
return PatchOptionsScreen(prefilledOptions = patchOptions)
|
||||
}
|
||||
|
||||
suspend fun fetchRemoteData() {
|
||||
private fun fetchInstallations(packages: List<PackageInfo>) {
|
||||
installsState = InstallsState.Fetching
|
||||
|
||||
try {
|
||||
val packageManager = application.packageManager
|
||||
val aliucordInstallations = packages.mapNotNull { pkg ->
|
||||
// `longVersionCode` is unnecessary since Discord doesn't use `versionCodeMajor`
|
||||
@Suppress("DEPRECATION")
|
||||
val versionCode = pkg.versionCode
|
||||
val versionName = pkg.versionName ?: return@mapNotNull null
|
||||
val applicationInfo = pkg.applicationInfo ?: return@mapNotNull null
|
||||
|
||||
InstallData(
|
||||
name = packageManager.getApplicationLabel(applicationInfo).toString(),
|
||||
packageName = pkg.packageName,
|
||||
isUpToDate = isInstallationUpToDate(pkg),
|
||||
icon = packageManager
|
||||
.getApplicationIcon(applicationInfo)
|
||||
.toBitmap()
|
||||
.asImageBitmap()
|
||||
.let(::BitmapPainter),
|
||||
version = DiscordVersion.Existing(
|
||||
type = DiscordVersion.parseVersionType(versionCode),
|
||||
name = versionName.split("-")[0].trim(),
|
||||
code = versionCode,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
installsState = if (aliucordInstallations.isNotEmpty()) {
|
||||
InstallsState.Fetched(data = aliucordInstallations.toUnsafeImmutable())
|
||||
} else {
|
||||
InstallsState.None
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to query Aliucord installations", t)
|
||||
installsState = InstallsState.Error
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshInstallationsUpToDate(packages: List<PackageInfo>) {
|
||||
val installations = (installsState as? InstallsState.Fetched)?.data
|
||||
?: return
|
||||
|
||||
try {
|
||||
val newInstallations = installations.map { data ->
|
||||
val packageInfo = packages.find { it.packageName == data.packageName }
|
||||
?: throw IllegalStateException("Checking up-to-date status for package that has not been fetched")
|
||||
|
||||
data.copy(isUpToDate = isInstallationUpToDate(packageInfo))
|
||||
}
|
||||
|
||||
installsState = InstallsState.Fetched(data = newInstallations.toUnsafeImmutable())
|
||||
} catch (t: Throwable) {
|
||||
Log.e(BuildConfig.TAG, "Failed to check installations up-to-date", t)
|
||||
installsState = InstallsState.Error
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Properly show state when one of these fetches fail,
|
||||
// to ensure the people know that the up-to-date check does not work
|
||||
private suspend fun fetchRemoteData() {
|
||||
github.getBuildData().fold(
|
||||
success = {
|
||||
val versionCode = it.discordVersionCode.toIntOrNull()
|
||||
|
|
@ -184,10 +209,29 @@ class HomeModel(
|
|||
)
|
||||
}
|
||||
|
||||
private fun isInstallationUpToDate(pkg: PackageInfo): Boolean {
|
||||
/**
|
||||
* Obtains all installed packages on the device that are an Aliucord installation.
|
||||
*/
|
||||
private fun fetchAliucordPackages(): List<PackageInfo> {
|
||||
return application.packageManager
|
||||
.getInstalledPackages(PackageManager.GET_META_DATA)
|
||||
.filter {
|
||||
// Packages installed via the legacy Installer do not have the metadata marker
|
||||
val isAliucordPkg = it.packageName == "com.aliucord"
|
||||
val hasAliucordMeta = it.applicationInfo?.metaData?.containsKey("isAliucord") == true
|
||||
isAliucordPkg || hasAliucordMeta
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to determine whether the Aliucord installation is up-to-date.
|
||||
* If `null` is returned, then the build data was not fetched, and as such
|
||||
* the status cannot be determined.
|
||||
*/
|
||||
private fun isInstallationUpToDate(pkg: PackageInfo): Boolean? {
|
||||
// Assume up-to-date when remote data hasn't been fetched yet
|
||||
val remoteBuildData = remoteDataJson ?: return true
|
||||
val latestAliuhookVersion = latestAliuhookVersion ?: return true
|
||||
val remoteBuildData = remoteDataJson ?: return null
|
||||
val latestAliuhookVersion = latestAliuhookVersion ?: return null
|
||||
|
||||
// `longVersionCode` is unnecessary since Discord doesn't use `versionCodeMajor`
|
||||
@Suppress("DEPRECATION")
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class HomeScreen : Screen, Parcelable {
|
|||
|
||||
// Refresh installations list when the screen changes or activity resumes
|
||||
LifecycleResumeEffect(Unit) {
|
||||
model.fetchInstallations()
|
||||
model.refresh()
|
||||
|
||||
onPauseOrDispose {}
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ class HomeScreen : Screen, Parcelable {
|
|||
Scaffold(
|
||||
topBar = { HomeAppBar() },
|
||||
) { padding ->
|
||||
when (val state = model.state) {
|
||||
when (val state = model.installsState) {
|
||||
is InstallsState.Fetched -> HomeScreenLoadedContent(
|
||||
state = state,
|
||||
supportedVersion = model.supportedVersion,
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ data class InstallData(
|
|||
val packageName: String,
|
||||
val version: DiscordVersion,
|
||||
val icon: BitmapPainter,
|
||||
val isUpToDate: Boolean,
|
||||
val isUpToDate: Boolean?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,8 @@ fun InstalledItemCard(
|
|||
onClick = onOpenInfo,
|
||||
)
|
||||
|
||||
if (data.isUpToDate) {
|
||||
// If the up-to-date status cannot be determined, assume it is up-to-date
|
||||
if (data.isUpToDate ?: true) {
|
||||
SegmentedButton(
|
||||
icon = painterResource(R.drawable.ic_launch),
|
||||
text = stringResource(R.string.action_launch),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import com.aliucord.manager.R
|
|||
import com.aliucord.manager.patcher.steps.StepGroup
|
||||
import com.aliucord.manager.ui.components.*
|
||||
import com.aliucord.manager.ui.components.dialogs.InstallerAbortDialog
|
||||
import com.aliucord.manager.ui.screens.log.LogScreen
|
||||
import com.aliucord.manager.ui.screens.patching.components.*
|
||||
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
|
||||
import com.aliucord.manager.ui.util.paddings.*
|
||||
|
|
@ -251,7 +252,7 @@ class PatchingScreen(private val data: PatchOptions) : Screen, Parcelable {
|
|||
MainActionButton(
|
||||
text = stringResource(R.string.action_open_error_log),
|
||||
icon = painterResource(R.drawable.ic_launch),
|
||||
onClick = { model.openLog(navigator) },
|
||||
onClick = { navigator.push(LogScreen(installId = model.getCurrentInstallId()!!)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import androidx.annotation.StringRes
|
|||
import androidx.compose.runtime.*
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import com.aliucord.manager.BuildConfig
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.manager.*
|
||||
|
|
@ -16,7 +15,6 @@ import com.aliucord.manager.patcher.StepRunner
|
|||
import com.aliucord.manager.patcher.steps.StepGroup
|
||||
import com.aliucord.manager.patcher.steps.base.*
|
||||
import com.aliucord.manager.patcher.steps.install.InstallStep
|
||||
import com.aliucord.manager.ui.screens.log.LogScreen
|
||||
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
|
||||
import com.aliucord.manager.ui.util.toUnsafeImmutable
|
||||
import com.aliucord.manager.util.launchBlock
|
||||
|
|
@ -64,6 +62,8 @@ class PatchingScreenModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun getCurrentInstallId(): String? = installId
|
||||
|
||||
fun launchApp() {
|
||||
if (state.value !is PatchingScreenState.Success)
|
||||
return
|
||||
|
|
@ -121,11 +121,6 @@ class PatchingScreenModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun openLog(navigator: Navigator) {
|
||||
val id = installId ?: return
|
||||
navigator.push(LogScreen(installId = id))
|
||||
}
|
||||
|
||||
private suspend fun startPatchRunner() {
|
||||
val runner = KotlinPatchRunner(options)
|
||||
.also { stepRunner = it }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue