andrud 1.0.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.
@@ -0,0 +1,1350 @@
1
+ /**
2
+ * Project generator - creates Android project structure and files
3
+ */
4
+
5
+ import { exists, writeFile, createDirectory, isDirectory } from '../utils/filesystem.js';
6
+ import type { TemplateContext, GenerationResult, GeneratedFile } from './types.js';
7
+ import { buildTemplateContext, buildDefaultProjectContext } from './context.js';
8
+ import { GRADLE_VERSIONS } from './config.js';
9
+ import pc from 'picocolors';
10
+ import { join } from 'path';
11
+
12
+ export interface GeneratorOptions {
13
+ overwrite?: boolean;
14
+ skipInstall?: boolean;
15
+ dryRun?: boolean;
16
+ verbose?: boolean;
17
+ }
18
+
19
+ /**
20
+ * Validate project directory can be used
21
+ */
22
+ export async function validateProjectDirectory(
23
+ projectPath: string,
24
+ options: { overwrite: boolean } = { overwrite: false }
25
+ ): Promise<{ valid: boolean; error?: string; existingFiles?: string[] }> {
26
+ const pathExists = await exists(projectPath);
27
+
28
+ if (pathExists) {
29
+ const isDir = await isDirectory(projectPath);
30
+ if (!isDir) {
31
+ return { valid: false, error: 'Project path exists but is not a directory' };
32
+ }
33
+
34
+ const dirContent = await getDirectoryContents(projectPath);
35
+ if (dirContent.length > 0 && !options.overwrite) {
36
+ return {
37
+ valid: false,
38
+ error: 'Directory is not empty. Use --force to overwrite existing files.',
39
+ existingFiles: dirContent
40
+ };
41
+ }
42
+ }
43
+
44
+ return { valid: true };
45
+ }
46
+
47
+ /**
48
+ * Validate context has all required fields
49
+ */
50
+ export function validateContext(context: Partial<TemplateContext>): { valid: boolean; errors: string[] } {
51
+ const errors: string[] = [];
52
+
53
+ if (!context.appName) errors.push('appName is required');
54
+ if (!context.packageName) errors.push('packageName is required');
55
+ if (!context.projectDirectory) errors.push('projectDirectory is required');
56
+ if (!context.template) errors.push('template is required');
57
+ if (!context.language) errors.push('language is required');
58
+ if (!context.uiFramework) errors.push('uiFramework is required');
59
+
60
+ return { valid: errors.length === 0, errors };
61
+ }
62
+
63
+ /**
64
+ * Prepare project directory structure
65
+ */
66
+ export async function prepareProjectStructure(
67
+ projectPath: string,
68
+ context: TemplateContext
69
+ ): Promise<{ success: boolean; createdPaths: string[]; errors: string[] }> {
70
+ const createdPaths: string[] = [];
71
+ const errors: string[] = [];
72
+
73
+ const directories = [
74
+ '',
75
+ 'gradle/wrapper',
76
+ 'app/src/main/java',
77
+ 'app/src/main/res/layout',
78
+ 'app/src/main/res/values',
79
+ 'app/src/main/res/values-night',
80
+ 'app/src/main/res/drawable',
81
+ 'app/src/main/res/xml',
82
+ 'app/src/main/res/mipmap-anydpi-v26',
83
+ 'app/src/main/res/mipmap-hdpi',
84
+ 'app/src/main/res/mipmap-mdpi',
85
+ 'app/src/main/res/mipmap-xhdpi',
86
+ 'app/src/main/res/mipmap-xxhdpi',
87
+ 'app/src/main/res/mipmap-xxxhdpi',
88
+ 'app/src/test/java',
89
+ 'app/src/androidTest/java',
90
+ context.language === 'kotlin' ? 'app/src/main/kotlin' : '',
91
+ context.template === 'native-cpp' ? 'app/src/main/cpp' : ''
92
+ ].filter(d => d !== '');
93
+
94
+ const packagePath = context.packageName.replace(/\./g, '/');
95
+ directories.push(`app/src/main/java/${packagePath}`);
96
+
97
+ if (context.language === 'kotlin') {
98
+ directories.push(`app/src/main/kotlin/${packagePath}`);
99
+ }
100
+
101
+ // Add UI theme directories for Jetpack Compose projects
102
+ if (context.uiFramework === 'compose' && context.language === 'kotlin') {
103
+ directories.push(`app/src/main/kotlin/${packagePath}/ui`);
104
+ directories.push(`app/src/main/kotlin/${packagePath}/ui/theme`);
105
+ }
106
+
107
+ // Add native cpp directories
108
+ if (context.template === 'native-cpp') {
109
+ directories.push(`app/src/main/cpp`);
110
+ directories.push(`app/src/main/jni`);
111
+ }
112
+
113
+ for (const dir of directories) {
114
+ const fullPath = join(projectPath, dir);
115
+ try {
116
+ await createDirectory(fullPath);
117
+ createdPaths.push(dir);
118
+ } catch (error) {
119
+ errors.push(`Failed to create directory ${dir}: ${(error as Error).message}`);
120
+ }
121
+ }
122
+
123
+ return { success: errors.length === 0, createdPaths, errors };
124
+ }
125
+
126
+ /**
127
+ * Generate all project files
128
+ */
129
+ export async function generateProject(
130
+ context: TemplateContext,
131
+ options: GeneratorOptions = {}
132
+ ): Promise<GenerationResult> {
133
+ const startTime = Date.now();
134
+ const generatedFiles: string[] = [];
135
+ const skippedFiles: string[] = [];
136
+ const errors: Array<{ file?: string; message: string; code?: string }> = [];
137
+ const warnings: string[] = [];
138
+ const projectPath = context.projectDirectory;
139
+
140
+ const contextValidation = validateContext(context);
141
+ if (!contextValidation.valid) {
142
+ return {
143
+ success: false,
144
+ projectPath,
145
+ generatedFiles: [],
146
+ skippedFiles: [],
147
+ errors: contextValidation.errors.map(msg => ({ message: msg })),
148
+ warnings,
149
+ duration: Date.now() - startTime
150
+ };
151
+ }
152
+
153
+ const dirValidation = await validateProjectDirectory(projectPath, { overwrite: options.overwrite ?? false });
154
+ if (!dirValidation.valid) {
155
+ return {
156
+ success: false,
157
+ projectPath,
158
+ generatedFiles: [],
159
+ skippedFiles: [],
160
+ errors: [{ message: dirValidation.error!, code: 'DIR_VALIDATION_ERROR' }],
161
+ warnings,
162
+ duration: Date.now() - startTime
163
+ };
164
+ }
165
+
166
+ const prepResult = await prepareProjectStructure(projectPath, context);
167
+ if (!prepResult.success) {
168
+ errors.push(...prepResult.errors.map(msg => ({ message: msg, code: 'DIR_CREATE_ERROR' })));
169
+ }
170
+ generatedFiles.push(...prepResult.createdPaths);
171
+
172
+ const filesToGenerate: GeneratedFile[] = [
173
+ generateSettingsGradle(context),
174
+ generateRootBuildGradle(context),
175
+ generateGradleProperties(context),
176
+ generateGitIgnore(context),
177
+ generateReadme(context),
178
+ generateGradleWrapperProperties(context),
179
+ generateGradlewBat(context),
180
+ generateGradlewUnix(context),
181
+ generateAppBuildGradle(context),
182
+ generateAppProguardRules(context),
183
+ generateAppManifest(context),
184
+ generateApplicationClass(context),
185
+ generateMainActivity(context),
186
+ generateStrings(context),
187
+ generateColors(context),
188
+ generateThemes(context),
189
+ generateAppIcon(context),
190
+ generateActivityLayout(context),
191
+ ...generateSourceSetFiles(context)
192
+ ].filter((f): f is GeneratedFile => f !== null && typeof f === 'object' && 'path' in f);
193
+
194
+ for (const file of filesToGenerate) {
195
+ try {
196
+ const filePath = join(projectPath, file.path);
197
+ const fileExists = await exists(filePath);
198
+
199
+ if (fileExists && !options.overwrite && file.overwrite !== true) {
200
+ skippedFiles.push(file.path);
201
+ if (options.verbose) {
202
+ console.log(pc.yellow(`Skipped: ${file.path} (already exists)`));
203
+ }
204
+ continue;
205
+ }
206
+
207
+ await writeFile(filePath, file.content);
208
+ generatedFiles.push(file.path);
209
+
210
+ if (options.verbose) {
211
+ console.log(pc.green(`Generated: ${file.path}`));
212
+ }
213
+ } catch (error) {
214
+ errors.push({
215
+ file: file.path,
216
+ message: (error as Error).message,
217
+ code: 'WRITE_ERROR'
218
+ });
219
+ }
220
+ }
221
+
222
+ const duration = Date.now() - startTime;
223
+ const success = errors.length === 0;
224
+
225
+ return {
226
+ success,
227
+ projectPath,
228
+ generatedFiles,
229
+ skippedFiles,
230
+ errors,
231
+ warnings,
232
+ duration
233
+ };
234
+ }
235
+
236
+ function generateSettingsGradle(ctx: TemplateContext): GeneratedFile {
237
+ return {
238
+ path: 'settings.gradle.kts',
239
+ content: `pluginManagement {
240
+ repositories {
241
+ google()
242
+ mavenCentral()
243
+ gradlePluginPortal()
244
+ }
245
+ }
246
+
247
+ dependencyResolutionManagement {
248
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
249
+ repositories {
250
+ google()
251
+ mavenCentral()
252
+ }
253
+ }
254
+
255
+ rootProject.name = "${ctx.appName}"
256
+ include(":app")
257
+ `
258
+ };
259
+ }
260
+
261
+ function generateRootBuildGradle(ctx: TemplateContext): GeneratedFile {
262
+ const kotlinPlugin = ctx.language === 'kotlin' ? `
263
+ id("org.jetbrains.kotlin.android") version "${ctx.gradle.kotlinVersion || GRADLE_VERSIONS.KOTLIN}" apply false` : '';
264
+
265
+ const composePlugin = ctx.uiFramework === 'compose' ? `
266
+ id("org.jetbrains.kotlin.plugin.compose") version "${ctx.gradle.kotlinVersion || GRADLE_VERSIONS.KOTLIN}" apply false` : '';
267
+
268
+ const kaptPlugin = ctx.language === 'kotlin' ? `
269
+ id("org.jetbrains.kotlin.kapt") version "${ctx.gradle.kotlinVersion || GRADLE_VERSIONS.KOTLIN}" apply false` : '';
270
+
271
+ return {
272
+ path: 'build.gradle.kts',
273
+ content: `plugins {
274
+ id("com.android.application") version "${ctx.gradle.agpVersion}" apply false${kotlinPlugin}${composePlugin}${kaptPlugin}
275
+ }
276
+ `
277
+ };
278
+ }
279
+
280
+ function generateGradleProperties(_ctx: TemplateContext): GeneratedFile {
281
+ return {
282
+ path: 'gradle.properties',
283
+ content: `# Project-wide Gradle settings
284
+ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
285
+ org.gradle.parallel=true
286
+ org.gradle.caching=true
287
+ org.gradle.configureondemand=true
288
+
289
+ # Android settings
290
+ android.useAndroidX=true
291
+ android.nonTransitiveRClass=true
292
+ android.suppressUnsupportedCompileSdk=36
293
+
294
+ # Kotlin settings
295
+ kotlin.code.style=official
296
+ `
297
+ };
298
+ }
299
+
300
+ function generateGitIgnore(_ctx: TemplateContext): GeneratedFile {
301
+ return {
302
+ path: '.gitignore',
303
+ content: `# Built application files
304
+ *.apk
305
+ *.ap_
306
+ *.aab
307
+ *.dex
308
+ *.class
309
+ bin/
310
+ gen/
311
+ out/
312
+ .gradle/
313
+ build/
314
+ local.properties
315
+ proguard/
316
+ *.log
317
+ .navigation/
318
+ captures/
319
+ *.iml
320
+ .idea/
321
+ *.jks
322
+ *.keystore
323
+ .externalNativeBuild
324
+ .cxx/
325
+ google-services.json
326
+ vcs.xml
327
+ `
328
+ };
329
+ }
330
+
331
+ function generateReadme(ctx: TemplateContext): GeneratedFile {
332
+ const lang = ctx.language === 'kotlin' ? 'Kotlin' : 'Java';
333
+ const ui = ctx.uiFramework === 'compose' ? 'Jetpack Compose' : 'XML Layouts';
334
+
335
+ return {
336
+ path: 'README.md',
337
+ content: `# ${ctx.appName}
338
+
339
+ An Android application built with ${lang} using ${ui}.
340
+
341
+ ## Requirements
342
+ - Android Studio Hedgehog (2023.1.1) or later
343
+ - JDK 17 or later
344
+ - Android SDK API ${ctx.android.targetSdk}
345
+ - Gradle ${ctx.gradle.gradleVersion}
346
+
347
+ ## Getting Started
348
+ 1. Open the project in Android Studio
349
+ 2. Sync Gradle files
350
+ 3. Build and run
351
+
352
+ ## License
353
+ MIT License
354
+ `
355
+ };
356
+ }
357
+
358
+ function generateGradleWrapperProperties(ctx: TemplateContext): GeneratedFile {
359
+ return {
360
+ path: 'gradle/wrapper/gradle-wrapper.properties',
361
+ content: `distributionBase=GRADLE_USER_HOME
362
+ distributionPath=wrapper/dists
363
+ distributionUrl=https\\://services.gradle.org/distributions/gradle-${ctx.gradle.gradleVersion}-bin.zip
364
+ networkTimeout=10000
365
+ validateDistributionUrl=true
366
+ zipStoreBase=GRADLE_USER_HOME
367
+ zipStorePath=wrapper/dists
368
+ `
369
+ };
370
+ }
371
+
372
+ function generateGradlewBat(_ctx: TemplateContext): GeneratedFile {
373
+ return {
374
+ path: 'gradlew.bat',
375
+ content: `@echo off
376
+ setlocal enabledelayedexpansion
377
+ set DIRNAME=%~dp0
378
+ if "%DIRNAME%"=="" set DIRNAME=.
379
+ set APP_BASE_NAME=%~n0
380
+ set APP_HOME=%DIRNAME%
381
+ for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
382
+ set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
383
+ set JAVA_EXE=java.exe
384
+ %JAVA_EXE% -version >NUL 2>&1
385
+ if %ERRORLEVEL% equ 0 goto execute
386
+ echo ERROR: JAVA_HOME is not set
387
+ goto fail
388
+ :execute
389
+ set CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar
390
+ "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
391
+ :end
392
+ if %ERRORLEVEL% equ 0 goto mainEnd
393
+ :fail
394
+ exit /b 1
395
+ :mainEnd
396
+ endlocal
397
+ `
398
+ };
399
+ }
400
+
401
+ function generateGradlewUnix(_ctx: TemplateContext): GeneratedFile {
402
+ return {
403
+ path: 'gradlew',
404
+ content: `#!/bin/sh
405
+
406
+ APP_HOME=$(dirname "$(cd "$(dirname "$0")" && pwd)")
407
+ CLASSPATH="$APP_HOME/gradle/wrapper/gradle-wrapper.jar"
408
+ JAVA_HOME="${'$'}{JAVA_HOME:-}"
409
+ JAVACMD="${'$'}{JAVACMD:-java}"
410
+
411
+ if [ ! -x "$JAVACMD" ] && [ -n "$JAVA_HOME" ]; then
412
+ JAVACMD="$JAVA_HOME/bin/java"
413
+ fi
414
+
415
+ if [ ! -x "$JAVACMD" ]; then
416
+ echo "ERROR: JAVA_HOME is not set and no 'java' command could be found"
417
+ exit 1
418
+ fi
419
+
420
+ DEFAULT_JVM_OPTS="-Xmx64m -Xms64m"
421
+
422
+ exec "$JAVACMD" $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "-Dorg.gradle.appname=$(basename "$0")" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
423
+ `
424
+ };
425
+ }
426
+
427
+ function generateAppBuildGradle(ctx: TemplateContext): GeneratedFile {
428
+ const isCompose = ctx.uiFramework === 'compose';
429
+ const isKotlin = ctx.language === 'kotlin';
430
+
431
+ let plugins = 'plugins {\n id("com.android.application")\n';
432
+ if (isKotlin) {
433
+ plugins += ' id("org.jetbrains.kotlin.android")\n';
434
+ if (isCompose) {
435
+ plugins += ' id("org.jetbrains.kotlin.plugin.compose")\n';
436
+ }
437
+ }
438
+ plugins += '}\n';
439
+
440
+ let config = `android {
441
+ namespace = "${ctx.packageName}"
442
+ compileSdk = ${ctx.android.compileSdk}
443
+
444
+ defaultConfig {
445
+ applicationId = "${ctx.packageName}"
446
+ minSdk = ${ctx.android.minSdk}
447
+ targetSdk = ${ctx.android.targetSdk}
448
+ versionCode = 1
449
+ versionName = "1.0.0"
450
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
451
+ `;
452
+
453
+ if (isCompose) {
454
+ config += ` vectorDrawables {
455
+ useSupportLibrary = true
456
+ }
457
+ `;
458
+ }
459
+ config += ` }
460
+
461
+ buildTypes {
462
+ release {
463
+ isMinifyEnabled = true
464
+ proguardFiles(
465
+ getDefaultProguardFile("proguard-android-optimize.txt"),
466
+ "proguard-rules.pro"
467
+ )
468
+ }
469
+ }
470
+
471
+ compileOptions {
472
+ sourceCompatibility = JavaVersion.VERSION_17
473
+ targetCompatibility = JavaVersion.VERSION_17
474
+ }
475
+ `;
476
+
477
+ if (isKotlin) {
478
+ config += ` kotlinOptions {
479
+ jvmTarget = "17"
480
+ }
481
+ `;
482
+ }
483
+
484
+ if (isCompose) {
485
+ config += ` buildFeatures {
486
+ compose = true
487
+ }
488
+ `;
489
+ }
490
+
491
+ config += ` packaging {
492
+ resources {
493
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
494
+ }
495
+ }
496
+ }
497
+
498
+ dependencies {
499
+ implementation("androidx.core:core-ktx:1.13.1")
500
+ implementation("androidx.appcompat:appcompat:1.7.0")
501
+ implementation("com.google.android.material:material:1.12.0")
502
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
503
+ `;
504
+
505
+ if (isCompose) {
506
+ config += ` implementation(platform("androidx.compose:compose-bom:2025.01.00"))
507
+ implementation("androidx.compose.ui:ui")
508
+ implementation("androidx.compose.ui:ui-graphics")
509
+ implementation("androidx.compose.ui:ui-tooling-preview")
510
+ implementation("androidx.compose.material3:material3")
511
+ implementation("androidx.compose.foundation:foundation")
512
+ implementation("androidx.activity:activity-compose:1.9.3")
513
+ implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
514
+ implementation("androidx.navigation:navigation-compose:2.8.4")
515
+ debugImplementation("androidx.compose.ui:ui-tooling")
516
+ debugImplementation("androidx.compose.ui:ui-test-manifest")
517
+ `;
518
+ } else {
519
+ config += ` implementation("androidx.activity:activity-ktx:1.9.2")
520
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.5")
521
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.5")
522
+ `;
523
+ }
524
+
525
+ // Add native C++ configuration if needed
526
+ if (ctx.template === 'native-cpp') {
527
+ const ndkVersion = ctx.nativeCpp?.ndkVersion ?? '28.2.13676358';
528
+ const abiFilters = ctx.nativeCpp?.abiFilters?.join(', ') ?? '"armeabi-v7a", "arm64-v8a", "x86", "x86_64"';
529
+
530
+ config = config.replace(
531
+ 'kotlinOptions {',
532
+ `externalNativeBuild {
533
+ cmake {
534
+ cppFlags += "-std=c++17"
535
+ arguments += "-DANDROID_STL=c++_static"
536
+ }
537
+ }
538
+
539
+ ndk {
540
+ abiFilters += [${abiFilters.split(', ').map((a: string) => `"${a.replace(/"/g, '')}"`).join(', ')}]
541
+ }
542
+
543
+ kotlinOptions {`
544
+ );
545
+ }
546
+
547
+ config += ` testImplementation("junit:junit:4.13.2")
548
+ androidTestImplementation("androidx.test.ext:junit:1.2.1")
549
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
550
+ }
551
+ `;
552
+
553
+ return {
554
+ path: 'app/build.gradle.kts',
555
+ content: plugins + config
556
+ };
557
+ }
558
+
559
+ function generateAppProguardRules(_ctx: TemplateContext): GeneratedFile {
560
+ return {
561
+ path: 'app/proguard-rules.pro',
562
+ content: `# ProGuard rules for Android
563
+ -keepattributes SourceFile,LineNumberTable
564
+ -renamesourcefileattribute SourceFile
565
+ -keep class kotlin.** { *; }
566
+ -keep class kotlin.Metadata { *; }
567
+ -dontwarn kotlin.**
568
+ -keep class androidx.** { *; }
569
+ -keep interface androidx.** { *; }
570
+ `
571
+ };
572
+ }
573
+
574
+ function generateAppManifest(ctx: TemplateContext): GeneratedFile {
575
+ const activityClass = ctx.language === 'kotlin' ? 'MainActivity' : '.MainActivity';
576
+
577
+ return {
578
+ path: 'app/src/main/AndroidManifest.xml',
579
+ content: `<?xml version="1.0" encoding="utf-8"?>
580
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
581
+
582
+ <application
583
+ android:allowBackup="true"
584
+ android:icon="@mipmap/ic_launcher"
585
+ android:label="@string/app_name"
586
+ android:roundIcon="@mipmap/ic_launcher_round"
587
+ android:supportsRtl="true"
588
+ android:theme="@style/Theme.${ctx.appNamePascal}">
589
+
590
+ <activity
591
+ android:name="${activityClass}"
592
+ android:exported="true"
593
+ android:theme="@style/Theme.${ctx.appNamePascal}">
594
+ <intent-filter>
595
+ <action android:name="android.intent.action.MAIN" />
596
+ <category android:name="android.intent.category.LAUNCHER" />
597
+ </intent-filter>
598
+ </activity>
599
+
600
+ </application>
601
+
602
+ </manifest>
603
+ `
604
+ };
605
+ }
606
+
607
+ function generateApplicationClass(ctx: TemplateContext): GeneratedFile {
608
+ const packagePath = ctx.packagePath;
609
+ const className = `${ctx.appNamePascal}Application`;
610
+
611
+ if (ctx.language === 'kotlin') {
612
+ return {
613
+ path: `app/src/main/kotlin/${packagePath}/${className}.kt`,
614
+ content: `package ${ctx.packageName}
615
+
616
+ import android.app.Application
617
+
618
+ class ${className} : Application() {
619
+ override fun onCreate() {
620
+ super.onCreate()
621
+ }
622
+ }
623
+ `
624
+ };
625
+ } else {
626
+ return {
627
+ path: `app/src/main/java/${packagePath}/${className}.java`,
628
+ content: `package ${ctx.packageName};
629
+
630
+ import android.app.Application;
631
+
632
+ public class ${className} extends Application {
633
+ @Override
634
+ public void onCreate() {
635
+ super.onCreate();
636
+ }
637
+ }
638
+ `
639
+ };
640
+ }
641
+ }
642
+
643
+ function generateMainActivity(ctx: TemplateContext): GeneratedFile {
644
+ const packagePath = ctx.packagePath;
645
+
646
+ if (ctx.uiFramework === 'compose' && ctx.language === 'kotlin') {
647
+ return {
648
+ path: `app/src/main/kotlin/${packagePath}/MainActivity.kt`,
649
+ content: `package ${ctx.packageName}
650
+
651
+ import android.os.Bundle
652
+ import androidx.activity.ComponentActivity
653
+ import androidx.activity.compose.setContent
654
+ import androidx.activity.enableEdgeToEdge
655
+ import androidx.compose.foundation.background
656
+ import androidx.compose.foundation.layout.Arrangement
657
+ import androidx.compose.foundation.layout.Column
658
+ import androidx.compose.foundation.layout.fillMaxSize
659
+ import androidx.compose.foundation.layout.padding
660
+ import androidx.compose.foundation.layout.safeDrawingPadding
661
+ import androidx.compose.foundation.layout.size
662
+ import androidx.compose.foundation.shape.RoundedCornerShape
663
+ import androidx.compose.material3.Card
664
+ import androidx.compose.material3.CardDefaults
665
+ import androidx.compose.material3.MaterialTheme
666
+ import androidx.compose.material3.Surface
667
+ import androidx.compose.material3.Text
668
+ import androidx.compose.runtime.Composable
669
+ import androidx.compose.runtime.getValue
670
+ import androidx.compose.runtime.mutableIntStateOf
671
+ import androidx.compose.runtime.remember
672
+ import androidx.compose.runtime.setValue
673
+ import androidx.compose.ui.Alignment
674
+ import androidx.compose.ui.Modifier
675
+ import androidx.compose.ui.text.font.FontWeight
676
+ import androidx.compose.ui.unit.dp
677
+ import androidx.compose.ui.unit.sp
678
+ import ${ctx.packageName}.ui.theme.${ctx.appNamePascal}Theme
679
+
680
+ class MainActivity : ComponentActivity() {
681
+ override fun onCreate(savedInstanceState: Bundle?) {
682
+ super.onCreate(savedInstanceState)
683
+ enableEdgeToEdge()
684
+ setContent {
685
+ ${ctx.appNamePascal}Theme {
686
+ MainScreen()
687
+ }
688
+ }
689
+ }
690
+ }
691
+
692
+ @Composable
693
+ fun MainScreen() {
694
+ var count by remember { mutableIntStateOf(0) }
695
+
696
+ Surface(
697
+ modifier = Modifier.fillMaxSize(),
698
+ color = MaterialTheme.colorScheme.background
699
+ ) {
700
+ Column(
701
+ modifier = Modifier
702
+ .fillMaxSize()
703
+ .padding(24.dp)
704
+ .safeDrawingPadding(),
705
+ horizontalAlignment = Alignment.CenterHorizontally,
706
+ verticalArrangement = Arrangement.Center
707
+ ) {
708
+ Text(
709
+ text = "${ctx.appNamePascal}",
710
+ style = MaterialTheme.typography.headlineMedium,
711
+ color = MaterialTheme.colorScheme.onBackground,
712
+ fontWeight = FontWeight.SemiBold
713
+ )
714
+
715
+ Text(
716
+ text = "$count",
717
+ style = MaterialTheme.typography.displayLarge,
718
+ color = MaterialTheme.colorScheme.primary,
719
+ fontWeight = FontWeight.Bold,
720
+ modifier = Modifier.padding(vertical = 32.dp)
721
+ )
722
+
723
+ Card(
724
+ onClick = { count++ },
725
+ modifier = Modifier.size(120.dp),
726
+ shape = RoundedCornerShape(60.dp),
727
+ colors = CardDefaults.cardColors(
728
+ containerColor = MaterialTheme.colorScheme.primary
729
+ )
730
+ ) {
731
+ Column(
732
+ modifier = Modifier.fillMaxSize(),
733
+ horizontalAlignment = Alignment.CenterHorizontally,
734
+ verticalArrangement = Arrangement.Center
735
+ ) {
736
+ Text(
737
+ text = "+",
738
+ fontSize = 48.sp,
739
+ fontWeight = FontWeight.Bold,
740
+ color = MaterialTheme.colorScheme.onPrimary
741
+ )
742
+ }
743
+ }
744
+
745
+ Text(
746
+ text = "Tap button to count",
747
+ style = MaterialTheme.typography.bodyMedium,
748
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
749
+ modifier = Modifier.padding(top = 24.dp)
750
+ )
751
+ }
752
+ }
753
+ }
754
+ `
755
+ };
756
+ }
757
+
758
+ if (ctx.language === 'kotlin') {
759
+ return {
760
+ path: `app/src/main/kotlin/${packagePath}/MainActivity.kt`,
761
+ content: `package ${ctx.packageName}
762
+
763
+ import android.os.Bundle
764
+ import androidx.appcompat.app.AppCompatActivity
765
+ import androidx.core.view.ViewCompat
766
+ import androidx.core.view.WindowCompat
767
+ import androidx.core.view.WindowInsetsCompat
768
+ import androidx.core.view.updatePadding
769
+ import ${ctx.packageName}.R
770
+
771
+ class MainActivity : AppCompatActivity() {
772
+ override fun onCreate(savedInstanceState: Bundle?) {
773
+ super.onCreate(savedInstanceState)
774
+
775
+ // Enable edge-to-edge
776
+ WindowCompat.setDecorFitsSystemWindows(window, false)
777
+
778
+ setContentView(R.layout.activity_main)
779
+
780
+ // Apply window insets for edge-to-edge
781
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content)) { view, windowInsets ->
782
+ val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
783
+ view.updatePadding(
784
+ left = insets.left,
785
+ right = insets.right
786
+ )
787
+ windowInsets
788
+ }
789
+ }
790
+ }
791
+ `
792
+ };
793
+ }
794
+
795
+ return {
796
+ path: `app/src/main/java/${packagePath}/MainActivity.java`,
797
+ content: `package ${ctx.packageName};
798
+
799
+ import android.os.Bundle;
800
+ import androidx.appcompat.app.AppCompatActivity;
801
+ import androidx.core.view.ViewCompat;
802
+ import androidx.core.view.WindowCompat;
803
+ import androidx.core.view.WindowInsetsCompat;
804
+ import androidx.core.view.WindowInsetsControllerCompat;
805
+ import ${ctx.packageName}.R;
806
+
807
+ public class MainActivity extends AppCompatActivity {
808
+ @Override
809
+ protected void onCreate(Bundle savedInstanceState) {
810
+ super.onCreate(savedInstanceState);
811
+
812
+ // Enable edge-to-edge
813
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
814
+
815
+ setContentView(R.layout.activity_main);
816
+
817
+ // Apply window insets for edge-to-edge
818
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), (view, windowInsets) -> {
819
+ var insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
820
+ view.setPadding(insets.left, insets.top, insets.right, insets.bottom);
821
+ return WindowInsetsCompat.CONSUMED;
822
+ });
823
+ }
824
+ }
825
+ `
826
+ };
827
+ }
828
+
829
+ function generateStrings(ctx: TemplateContext): GeneratedFile {
830
+ const displayName = ctx.appName.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
831
+
832
+ return {
833
+ path: 'app/src/main/res/values/strings.xml',
834
+ content: `<?xml version="1.0" encoding="utf-8"?>
835
+ <resources>
836
+ <string name="app_name">${displayName}</string>
837
+ <string name="app_name_short">${ctx.appNamePascal}</string>
838
+ <string name="content_description_app_icon">Application icon</string>
839
+ </resources>
840
+ `
841
+ };
842
+ }
843
+
844
+ function generateColors(_ctx: TemplateContext): GeneratedFile {
845
+ return {
846
+ path: 'app/src/main/res/values/colors.xml',
847
+ content: `<?xml version="1.0" encoding="utf-8"?>
848
+ <resources>
849
+ <!-- Primary Colors -->
850
+ <color name="primary">#6750A4</color>
851
+ <color name="primary_variant">#7F67BE</color>
852
+ <color name="on_primary">#FFFFFF</color>
853
+
854
+ <!-- Secondary Colors -->
855
+ <color name="secondary">#625B71</color>
856
+ <color name="secondary_variant">#7A7289</color>
857
+ <color name="on_secondary">#FFFFFF</color>
858
+
859
+ <!-- Tertiary Colors -->
860
+ <color name="tertiary">#7D5260</color>
861
+ <color name="on_tertiary">#FFFFFF</color>
862
+
863
+ <!-- Background Colors -->
864
+ <color name="background">#FFFBFE</color>
865
+ <color name="on_background">#1C1B1F</color>
866
+ <color name="surface">#FFFBFE</color>
867
+ <color name="on_surface">#1C1B1F</color>
868
+
869
+ <!-- Status Colors -->
870
+ <color name="error">#B3261E</color>
871
+ <color name="on_error">#FFFFFF</color>
872
+ <color name="outline">#79747E</color>
873
+
874
+ <!-- Icon Colors -->
875
+ <color name="ic_launcher_background">#6750A4</color>
876
+ </resources>
877
+ `
878
+ };
879
+ }
880
+
881
+ function generateThemes(ctx: TemplateContext): GeneratedFile {
882
+ const themeName = `Theme.${ctx.appNamePascal}`;
883
+
884
+ if (ctx.uiFramework === 'compose') {
885
+ return {
886
+ path: 'app/src/main/res/values/themes.xml',
887
+ content: `<?xml version="1.0" encoding="utf-8"?>
888
+ <resources>
889
+ <style name="${themeName}" parent="Theme.Material3.Light.NoActionBar">
890
+ <item name="android:statusBarColor">@android:color/transparent</item>
891
+ <item name="android:navigationBarColor">@android:color/transparent</item>
892
+ <item name="android:windowLightStatusBar">true</item>
893
+ <item name="android:windowLightNavigationBar">true</item>
894
+ </style>
895
+ </resources>
896
+ `
897
+ };
898
+ }
899
+
900
+ return {
901
+ path: 'app/src/main/res/values/themes.xml',
902
+ content: `<?xml version="1.0" encoding="utf-8"?>
903
+ <resources>
904
+ <style name="${themeName}" parent="Theme.Material3.Light.NoActionBar">
905
+ <item name="colorPrimary">@color/primary</item>
906
+ <item name="colorOnPrimary">@color/on_primary</item>
907
+ <item name="colorPrimaryContainer">@color/primary_variant</item>
908
+ <item name="colorSecondary">@color/secondary</item>
909
+ <item name="colorOnSecondary">@color/on_secondary</item>
910
+ <item name="colorTertiary">@color/tertiary</item>
911
+ <item name="colorOnTertiary">@color/on_tertiary</item>
912
+ <item name="android:colorBackground">@color/background</item>
913
+ <item name="colorOnBackground">@color/on_background</item>
914
+ <item name="colorSurface">@color/surface</item>
915
+ <item name="colorOnSurface">@color/on_surface</item>
916
+ <item name="colorError">@color/error</item>
917
+ <item name="colorOnError">@color/on_error</item>
918
+ <item name="colorOutline">@color/outline</item>
919
+ <item name="android:statusBarColor">@color/surface</item>
920
+ <item name="android:navigationBarColor">@color/surface</item>
921
+ <item name="android:windowLightStatusBar">true</item>
922
+ <item name="android:windowLightNavigationBar">true</item>
923
+ </style>
924
+ </resources>
925
+ `
926
+ };
927
+ }
928
+
929
+ function generateAppIcon(_ctx: TemplateContext): GeneratedFile {
930
+ return {
931
+ path: 'app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml',
932
+ content: `<?xml version="1.0" encoding="utf-8"?>
933
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
934
+ <background android:drawable="@drawable/ic_launcher_background"/>
935
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
936
+ </adaptive-icon>
937
+ `
938
+ };
939
+ }
940
+
941
+ function generateActivityLayout(ctx: TemplateContext): GeneratedFile {
942
+ if (ctx.uiFramework === 'compose') {
943
+ return {
944
+ path: 'app/src/main/res/layout/activity_main.xml',
945
+ content: `<?xml version="1.0" encoding="utf-8"?>
946
+ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
947
+ android:layout_width="match_parent"
948
+ android:layout_height="match_parent"
949
+ android:background="@color/background">
950
+
951
+ <TextView
952
+ android:id="@+id/textView"
953
+ android:layout_width="wrap_content"
954
+ android:layout_height="wrap_content"
955
+ android:layout_gravity="center"
956
+ android:text="@string/app_name"
957
+ android:textAppearance="?attr/textAppearanceHeadlineMedium"
958
+ android:textColor="@color/on_background" />
959
+
960
+ </FrameLayout>
961
+ `
962
+ };
963
+ }
964
+
965
+ return {
966
+ path: 'app/src/main/res/layout/activity_main.xml',
967
+ content: `<?xml version="1.0" encoding="utf-8"?>
968
+ <androidx.constraintlayout.widget.ConstraintLayout
969
+ xmlns:android="http://schemas.android.com/apk/res/android"
970
+ xmlns:app="http://schemas.android.com/apk/res-auto"
971
+ xmlns:tools="http://schemas.android.com/tools"
972
+ android:layout_width="match_parent"
973
+ android:layout_height="match_parent"
974
+ android:background="@color/background"
975
+ tools:context=".MainActivity">
976
+
977
+ <TextView
978
+ android:id="@+id/textView"
979
+ android:layout_width="wrap_content"
980
+ android:layout_height="wrap_content"
981
+ android:text="@string/app_name"
982
+ android:textAppearance="?attr/textAppearanceHeadlineMedium"
983
+ android:textColor="@color/on_background"
984
+ app:layout_constraintBottom_toBottomOf="parent"
985
+ app:layout_constraintEnd_toEndOf="parent"
986
+ app:layout_constraintStart_toStartOf="parent"
987
+ app:layout_constraintTop_toTopOf="parent" />
988
+
989
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
990
+ android:id="@+id/fab"
991
+ android:layout_width="wrap_content"
992
+ android:layout_height="wrap_content"
993
+ android:layout_margin="16dp"
994
+ android:contentDescription="@string/app_name_short"
995
+ android:src="@drawable/ic_add"
996
+ app:layout_constraintBottom_toBottomOf="parent"
997
+ app:layout_constraintEnd_toEndOf="parent"
998
+ app:tint="@color/on_primary" />
999
+
1000
+ </androidx.constraintlayout.widget.ConstraintLayout>
1001
+ `
1002
+ };
1003
+ }
1004
+
1005
+ function generateSourceSetFiles(ctx: TemplateContext): GeneratedFile[] {
1006
+ const files: GeneratedFile[] = [];
1007
+ const packagePath = ctx.packagePath;
1008
+
1009
+ // Professional adaptive icon background
1010
+ files.push({
1011
+ path: 'app/src/main/res/drawable/ic_launcher_background.xml',
1012
+ content: `<?xml version="1.0" encoding="utf-8"?>
1013
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
1014
+ android:width="108dp"
1015
+ android:height="108dp"
1016
+ android:viewportWidth="108"
1017
+ android:viewportHeight="108">
1018
+ <path
1019
+ android:fillColor="#6750A4"
1020
+ android:pathData="M0,0h108v108h-108z"/>
1021
+ </vector>
1022
+ `
1023
+ });
1024
+
1025
+ // Professional icon foreground - Android robot stylized "A"
1026
+ files.push({
1027
+ path: 'app/src/main/res/drawable/ic_launcher_foreground.xml',
1028
+ content: `<?xml version="1.0" encoding="utf-8"?>
1029
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
1030
+ android:width="108dp"
1031
+ android:height="108dp"
1032
+ android:viewportWidth="108"
1033
+ android:viewportHeight="108">
1034
+ <!-- Android robot head stylized as "A" -->
1035
+ <group android:translateX="27" android:translateY="27">
1036
+ <!-- Circle background -->
1037
+ <path
1038
+ android:fillColor="#FFFFFF"
1039
+ android:pathData="M27,27m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"/>
1040
+ <!-- A shape -->
1041
+ <path
1042
+ android:fillColor="#6750A4"
1043
+ android:pathData="M27,42L17,22h4l6,12 6,-12h4L27,42zM24,35l2.5,-5 2.5,5h-5z"/>
1044
+ <!-- Antenna -->
1045
+ <path
1046
+ android:strokeColor="#6750A4"
1047
+ android:strokeWidth="2"
1048
+ android:pathData="M20,18L12,10"/>
1049
+ <path
1050
+ android:strokeColor="#6750A4"
1051
+ android:strokeWidth="2"
1052
+ android:pathData="M34,18L42,10"/>
1053
+ </group>
1054
+ </vector>
1055
+ `
1056
+ });
1057
+
1058
+ files.push({
1059
+ path: 'app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml',
1060
+ content: `<?xml version="1.0" encoding="utf-8"?>
1061
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
1062
+ <background android:drawable="@drawable/ic_launcher_background"/>
1063
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
1064
+ </adaptive-icon>
1065
+ `
1066
+ });
1067
+
1068
+ // Add icon for FAB
1069
+ files.push({
1070
+ path: 'app/src/main/res/drawable/ic_add.xml',
1071
+ content: `<?xml version="1.0" encoding="utf-8"?>
1072
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
1073
+ android:width="24dp"
1074
+ android:height="24dp"
1075
+ android:viewportWidth="24"
1076
+ android:viewportHeight="24">
1077
+ <path
1078
+ android:fillColor="#FFFFFF"
1079
+ android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
1080
+ </vector>
1081
+ `
1082
+ });
1083
+
1084
+ if (ctx.uiFramework === 'compose') {
1085
+ files.push({
1086
+ path: `app/src/main/kotlin/${packagePath}/ui/theme/Color.kt`,
1087
+ content: `package ${ctx.packageName}.ui.theme
1088
+
1089
+ import androidx.compose.ui.graphics.Color
1090
+
1091
+ // Light theme primary colors
1092
+ val Purple40 = Color(0xFF6750A4)
1093
+ val PurpleGrey40 = Color(0xFF625B71)
1094
+ val Pink40 = Color(0xFF7D5260)
1095
+
1096
+ // Dark theme primary colors
1097
+ val Purple80 = Color(0xFFD0BCFF)
1098
+ val PurpleGrey80 = Color(0xFFCCC2DC)
1099
+ val Pink80 = Color(0xFFEFB8C8)
1100
+
1101
+ // Custom brand colors
1102
+ val Teal40 = Color(0xFF006B5B)
1103
+ val Teal80 = Color(0xFF8BD8CE)
1104
+ `
1105
+ });
1106
+
1107
+ files.push({
1108
+ path: `app/src/main/kotlin/${packagePath}/ui/theme/Theme.kt`,
1109
+ content: `package ${ctx.packageName}.ui.theme
1110
+
1111
+ import android.app.Activity
1112
+ import android.os.Build
1113
+ import androidx.compose.foundation.isSystemInDarkTheme
1114
+ import androidx.compose.material3.MaterialTheme
1115
+ import androidx.compose.material3.darkColorScheme
1116
+ import androidx.compose.material3.dynamicDarkColorScheme
1117
+ import androidx.compose.material3.dynamicLightColorScheme
1118
+ import androidx.compose.material3.lightColorScheme
1119
+ import androidx.compose.runtime.Composable
1120
+ import androidx.compose.runtime.SideEffect
1121
+ import androidx.compose.ui.graphics.Color
1122
+ import androidx.compose.ui.graphics.toArgb
1123
+ import androidx.compose.ui.platform.LocalView
1124
+ import androidx.core.view.WindowCompat
1125
+
1126
+ private val DarkColorScheme = darkColorScheme(
1127
+ primary = Purple80,
1128
+ onPrimary = Color(0xFF381E72),
1129
+ primaryContainer = Color(0xFF4F378B),
1130
+ onPrimaryContainer = Color(0xFFEADDFF),
1131
+ secondary = PurpleGrey80,
1132
+ onSecondary = Color(0xFF332D41),
1133
+ secondaryContainer = Color(0xFF4A4458),
1134
+ onSecondaryContainer = Color(0xFFE8DEF8),
1135
+ tertiary = Pink80,
1136
+ onTertiary = Color(0xFF492532),
1137
+ tertiaryContainer = Color(0xFF633B48),
1138
+ onTertiaryContainer = Color(0xFFFFD8E4),
1139
+ background = Color(0xFF1C1B1F),
1140
+ onBackground = Color(0xFFE6E1E5),
1141
+ surface = Color(0xFF1C1B1F),
1142
+ onSurface = Color(0xFFE6E1E5),
1143
+ surfaceVariant = Color(0xFF49454F),
1144
+ onSurfaceVariant = Color(0xFFCAC4D0)
1145
+ )
1146
+
1147
+ private val LightColorScheme = lightColorScheme(
1148
+ primary = Purple40,
1149
+ onPrimary = Color.White,
1150
+ primaryContainer = Color(0xFFEADDFF),
1151
+ onPrimaryContainer = Color(0xFF21005D),
1152
+ secondary = PurpleGrey40,
1153
+ onSecondary = Color.White,
1154
+ secondaryContainer = Color(0xFFE8DEF8),
1155
+ onSecondaryContainer = Color(0xFF1D192B),
1156
+ tertiary = Pink40,
1157
+ onTertiary = Color.White,
1158
+ tertiaryContainer = Color(0xFFFFD8E4),
1159
+ onTertiaryContainer = Color(0xFF31111D),
1160
+ background = Color(0xFFFFFBFE),
1161
+ onBackground = Color(0xFF1C1B1F),
1162
+ surface = Color(0xFFFFFBFE),
1163
+ onSurface = Color(0xFF1C1B1F),
1164
+ surfaceVariant = Color(0xFFE7E0EC),
1165
+ onSurfaceVariant = Color(0xFF49454F)
1166
+ )
1167
+
1168
+ @Composable
1169
+ fun ${ctx.appNamePascal}Theme(
1170
+ darkTheme: Boolean = isSystemInDarkTheme(),
1171
+ dynamicColor: Boolean = false,
1172
+ content: @Composable () -> Unit
1173
+ ) {
1174
+ val colorScheme = when {
1175
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
1176
+ if (darkTheme) dynamicDarkColorScheme(LocalView.current.context) else dynamicLightColorScheme(LocalView.current.context)
1177
+ }
1178
+ darkTheme -> DarkColorScheme
1179
+ else -> LightColorScheme
1180
+ }
1181
+
1182
+ val view = LocalView.current
1183
+ if (!view.isInEditMode) {
1184
+ SideEffect {
1185
+ val window = (view.context as Activity).window
1186
+ window.statusBarColor = Color.Transparent.toArgb()
1187
+ window.navigationBarColor = Color.Transparent.toArgb()
1188
+ WindowCompat.getInsetsController(window, view).apply {
1189
+ isAppearanceLightStatusBars = !darkTheme
1190
+ isAppearanceLightNavigationBars = !darkTheme
1191
+ }
1192
+ }
1193
+ }
1194
+
1195
+ MaterialTheme(
1196
+ colorScheme = colorScheme,
1197
+ typography = Typography,
1198
+ content = content
1199
+ )
1200
+ }
1201
+ `
1202
+ });
1203
+
1204
+ files.push({
1205
+ path: `app/src/main/kotlin/${packagePath}/ui/theme/Type.kt`,
1206
+ content: `package ${ctx.packageName}.ui.theme
1207
+
1208
+ import androidx.compose.material3.Typography
1209
+ import androidx.compose.ui.text.TextStyle
1210
+ import androidx.compose.ui.text.font.FontFamily
1211
+ import androidx.compose.ui.text.font.FontWeight
1212
+ import androidx.compose.ui.unit.sp
1213
+
1214
+ val Typography = Typography(
1215
+ displayLarge = TextStyle(
1216
+ fontFamily = FontFamily.Default,
1217
+ fontWeight = FontWeight.Normal,
1218
+ fontSize = 57.sp,
1219
+ lineHeight = 64.sp,
1220
+ letterSpacing = (-0.25).sp
1221
+ ),
1222
+ displayMedium = TextStyle(
1223
+ fontFamily = FontFamily.Default,
1224
+ fontWeight = FontWeight.Normal,
1225
+ fontSize = 45.sp,
1226
+ lineHeight = 52.sp,
1227
+ letterSpacing = 0.sp
1228
+ ),
1229
+ displaySmall = TextStyle(
1230
+ fontFamily = FontFamily.Default,
1231
+ fontWeight = FontWeight.Normal,
1232
+ fontSize = 36.sp,
1233
+ lineHeight = 44.sp,
1234
+ letterSpacing = 0.sp
1235
+ ),
1236
+ headlineLarge = TextStyle(
1237
+ fontFamily = FontFamily.Default,
1238
+ fontWeight = FontWeight.SemiBold,
1239
+ fontSize = 32.sp,
1240
+ lineHeight = 40.sp,
1241
+ letterSpacing = 0.sp
1242
+ ),
1243
+ headlineMedium = TextStyle(
1244
+ fontFamily = FontFamily.Default,
1245
+ fontWeight = FontWeight.SemiBold,
1246
+ fontSize = 28.sp,
1247
+ lineHeight = 36.sp,
1248
+ letterSpacing = 0.sp
1249
+ ),
1250
+ headlineSmall = TextStyle(
1251
+ fontFamily = FontFamily.Default,
1252
+ fontWeight = FontWeight.SemiBold,
1253
+ fontSize = 24.sp,
1254
+ lineHeight = 32.sp,
1255
+ letterSpacing = 0.sp
1256
+ ),
1257
+ titleLarge = TextStyle(
1258
+ fontFamily = FontFamily.Default,
1259
+ fontWeight = FontWeight.Medium,
1260
+ fontSize = 22.sp,
1261
+ lineHeight = 28.sp,
1262
+ letterSpacing = 0.sp
1263
+ ),
1264
+ titleMedium = TextStyle(
1265
+ fontFamily = FontFamily.Default,
1266
+ fontWeight = FontWeight.Medium,
1267
+ fontSize = 16.sp,
1268
+ lineHeight = 24.sp,
1269
+ letterSpacing = 0.15.sp
1270
+ ),
1271
+ titleSmall = TextStyle(
1272
+ fontFamily = FontFamily.Default,
1273
+ fontWeight = FontWeight.Medium,
1274
+ fontSize = 14.sp,
1275
+ lineHeight = 20.sp,
1276
+ letterSpacing = 0.1.sp
1277
+ ),
1278
+ bodyLarge = TextStyle(
1279
+ fontFamily = FontFamily.Default,
1280
+ fontWeight = FontWeight.Normal,
1281
+ fontSize = 16.sp,
1282
+ lineHeight = 24.sp,
1283
+ letterSpacing = 0.5.sp
1284
+ ),
1285
+ bodyMedium = TextStyle(
1286
+ fontFamily = FontFamily.Default,
1287
+ fontWeight = FontWeight.Normal,
1288
+ fontSize = 14.sp,
1289
+ lineHeight = 20.sp,
1290
+ letterSpacing = 0.25.sp
1291
+ ),
1292
+ bodySmall = TextStyle(
1293
+ fontFamily = FontFamily.Default,
1294
+ fontWeight = FontWeight.Normal,
1295
+ fontSize = 12.sp,
1296
+ lineHeight = 16.sp,
1297
+ letterSpacing = 0.4.sp
1298
+ ),
1299
+ labelLarge = TextStyle(
1300
+ fontFamily = FontFamily.Default,
1301
+ fontWeight = FontWeight.Medium,
1302
+ fontSize = 14.sp,
1303
+ lineHeight = 20.sp,
1304
+ letterSpacing = 0.1.sp
1305
+ ),
1306
+ labelMedium = TextStyle(
1307
+ fontFamily = FontFamily.Default,
1308
+ fontWeight = FontWeight.Medium,
1309
+ fontSize = 12.sp,
1310
+ lineHeight = 16.sp,
1311
+ letterSpacing = 0.5.sp
1312
+ ),
1313
+ labelSmall = TextStyle(
1314
+ fontFamily = FontFamily.Default,
1315
+ fontWeight = FontWeight.Medium,
1316
+ fontSize = 11.sp,
1317
+ lineHeight = 16.sp,
1318
+ letterSpacing = 0.5.sp
1319
+ )
1320
+ )
1321
+ `
1322
+ });
1323
+ }
1324
+
1325
+ return files;
1326
+ }
1327
+
1328
+ function isKotlinActivity(ctx: TemplateContext): boolean {
1329
+ return ctx.language === 'kotlin';
1330
+ }
1331
+
1332
+ function getAndroidVersionName(apiLevel: number): string {
1333
+ const versions: Record<number, string> = {
1334
+ 35: '15', 34: '14', 33: '13', 32: '12L', 31: '12',
1335
+ 30: '11', 29: '10', 28: '9', 27: '8.1', 26: '8.0', 24: '7.0', 21: '5.0'
1336
+ };
1337
+ return versions[apiLevel] || 'Unknown';
1338
+ }
1339
+
1340
+ /**
1341
+ * Get directory contents
1342
+ */
1343
+ export async function getDirectoryContents(path: string): Promise<string[]> {
1344
+ try {
1345
+ const items = await import('fs-extra').then(fse => fse.default.readdir(path));
1346
+ return items as string[];
1347
+ } catch (error) {
1348
+ return [];
1349
+ }
1350
+ }