feat: better composable stability

This commit is contained in:
Diamond 2022-12-14 19:21:40 -08:00
parent a85f9adeb1
commit b60d4bcbca
No known key found for this signature in database
GPG key ID: DCBE5952BB3B6420
11 changed files with 61 additions and 31 deletions

View file

@ -19,7 +19,9 @@ android {
versionCode = 1 versionCode = 1
versionName = "0.0.1" versionName = "0.0.1"
vectorDrawables.useSupportLibrary = true vectorDrawables {
useSupportLibrary = true
}
buildConfigField("String", "TAG", "\"AliucordManager\"") buildConfigField("String", "TAG", "\"AliucordManager\"")
buildConfigField("String", "SUPPORT_SERVER", "\"EsNDvBaHVU\"") buildConfigField("String", "SUPPORT_SERVER", "\"EsNDvBaHVU\"")
@ -68,11 +70,20 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
freeCompilerArgs += "-Xcontext-receivers" freeCompilerArgs += listOf(
"-Xcontext-receivers",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${buildDir.resolve("report").absolutePath}",
)
} }
buildFeatures.compose = true buildFeatures {
composeOptions.kotlinCompilerExtensionVersion = "1.3.1" compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.1"
}
} }
dependencies { dependencies {
@ -114,6 +125,7 @@ dependencies {
implementation("com.aliucord:axml:1.0.1") implementation("com.aliucord:axml:1.0.1")
implementation("com.android.tools.build:apksig:7.4.0-beta04") implementation("com.android.tools.build:apksig:7.4.0-beta04")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
} }
fun getCurrentBranch(): String? = fun getCurrentBranch(): String? =

View file

@ -21,6 +21,7 @@ import com.aliucord.manager.ui.screen.*
import com.aliucord.manager.ui.theme.ManagerTheme import com.aliucord.manager.ui.theme.ManagerTheme
import com.aliucord.manager.ui.theme.Theme import com.aliucord.manager.ui.theme.Theme
import dev.olshevski.navigation.reimagined.* import dev.olshevski.navigation.reimagined.*
import kotlinx.collections.immutable.persistentListOf
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord") val aliucordDir = Environment.getExternalStorageDirectory().resolve("Aliucord")
@ -63,7 +64,7 @@ class MainActivity : ComponentActivity() {
when (val dest = this.currentHostEntry.destination) { when (val dest = this.currentHostEntry.destination) {
is BaseScreenDestination -> BaseScreen( is BaseScreenDestination -> BaseScreen(
currentNavItem = dest, currentNavItem = dest,
bottomNavItems = listOf(AppDestination.Home, AppDestination.Plugins, AppDestination.Settings), bottomNavItems = persistentListOf(AppDestination.Home, AppDestination.Plugins, AppDestination.Settings),
onNavChanged = { navController.replaceLast(it) } onNavChanged = { navController.replaceLast(it) }
) { ) {
when (dest) { when (dest) {

View file

@ -5,9 +5,10 @@
package com.aliucord.manager.domain.model package com.aliucord.manager.domain.model
import androidx.compose.runtime.Immutable
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Immutable
@Serializable @Serializable
data class Manifest( data class Manifest(
val name: String, val name: String,
@ -17,7 +18,6 @@ data class Manifest(
val updateUrl: String, val updateUrl: String,
val changelog: String?, val changelog: String?,
val changelogMedia: String?, val changelogMedia: String?,
val links: JsonObject
) { ) {
val repositoryUrl = updateUrl val repositoryUrl = updateUrl
.replace("raw.githubusercontent.com", "github.com") .replace("raw.githubusercontent.com", "github.com")

View file

@ -5,6 +5,7 @@
package com.aliucord.manager.domain.model package com.aliucord.manager.domain.model
import androidx.compose.runtime.Immutable
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
@ -17,6 +18,7 @@ private val json = Json {
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
@Immutable
data class Plugin(val file: File) { data class Plugin(val file: File) {
val manifest: Manifest val manifest: Manifest

View file

@ -24,6 +24,7 @@ import com.aliucord.manager.R
import com.aliucord.manager.network.dto.Commit import com.aliucord.manager.network.dto.Commit
import com.aliucord.manager.ui.component.LoadFailure import com.aliucord.manager.ui.component.LoadFailure
import com.aliucord.manager.ui.util.annotatingStringResource import com.aliucord.manager.ui.util.annotatingStringResource
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Composable @Composable
@ -142,8 +143,10 @@ fun CommitItem(
Text( Text(
annotatingStringResource( annotatingStringResource(
R.string.contributors_commit_title, R.string.contributors_commit_title,
commit.author.name, persistentListOf(
commit.sha.take(8) commit.author.name,
commit.sha.take(8),
)
) { ) {
when (it) { when (it) {
"name" -> SpanStyle( "name" -> SpanStyle(

View file

@ -9,7 +9,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.Circle import androidx.compose.material.icons.outlined.Circle
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -21,6 +21,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import com.aliucord.manager.R import com.aliucord.manager.R
import kotlinx.collections.immutable.ImmutableList
import kotlin.math.floor import kotlin.math.floor
enum class InstallStatus { enum class InstallStatus {
@ -30,18 +31,23 @@ enum class InstallStatus {
QUEUED QUEUED
} }
data class InstallStepData( @Stable
var nameResId: Int, class InstallStepData(
var status: InstallStatus, val nameResId: Int,
var duration: Float = 0f, status: InstallStatus,
var cached: Boolean = false duration: Float = 0f,
) cached: Boolean = false
) {
var status by mutableStateOf(status)
var duration by mutableStateOf(duration)
var cached by mutableStateOf(cached)
}
@Composable @Composable
fun InstallGroup( fun InstallGroup(
name: String, name: String,
isCurrent: Boolean, isCurrent: Boolean,
subSteps: List<InstallStepData>, subSteps: ImmutableList<InstallStepData>,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val status = when { val status = when {

View file

@ -28,6 +28,7 @@ import com.aliucord.manager.R
import com.aliucord.manager.domain.model.Plugin import com.aliucord.manager.domain.model.Plugin
import com.aliucord.manager.ui.util.annotatingStringResource import com.aliucord.manager.ui.util.annotatingStringResource
import com.aliucord.manager.ui.util.joinToAnnotatedString import com.aliucord.manager.ui.util.joinToAnnotatedString
import kotlinx.collections.immutable.persistentListOf
@Composable @Composable
fun PluginCard( fun PluginCard(
@ -51,8 +52,10 @@ fun PluginCard(
Text( Text(
annotatingStringResource( annotatingStringResource(
R.string.plugins_plugin_title, R.string.plugins_plugin_title,
plugin.manifest.name, persistentListOf(
plugin.manifest.version plugin.manifest.name,
plugin.manifest.version,
)
) { ) {
when (it) { when (it) {
"plugin" -> SpanStyle(fontWeight = FontWeight.Bold) "plugin" -> SpanStyle(fontWeight = FontWeight.Bold)

View file

@ -15,11 +15,12 @@ import androidx.compose.ui.res.stringResource
import com.aliucord.manager.BuildConfig import com.aliucord.manager.BuildConfig
import com.aliucord.manager.R import com.aliucord.manager.R
import com.aliucord.manager.ui.screen.* import com.aliucord.manager.ui.screen.*
import kotlinx.collections.immutable.ImmutableList
@Composable @Composable
fun BaseScreen( fun BaseScreen(
currentNavItem: BaseScreenDestination, currentNavItem: BaseScreenDestination,
bottomNavItems: List<BaseScreenDestination>, bottomNavItems: ImmutableList<BaseScreenDestination>,
onNavChanged: (AppDestination) -> Unit, onNavChanged: (AppDestination) -> Unit,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {

View file

@ -26,11 +26,13 @@ import com.aliucord.manager.ui.dialog.DiscordType
import com.aliucord.manager.ui.dialog.DownloadMethod import com.aliucord.manager.ui.dialog.DownloadMethod
import com.aliucord.manager.ui.viewmodel.InstallViewModel import com.aliucord.manager.ui.viewmodel.InstallViewModel
import com.aliucord.manager.ui.viewmodel.InstallViewModel.InstallStepGroup import com.aliucord.manager.ui.viewmodel.InstallViewModel.InstallStepGroup
import kotlinx.collections.immutable.toImmutableList
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import java.util.* import java.util.*
@Immutable // this isn't *really* stable, but this never gets modified after being passed to a composable, so...
@Parcelize @Parcelize
data class InstallData( data class InstallData(
val downloadMethod: DownloadMethod, val downloadMethod: DownloadMethod,
@ -91,7 +93,7 @@ fun InstallerScreen(
name = stringResource(group.nameResId), name = stringResource(group.nameResId),
isCurrent = expandedGroup == group, isCurrent = expandedGroup == group,
onClick = { expandedGroup = group }, onClick = { expandedGroup = group },
subSteps = viewModel.getSteps(group) subSteps = viewModel.getSteps(group).toImmutableList(),
) )
} }

View file

@ -4,6 +4,7 @@ import androidx.annotation.StringRes
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.* import androidx.compose.ui.text.*
import kotlinx.collections.immutable.ImmutableList
context(AnnotatedString.Builder) context(AnnotatedString.Builder)
inline fun <T> Iterable<T>.joinToAnnotatedString( inline fun <T> Iterable<T>.joinToAnnotatedString(
@ -35,12 +36,11 @@ context(AnnotatedString.Builder)
@Composable @Composable
inline fun annotatingStringResource( inline fun annotatingStringResource(
@StringRes id: Int, @StringRes id: Int,
vararg args: Any, args: ImmutableList<Any>,
crossinline annotationHandler: @Composable (annotationName: String) -> SpanStyle? crossinline annotationHandler: @Composable (annotationName: String) -> SpanStyle?,
): AnnotatedString { ): AnnotatedString {
val string = stringResource(id, *args) val string = stringResource(id, *args.toTypedArray())
// TODO: figure out a way to put this in remember {} while still having @Composable annotationHandler
val markerIndexes = string val markerIndexes = string
.mapIndexedNotNull { index, elem -> index.takeIf { elem == '§' } } .mapIndexedNotNull { index, elem -> index.takeIf { elem == '§' } }
.takeIf { it.isNotEmpty() && it.size % 3 == 0 } .takeIf { it.isNotEmpty() && it.size % 3 == 0 }

View file

@ -583,8 +583,7 @@ class InstallViewModel(
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
private inline fun <T> step(step: InstallStep, block: InstallStepData.() -> T): T { private inline fun <T> step(step: InstallStep, block: InstallStepData.() -> T): T {
steps[step] = steps[step]!!.copy(status = InstallStatus.ONGOING) steps[step]!!.status = InstallStatus.ONGOING
currentStep = step currentStep = step
try { try {
@ -596,15 +595,15 @@ class InstallViewModel(
Thread.sleep(1000 - millis) Thread.sleep(1000 - millis)
} }
steps[step] = steps[step]!!.copy( steps[step]!!.apply {
duration = millis.div(1000f), duration = millis.div(1000f)
status = InstallStatus.SUCCESSFUL status = InstallStatus.SUCCESSFUL
) }
currentStep = step currentStep = step
return value.value return value.value
} catch (t: Throwable) { } catch (t: Throwable) {
steps[step] = steps[step]!!.copy(status = InstallStatus.UNSUCCESSFUL) steps[step]!!.status = InstallStatus.UNSUCCESSFUL
currentStep = step currentStep = step
throw t throw t
@ -656,6 +655,7 @@ class InstallViewModel(
var currentStep: InstallStep? by mutableStateOf(null) var currentStep: InstallStep? by mutableStateOf(null)
val steps = mutableStateMapOf<InstallStep, InstallStepData>() val steps = mutableStateMapOf<InstallStep, InstallStepData>()
// TODO: cache this instead
fun getSteps(group: InstallStepGroup): List<InstallStepData> { fun getSteps(group: InstallStepGroup): List<InstallStepData> {
return steps return steps
.filterKeys { it.group == group }.entries .filterKeys { it.group == group }.entries