diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml
index c5ad6558..f517791e 100644
--- a/app/detekt-baseline.xml
+++ b/app/detekt-baseline.xml
@@ -4,10 +4,8 @@
ForbiddenImport:AcsMissingScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:CrashReportScreen.kt$import androidx.compose.material3.MaterialTheme
- LambdaParameterInRestartableEffect:AslNavHost.kt$onOpenProjectConsumed
LongMethod:AslNavHost.kt$@Composable fun AslNavHost( startDestination: String, openProjectId: String? = null, onOpenProjectConsumed: () -> Unit = {}, navController: NavHostController = rememberNavController(), )
MatchingDeclarationName:BlockingErrorScreen.kt$BlockingErrorType
ParameterNaming:AslNavHost.kt$onOpenProjectConsumed
- WildcardImport:ExampleUnitTest.kt$import org.junit.Assert.*
diff --git a/app/src/main/java/com/ahmadkharfan/androidstudiolite/navigation/AslNavHost.kt b/app/src/main/java/com/ahmadkharfan/androidstudiolite/navigation/AslNavHost.kt
index cd4162df..72c9e5f6 100644
--- a/app/src/main/java/com/ahmadkharfan/androidstudiolite/navigation/AslNavHost.kt
+++ b/app/src/main/java/com/ahmadkharfan/androidstudiolite/navigation/AslNavHost.kt
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -44,6 +45,7 @@ fun AslNavHost(
onOpenProjectConsumed: () -> Unit = {},
navController: NavHostController = rememberNavController(),
) {
+ val currentOnOpenProjectConsumed by rememberUpdatedState(onOpenProjectConsumed)
LaunchedEffect(openProjectId) {
val id = openProjectId ?: return@LaunchedEffect
@@ -52,7 +54,7 @@ fun AslNavHost(
launchSingleTop = true
}
}
- onOpenProjectConsumed()
+ currentOnOpenProjectConsumed()
}
NavHost(
navController = navController,
@@ -180,7 +182,13 @@ fun AslNavHost(
val target = runCatching {
GitDiffTarget.valueOf(backStackEntry.arguments?.getString("target").orEmpty())
}.getOrDefault(GitDiffTarget.INDEX_TO_WORKTREE)
- GitDiffRoute(projectId, path, target, commitId, onBack = { navController.popBackStack() })
+ GitDiffRoute(
+ projectId = projectId,
+ path = path,
+ target = target,
+ onBack = { navController.popBackStack() },
+ commitId = commitId,
+ )
}
composable(
diff --git a/app/src/test/java/com/ahmadkharfan/androidstudiolite/ExampleUnitTest.kt b/app/src/test/java/com/ahmadkharfan/androidstudiolite/ExampleUnitTest.kt
index f969470c..cb5ef526 100644
--- a/app/src/test/java/com/ahmadkharfan/androidstudiolite/ExampleUnitTest.kt
+++ b/app/src/test/java/com/ahmadkharfan/androidstudiolite/ExampleUnitTest.kt
@@ -2,7 +2,7 @@ package com.ahmadkharfan.androidstudiolite
import org.junit.Test
-import org.junit.Assert.*
+import org.junit.Assert.assertEquals
class ExampleUnitTest {
@Test
diff --git a/data/ai/detekt-baseline.xml b/data/ai/detekt-baseline.xml
index 91239d43..0d4e72a5 100644
--- a/data/ai/detekt-baseline.xml
+++ b/data/ai/detekt-baseline.xml
@@ -9,13 +9,6 @@
CyclomaticComplexMethod:AgentReplyParser.kt$AgentReplyParser$fun toActionOrNull(element: JsonElement): AgentAction?
InstanceOfCheckForException:AiLlmGateway.kt$AiLlmGateway$e is AiLlmException
LoopWithTooManyJumpStatements:AgentReplySalvage.kt$AgentReplySalvage$while
- NewLineAtEndOfFile:AnthropicProvider.kt$com.ahmadkharfan.androidstudiolite.data.ai.llm.AnthropicProvider.kt
- NewLineAtEndOfFile:GeminiProvider.kt$com.ahmadkharfan.androidstudiolite.data.ai.llm.GeminiProvider.kt
- NewLineAtEndOfFile:LlmHttp.kt$com.ahmadkharfan.androidstudiolite.data.ai.llm.LlmHttp.kt
- NewLineAtEndOfFile:LlmProvider.kt$com.ahmadkharfan.androidstudiolite.data.ai.llm.LlmProvider.kt
- NewLineAtEndOfFile:LlmProviderRegistry.kt$com.ahmadkharfan.androidstudiolite.data.ai.llm.LlmProviderRegistry.kt
- NewLineAtEndOfFile:LlmProviderRegistryTest.kt$com.ahmadkharfan.androidstudiolite.data.ai.llm.LlmProviderRegistryTest.kt
- NewLineAtEndOfFile:OpenAiCompatProvider.kt$com.ahmadkharfan.androidstudiolite.data.ai.llm.OpenAiCompatProvider.kt
TooGenericExceptionCaught:AiLlmGateway.kt$AiLlmGateway$e: Exception
diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/AnthropicProvider.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/AnthropicProvider.kt
index 8b0d17ba..1e55c04b 100644
--- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/AnthropicProvider.kt
+++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/AnthropicProvider.kt
@@ -125,4 +125,4 @@ private data class AnthropicMessage(val role: String, val content: String)
private data class AnthropicResponse(val content: List)
@Serializable
-private data class AnthropicContentBlock(val type: String, val text: String = "")
\ No newline at end of file
+private data class AnthropicContentBlock(val type: String, val text: String = "")
diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/GeminiProvider.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/GeminiProvider.kt
index beed81b2..7eea2791 100644
--- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/GeminiProvider.kt
+++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/GeminiProvider.kt
@@ -116,4 +116,4 @@ private data class GeminiCandidate(val content: GeminiContent? = null)
private data class GeminiModelsResponse(val models: List = emptyList())
@Serializable
-private data class GeminiModelInfo(val name: String)
\ No newline at end of file
+private data class GeminiModelInfo(val name: String)
diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmHttp.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmHttp.kt
index 1b9f779c..fc935939 100644
--- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmHttp.kt
+++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmHttp.kt
@@ -85,4 +85,4 @@ internal fun llmErrorMessage(body: String, code: Int): String {
?: root["message"]?.jsonPrimitive?.contentOrNull
?: body.take(200)
}.getOrDefault(body.take(200)).let { "HTTP $code: $it" }
-}
\ No newline at end of file
+}
diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProvider.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProvider.kt
index 35b28672..190f080b 100644
--- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProvider.kt
+++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProvider.kt
@@ -19,4 +19,4 @@ internal interface LlmProvider {
fun stream(request: LlmChatRequest, onDelta: (String) -> Unit)
fun listModels(apiKey: String, baseUrl: String?): List
-}
\ No newline at end of file
+}
diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt
index 775e5116..35164ff3 100644
--- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt
+++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistry.kt
@@ -17,4 +17,4 @@ internal class LlmProviderRegistry(http: LlmHttpClient) {
providers[providerId] ?: throw AiLlmException("Unknown provider: $providerId")
fun find(providerId: String): LlmProvider? = providers[providerId]
-}
\ No newline at end of file
+}
diff --git a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt
index 11e02e3f..1ecff3ba 100644
--- a/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt
+++ b/data/ai/src/main/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/OpenAiCompatProvider.kt
@@ -120,4 +120,4 @@ private data class OpenAiMessage(val role: String, val content: String)
private data class OpenAiChatResponse(val choices: List = emptyList())
@Serializable
-private data class OpenAiChoice(val message: OpenAiMessage? = null)
\ No newline at end of file
+private data class OpenAiChoice(val message: OpenAiMessage? = null)
diff --git a/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt b/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt
index c7e54cba..b479c72c 100644
--- a/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt
+++ b/data/ai/src/test/java/com/ahmadkharfan/androidstudiolite/data/ai/llm/LlmProviderRegistryTest.kt
@@ -160,4 +160,4 @@ class LlmErrorMessageTest {
assertEquals("HTTP 500: not json", llmErrorMessage("not json", 500))
assertEquals("HTTP 503", llmErrorMessage("", 503))
}
-}
\ No newline at end of file
+}
diff --git a/data/build/detekt-baseline.xml b/data/build/detekt-baseline.xml
index 5b7af5a1..16ba5907 100644
--- a/data/build/detekt-baseline.xml
+++ b/data/build/detekt-baseline.xml
@@ -5,12 +5,6 @@
ComplexCondition:GradleScriptScanner.kt$GToken$s.length >= 2 && (s[0] == '"' || s[0] == '\'') && s.last() == s[0]
ComplexCondition:GradleScriptScanner.kt$GradleScriptScanner.GradleTokenizer$end < text.length && (text[end].isLetterOrDigit() || text[end] == '.' || text[end] == '_')
ComplexCondition:VersionCatalogParser.kt$VersionCatalogParser$t.length >= 2 && (t.first() == '"' || t.first() == '\'') && t.last() == t.first()
- ConstructorParameterNaming:GitHubDeviceFlowAuthenticator.kt$GitHubDeviceFlowAuthenticator.AccessTokenResponse$val access_token: String? = null
- ConstructorParameterNaming:GitHubDeviceFlowAuthenticator.kt$GitHubDeviceFlowAuthenticator.AccessTokenResponse$val error_description: String? = null
- ConstructorParameterNaming:GitHubDeviceFlowAuthenticator.kt$GitHubDeviceFlowAuthenticator.DeviceCodeResponse$val device_code: String? = null
- ConstructorParameterNaming:GitHubDeviceFlowAuthenticator.kt$GitHubDeviceFlowAuthenticator.DeviceCodeResponse$val expires_in: Int? = null
- ConstructorParameterNaming:GitHubDeviceFlowAuthenticator.kt$GitHubDeviceFlowAuthenticator.DeviceCodeResponse$val user_code: String? = null
- ConstructorParameterNaming:GitHubDeviceFlowAuthenticator.kt$GitHubDeviceFlowAuthenticator.DeviceCodeResponse$val verification_uri: String? = null
CyclomaticComplexMethod:BuildGradleParser.kt$BuildGradleParser$private fun parseAndroid(tokens: List<GToken>): ParsedAndroidBlock?
CyclomaticComplexMethod:GradleProjectReader.kt$GradleProjectReader$private fun moduleType(script: ParsedBuildScript, catalog: VersionCatalog?): ModuleType
CyclomaticComplexMethod:MiniJson.kt$MiniJson.Parser$private fun parseString(): String
@@ -23,8 +17,6 @@
LoopWithTooManyJumpStatements:MiniJson.kt$MiniJson.Parser$while
LoopWithTooManyJumpStatements:SelfSignedCertGenerator.kt$SelfSignedCertGenerator$while
LoopWithTooManyJumpStatements:VersionCatalogParser.kt$VersionCatalogParser$for
- SwallowedException:KeystoreFiles.kt$KeystoreFiles$e: UnrecoverableKeyException
- SwallowedException:KeystoreFiles.kt$KeystoreFiles$e: java.io.IOException
ThrowsCount:ArtifactDownloader.kt$ArtifactDownloader$private fun validateArtifact(file: File, expectation: ArtifactExpectation)
ThrowsCount:KeystoreFiles.kt$KeystoreFiles$fun import(storeFile: File, storePassword: String, keyAlias: String, keyPassword: String): SigningConfig
ThrowsCount:RemoteClient.kt$RemoteClient$private suspend fun executeWithRetry(request: Request, allowUnauthorizedThrow: Boolean): ResponseSnapshot
@@ -33,11 +25,5 @@
TooGenericExceptionCaught:PlayIntegrityTokenProvider.kt$PlayIntegrityTokenProvider$e: Throwable
TooGenericExceptionCaught:RemoteBuildSystem.kt$RemoteBuildSystem$t: Throwable
TooGenericExceptionCaught:RemoteClient.kt$RemoteClient$t: Throwable
- UnusedParameter:DependenciesBlockEditor.kt$DependenciesBlockEditor$text: String
- UseCheckOrError:RemoteBuildSystem.kt$RemoteBuildSystem$throw IllegalStateException("Configure a release keystore in Settings before building a release artifact.")
- UseCheckOrError:RemoteBuildSystem.kt$RemoteBuildSystem$throw IllegalStateException("The configured release keystore is missing or unreadable.")
- UseRequire:MiniJson.kt$MiniJson.Parser$throw IllegalArgumentException("Expected '$c' at $i")
- UseRequire:MiniJson.kt$MiniJson.Parser$throw IllegalArgumentException("Invalid literal at $i")
- UseRequire:MiniJson.kt$MiniJson.Parser$throw IllegalArgumentException("Unexpected end of JSON")
diff --git a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/signing/KeystoreFiles.kt b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/signing/KeystoreFiles.kt
index 35ea3692..50b7bc9f 100644
--- a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/signing/KeystoreFiles.kt
+++ b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/buildsystem/signing/KeystoreFiles.kt
@@ -38,7 +38,7 @@ internal object KeystoreFiles {
try {
params.storeFile.outputStream().use { keyStore.store(it, params.storePassword.toCharArray()) }
} catch (e: java.io.IOException) {
- throw KeystoreException(KeystoreError.Io(e.message ?: "Could not write keystore"))
+ throw KeystoreException(KeystoreError.Io(e.message ?: "Could not write keystore"), e)
}
return SigningConfig(
storeFile = params.storeFile,
@@ -71,7 +71,7 @@ internal object KeystoreFiles {
throw KeystoreException(KeystoreError.InvalidParams("Alias '$keyAlias' is not a private-key entry"))
}
} catch (e: UnrecoverableKeyException) {
- throw KeystoreException(KeystoreError.WrongKeyPassword)
+ throw KeystoreException(KeystoreError.WrongKeyPassword, e)
}
return SigningConfig(storeFile, storePassword, keyAlias, keyPassword, isDebug = false)
}
diff --git a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/deps/DependenciesBlockEditor.kt b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/deps/DependenciesBlockEditor.kt
index e1369cd1..f331bd5b 100644
--- a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/deps/DependenciesBlockEditor.kt
+++ b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/deps/DependenciesBlockEditor.kt
@@ -38,7 +38,7 @@ object DependenciesBlockEditor {
sb.append("\n\ndependencies {\n ").append(line).append("\n}\n")
Result.Changed(sb.toString())
} else {
- val insertOffset = insertionOffset(tokens, body, text)
+ val insertOffset = insertionOffset(tokens, body)
val indent = detectIndent(text, tokens, body)
val edited = StringBuilder(text)
.insert(insertOffset, "\n$indent$line")
@@ -74,7 +74,6 @@ object DependenciesBlockEditor {
private fun insertionOffset(
tokens: List,
body: IntRange,
- text: String,
): Int {
for (i in body.last downTo body.first) {
diff --git a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/util/MiniJson.kt b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/util/MiniJson.kt
index 4e3c55b6..0ee9391a 100644
--- a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/util/MiniJson.kt
+++ b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/gradle/util/MiniJson.kt
@@ -9,7 +9,7 @@ object MiniJson {
fun parseValue(): Any? {
skipWs()
- if (i >= s.length) throw IllegalArgumentException("Unexpected end of JSON")
+ require(i < s.length) { "Unexpected end of JSON" }
return when (s[i]) {
'{' -> parseObject()
'[' -> parseArray()
@@ -85,19 +85,26 @@ object MiniJson {
return s.substring(start, i).toDouble()
}
- private fun parseBoolean(): Boolean =
- if (s.startsWith("true", i)) { i += 4; true }
- else if (s.startsWith("false", i)) { i += 5; false }
- else throw IllegalArgumentException("Invalid literal at $i")
+ private fun parseBoolean(): Boolean {
+ if (s.startsWith("true", i)) {
+ i += 4
+ return true
+ }
+ require(s.startsWith("false", i)) { "Invalid literal at $i" }
+ i += 5
+ return false
+ }
- private fun parseNull(): Any? =
- if (s.startsWith("null", i)) { i += 4; null }
- else throw IllegalArgumentException("Invalid literal at $i")
+ private fun parseNull(): Any? {
+ require(s.startsWith("null", i)) { "Invalid literal at $i" }
+ i += 4
+ return null
+ }
private fun skipWs() { while (i < s.length && s[i].isWhitespace()) i++ }
private fun peek(): Char = if (i < s.length) s[i] else '\u0000'
private fun expect(c: Char) {
- if (peek() != c) throw IllegalArgumentException("Expected '$c' at $i")
+ require(peek() == c) { "Expected '$c' at $i" }
i++
}
}
diff --git a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/RemoteBuildSystem.kt b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/RemoteBuildSystem.kt
index 136167fd..1c52e719 100644
--- a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/RemoteBuildSystem.kt
+++ b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/RemoteBuildSystem.kt
@@ -239,9 +239,9 @@ class RemoteBuildSystem internal constructor(
if (request.buildType.equals("release", ignoreCase = true) ||
(request.buildType == null && RemoteBuildRequestFactory.isReleaseVariant(request.variantName))) {
val config = releaseSigningResolver()
- ?: throw IllegalStateException("Configure a release keystore in Settings before building a release artifact.")
+ ?: error("Configure a release keystore in Settings before building a release artifact.")
RemoteBuildRequestFactory.releaseSigningMaterial(config, encodeBase64 = encodeBase64)
- ?: throw IllegalStateException("The configured release keystore is missing or unreadable.")
+ ?: error("The configured release keystore is missing or unreadable.")
} else {
null
}
diff --git a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/github/GitHubDeviceFlowAuthenticator.kt b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/github/GitHubDeviceFlowAuthenticator.kt
index 6245ecc4..46de2612 100644
--- a/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/github/GitHubDeviceFlowAuthenticator.kt
+++ b/data/build/src/main/java/com/ahmadkharfan/androidstudiolite/data/remote/github/GitHubDeviceFlowAuthenticator.kt
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
+import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.FormBody
@@ -184,27 +185,19 @@ class GitHubDeviceFlowAuthenticator(
@Serializable
private data class DeviceCodeResponse(
- val device_code: String? = null,
- val user_code: String? = null,
- val verification_uri: String? = null,
- val expires_in: Int? = null,
+ @SerialName("device_code") val deviceCode: String? = null,
+ @SerialName("user_code") val userCode: String? = null,
+ @SerialName("verification_uri") val verificationUri: String? = null,
+ @SerialName("expires_in") val expiresIn: Int? = null,
val interval: Int? = null,
- ) {
- val deviceCode get() = device_code
- val userCode get() = user_code
- val verificationUri get() = verification_uri
- val expiresIn get() = expires_in
- }
+ )
@Serializable
private data class AccessTokenResponse(
- val access_token: String? = null,
+ @SerialName("access_token") val accessToken: String? = null,
val error: String? = null,
- val error_description: String? = null,
- ) {
- val accessToken get() = access_token
- val errorDescription get() = error_description
- }
+ @SerialName("error_description") val errorDescription: String? = null,
+ )
@Serializable
private data class GitHubUser(val login: String? = null)
diff --git a/designsystem/detekt-baseline.xml b/designsystem/detekt-baseline.xml
index 9d56895a..ca6370af 100644
--- a/designsystem/detekt-baseline.xml
+++ b/designsystem/detekt-baseline.xml
@@ -2,8 +2,6 @@
- ComposableParamOrder:Appear.kt$AslStaggeredAppear
- ComposableParamOrder:StatusChip.kt$AslStatusChip
CyclomaticComplexMethod:ApiKeyCard.kt$@Composable fun AslApiKeyCard( provider: String, value: String, onValueChange: (String) -> Unit, modifier: Modifier = Modifier, providerIcon: String = "sparkles", description: String? = null, placeholder: String = "sk-…", status: AslApiKeyStatus = AslApiKeyStatus.None, errorMessage: String? = null, onTest: (String) -> Unit = {}, testing: Boolean = false, onCollapse: (() -> Unit)? = null, )
CyclomaticComplexMethod:AslMarkdownText.kt$@Composable fun AslMarkdownText( markdown: String, modifier: Modifier = Modifier, onCopyCode: (String) -> Unit = {}, )
CyclomaticComplexMethod:BottomToolPanel.kt$@Composable fun AslBottomToolPanel( tabs: List<AslBottomPanelTab>, activeId: String?, modifier: Modifier = Modifier, contentHeight: Dp = 0.dp, defaultContentHeight: Dp = 260.dp, onContentHeightChange: (Dp) -> Unit = {}, onSelect: (String) -> Unit = {}, onToggle: () -> Unit = {}, content: @Composable () -> Unit = {}, )
diff --git a/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/animation/Appear.kt b/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/animation/Appear.kt
index 7a605e79..c6848ea6 100644
--- a/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/animation/Appear.kt
+++ b/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/animation/Appear.kt
@@ -12,8 +12,8 @@ import com.ahmadkharfan.androidstudiolite.designsystem.theme.AslMotion
@Composable
fun AslStaggeredAppear(
- index: Int = 0,
modifier: Modifier = Modifier,
+ index: Int = 0,
staggerMillis: Int = 45,
content: @Composable () -> Unit,
) {
diff --git a/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/feedback/StatusChip.kt b/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/feedback/StatusChip.kt
index c014bd7b..682a7d51 100644
--- a/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/feedback/StatusChip.kt
+++ b/designsystem/src/main/java/com/ahmadkharfan/androidstudiolite/designsystem/component/feedback/StatusChip.kt
@@ -37,8 +37,8 @@ private fun spec(status: AslStatus): StatusSpec = when (status) {
@Composable
fun AslStatusChip(
- status: AslStatus = AslStatus.Success,
modifier: Modifier = Modifier,
+ status: AslStatus = AslStatus.Success,
label: String? = null,
) {
val colors = AslTheme.colors
diff --git a/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/signing/SigningModels.kt b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/signing/SigningModels.kt
index f8f5d5c0..f71951d3 100644
--- a/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/signing/SigningModels.kt
+++ b/domain/src/main/java/com/ahmadkharfan/androidstudiolite/domain/signing/SigningModels.kt
@@ -45,4 +45,7 @@ sealed interface KeystoreError {
data class Io(val message: String) : KeystoreError
}
-class KeystoreException(val error: KeystoreError) : Exception(error.toString())
+class KeystoreException(
+ val error: KeystoreError,
+ cause: Throwable? = null,
+) : Exception(error.toString(), cause)
diff --git a/feature/editor/detekt-baseline.xml b/feature/editor/detekt-baseline.xml
index 8d214ecf..31453613 100644
--- a/feature/editor/detekt-baseline.xml
+++ b/feature/editor/detekt-baseline.xml
@@ -25,8 +25,6 @@
ForbiddenImport:AssetsScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:FileTreeSearchPanel.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:VariantsScreen.kt$import androidx.compose.material3.MaterialTheme
- LambdaParameterInRestartableEffect:AslEditableCodeEditor.kt$onVolumeKey
- LambdaParameterInRestartableEffect:EditorScreen.kt$onConflictPathOpened
LongMethod:AiChatScreen.kt$@Composable private fun AiChatScreen( uiState: AiChatUiState, interactionListener: AiChatInteractionListener, onClose: () -> Unit, onOpenAiAgentSettings: () -> Unit, )
LongMethod:AiChatScreen.kt$@Composable private fun ChatControlsSheet( uiState: AiChatUiState, interactionListener: AiChatInteractionListener, )
LongMethod:AslEditableCodeEditor.kt$@Composable fun AslEditableCodeEditor( session: EditorSession, fontSizeSp: Int, tabSize: Int, onEdited: () -> Unit, onCaretMoved: (line: Int, column: Int) -> Unit, modifier: Modifier = Modifier, colorSchemeId: String = "darcula", fontFamilyId: String = "jetbrains", gitLineStatus: Map<Int, AslLineGit> = emptyMap(), breakpoints: Set<Int> = emptySet(), findQuery: String = "", findCurrentMatch: Int = 0, revealNonce: Int = 0, revealOffset: Int = 0, enableVolumeKeys: Boolean = true, projectIndex: ProjectSymbolIndex = ProjectSymbolIndex.EMPTY, )
diff --git a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorScreen.kt b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorScreen.kt
index 3c39f14e..dbeab724 100644
--- a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorScreen.kt
+++ b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/EditorScreen.kt
@@ -23,15 +23,14 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.zIndex
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.lifecycle.compose.LocalLifecycleOwner
+import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.collectLatest
import org.koin.androidx.compose.koinViewModel
@@ -95,6 +94,7 @@ fun EditorRoute(
viewModel: EditorViewModel = koinViewModel { parametersOf(projectId) },
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
+ val currentOnConflictPathOpened by rememberUpdatedState(onConflictPathOpened)
val notificationPermissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
@@ -103,7 +103,7 @@ fun EditorRoute(
LaunchedEffect(openConflictPath) {
openConflictPath?.let {
viewModel.onOpenFile(it, File(it).name)
- onConflictPathOpened()
+ currentOnConflictPathOpened()
}
}
@@ -123,18 +123,8 @@ fun EditorRoute(
}
- val lifecycleOwner = LocalLifecycleOwner.current
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- when (event) {
- Lifecycle.Event.ON_RESUME -> viewModel.onAppForegrounded()
- Lifecycle.Event.ON_STOP -> viewModel.flushPendingSaves()
- else -> Unit
- }
- }
- lifecycleOwner.lifecycle.addObserver(observer)
- onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
- }
+ LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { viewModel.onAppForegrounded() }
+ LifecycleEventEffect(Lifecycle.Event.ON_STOP) { viewModel.flushPendingSaves() }
EditorScreen(
uiState = uiState,
diff --git a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/view/AslEditableCodeEditor.kt b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/view/AslEditableCodeEditor.kt
index b9686896..d72e1094 100644
--- a/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/view/AslEditableCodeEditor.kt
+++ b/feature/editor/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/view/AslEditableCodeEditor.kt
@@ -13,6 +13,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
@@ -168,16 +169,17 @@ fun EditorVolumeScrollEffect(
enabled: Boolean,
onVolumeKey: (volumeUp: Boolean) -> Unit,
) {
+ val currentOnVolumeKey by rememberUpdatedState(onVolumeKey)
DisposableEffect(enabled) {
if (!enabled) return@DisposableEffect onDispose {}
EditorVolumeKeyDispatcher.handler = { event ->
when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
- onVolumeKey(false)
+ currentOnVolumeKey(false)
true
}
KeyEvent.KEYCODE_VOLUME_UP -> {
- onVolumeKey(true)
+ currentOnVolumeKey(true)
true
}
else -> false
diff --git a/feature/git/detekt-baseline.xml b/feature/git/detekt-baseline.xml
index 69c0e8e1..3963bc55 100644
--- a/feature/git/detekt-baseline.xml
+++ b/feature/git/detekt-baseline.xml
@@ -2,14 +2,12 @@
- ComposableParamOrder:GitDiffScreen.kt$GitDiffRoute
CyclomaticComplexMethod:GitAuthPrompt.kt$@Composable fun GitHubAuthDialog(state: GitAuthPromptState, actions: GitAuthPromptActions)
CyclomaticComplexMethod:GitErrorMessageMapper.kt$fun gitErrorMessage(error: Throwable): String
CyclomaticComplexMethod:GitHistoryScreen.kt$@Composable private fun GitHistoryScreen( uiState: GitHistoryUiState, interactionListener: GitHistoryInteractionListener, onBack: () -> Unit, onOpenDiff: (String, String) -> Unit, )
CyclomaticComplexMethod:GitPanelScreen.kt$@Composable private fun GitPanelScreen( uiState: GitPanelUiState, interactionListener: GitPanelInteractionListener, onClose: () -> Unit, onOpenDiff: (String, GitDiffTarget) -> Unit, onOpenHistory: () -> Unit, onOpenBranches: () -> Unit, onOpenTags: () -> Unit, onOpenStashes: () -> Unit, onOpenConflicts: () -> Unit, )
ForbiddenImport:GitDiffScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:GitHistoryScreen.kt$import androidx.compose.material3.MaterialTheme
- LambdaParameterInRestartableEffect:GitHistoryScreen.kt$onLoadNext
LongMethod:GitAuthPrompt.kt$@Composable fun GitHubAuthDialog(state: GitAuthPromptState, actions: GitAuthPromptActions)
LongMethod:GitDiffScreen.kt$@Composable private fun GitDiffScreen( uiState: GitDiffUiState, interactionListener: GitDiffInteractionListener, onBack: () -> Unit, )
LongMethod:GitHistoryScreen.kt$@Composable private fun GitHistoryScreen( uiState: GitHistoryUiState, interactionListener: GitHistoryInteractionListener, onBack: () -> Unit, onOpenDiff: (String, String) -> Unit, )
diff --git a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt
index 0955f125..10397fb5 100644
--- a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt
+++ b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/GitPanelScreen.kt
@@ -18,7 +18,6 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -27,10 +26,9 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.lifecycle.compose.LocalLifecycleOwner
import kotlinx.coroutines.delay
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
@@ -81,14 +79,7 @@ fun GitPanelRoute(
viewModel: GitPanelViewModel = koinViewModel { parametersOf(projectId) },
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
- val lifecycleOwner = LocalLifecycleOwner.current
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_RESUME) viewModel.onAppForegrounded()
- }
- lifecycleOwner.lifecycle.addObserver(observer)
- onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
- }
+ LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { viewModel.onAppForegrounded() }
GitPanelScreen(
uiState = uiState,
interactionListener = viewModel,
diff --git a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/diff/GitDiffScreen.kt b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/diff/GitDiffScreen.kt
index 0bdfe1bb..fe105600 100644
--- a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/diff/GitDiffScreen.kt
+++ b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/diff/GitDiffScreen.kt
@@ -74,8 +74,8 @@ fun GitDiffRoute(
projectId: String,
path: String,
target: GitDiffTarget,
- commitId: String? = null,
onBack: () -> Unit,
+ commitId: String? = null,
viewModel: GitDiffViewModel = koinViewModel { parametersOf(projectId, path, target, commitId.orEmpty()) },
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
diff --git a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/history/GitHistoryScreen.kt b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/history/GitHistoryScreen.kt
index ac89aaaf..88868347 100644
--- a/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/history/GitHistoryScreen.kt
+++ b/feature/git/src/main/java/com/ahmadkharfan/androidstudiolite/feature/editor/git/history/GitHistoryScreen.kt
@@ -24,6 +24,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
@@ -195,10 +196,13 @@ private fun HistoryList(
onReset: (String) -> Unit,
) {
val listState = rememberLazyListState()
+ val currentOnLoadNext by rememberUpdatedState(onLoadNext)
LaunchedEffect(listState, state.nextCursor) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0 }
.distinctUntilChanged()
- .collect { index -> if (state.nextCursor != null && index >= state.commits.lastIndex - 4) onLoadNext() }
+ .collect { index ->
+ if (state.nextCursor != null && index >= state.commits.lastIndex - 4) currentOnLoadNext()
+ }
}
LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
items(state.commits, key = { it.id }) { commit ->
diff --git a/feature/onboarding/detekt-baseline.xml b/feature/onboarding/detekt-baseline.xml
index 6ba975d1..8f37cc4a 100644
--- a/feature/onboarding/detekt-baseline.xml
+++ b/feature/onboarding/detekt-baseline.xml
@@ -2,13 +2,11 @@
- ExplicitItLambdaParameter:PermissionsScreen.kt${ _, it -> it.id }
ForbiddenImport:CompleteScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:HowItWorksScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:PermissionsScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:WelcomeScreen.kt$import androidx.compose.material3.MaterialTheme
LongMethod:HowItWorksScreen.kt$@Composable private fun HowItWorksScreen(onContinue: () -> Unit)
ParameterNaming:WelcomeScreen.kt$onGetStarted
- UnusedPrivateMember:WelcomeScreen.kt$@Preview @Composable private fun WelcomeBulletPreview()
diff --git a/feature/onboarding/src/main/java/com/ahmadkharfan/androidstudiolite/feature/onboarding/permissions/PermissionsScreen.kt b/feature/onboarding/src/main/java/com/ahmadkharfan/androidstudiolite/feature/onboarding/permissions/PermissionsScreen.kt
index d7e49acb..2b77de79 100644
--- a/feature/onboarding/src/main/java/com/ahmadkharfan/androidstudiolite/feature/onboarding/permissions/PermissionsScreen.kt
+++ b/feature/onboarding/src/main/java/com/ahmadkharfan/androidstudiolite/feature/onboarding/permissions/PermissionsScreen.kt
@@ -140,7 +140,7 @@ private fun PermissionsList(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
- itemsIndexed(uiState.permissions, key = { _, it -> it.id }) { index, permission ->
+ itemsIndexed(uiState.permissions, key = { _, permission -> permission.id }) { index, permission ->
AslStaggeredAppear(index = index) {
AslPermissionCard(
title = permission.title,
diff --git a/feature/projects/detekt-baseline.xml b/feature/projects/detekt-baseline.xml
index 4eb3d9a1..01518266 100644
--- a/feature/projects/detekt-baseline.xml
+++ b/feature/projects/detekt-baseline.xml
@@ -5,15 +5,6 @@
ForbiddenImport:CloneRepoScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:ConfigureStep.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:HubScreen.kt$import androidx.compose.material3.MaterialTheme
- ForbiddenImport:HubSectionHeader.kt$import androidx.compose.material3.MaterialTheme
- LambdaParameterInRestartableEffect:CloneRepoScreen.kt$onCloned
- LambdaParameterInRestartableEffect:CreateProjectScreen.kt$onCreated
- LambdaParameterInRestartableEffect:CreateProjectScreen.kt$onPickedFolderConsumed
- LambdaParameterInRestartableEffect:HubScreen.kt$onCreateProject
- LambdaParameterInRestartableEffect:HubScreen.kt$onOpenPreferences
- LambdaParameterInRestartableEffect:HubScreen.kt$onOpenProject
- LambdaParameterInRestartableEffect:HubScreen.kt$onPickedFolderConsumed
- LambdaParameterInRestartableEffect:OpenProjectScreen.kt$onProjectSelected
LongMethod:FolderPickerScreen.kt$@Composable private fun FolderPickerScreen( uiState: FolderPickerUiState, interactionListener: FolderPickerInteractionListener, onCancel: () -> Unit, onSelect: () -> Unit, )
LongParameterList:ConfigureStep.kt$( projectName: String, packageName: String, location: String, minSdk: String, language: String, supportsJava: Boolean, nameError: String?, packageError: String?, onNameChanged: (String) -> Unit, onPackageChanged: (String) -> Unit, onLocationChanged: (String) -> Unit, onMinSdkChanged: (String) -> Unit, onLanguageChanged: (String) -> Unit, onBrowseLocation: () -> Unit, modifier: Modifier = Modifier, )
ModifierMissing:HubScreen.kt$HubRoute
@@ -30,6 +21,5 @@
ParameterNaming:FolderPickerScreen.kt$onFolderSelected
ParameterNaming:HubScreen.kt$onPickedFolderConsumed
ParameterNaming:OpenProjectScreen.kt$onProjectSelected
- UseCheckOrError:CreateProjectViewModel.kt$CreateProjectViewModel$throw IllegalStateException("No template selected")
diff --git a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/clonerepo/CloneRepoScreen.kt b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/clonerepo/CloneRepoScreen.kt
index 38e4c767..ba896aa3 100644
--- a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/clonerepo/CloneRepoScreen.kt
+++ b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/clonerepo/CloneRepoScreen.kt
@@ -9,6 +9,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -35,10 +36,11 @@ fun CloneRepoRoute(
viewModel: CloneRepoViewModel = koinViewModel(),
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
+ val currentOnCloned by rememberUpdatedState(onCloned)
LaunchedEffect(uiState.clonedProjectId) {
val clonedProjectId = uiState.clonedProjectId ?: return@LaunchedEffect
- onCloned(clonedProjectId)
+ currentOnCloned(clonedProjectId)
viewModel.onClonedProjectOpened()
}
diff --git a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectScreen.kt b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectScreen.kt
index e45c505e..43e937ac 100644
--- a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectScreen.kt
+++ b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectScreen.kt
@@ -7,6 +7,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -34,14 +35,16 @@ fun CreateProjectRoute(
viewModel: CreateProjectViewModel = koinViewModel(),
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
+ val currentOnCreated by rememberUpdatedState(onCreated)
+ val currentOnPickedFolderConsumed by rememberUpdatedState(onPickedFolderConsumed)
LaunchedEffect(uiState.createdProjectId) {
- uiState.createdProjectId?.let(onCreated)
+ uiState.createdProjectId?.let(currentOnCreated)
}
LaunchedEffect(pickedFolder) {
pickedFolder?.let {
viewModel.onLocationChanged(it)
- onPickedFolderConsumed()
+ currentOnPickedFolderConsumed()
}
}
diff --git a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectViewModel.kt b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectViewModel.kt
index 7448b88a..72d0a545 100644
--- a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectViewModel.kt
+++ b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/createproject/CreateProjectViewModel.kt
@@ -148,7 +148,7 @@ class CreateProjectViewModel(
onStart = { updateState { copy(creating = true) } },
block = {
val templateId = state.value.selectedTemplateId
- ?: throw IllegalStateException("No template selected")
+ ?: error("No template selected")
projectRepository.createProject(
NewProjectSpec(
name = s.projectName,
diff --git a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/hub/HubScreen.kt b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/hub/HubScreen.kt
index e9c5b5cb..c1d45f24 100644
--- a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/hub/HubScreen.kt
+++ b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/hub/HubScreen.kt
@@ -29,6 +29,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -77,20 +78,24 @@ fun HubRoute(
viewModel: HubViewModel = koinViewModel(),
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
+ val currentOnOpenProject by rememberUpdatedState(onOpenProject)
+ val currentOnCreateProject by rememberUpdatedState(onCreateProject)
+ val currentOnOpenPreferences by rememberUpdatedState(onOpenPreferences)
+ val currentOnPickedFolderConsumed by rememberUpdatedState(onPickedFolderConsumed)
LaunchedEffect(pickedFolder) {
pickedFolder?.let { path ->
viewModel.onFolderPicked(path)
- onPickedFolderConsumed()
+ currentOnPickedFolderConsumed()
}
}
LaunchedEffect(Unit) {
viewModel.effect.collectLatest { effect ->
when (effect) {
- is HubEffect.NavigateToProject -> onOpenProject(effect.id)
- HubEffect.NavigateToCreateProject -> onCreateProject()
- HubEffect.NavigateToPreferences -> onOpenPreferences()
+ is HubEffect.NavigateToProject -> currentOnOpenProject(effect.id)
+ HubEffect.NavigateToCreateProject -> currentOnCreateProject()
+ HubEffect.NavigateToPreferences -> currentOnOpenPreferences()
}
}
}
diff --git a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/openproject/OpenProjectScreen.kt b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/openproject/OpenProjectScreen.kt
index ad1a7d48..c10e9dd3 100644
--- a/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/openproject/OpenProjectScreen.kt
+++ b/feature/projects/src/main/java/com/ahmadkharfan/androidstudiolite/feature/openproject/OpenProjectScreen.kt
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -29,11 +30,12 @@ fun OpenProjectRoute(
viewModel: OpenProjectViewModel = koinViewModel(),
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
+ val currentOnProjectSelected by rememberUpdatedState(onProjectSelected)
LaunchedEffect(Unit) {
viewModel.effect.collectLatest { effect ->
when (effect) {
- is OpenProjectEffect.NavigateToProject -> onProjectSelected(effect.id)
+ is OpenProjectEffect.NavigateToProject -> currentOnProjectSelected(effect.id)
}
}
}
diff --git a/feature/terminal/detekt-baseline.xml b/feature/terminal/detekt-baseline.xml
index 837b901b..66bbb289 100644
--- a/feature/terminal/detekt-baseline.xml
+++ b/feature/terminal/detekt-baseline.xml
@@ -6,24 +6,15 @@
CyclomaticComplexMethod:TerminalEmulator.kt$TerminalEmulator$private fun applySgr(params: List<Int?>)
CyclomaticComplexMethod:TerminalEmulator.kt$TerminalEmulator$private fun dispatchCsi(final: Char)
CyclomaticComplexMethod:TerminalEmulatorView.kt$@Composable fun TerminalEmulatorView( screen: TerminalScreen, background: Color, foreground: Color, cursorColor: Color, onKey: (String) -> Unit, onSpecialKey: (TerminalKey) -> Unit, onResize: (rows: Int, cols: Int) -> Unit, modifier: Modifier = Modifier, enableVolumeKeys: Boolean = true, requestKeyboardOnAttach: Boolean = true, )
- EmptyElseBlock:TerminalEmulator.kt$TerminalEmulator$if (cursorCol > 0) cursorCol--
EmptyFunctionBlock:TerminalSessionManagerTest.kt$TerminalSessionManagerTest.FakeTerminalRepository${}
ForbiddenImport:TerminalScreen.kt$import androidx.compose.material3.MaterialTheme
ForbiddenImport:TerminalSettingsSheet.kt$import androidx.compose.material3.MaterialTheme
- LambdaParameterInRestartableEffect:TerminalEmulatorView.kt$onResize
- LambdaParameterInRestartableEffect:TerminalVolumeScrollEffect.kt$onVolumeKey
LongMethod:TerminalEmulatorView.kt$@Composable fun TerminalEmulatorView( screen: TerminalScreen, background: Color, foreground: Color, cursorColor: Color, onKey: (String) -> Unit, onSpecialKey: (TerminalKey) -> Unit, onResize: (rows: Int, cols: Int) -> Unit, modifier: Modifier = Modifier, enableVolumeKeys: Boolean = true, requestKeyboardOnAttach: Boolean = true, )
LongMethod:TerminalSettingsSheet.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun TerminalSettingsSheet( linux: LinuxStatus, onDismiss: () -> Unit, onInstallLinux: () -> Unit, onReinstallLinux: () -> Unit, )
LongParameterList:TerminalEmulatorView.kt$( native: android.graphics.Canvas, paint: Paint, builder: StringBuilder, cells: List<TerminalCell>, top: Float, baseline: Float, charWidthPx: Float, lineHeightPx: Float, foreground: Color, background: Color, cursorColor: Color, cursorCol: Int, lightBackground: Boolean, )
LongParameterList:TerminalEmulatorView.kt$( screen: TerminalScreen, background: Color, foreground: Color, cursorColor: Color, onKey: (String) -> Unit, onSpecialKey: (TerminalKey) -> Unit, onResize: (rows: Int, cols: Int) -> Unit, modifier: Modifier = Modifier, enableVolumeKeys: Boolean = true, requestKeyboardOnAttach: Boolean = true, )
LoopWithTooManyJumpStatements:TerminalEmulatorView.kt$for
LoopWithTooManyJumpStatements:TerminalEmulatorView.kt$while
- ThrowsCount:LinuxBootstrapInstaller.kt$LinuxBootstrapInstaller$private fun downloadWithChecksum(url: String, expectedSha256: String, dest: File)
TooGenericExceptionCaught:LinuxBootstrapInstaller.kt$LinuxBootstrapInstaller$t: Throwable
- UseCheckOrError:LinuxBootstrapInstaller.kt$LinuxBootstrapInstaller$throw IllegalStateException("checksum mismatch")
- UseCheckOrError:LinuxBootstrapInstaller.kt$LinuxBootstrapInstaller$throw IllegalStateException("could not move rootfs into place")
- UseCheckOrError:LinuxBootstrapInstaller.kt$LinuxBootstrapInstaller$throw IllegalStateException("download failed: HTTP ${response.code}")
- UseCheckOrError:LinuxBootstrapInstaller.kt$LinuxBootstrapInstaller$throw IllegalStateException("empty response body")
- UseCheckOrError:LinuxBootstrapInstaller.kt$LinuxBootstrapInstaller$throw IllegalStateException("unsafe path in archive: ${entry.name}")
diff --git a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/core/linux/LinuxBootstrapInstaller.kt b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/core/linux/LinuxBootstrapInstaller.kt
index 4fa8115d..4112e020 100644
--- a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/core/linux/LinuxBootstrapInstaller.kt
+++ b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/core/linux/LinuxBootstrapInstaller.kt
@@ -96,9 +96,7 @@ class LinuxBootstrapInstaller(
extract(archive, staging)
proot.rootfsDir.deleteRecursively()
- if (!staging.renameTo(proot.rootfsDir)) {
- throw IllegalStateException("could not move rootfs into place")
- }
+ check(staging.renameTo(proot.rootfsDir)) { "could not move rootfs into place" }
proot.prepareRuntime()
File(proot.ensureRootfsParent(), ".linux-installed").writeText("ok")
bootstrapPackages()
@@ -156,8 +154,8 @@ class LinuxBootstrapInstaller(
private fun downloadWithChecksum(url: String, expectedSha256: String, dest: File) {
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
- if (!response.isSuccessful) throw IllegalStateException("download failed: HTTP ${response.code}")
- val body = response.body ?: throw IllegalStateException("empty response body")
+ check(response.isSuccessful) { "download failed: HTTP ${response.code}" }
+ val body = response.body ?: error("empty response body")
val total = body.contentLength()
val digest = MessageDigest.getInstance("SHA-256")
body.byteStream().use { input ->
@@ -176,9 +174,7 @@ class LinuxBootstrapInstaller(
}
}
val actual = digest.digest().joinToString("") { "%02x".format(it) }
- if (!actual.equals(expectedSha256, ignoreCase = true)) {
- throw IllegalStateException("checksum mismatch")
- }
+ check(actual.equals(expectedSha256, ignoreCase = true)) { "checksum mismatch" }
}
}
@@ -194,9 +190,10 @@ class LinuxBootstrapInstaller(
}
val out = File(into, relative)
- if (!out.canonicalPath.startsWith(canonicalInto + File.separator) && out.canonicalPath != canonicalInto) {
- throw IllegalStateException("unsafe path in archive: ${entry.name}")
- }
+ check(
+ out.canonicalPath.startsWith(canonicalInto + File.separator) ||
+ out.canonicalPath == canonicalInto,
+ ) { "unsafe path in archive: ${entry.name}" }
when {
entry.isDirectory -> out.mkdirs()
entry.isSymbolicLink -> {
diff --git a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalEmulatorView.kt b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalEmulatorView.kt
index d9423b93..ae1b5b0b 100644
--- a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalEmulatorView.kt
+++ b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalEmulatorView.kt
@@ -107,6 +107,7 @@ fun TerminalEmulatorView(
val context = LocalContext.current
val density = LocalDensity.current
val colors = AslTheme.colors
+ val currentOnResize by rememberUpdatedState(onResize)
val lightTerminal = remember(background) {
val r = background.red
val g = background.green
@@ -382,7 +383,7 @@ fun TerminalEmulatorView(
if (rows != lastEmittedRows || cols != lastEmittedCols) {
lastEmittedRows = rows
lastEmittedCols = cols
- onResize(rows, cols)
+ currentOnResize(rows, cols)
}
}
diff --git a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalVolumeScrollEffect.kt b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalVolumeScrollEffect.kt
index 3c558064..6d5d0ec2 100644
--- a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalVolumeScrollEffect.kt
+++ b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/TerminalVolumeScrollEffect.kt
@@ -3,22 +3,25 @@ package com.ahmadkharfan.androidstudiolite.feature.terminal
import android.view.KeyEvent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberUpdatedState
@Composable
fun TerminalVolumeScrollEffect(
enabled: Boolean,
onVolumeKey: (volumeUp: Boolean) -> Unit,
) {
+ val currentOnVolumeKey by rememberUpdatedState(onVolumeKey)
DisposableEffect(enabled) {
if (!enabled) return@DisposableEffect onDispose {}
TerminalVolumeKeyDispatcher.handler = { event ->
when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
- onVolumeKey(false)
+ currentOnVolumeKey(false)
true
}
KeyEvent.KEYCODE_VOLUME_UP -> {
- onVolumeKey(true)
+ currentOnVolumeKey(true)
true
}
else -> false
diff --git a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/emulator/TerminalEmulator.kt b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/emulator/TerminalEmulator.kt
index 0cc3aebf..52475def 100644
--- a/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/emulator/TerminalEmulator.kt
+++ b/feature/terminal/src/main/java/com/ahmadkharfan/androidstudiolite/feature/terminal/emulator/TerminalEmulator.kt
@@ -128,7 +128,10 @@ class TerminalEmulator(rows: Int, cols: Int) {
'\u001B' -> { state = State.ESC; paramBuf.setLength(0) }
'\n', '\u000B', '\u000C' -> lineFeed()
'\r' -> { cursorCol = 0; wrapPending = false }
- '\b' -> { if (cursorCol > 0) cursorCol--; wrapPending = false }
+ '\b' -> {
+ if (cursorCol > 0) cursorCol--
+ wrapPending = false
+ }
'\t' -> tab()
'\u0007' -> Unit
else -> if (c >= ' ') putChar(c)