Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5e3e4cf
Implement IDE onboarding flow state machine and validation
MihaiCristianCondrea Mar 26, 2026
59b6f9d
Add permission coordinator, environment checks, and template adapter
MihaiCristianCondrea Mar 26, 2026
34084ba
Add project creation flow, IDE activity handoff, and draft persistence
MihaiCristianCondrea Mar 26, 2026
23cfd5d
Harden onboarding UX strings, accessibility, and add focused tests
MihaiCristianCondrea Mar 26, 2026
7ec6b12
updated dependencies
Mar 26, 2026
25c67f7
updated dependencies
Mar 26, 2026
41059a9
Fix IDE onboarding FIXMEs and stabilize step counting
MihaiCristianCondrea Mar 26, 2026
c936b78
Merge pull request #41 from MihaiCristianCondrea/codex/review-onboard…
MihaiCristianCondrea Mar 26, 2026
870fd95
updated dependencies
Mar 26, 2026
cd0307a
updated dependencies
Mar 26, 2026
366d9c8
Make IDE onboarding permissions optional and fix install status
MihaiCristianCondrea Mar 26, 2026
2d9a1fa
Resolve IDE permission coordinator fixme issues
MihaiCristianCondrea Mar 26, 2026
983030b
Merge pull request #42 from MihaiCristianCondrea/codex/fix-ide-onboar…
MihaiCristianCondrea Mar 26, 2026
35e6779
updated dependencies
Mar 26, 2026
5a58085
Fix IDE onboarding permission step restore regression
MihaiCristianCondrea Mar 26, 2026
df15336
Merge pull request #43 from MihaiCristianCondrea/codex/fix-onboarding…
MihaiCristianCondrea Mar 26, 2026
e1059f7
updated dependencies
Mar 26, 2026
d49c598
Add IDE start screen and move onboarding into dedicated activity
MihaiCristianCondrea Mar 26, 2026
1f66669
Refine IDE onboarding UI to match AndroidIDE flow
MihaiCristianCondrea Mar 26, 2026
8d33d82
Make onboarding navigation controls sticky at scaffold bottom
MihaiCristianCondrea Mar 26, 2026
cf56acc
Merge pull request #44 from MihaiCristianCondrea/codex/create-onboard…
MihaiCristianCondrea Mar 26, 2026
a906c51
updated dependencies
Mar 26, 2026
e3af789
updated dependencies
Mar 26, 2026
035278e
Refine onboarding app bar actions and SAF location UX
MihaiCristianCondrea Mar 26, 2026
5ba917d
Merge pull request #45 from MihaiCristianCondrea/codex/animate-onboar…
MihaiCristianCondrea Mar 26, 2026
df92a92
Update Gradle to 9.4.0 and refresh ad unit IDs
MihaiCristianCondrea Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 18 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ android {
}
}

/**
* Code shrinking and obfuscation are intentionally disabled for the `release` build.
*
* This application embeds Android Studio–like tooling (editor/runtime features) that rely on:
* - reflection
* - dynamic class loading
* - stable class and method names
*
* Enabling `isMinifyEnabled` or `isShrinkResources` may break these mechanisms,
* leading to runtime instability or non-functional tooling inside the app.
*
* Note:
* If minification is ever enabled, extensive keep rules will be required to preserve
* all dynamically accessed APIs and internal tooling components.
*/
buildTypes {
release {
val signingFile = rootProject.file("signing.properties")
Expand All @@ -133,8 +148,8 @@ android {
} else {
null
}
isMinifyEnabled = true
isShrinkResources = true
isMinifyEnabled = false
isShrinkResources = false
proguardFiles(getDefaultProguardFile(name = "proguard-android-optimize.txt"), "proguard-rules.pro")
if (hasGoogleServicesConfig) {
configure<CrashlyticsExtension> {
Expand Down Expand Up @@ -170,7 +185,7 @@ android {
dependencies {

// App Core
implementation(dependencyNotation = "com.github.MihaiCristianCondrea:App-Toolkit-for-Android:2.0.8") {
implementation(dependencyNotation = "com.github.MihaiCristianCondrea:App-Toolkit-for-Android:2.0.10") {
isTransitive = true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.d4rk.androidtutorials.app.codestudio.onboarding.ui

import androidx.activity.ComponentActivity
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.d4rk.android.libs.apptoolkit.core.ui.window.AppWindowWidthSizeClass
import com.d4rk.androidtutorials.app.codestudio.onboarding.domain.model.CodeStudioOnboardingStep
import com.d4rk.androidtutorials.app.codestudio.onboarding.domain.model.CodeStudioProjectForm
import com.d4rk.androidtutorials.app.codestudio.onboarding.domain.reducer.IdeOnboardingReducer
import com.d4rk.androidtutorials.app.codestudio.onboarding.domain.validation.IdeProjectFormValidator
import com.d4rk.androidtutorials.app.codestudio.onboarding.ui.contract.CodeStudioOnboardingAction
import com.d4rk.androidtutorials.app.codestudio.onboarding.ui.state.CodeStudioOnboardingUiState
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

// FIXME: FIX THE UNIT TESTS
@RunWith(AndroidJUnit4::class)
class IdeOnboardingScreenTest {

@get:Rule
val composeRule = createAndroidComposeRule<ComponentActivity>()

@Test
fun stepNavigation_nextAndBack_updatesStepCounter() {
composeRule.setContent {
var state by mutableStateOf(CodeStudioOnboardingUiState())
CodeStudioOnboardingScreen(
state = state,
windowWidthSizeClass = AppWindowWidthSizeClass.Compact,
paddingValues = androidx.compose.foundation.layout.PaddingValues(),
onAction = { action -> state = IdeOnboardingReducer.reduce(state, action).state },
onRequestPermissionCapability = {},
onRunEnvironmentChecks = {},
)
}

composeRule.onNodeWithText("Step 1 of 7").assertIsDisplayed()
composeRule.onNodeWithText("Next").performClick()
composeRule.onNodeWithText("Step 2 of 7").assertIsDisplayed()
composeRule.onNodeWithText("Next").performClick()
composeRule.onNodeWithText("Step 3 of 7").assertIsDisplayed()
composeRule.onNodeWithText("Back").performClick()
composeRule.onNodeWithText("Step 2 of 7").assertIsDisplayed()
}

@Test
fun nextDisabled_whenPermissionStepNotGranted() {
composeRule.setContent {
val state = CodeStudioOnboardingUiState(currentStep = CodeStudioOnboardingStep.PERMISSIONS)
IdeOnboardingScreen(
state = state,
windowWidthSizeClass = AppWindowWidthSizeClass.Compact,
paddingValues = androidx.compose.foundation.layout.PaddingValues(),
onAction = {},
onRequestPermissionCapability = {},
onRunEnvironmentChecks = {},
)
}

composeRule.onNodeWithText("Next").assertIsNotEnabled()
}

@Test
fun errorMessage_isRenderedWithLiveRegionCard() {
composeRule.setContent {
val state = CodeStudioOnboardingUiState(errorMessage = "Project cannot be created yet.")
IdeOnboardingScreen(
state = state,
windowWidthSizeClass = AppWindowWidthSizeClass.Compact,
paddingValues = androidx.compose.foundation.layout.PaddingValues(),
onAction = {},
onRequestPermissionCapability = {},
onRunEnvironmentChecks = {},
)
}

composeRule.onNodeWithText("Project cannot be created yet.").assertIsDisplayed()
}

@Test
fun createButton_enabledOnValidReview_andDispatchesAction() {
var lastAction: CodeStudioOnboardingAction? = null

composeRule.setContent {
val form = CodeStudioProjectForm(
projectName = "MyProject",
packageName = "com.example.project",
saveLocation = "/tmp",
selectedTemplateId = "empty_activity",
)
val state = CodeStudioOnboardingUiState(
currentStep = CodeStudioOnboardingStep.REVIEW_AND_CREATE,
projectForm = form,
validationResult = IdeProjectFormValidator.validate(form),
isPermissionGranted = true,
isEnvironmentReady = true,
)
IdeOnboardingScreen(
state = state,
windowWidthSizeClass = AppWindowWidthSizeClass.Compact,
paddingValues = androidx.compose.foundation.layout.PaddingValues(),
onAction = { action -> lastAction = action },
onRequestPermissionCapability = {},
onRunEnvironmentChecks = {},
)
}

composeRule.onNodeWithText("Create Project").assertIsEnabled().performClick()
composeRule.runOnIdle {
assertEquals(CodeStudioOnboardingAction.CreateProjectClicked, lastAction)
}
composeRule.onNodeWithText("Next").assertDoesNotExist()
}
}
14 changes: 14 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
android:installLocation="auto">

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<application
android:name="com.d4rk.androidtutorials.AndroidStudioTutorials"
Expand Down Expand Up @@ -48,6 +50,18 @@
android:resource="@xml/shortcuts" />
</activity>


<activity
android:name="com.d4rk.androidtutorials.app.codestudio.setup.ui.CodeStudioSetupActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />


<activity
android:name="com.d4rk.androidtutorials.app.codestudio.common.runtime.ui.IdeActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />

<activity
android:name="com.d4rk.androidtutorials.app.lessons.details.ui.LessonActivity"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.d4rk.androidtutorials.app.codestudio.common.runtime.navigation

import android.content.Context
import android.content.Intent
import com.d4rk.androidtutorials.app.codestudio.common.runtime.ui.IdeActivity

data class IdeLaunchPayload(
val projectId: String,
val projectPath: String,
val templateId: String,
)

object IdeActivityContract {
private const val EXTRA_PROJECT_ID = "extra_project_id"
private const val EXTRA_PROJECT_PATH = "extra_project_path"
private const val EXTRA_TEMPLATE_ID = "extra_template_id"

fun createIntent(context: Context, payload: IdeLaunchPayload): Intent =
Intent(context, IdeActivity::class.java).apply {
putExtra(EXTRA_PROJECT_ID, payload.projectId)
putExtra(EXTRA_PROJECT_PATH, payload.projectPath)
putExtra(EXTRA_TEMPLATE_ID, payload.templateId)
}

fun parse(intent: Intent): IdeLaunchPayload? {
val id = intent.getStringExtra(EXTRA_PROJECT_ID) ?: return null
val path = intent.getStringExtra(EXTRA_PROJECT_PATH) ?: return null
val templateId = intent.getStringExtra(EXTRA_TEMPLATE_ID) ?: return null
return IdeLaunchPayload(id, path, templateId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.d4rk.androidtutorials.app.codestudio.common.runtime.ui

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.d4rk.android.libs.apptoolkit.app.theme.ui.style.AppTheme
import com.d4rk.androidtutorials.app.codestudio.common.runtime.navigation.IdeActivityContract
import com.d4rk.androidtutorials.app.codestudio.common.runtime.navigation.IdeLaunchPayload

class IdeActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val payload = IdeActivityContract.parse(intent)

setContent {
AppTheme {
IdeActivityScreen(payload)
}
}
}
}

@Composable
private fun IdeActivityScreen(payload: IdeLaunchPayload?) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
Text("IDE Runtime Placeholder", style = MaterialTheme.typography.headlineSmall)
Text("Handoff successful. This screen will host the embedded IDE runtime.")

if (payload != null) {
Text("Project ID: ${payload.projectId}")
Text("Project Path: ${payload.projectPath}")
Text("Template ID: ${payload.templateId}")
} else {
Text("No launch payload provided.")
}
}
}
Loading