create-droid 1.0.7 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,12 @@ npx create-droid my-app
35
35
 
36
36
  Follow the interactive prompts:
37
37
  1. **Project Name**: Defaults to directory name.
38
- 2. **UI Framework**: Choose **Jetpack Compose** (Modern declarative UI) or **XML Views** (Classic).
38
+ 2. **Template Selection**:
39
+ * **Jetpack Compose (Mobile)**: Modern phone/tablet starter.
40
+ * **Compose with Navigation**: Includes Navigation, BottomBar, and multi-screen setup.
41
+ * **Compose for TV**: Optimized for Android TV with `tv-material`.
42
+ * **Compose Library**: Foundation for publishing reusable UI components.
43
+ * **XML Views (Legacy)**: For maintenance or classic development.
39
44
 
40
45
  ### After Scaffolding
41
46
 
@@ -54,9 +59,10 @@ npm run build
54
59
 
55
60
  The generated project is **clean** and follows modern best practices. It includes a `package.json` with convenience scripts:
56
61
 
57
- * `npm run dev`: Watches your code and auto-deploys changes (`./gradlew -t installDebug`).
58
- * `npm run build`: Generates a release APK (`./gradlew assembleRelease`).
59
- * `npm test`: Runs unit tests (`./gradlew test`).
62
+ * `npm run dev`: High-speed development loop. Watches code and auto-deploys via `--continuous` and `--configuration-cache`.
63
+ * `npm run build`: Generates a production release APK.
64
+ * `npm run clean:deep`: Purges all build artifacts and Gradle cache to reclaim disk space.
65
+ * `npm test`: Runs unit tests.
60
66
 
61
67
  ### 📱 ADB Scripts (Wireless Debugging)
62
68
 
package/dist/index.js CHANGED
@@ -20,10 +20,13 @@ export async function run(args) {
20
20
  {
21
21
  type: 'select',
22
22
  name: 'uiType',
23
- message: 'Select UI Framework:',
23
+ message: 'Select Template:',
24
24
  choices: [
25
- { title: 'Jetpack Compose (Recommended)', value: 'compose' },
26
- { title: 'XML Views (Legacy)', value: 'views' }
25
+ { title: 'Jetpack Compose (Mobile)', value: 'compose', description: 'Recommended for phone/tablet apps' },
26
+ { title: 'Compose with Navigation', value: 'mobile-compose-navigation', description: 'Includes Navigation, BottomBar, Screens' },
27
+ { title: 'Compose for TV', value: 'tv-compose', description: 'Optimized for Android TV (Leanback)' },
28
+ { title: 'Compose Library', value: 'compose-library', description: 'Scaffold for publishing UI libraries' },
29
+ { title: 'XML Views (Legacy)', value: 'views', description: 'Classic View-based Android development' }
27
30
  ],
28
31
  initial: 0
29
32
  }
@@ -8,19 +8,24 @@ const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = path.dirname(__filename);
9
9
  export async function generateProject(options) {
10
10
  const { projectPath, projectName, uiType, sdkPath } = options;
11
+ const isLibrary = uiType === 'compose-library';
12
+ const moduleName = isLibrary ? 'library' : 'app';
11
13
  // 1. Ensure target directory
12
14
  await fs.ensureDir(projectPath);
13
15
  // 2. Resolve template paths
14
16
  const templateRoot = path.resolve(__dirname, '../../templates');
15
17
  const baseTemplate = path.join(templateRoot, 'base');
16
- const uiTemplateName = uiType === 'compose' ? 'ui-compose' : 'ui-views';
17
- const uiTemplate = path.join(templateRoot, uiTemplateName);
18
+ const uiTemplate = path.join(templateRoot, uiType);
18
19
  if (!fs.existsSync(baseTemplate)) {
19
20
  throw new Error(`Template not found at ${baseTemplate}`);
20
21
  }
21
22
  // 3. Copy Base
22
23
  logger.info(`Copying base template from ${baseTemplate}...`);
23
24
  await fs.copy(baseTemplate, projectPath);
25
+ if (isLibrary) {
26
+ // Remove default app module if it's a library
27
+ await fs.remove(path.join(projectPath, 'app'));
28
+ }
24
29
  // Rename _gitignore to .gitignore
25
30
  const gitignorePath = path.join(projectPath, '_gitignore');
26
31
  if (fs.existsSync(gitignorePath)) {
@@ -37,49 +42,53 @@ export async function generateProject(options) {
37
42
  // Replace in settings.gradle.kts
38
43
  await patchFile(path.join(projectPath, 'settings.gradle.kts'), {
39
44
  '{{PROJECT_NAME}}': projectName,
45
+ 'include(":app")': `include(":${moduleName}")`
40
46
  });
41
- // Replace in app/build.gradle.kts
42
- await patchFile(path.join(projectPath, 'app/build.gradle.kts'), {
47
+ // Replace in module build.gradle.kts
48
+ const moduleBuildFile = path.join(projectPath, moduleName, 'build.gradle.kts');
49
+ await patchFile(moduleBuildFile, {
43
50
  '{{APPLICATION_ID}}': packageName,
44
51
  '{{COMPILE_SDK}}': CONSTANTS.COMPILE_SDK.toString(),
45
52
  '{{MIN_SDK}}': CONSTANTS.MIN_SDK.toString(),
46
53
  '{{TARGET_SDK}}': CONSTANTS.TARGET_SDK.toString(),
47
54
  });
48
- // Replace in app/src/main/res/values/strings.xml
49
- await patchFile(path.join(projectPath, 'app/src/main/res/values/strings.xml'), {
50
- '{{PROJECT_NAME}}': projectName,
51
- });
55
+ // Replace in strings.xml
56
+ const stringsPath = path.join(projectPath, moduleName, 'src/main/res/values/strings.xml');
57
+ if (fs.existsSync(stringsPath)) {
58
+ await patchFile(stringsPath, {
59
+ '{{PROJECT_NAME}}': projectName,
60
+ });
61
+ }
52
62
  // Handle Source Code Relocation
53
- const srcBase = path.join(projectPath, 'app/src/main/java');
63
+ const srcBase = path.join(projectPath, moduleName, 'src/main/java');
54
64
  const oldPackagePath = path.join(srcBase, 'com/example/template');
55
65
  const newPackagePath = path.join(srcBase, ...packageName.split('.'));
56
66
  if (fs.existsSync(oldPackagePath)) {
57
67
  await fs.move(oldPackagePath, newPackagePath, { overwrite: true });
58
68
  // Recursive file patching for package statement
59
69
  await patchSourceFiles(newPackagePath, packageName);
60
- // Clean up empty dirs (com/example/template -> com/example -> com)
61
- // Only if empty.
70
+ // Clean up empty dirs
62
71
  await cleanEmptyDirs(srcBase);
63
72
  }
64
73
  // Create local.properties with SDK location
65
74
  const localProperties = `sdk.dir=${sdkPath}`;
66
75
  await fs.writeFile(path.join(projectPath, 'local.properties'), localProperties);
67
- // 6. Setup NPM Scripts (The "Vite-like" experience)
76
+ // 6. Setup NPM Scripts
68
77
  logger.info('Adding npm convenience scripts...');
69
78
  const packageJson = {
70
79
  name: projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
71
80
  version: "0.1.0",
72
81
  private: true,
73
82
  scripts: {
74
- "dev": "./gradlew installDebug --continuous --configuration-cache --parallel --offline",
83
+ "dev": `./gradlew installDebug --continuous --configuration-cache --parallel --offline`,
75
84
  "start": "npm run dev",
76
- "build": "./gradlew assembleRelease",
77
- "build:debug": "./gradlew assembleDebug",
78
- "test": "./gradlew test",
79
- "lint": "./gradlew lint",
80
- "clean": "./gradlew clean",
81
- "clean:deep": "rm -rf .gradle app/build build",
82
- "help": "./gradlew --help",
85
+ "build": `./gradlew assembleRelease`,
86
+ "build:debug": `./gradlew assembleDebug`,
87
+ "test": `./gradlew test`,
88
+ "lint": `./gradlew lint`,
89
+ "clean": `./gradlew clean`,
90
+ "clean:deep": "rm -rf .gradle app/build build library/build",
91
+ "help": `./gradlew --help`,
83
92
  "adb": "node scripts/adb.js",
84
93
  "adb:devices": "npm run adb devices",
85
94
  "adb:connect": "npm run adb connect",
@@ -89,12 +98,6 @@ export async function generateProject(options) {
89
98
  }
90
99
  };
91
100
  await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
92
- // Ensure scripts dir exists (since we copy base first, but adb.js is new)
93
- // Wait, templates/base/scripts/adb.js is ALREADY in the base template.
94
- // We just need to ensure the `scripts` folder is copied correctly.
95
- // Since we copy `templates/base`, it should be fine.
96
- // Make scripts executable if needed (node doesn't need +x but good practice)
97
- // await fs.chmod(path.join(projectPath, 'scripts/adb.js'), 0o755);
98
101
  // 7. Initialize Git
99
102
  try {
100
103
  logger.info('Initializing git repository...');
@@ -104,7 +107,7 @@ export async function generateProject(options) {
104
107
  logger.success('Git repository initialized.');
105
108
  }
106
109
  catch (e) {
107
- logger.warn('Failed to initialize git repository. You may need to run "git init" manually.');
110
+ logger.warn('Failed to initialize git repository.');
108
111
  }
109
112
  }
110
113
  async function patchFile(filePath, replacements) {
@@ -132,11 +135,6 @@ async function patchSourceFiles(dir, packageName) {
132
135
  }
133
136
  }
134
137
  async function cleanEmptyDirs(dir) {
135
- // Basic cleanup: remove com/example/template if empty
136
- // We moved content from com/example/template to new path.
137
- // So we check if com/example/template is empty, then com/example, then com.
138
- // This is tricky because we don't know the exact depth of the old structure easily without hardcoding.
139
- // Hardcoded for "com/example/template":
140
138
  const oldPath = path.join(dir, 'com/example/template');
141
139
  try {
142
140
  if (fs.existsSync(oldPath) && (await fs.readdir(oldPath)).length === 0) {
@@ -151,7 +149,5 @@ async function cleanEmptyDirs(dir) {
151
149
  }
152
150
  }
153
151
  }
154
- catch (e) {
155
- // Ignore errors during cleanup
156
- }
152
+ catch (e) { }
157
153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-droid",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "description": "The fastest way to start an Android project. No Studio required.",
5
5
  "author": "YELrhilassi",
6
6
  "license": "MIT",
@@ -0,0 +1,46 @@
1
+ plugins {
2
+ alias(libs.plugins.android.library)
3
+ alias(libs.plugins.jetbrains.kotlin.android)
4
+ }
5
+
6
+ android {
7
+ namespace = "{{APPLICATION_ID}}"
8
+ compileSdk = {{COMPILE_SDK}}
9
+
10
+ defaultConfig {
11
+ minSdk = {{MIN_SDK}}
12
+
13
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14
+ consumerProguardFiles("consumer-rules.pro")
15
+ }
16
+
17
+ buildTypes {
18
+ release {
19
+ isMinifyEnabled = false
20
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
21
+ }
22
+ }
23
+ compileOptions {
24
+ sourceCompatibility = JavaVersion.VERSION_1_8
25
+ targetCompatibility = JavaVersion.VERSION_1_8
26
+ }
27
+ kotlinOptions {
28
+ jvmTarget = "1.8"
29
+ }
30
+ buildFeatures {
31
+ compose = true
32
+ }
33
+ composeOptions {
34
+ kotlinCompilerExtensionVersion = "1.5.11"
35
+ }
36
+ }
37
+
38
+ dependencies {
39
+ implementation(libs.androidx.core.ktx)
40
+ implementation(platform(libs.androidx.compose.bom))
41
+ implementation(libs.androidx.ui)
42
+ implementation(libs.androidx.material3)
43
+ testImplementation(libs.junit)
44
+ androidTestImplementation(libs.androidx.junit)
45
+ androidTestImplementation(libs.androidx.espresso.core)
46
+ }
@@ -0,0 +1,16 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import androidx.compose.material3.Text
4
+ import androidx.compose.runtime.Composable
5
+ import androidx.compose.ui.Modifier
6
+
7
+ /**
8
+ * An example component from your library.
9
+ */
10
+ @Composable
11
+ fun LibraryGreeting(name: String, modifier: Modifier = Modifier) {
12
+ Text(
13
+ text = "Hello $name from the Library!",
14
+ modifier = modifier
15
+ )
16
+ }
@@ -0,0 +1,56 @@
1
+ plugins {
2
+ alias(libs.plugins.android.application)
3
+ alias(libs.plugins.jetbrains.kotlin.android)
4
+ }
5
+
6
+ android {
7
+ namespace = "{{APPLICATION_ID}}"
8
+ compileSdk = {{COMPILE_SDK}}
9
+
10
+ defaultConfig {
11
+ applicationId = "{{APPLICATION_ID}}"
12
+ minSdk = {{MIN_SDK}}
13
+ targetSdk = {{TARGET_SDK}}
14
+ versionCode = 1
15
+ versionName = "1.0"
16
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
17
+ }
18
+
19
+ buildTypes {
20
+ release {
21
+ isMinifyEnabled = false
22
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
23
+ }
24
+ }
25
+ compileOptions {
26
+ sourceCompatibility = JavaVersion.VERSION_1_8
27
+ targetCompatibility = JavaVersion.VERSION_1_8
28
+ }
29
+ kotlinOptions {
30
+ jvmTarget = "1.8"
31
+ }
32
+ buildFeatures {
33
+ compose = true
34
+ }
35
+ composeOptions {
36
+ kotlinCompilerExtensionVersion = "1.5.11"
37
+ }
38
+ }
39
+
40
+ dependencies {
41
+ implementation(libs.androidx.core.ktx)
42
+ implementation(libs.androidx.lifecycle.runtime.ktx)
43
+ implementation(libs.androidx.activity.compose)
44
+ implementation(platform(libs.androidx.compose.bom))
45
+ implementation(libs.androidx.ui)
46
+ implementation(libs.androidx.ui.graphics)
47
+ implementation(libs.androidx.ui.tooling.preview)
48
+ implementation(libs.androidx.material3)
49
+
50
+ // Navigation
51
+ implementation("androidx.navigation:navigation-compose:2.7.7")
52
+
53
+ testImplementation(libs.junit)
54
+ androidTestImplementation(libs.androidx.junit)
55
+ androidTestImplementation(libs.androidx.espresso.core)
56
+ }
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <application
4
+ android:allowBackup="true"
5
+ android:icon="@mipmap/ic_launcher"
6
+ android:label="@string/app_name"
7
+ android:roundIcon="@mipmap/ic_launcher_round"
8
+ android:supportsRtl="true"
9
+ android:theme="@style/Theme.MyApplication">
10
+ <activity
11
+ android:name=".MainActivity"
12
+ android:exported="true"
13
+ android:label="@string/app_name"
14
+ android:theme="@style/Theme.MyApplication">
15
+ <intent-filter>
16
+ <action android:name="android.intent.action.MAIN" />
17
+ <category android:name="android.intent.category.LAUNCHER" />
18
+ </intent-filter>
19
+ </activity>
20
+ </application>
21
+ </manifest>
@@ -0,0 +1,84 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import android.os.Bundle
4
+ import androidx.activity.ComponentActivity
5
+ import androidx.activity.compose.setContent
6
+ import androidx.compose.foundation.layout.padding
7
+ import androidx.compose.material.icons.Icons
8
+ import androidx.compose.material.icons.filled.Home
9
+ import androidx.compose.material.icons.filled.Settings
10
+ import androidx.compose.material3.*
11
+ import androidx.compose.runtime.*
12
+ import androidx.compose.ui.Modifier
13
+ import androidx.navigation.NavDestination.Companion.hierarchy
14
+ import androidx.navigation.NavGraph.Companion.findStartDestination
15
+ import androidx.navigation.compose.NavHost
16
+ import androidx.navigation.compose.composable
17
+ import androidx.navigation.compose.currentBackStackEntryAsState
18
+ import androidx.navigation.compose.rememberNavController
19
+
20
+ class MainActivity : ComponentActivity() {
21
+ override fun onCreate(savedInstanceState: Bundle?) {
22
+ super.onCreate(savedInstanceState)
23
+ setContent {
24
+ MainScreen()
25
+ }
26
+ }
27
+ }
28
+
29
+ sealed class Screen(val route: String, val label: String, val icon: @Composable () -> Unit) {
30
+ object Home : Screen("home", "Home", { Icon(Icons.Filled.Home, contentDescription = null) })
31
+ object Settings : Screen("settings", "Settings", { Icon(Icons.Filled.Settings, contentDescription = null) })
32
+ }
33
+
34
+ @Composable
35
+ fun MainScreen() {
36
+ val navController = rememberNavController()
37
+ val items = listOf(Screen.Home, Screen.Settings)
38
+
39
+ MaterialTheme {
40
+ Scaffold(
41
+ bottomBar = {
42
+ NavigationBar {
43
+ val navBackStackEntry by navController.currentBackStackEntryAsState()
44
+ val currentDestination = navBackStackEntry?.destination
45
+ items.forEach { screen ->
46
+ NavigationBarItem(
47
+ icon = screen.icon,
48
+ label = { Text(screen.label) },
49
+ selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
50
+ onClick = {
51
+ navController.navigate(screen.route) {
52
+ popUpTo(navController.graph.findStartDestination().id) {
53
+ saveState = true
54
+ }
55
+ launchSingleTop = true
56
+ restoreState = true
57
+ }
58
+ }
59
+ )
60
+ }
61
+ }
62
+ }
63
+ ) { innerPadding ->
64
+ NavHost(navController, startDestination = Screen.Home.route, Modifier.padding(innerPadding)) {
65
+ composable(Screen.Home.route) { HomeScreen() }
66
+ composable(Screen.Settings.route) { SettingsScreen() }
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ @Composable
73
+ fun HomeScreen() {
74
+ Surface {
75
+ Text("Welcome Home!")
76
+ }
77
+ }
78
+
79
+ @Composable
80
+ fun SettingsScreen() {
81
+ Surface {
82
+ Text("Settings Screen")
83
+ }
84
+ }
@@ -0,0 +1,53 @@
1
+ plugins {
2
+ alias(libs.plugins.android.application)
3
+ alias(libs.plugins.jetbrains.kotlin.android)
4
+ }
5
+
6
+ android {
7
+ namespace = "{{APPLICATION_ID}}"
8
+ compileSdk = {{COMPILE_SDK}}
9
+
10
+ defaultConfig {
11
+ applicationId = "{{APPLICATION_ID}}"
12
+ minSdk = {{MIN_SDK}}
13
+ targetSdk = {{TARGET_SDK}}
14
+ versionCode = 1
15
+ versionName = "1.0"
16
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
17
+ }
18
+
19
+ buildTypes {
20
+ release {
21
+ isMinifyEnabled = false
22
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
23
+ }
24
+ }
25
+ compileOptions {
26
+ sourceCompatibility = JavaVersion.VERSION_1_8
27
+ targetCompatibility = JavaVersion.VERSION_1_8
28
+ }
29
+ kotlinOptions {
30
+ jvmTarget = "1.8"
31
+ }
32
+ buildFeatures {
33
+ compose = true
34
+ }
35
+ composeOptions {
36
+ kotlinCompilerExtensionVersion = "1.5.11"
37
+ }
38
+ }
39
+
40
+ dependencies {
41
+ implementation(libs.androidx.core.ktx)
42
+ implementation(libs.androidx.lifecycle.runtime.ktx)
43
+ implementation(libs.androidx.activity.compose)
44
+
45
+ // TV Compose
46
+ implementation("androidx.tv:tv-foundation:1.0.0-alpha10")
47
+ implementation("androidx.tv:tv-material:1.0.0-alpha10")
48
+
49
+ implementation(platform(libs.androidx.compose.bom))
50
+ implementation(libs.androidx.ui)
51
+ implementation(libs.androidx.ui.graphics)
52
+ implementation(libs.androidx.ui.tooling.preview)
53
+ }
@@ -0,0 +1,26 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+
4
+ <uses-feature android:name="android.software.leanback" android:required="false" />
5
+ <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
6
+
7
+ <application
8
+ android:allowBackup="true"
9
+ android:icon="@mipmap/ic_launcher"
10
+ android:label="@string/app_name"
11
+ android:banner="@mipmap/ic_launcher"
12
+ android:roundIcon="@mipmap/ic_launcher_round"
13
+ android:supportsRtl="true"
14
+ android:theme="@style/Theme.MyApplication">
15
+ <activity
16
+ android:name=".MainActivity"
17
+ android:exported="true"
18
+ android:label="@string/app_name"
19
+ android:theme="@style/Theme.MyApplication">
20
+ <intent-filter>
21
+ <action android:name="android.intent.action.MAIN" />
22
+ <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
23
+ </intent-filter>
24
+ </activity>
25
+ </application>
26
+ </manifest>
@@ -0,0 +1,64 @@
1
+ package {{PACKAGE_NAME}}
2
+
3
+ import android.os.Bundle
4
+ import androidx.activity.ComponentActivity
5
+ import androidx.activity.compose.setContent
6
+ import androidx.compose.foundation.layout.*
7
+ import androidx.compose.runtime.*
8
+ import androidx.compose.ui.Modifier
9
+ import androidx.compose.ui.graphics.Color
10
+ import androidx.compose.ui.unit.dp
11
+ import androidx.tv.material3.*
12
+
13
+ class MainActivity : ComponentActivity() {
14
+ @OptIn(ExperimentalTvMaterial3Api::class)
15
+ override fun onCreate(savedInstanceState: Bundle?) {
16
+ super.onCreate(savedInstanceState)
17
+ setContent {
18
+ MaterialTheme {
19
+ Surface(
20
+ modifier = Modifier.fillMaxSize(),
21
+ shape = ClickableSurfaceDefaults.shape(),
22
+ color = ClickableSurfaceDefaults.color()
23
+ ) {
24
+ TvContent()
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+
31
+ @OptIn(ExperimentalTvMaterial3Api::class)
32
+ @Composable
33
+ fun TvContent() {
34
+ Column(
35
+ modifier = Modifier.padding(48.dp),
36
+ verticalArrangement = Arrangement.spacedBy(16.dp)
37
+ ) {
38
+ Text(
39
+ text = "Welcome to Android TV",
40
+ style = MaterialTheme.typography.displayMedium
41
+ )
42
+
43
+ Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
44
+ repeat(5) { index ->
45
+ StandardCard(index)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ @OptIn(ExperimentalTvMaterial3Api::class)
52
+ @Composable
53
+ fun StandardCard(index: Int) {
54
+ Surface(
55
+ onClick = { /* Handle click */ },
56
+ modifier = Modifier.size(150.dp, 100.dp),
57
+ shape = ClickableSurfaceDefaults.shape(shape = MaterialTheme.shapes.medium),
58
+ color = ClickableSurfaceDefaults.color(focusedColor = Color.White)
59
+ ) {
60
+ Box(contentAlignment = androidx.compose.ui.Alignment.Center) {
61
+ Text("Item $index")
62
+ }
63
+ }
64
+ }