@jetstart/cli 1.1.3 → 1.2.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/dist/utils/template.js +603 -29
- package/dist/utils/template.js.map +1 -1
- package/package.json +3 -3
- package/src/utils/template.ts +609 -35
package/dist/utils/template.js
CHANGED
|
@@ -472,45 +472,619 @@ sdk.dir=${androidSdkPath.replace(/\\/g, '\\\\')}
|
|
|
472
472
|
async function generateHotReload(projectPath, packageName) {
|
|
473
473
|
const packagePath = packageName.replace(/\./g, '/');
|
|
474
474
|
const hotReloadPath = path_1.default.join(projectPath, 'app/src/main/java', packagePath, 'HotReload.kt');
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
475
|
+
const content = `package ${packageName}
|
|
476
|
+
|
|
477
|
+
import android.app.Activity
|
|
478
|
+
import android.content.Intent
|
|
479
|
+
import android.net.Uri
|
|
480
|
+
import android.os.Build
|
|
481
|
+
import android.util.Log
|
|
482
|
+
import androidx.core.content.FileProvider
|
|
483
|
+
import okhttp3.*
|
|
484
|
+
import org.json.JSONObject
|
|
485
|
+
import java.io.File
|
|
486
|
+
import java.io.IOException
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Hot Reload Manager
|
|
490
|
+
* Connects to JetStart dev server and automatically reloads the app when code changes
|
|
491
|
+
*/
|
|
492
|
+
object HotReload {
|
|
493
|
+
private const val TAG = "HotReload"
|
|
494
|
+
private var webSocket: WebSocket? = null
|
|
495
|
+
private var activity: Activity? = null
|
|
496
|
+
private var connectionTime: Long = 0
|
|
497
|
+
private var ignoreFirstBuild = true
|
|
498
|
+
private val httpClient = OkHttpClient()
|
|
499
|
+
|
|
500
|
+
fun connect(activity: Activity, serverUrl: String, sessionId: String) {
|
|
501
|
+
this.activity = activity
|
|
502
|
+
|
|
503
|
+
val wsUrl = serverUrl.replace("http://", "ws://").replace("https://", "wss://")
|
|
504
|
+
Log.d(TAG, "Connecting to dev server: \$wsUrl")
|
|
505
|
+
|
|
506
|
+
val client = OkHttpClient()
|
|
507
|
+
val request = Request.Builder()
|
|
508
|
+
.url(wsUrl)
|
|
509
|
+
.build()
|
|
510
|
+
|
|
511
|
+
webSocket = client.newWebSocket(request, object : WebSocketListener() {
|
|
512
|
+
override fun onOpen(webSocket: WebSocket, response: Response) {
|
|
513
|
+
Log.d(TAG, "WebSocket connected")
|
|
514
|
+
connectionTime = System.currentTimeMillis()
|
|
515
|
+
ignoreFirstBuild = true // Ignore the first build-complete after connecting
|
|
516
|
+
|
|
517
|
+
// Send connect message
|
|
518
|
+
val connectMsg = JSONObject().apply {
|
|
519
|
+
put("type", "client:connect")
|
|
520
|
+
put("sessionId", sessionId)
|
|
521
|
+
put("clientType", "test-app")
|
|
522
|
+
}
|
|
523
|
+
webSocket.send(connectMsg.toString())
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
override fun onMessage(webSocket: WebSocket, text: String) {
|
|
527
|
+
Log.d(TAG, "Received: \$text")
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
val json = JSONObject(text)
|
|
531
|
+
val type = json.getString("type")
|
|
532
|
+
|
|
533
|
+
when (type) {
|
|
534
|
+
"core:ui-update" -> {
|
|
535
|
+
// DSL-based hot reload (FAST)
|
|
536
|
+
if (ignoreFirstBuild) {
|
|
537
|
+
Log.d(TAG, "Ignoring first UI update (old build)")
|
|
538
|
+
ignoreFirstBuild = false
|
|
539
|
+
return@onMessage
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
val timestamp = json.optLong("timestamp", 0)
|
|
543
|
+
val dslContent = json.optString("dslContent", "")
|
|
544
|
+
|
|
545
|
+
Log.d(TAG, "UI update received at \$timestamp, connection at \$connectionTime")
|
|
546
|
+
|
|
547
|
+
// Only update if changes happened AFTER we connected
|
|
548
|
+
if (timestamp > connectionTime && dslContent.isNotEmpty()) {
|
|
549
|
+
Log.d(TAG, "New UI update detected, recomposing (\${dslContent.length} bytes)")
|
|
550
|
+
|
|
551
|
+
// Update DSL on main thread
|
|
552
|
+
activity?.runOnUiThread {
|
|
553
|
+
DSLInterpreter.updateDSL(dslContent)
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
Log.d(TAG, "Ignoring old UI update")
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
"core:reload" -> {
|
|
561
|
+
Log.d(TAG, "Reload triggered!")
|
|
562
|
+
// Restart activity on main thread
|
|
563
|
+
activity?.runOnUiThread {
|
|
564
|
+
activity?.recreate()
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
"core:build-complete" -> {
|
|
569
|
+
// Full APK rebuild (SLOW - fallback for non-UI changes)
|
|
570
|
+
if (ignoreFirstBuild) {
|
|
571
|
+
Log.d(TAG, "Ignoring first build-complete (old build)")
|
|
572
|
+
ignoreFirstBuild = false
|
|
573
|
+
return@onMessage
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
val timestamp = json.optLong("timestamp", 0)
|
|
577
|
+
val downloadUrl = json.optString("downloadUrl", "")
|
|
578
|
+
|
|
579
|
+
Log.d(TAG, "Build complete at \$timestamp, connection at \$connectionTime")
|
|
580
|
+
Log.d(TAG, "Download URL: \$downloadUrl")
|
|
581
|
+
|
|
582
|
+
// Only reload if build happened AFTER we connected
|
|
583
|
+
if (timestamp > connectionTime && downloadUrl.isNotEmpty()) {
|
|
584
|
+
Log.d(TAG, "New build detected, downloading and installing APK")
|
|
585
|
+
downloadAndInstallApk(downloadUrl)
|
|
586
|
+
} else {
|
|
587
|
+
Log.d(TAG, "Ignoring old build (timestamp before connection)")
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch (e: Exception) {
|
|
592
|
+
Log.e(TAG, "Failed to parse message: \${e.message}")
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
|
597
|
+
Log.e(TAG, "WebSocket error: \${t.message}")
|
|
598
|
+
// Auto-reconnect after 5 seconds
|
|
599
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
600
|
+
connect(activity!!, serverUrl, sessionId)
|
|
601
|
+
}, 5000)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
|
605
|
+
Log.d(TAG, "WebSocket closed: \$reason")
|
|
606
|
+
}
|
|
607
|
+
})
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
fun disconnect() {
|
|
611
|
+
webSocket?.close(1000, "App closing")
|
|
612
|
+
webSocket = null
|
|
613
|
+
activity = null
|
|
486
614
|
}
|
|
615
|
+
|
|
616
|
+
private fun downloadAndInstallApk(downloadUrl: String) {
|
|
617
|
+
Log.d(TAG, "Starting APK download from: \$downloadUrl")
|
|
618
|
+
|
|
619
|
+
val request = Request.Builder()
|
|
620
|
+
.url(downloadUrl)
|
|
621
|
+
.build()
|
|
622
|
+
|
|
623
|
+
httpClient.newCall(request).enqueue(object : Callback {
|
|
624
|
+
override fun onFailure(call: Call, e: IOException) {
|
|
625
|
+
Log.e(TAG, "APK download failed: \${e.message}")
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
override fun onResponse(call: Call, response: Response) {
|
|
629
|
+
if (!response.isSuccessful) {
|
|
630
|
+
Log.e(TAG, "APK download failed with code: \${response.code}")
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
val apkData = response.body?.bytes()
|
|
636
|
+
if (apkData == null) {
|
|
637
|
+
Log.e(TAG, "APK data is null")
|
|
638
|
+
return
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
Log.d(TAG, "APK downloaded successfully (\${apkData.size} bytes)")
|
|
642
|
+
|
|
643
|
+
// Save APK to cache directory
|
|
644
|
+
val cacheDir = activity?.cacheDir
|
|
645
|
+
if (cacheDir == null) {
|
|
646
|
+
Log.e(TAG, "Cache directory is null")
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
val apkFile = File(cacheDir, "update.apk")
|
|
651
|
+
apkFile.outputStream().use { output ->
|
|
652
|
+
output.write(apkData)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
Log.d(TAG, "APK saved to: \${apkFile.absolutePath}")
|
|
656
|
+
|
|
657
|
+
// Install APK on main thread
|
|
658
|
+
activity?.runOnUiThread {
|
|
659
|
+
installApk(apkFile)
|
|
660
|
+
}
|
|
661
|
+
} catch (e: Exception) {
|
|
662
|
+
Log.e(TAG, "Failed to save/install APK: \${e.message}")
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
})
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
private fun installApk(apkFile: File) {
|
|
669
|
+
try {
|
|
670
|
+
val context = activity ?: return
|
|
671
|
+
|
|
672
|
+
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
673
|
+
FileProvider.getUriForFile(
|
|
674
|
+
context,
|
|
675
|
+
"\${context.packageName}.fileprovider",
|
|
676
|
+
apkFile
|
|
677
|
+
)
|
|
678
|
+
} else {
|
|
679
|
+
Uri.fromFile(apkFile)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
val intent = Intent(Intent.ACTION_VIEW).apply {
|
|
683
|
+
setDataAndType(uri, "application/vnd.android.package-archive")
|
|
684
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
685
|
+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
context.startActivity(intent)
|
|
689
|
+
Log.d(TAG, "Installation intent started")
|
|
690
|
+
} catch (e: Exception) {
|
|
691
|
+
Log.e(TAG, "Failed to install APK: \${e.message}")
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
`;
|
|
696
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(hotReloadPath));
|
|
697
|
+
await fs_extra_1.default.writeFile(hotReloadPath, content);
|
|
487
698
|
}
|
|
488
699
|
async function generateDSLInterpreter(projectPath, packageName) {
|
|
489
700
|
const packagePath = packageName.replace(/\./g, '/');
|
|
490
701
|
const interpreterPath = path_1.default.join(projectPath, 'app/src/main/java', packagePath, 'DSLInterpreter.kt');
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
702
|
+
const content = `package ${packageName}
|
|
703
|
+
|
|
704
|
+
import android.util.Log
|
|
705
|
+
import androidx.compose.foundation.layout.*
|
|
706
|
+
import androidx.compose.material3.*
|
|
707
|
+
import androidx.compose.runtime.*
|
|
708
|
+
import androidx.compose.ui.Alignment
|
|
709
|
+
import androidx.compose.ui.Modifier
|
|
710
|
+
import androidx.compose.ui.graphics.Color
|
|
711
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
712
|
+
import androidx.compose.ui.unit.dp
|
|
713
|
+
import kotlinx.coroutines.flow.MutableStateFlow
|
|
714
|
+
import kotlinx.coroutines.flow.StateFlow
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* DSL Interpreter
|
|
718
|
+
* Converts JSON DSL to Compose UI at runtime
|
|
719
|
+
*/
|
|
720
|
+
object DSLInterpreter {
|
|
721
|
+
private const val TAG = "DSLInterpreter"
|
|
722
|
+
|
|
723
|
+
private val _currentDSL = MutableStateFlow<UIDefinition?>(null)
|
|
724
|
+
val currentDSL: StateFlow<UIDefinition?> = _currentDSL
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Update the current DSL definition
|
|
728
|
+
*/
|
|
729
|
+
fun updateDSL(jsonString: String) {
|
|
730
|
+
try {
|
|
731
|
+
val definition = parseUIDefinition(jsonString)
|
|
732
|
+
_currentDSL.value = definition
|
|
733
|
+
Log.d(TAG, "DSL updated successfully")
|
|
734
|
+
} catch (e: Exception) {
|
|
735
|
+
Log.e(TAG, "Failed to parse DSL: \${e.message}", e)
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Render DSL as Compose UI
|
|
741
|
+
*/
|
|
742
|
+
@Composable
|
|
743
|
+
fun RenderDSL(definition: UIDefinition) {
|
|
744
|
+
RenderElement(definition.screen)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Render individual DSL element
|
|
749
|
+
*/
|
|
750
|
+
@Composable
|
|
751
|
+
fun RenderElement(element: DSLElement) {
|
|
752
|
+
when (element.type) {
|
|
753
|
+
"Column" -> RenderColumn(element)
|
|
754
|
+
"Row" -> RenderRow(element)
|
|
755
|
+
"Box" -> RenderBox(element)
|
|
756
|
+
"Text" -> RenderText(element)
|
|
757
|
+
"Button" -> RenderButton(element)
|
|
758
|
+
"Spacer" -> RenderSpacer(element)
|
|
759
|
+
else -> {
|
|
760
|
+
Log.w(TAG, "Unknown element type: \${element.type}")
|
|
761
|
+
Text("Unsupported: \${element.type}", color = Color.Red)
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
@Composable
|
|
767
|
+
private fun RenderColumn(element: DSLElement) {
|
|
768
|
+
Column(
|
|
769
|
+
modifier = parseModifier(element.modifier),
|
|
770
|
+
horizontalAlignment = parseHorizontalAlignment(element.horizontalAlignment),
|
|
771
|
+
verticalArrangement = parseVerticalArrangement(element.verticalArrangement)
|
|
772
|
+
) {
|
|
773
|
+
element.children?.forEach { child ->
|
|
774
|
+
RenderElement(child)
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
@Composable
|
|
780
|
+
private fun RenderRow(element: DSLElement) {
|
|
781
|
+
Row(
|
|
782
|
+
modifier = parseModifier(element.modifier),
|
|
783
|
+
verticalAlignment = parseVerticalAlignment(element.horizontalAlignment),
|
|
784
|
+
horizontalArrangement = parseHorizontalArrangement(element.verticalArrangement)
|
|
785
|
+
) {
|
|
786
|
+
element.children?.forEach { child ->
|
|
787
|
+
RenderElement(child)
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
@Composable
|
|
793
|
+
private fun RenderBox(element: DSLElement) {
|
|
794
|
+
Box(
|
|
795
|
+
modifier = parseModifier(element.modifier),
|
|
796
|
+
contentAlignment = parseContentAlignment(element.contentAlignment)
|
|
797
|
+
) {
|
|
798
|
+
element.children?.forEach { child ->
|
|
799
|
+
RenderElement(child)
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
@Composable
|
|
805
|
+
private fun RenderText(element: DSLElement) {
|
|
806
|
+
Text(
|
|
807
|
+
text = element.text ?: "",
|
|
808
|
+
style = parseTextStyle(element.style),
|
|
809
|
+
color = parseColor(element.color) ?: Color.Unspecified,
|
|
810
|
+
modifier = parseModifier(element.modifier)
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
@Composable
|
|
815
|
+
private fun RenderButton(element: DSLElement) {
|
|
816
|
+
Button(
|
|
817
|
+
onClick = { handleClick(element.onClick) },
|
|
818
|
+
modifier = parseModifier(element.modifier),
|
|
819
|
+
enabled = element.enabled ?: true
|
|
820
|
+
) {
|
|
821
|
+
Text(element.text ?: "Button")
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
@Composable
|
|
826
|
+
private fun RenderSpacer(element: DSLElement) {
|
|
827
|
+
Spacer(
|
|
828
|
+
modifier = Modifier
|
|
829
|
+
.height(element.height?.dp ?: 0.dp)
|
|
830
|
+
.width(element.width?.dp ?: 0.dp)
|
|
831
|
+
)
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Parse DSL modifier to Compose Modifier
|
|
836
|
+
*/
|
|
837
|
+
private fun parseModifier(dslModifier: DSLModifier?): Modifier {
|
|
838
|
+
var modifier: Modifier = Modifier
|
|
839
|
+
|
|
840
|
+
dslModifier?.let { m ->
|
|
841
|
+
if (m.fillMaxSize == true) modifier = modifier.fillMaxSize()
|
|
842
|
+
if (m.fillMaxWidth == true) modifier = modifier.fillMaxWidth()
|
|
843
|
+
if (m.fillMaxHeight == true) modifier = modifier.fillMaxHeight()
|
|
844
|
+
|
|
845
|
+
m.padding?.let { modifier = modifier.padding(it.dp) }
|
|
846
|
+
m.paddingHorizontal?.let { modifier = modifier.padding(horizontal = it.dp) }
|
|
847
|
+
m.paddingVertical?.let { modifier = modifier.padding(vertical = it.dp) }
|
|
848
|
+
|
|
849
|
+
m.size?.let { modifier = modifier.size(it.dp) }
|
|
850
|
+
m.height?.let { modifier = modifier.height(it.dp) }
|
|
851
|
+
m.width?.let { modifier = modifier.width(it.dp) }
|
|
852
|
+
|
|
853
|
+
// Note: weight() is only available in RowScope/ColumnScope
|
|
854
|
+
// We'll handle it separately when needed
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return modifier
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Parse alignment strings
|
|
862
|
+
*/
|
|
863
|
+
private fun parseHorizontalAlignment(alignment: String?): Alignment.Horizontal {
|
|
864
|
+
return when (alignment?.lowercase()) {
|
|
865
|
+
"start" -> Alignment.Start
|
|
866
|
+
"centerhorizontally", "center" -> Alignment.CenterHorizontally
|
|
867
|
+
"end" -> Alignment.End
|
|
868
|
+
else -> Alignment.Start
|
|
869
|
+
}
|
|
497
870
|
}
|
|
498
|
-
|
|
499
|
-
|
|
871
|
+
|
|
872
|
+
private fun parseVerticalAlignment(alignment: String?): Alignment.Vertical {
|
|
873
|
+
return when (alignment?.lowercase()) {
|
|
874
|
+
"top" -> Alignment.Top
|
|
875
|
+
"centervertically", "center" -> Alignment.CenterVertically
|
|
876
|
+
"bottom" -> Alignment.Bottom
|
|
877
|
+
else -> Alignment.Top
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
private fun parseContentAlignment(alignment: String?): Alignment {
|
|
882
|
+
return when (alignment?.lowercase()) {
|
|
883
|
+
"center" -> Alignment.Center
|
|
884
|
+
"topcenter" -> Alignment.TopCenter
|
|
885
|
+
"topstart" -> Alignment.TopStart
|
|
886
|
+
"topend" -> Alignment.TopEnd
|
|
887
|
+
"bottomcenter" -> Alignment.BottomCenter
|
|
888
|
+
"bottomstart" -> Alignment.BottomStart
|
|
889
|
+
"bottomend" -> Alignment.BottomEnd
|
|
890
|
+
"centerstart" -> Alignment.CenterStart
|
|
891
|
+
"centerend" -> Alignment.CenterEnd
|
|
892
|
+
else -> Alignment.TopStart
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
private fun parseVerticalArrangement(arrangement: String?): Arrangement.Vertical {
|
|
897
|
+
return when (arrangement?.lowercase()) {
|
|
898
|
+
"top" -> Arrangement.Top
|
|
899
|
+
"center" -> Arrangement.Center
|
|
900
|
+
"bottom" -> Arrangement.Bottom
|
|
901
|
+
"spacebetween" -> Arrangement.SpaceBetween
|
|
902
|
+
"spacearound" -> Arrangement.SpaceAround
|
|
903
|
+
"spaceevenly" -> Arrangement.SpaceEvenly
|
|
904
|
+
else -> Arrangement.Top
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
private fun parseHorizontalArrangement(arrangement: String?): Arrangement.Horizontal {
|
|
909
|
+
return when (arrangement?.lowercase()) {
|
|
910
|
+
"start" -> Arrangement.Start
|
|
911
|
+
"center" -> Arrangement.Center
|
|
912
|
+
"end" -> Arrangement.End
|
|
913
|
+
"spacebetween" -> Arrangement.SpaceBetween
|
|
914
|
+
"spacearound" -> Arrangement.SpaceAround
|
|
915
|
+
"spaceevenly" -> Arrangement.SpaceEvenly
|
|
916
|
+
else -> Arrangement.Start
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Parse text style
|
|
922
|
+
*/
|
|
923
|
+
@Composable
|
|
924
|
+
private fun parseTextStyle(style: String?): androidx.compose.ui.text.TextStyle {
|
|
925
|
+
return when (style?.lowercase()) {
|
|
926
|
+
"headlinelarge" -> MaterialTheme.typography.headlineLarge
|
|
927
|
+
"headlinemedium" -> MaterialTheme.typography.headlineMedium
|
|
928
|
+
"headlinesmall" -> MaterialTheme.typography.headlineSmall
|
|
929
|
+
"titlelarge" -> MaterialTheme.typography.titleLarge
|
|
930
|
+
"titlemedium" -> MaterialTheme.typography.titleMedium
|
|
931
|
+
"titlesmall" -> MaterialTheme.typography.titleSmall
|
|
932
|
+
"bodylarge" -> MaterialTheme.typography.bodyLarge
|
|
933
|
+
"bodymedium" -> MaterialTheme.typography.bodyMedium
|
|
934
|
+
"bodysmall" -> MaterialTheme.typography.bodySmall
|
|
935
|
+
"labellarge" -> MaterialTheme.typography.labelLarge
|
|
936
|
+
"labelmedium" -> MaterialTheme.typography.labelMedium
|
|
937
|
+
"labelsmall" -> MaterialTheme.typography.labelSmall
|
|
938
|
+
else -> MaterialTheme.typography.bodyMedium
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Parse color from string
|
|
944
|
+
*/
|
|
945
|
+
private fun parseColor(colorString: String?): Color? {
|
|
946
|
+
if (colorString == null) return null
|
|
947
|
+
|
|
948
|
+
return try {
|
|
949
|
+
when {
|
|
950
|
+
colorString.startsWith("#") -> {
|
|
951
|
+
// Hex color
|
|
952
|
+
Color(android.graphics.Color.parseColor(colorString))
|
|
953
|
+
}
|
|
954
|
+
else -> null
|
|
955
|
+
}
|
|
956
|
+
} catch (e: Exception) {
|
|
957
|
+
Log.w(TAG, "Failed to parse color: \$colorString")
|
|
958
|
+
null
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Handle click events
|
|
964
|
+
*/
|
|
965
|
+
private fun handleClick(action: String?) {
|
|
966
|
+
if (action != null) {
|
|
967
|
+
Log.d(TAG, "Button clicked: \$action")
|
|
968
|
+
// TODO: Implement action handling
|
|
969
|
+
}
|
|
500
970
|
}
|
|
501
971
|
}
|
|
972
|
+
`;
|
|
973
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(interpreterPath));
|
|
974
|
+
await fs_extra_1.default.writeFile(interpreterPath, content);
|
|
975
|
+
}
|
|
502
976
|
async function generateDSLTypes(projectPath, packageName) {
|
|
503
977
|
const packagePath = packageName.replace(/\./g, '/');
|
|
504
978
|
const typesPath = path_1.default.join(projectPath, 'app/src/main/java', packagePath, 'DSLTypes.kt');
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
979
|
+
const content = `package ${packageName}
|
|
980
|
+
|
|
981
|
+
import org.json.JSONArray
|
|
982
|
+
import org.json.JSONObject
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* DSL Type Definitions
|
|
986
|
+
* Represents UI elements in JSON format that can be interpreted at runtime
|
|
987
|
+
*/
|
|
988
|
+
|
|
989
|
+
data class UIDefinition(
|
|
990
|
+
val version: String = "1.0",
|
|
991
|
+
val screen: DSLElement
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
data class DSLElement(
|
|
995
|
+
val type: String,
|
|
996
|
+
val text: String? = null,
|
|
997
|
+
val style: String? = null,
|
|
998
|
+
val color: String? = null,
|
|
999
|
+
val modifier: DSLModifier? = null,
|
|
1000
|
+
val horizontalAlignment: String? = null,
|
|
1001
|
+
val verticalArrangement: String? = null,
|
|
1002
|
+
val contentAlignment: String? = null,
|
|
1003
|
+
val height: Int? = null,
|
|
1004
|
+
val width: Int? = null,
|
|
1005
|
+
val onClick: String? = null,
|
|
1006
|
+
val enabled: Boolean? = true,
|
|
1007
|
+
val imageVector: String? = null,
|
|
1008
|
+
val tint: String? = null,
|
|
1009
|
+
val contentDescription: String? = null,
|
|
1010
|
+
val children: List<DSLElement>? = null
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
data class DSLModifier(
|
|
1014
|
+
val fillMaxSize: Boolean? = null,
|
|
1015
|
+
val fillMaxWidth: Boolean? = null,
|
|
1016
|
+
val fillMaxHeight: Boolean? = null,
|
|
1017
|
+
val padding: Int? = null,
|
|
1018
|
+
val paddingHorizontal: Int? = null,
|
|
1019
|
+
val paddingVertical: Int? = null,
|
|
1020
|
+
val size: Int? = null,
|
|
1021
|
+
val height: Int? = null,
|
|
1022
|
+
val width: Int? = null,
|
|
1023
|
+
val weight: Float? = null
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Parse JSON string to UIDefinition
|
|
1028
|
+
*/
|
|
1029
|
+
fun parseUIDefinition(json: String): UIDefinition {
|
|
1030
|
+
val obj = JSONObject(json)
|
|
1031
|
+
val version = obj.optString("version", "1.0")
|
|
1032
|
+
val screenObj = obj.getJSONObject("screen")
|
|
1033
|
+
|
|
1034
|
+
return UIDefinition(
|
|
1035
|
+
version = version,
|
|
1036
|
+
screen = parseDSLElement(screenObj)
|
|
1037
|
+
)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* Parse JSONObject to DSLElement
|
|
1042
|
+
*/
|
|
1043
|
+
fun parseDSLElement(obj: JSONObject): DSLElement {
|
|
1044
|
+
val children = if (obj.has("children")) {
|
|
1045
|
+
val childrenArray = obj.getJSONArray("children")
|
|
1046
|
+
List(childrenArray.length()) { i ->
|
|
1047
|
+
parseDSLElement(childrenArray.getJSONObject(i))
|
|
1048
|
+
}
|
|
1049
|
+
} else null
|
|
1050
|
+
|
|
1051
|
+
val modifier = if (obj.has("modifier")) {
|
|
1052
|
+
val modObj = obj.getJSONObject("modifier")
|
|
1053
|
+
DSLModifier(
|
|
1054
|
+
fillMaxSize = modObj.optBoolean("fillMaxSize"),
|
|
1055
|
+
fillMaxWidth = modObj.optBoolean("fillMaxWidth"),
|
|
1056
|
+
fillMaxHeight = modObj.optBoolean("fillMaxHeight"),
|
|
1057
|
+
padding = if (modObj.has("padding")) modObj.getInt("padding") else null,
|
|
1058
|
+
paddingHorizontal = if (modObj.has("paddingHorizontal")) modObj.getInt("paddingHorizontal") else null,
|
|
1059
|
+
paddingVertical = if (modObj.has("paddingVertical")) modObj.getInt("paddingVertical") else null,
|
|
1060
|
+
size = if (modObj.has("size")) modObj.getInt("size") else null,
|
|
1061
|
+
height = if (modObj.has("height")) modObj.getInt("height") else null,
|
|
1062
|
+
width = if (modObj.has("width")) modObj.getInt("width") else null,
|
|
1063
|
+
weight = if (modObj.has("weight")) modObj.getDouble("weight").toFloat() else null
|
|
1064
|
+
)
|
|
1065
|
+
} else null
|
|
1066
|
+
|
|
1067
|
+
return DSLElement(
|
|
1068
|
+
type = obj.getString("type"),
|
|
1069
|
+
text = if (obj.has("text")) obj.getString("text") else null,
|
|
1070
|
+
style = if (obj.has("style")) obj.getString("style") else null,
|
|
1071
|
+
color = if (obj.has("color")) obj.getString("color") else null,
|
|
1072
|
+
modifier = modifier,
|
|
1073
|
+
horizontalAlignment = if (obj.has("horizontalAlignment")) obj.getString("horizontalAlignment") else null,
|
|
1074
|
+
verticalArrangement = if (obj.has("verticalArrangement")) obj.getString("verticalArrangement") else null,
|
|
1075
|
+
contentAlignment = if (obj.has("contentAlignment")) obj.getString("contentAlignment") else null,
|
|
1076
|
+
height = if (obj.has("height")) obj.getInt("height") else null,
|
|
1077
|
+
width = if (obj.has("width")) obj.getInt("width") else null,
|
|
1078
|
+
onClick = if (obj.has("onClick")) obj.getString("onClick") else null,
|
|
1079
|
+
enabled = obj.optBoolean("enabled", true),
|
|
1080
|
+
imageVector = if (obj.has("imageVector")) obj.getString("imageVector") else null,
|
|
1081
|
+
tint = if (obj.has("tint")) obj.getString("tint") else null,
|
|
1082
|
+
contentDescription = if (obj.has("contentDescription")) obj.getString("contentDescription") else null,
|
|
1083
|
+
children = children
|
|
1084
|
+
)
|
|
1085
|
+
}
|
|
1086
|
+
`;
|
|
1087
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(typesPath));
|
|
1088
|
+
await fs_extra_1.default.writeFile(typesPath, content);
|
|
515
1089
|
}
|
|
516
1090
|
//# sourceMappingURL=template.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetstart/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Command-line interface for JetStart",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/dev-phantom/jetstart#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@jetstart/core": "^1.
|
|
40
|
-
"@jetstart/shared": "^1.
|
|
39
|
+
"@jetstart/core": "^1.2.0",
|
|
40
|
+
"@jetstart/shared": "^1.2.0",
|
|
41
41
|
"axios": "^1.6.2",
|
|
42
42
|
"chalk": "^4.1.2",
|
|
43
43
|
"commander": "^11.1.0",
|
package/src/utils/template.ts
CHANGED
|
@@ -569,19 +569,230 @@ async function generateHotReload(
|
|
|
569
569
|
'HotReload.kt'
|
|
570
570
|
);
|
|
571
571
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
572
|
+
const content = `package ${packageName}
|
|
573
|
+
|
|
574
|
+
import android.app.Activity
|
|
575
|
+
import android.content.Intent
|
|
576
|
+
import android.net.Uri
|
|
577
|
+
import android.os.Build
|
|
578
|
+
import android.util.Log
|
|
579
|
+
import androidx.core.content.FileProvider
|
|
580
|
+
import okhttp3.*
|
|
581
|
+
import org.json.JSONObject
|
|
582
|
+
import java.io.File
|
|
583
|
+
import java.io.IOException
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Hot Reload Manager
|
|
587
|
+
* Connects to JetStart dev server and automatically reloads the app when code changes
|
|
588
|
+
*/
|
|
589
|
+
object HotReload {
|
|
590
|
+
private const val TAG = "HotReload"
|
|
591
|
+
private var webSocket: WebSocket? = null
|
|
592
|
+
private var activity: Activity? = null
|
|
593
|
+
private var connectionTime: Long = 0
|
|
594
|
+
private var ignoreFirstBuild = true
|
|
595
|
+
private val httpClient = OkHttpClient()
|
|
596
|
+
|
|
597
|
+
fun connect(activity: Activity, serverUrl: String, sessionId: String) {
|
|
598
|
+
this.activity = activity
|
|
599
|
+
|
|
600
|
+
val wsUrl = serverUrl.replace("http://", "ws://").replace("https://", "wss://")
|
|
601
|
+
Log.d(TAG, "Connecting to dev server: \$wsUrl")
|
|
602
|
+
|
|
603
|
+
val client = OkHttpClient()
|
|
604
|
+
val request = Request.Builder()
|
|
605
|
+
.url(wsUrl)
|
|
606
|
+
.build()
|
|
607
|
+
|
|
608
|
+
webSocket = client.newWebSocket(request, object : WebSocketListener() {
|
|
609
|
+
override fun onOpen(webSocket: WebSocket, response: Response) {
|
|
610
|
+
Log.d(TAG, "WebSocket connected")
|
|
611
|
+
connectionTime = System.currentTimeMillis()
|
|
612
|
+
ignoreFirstBuild = true // Ignore the first build-complete after connecting
|
|
613
|
+
|
|
614
|
+
// Send connect message
|
|
615
|
+
val connectMsg = JSONObject().apply {
|
|
616
|
+
put("type", "client:connect")
|
|
617
|
+
put("sessionId", sessionId)
|
|
618
|
+
put("clientType", "test-app")
|
|
619
|
+
}
|
|
620
|
+
webSocket.send(connectMsg.toString())
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
override fun onMessage(webSocket: WebSocket, text: String) {
|
|
624
|
+
Log.d(TAG, "Received: \$text")
|
|
625
|
+
|
|
626
|
+
try {
|
|
627
|
+
val json = JSONObject(text)
|
|
628
|
+
val type = json.getString("type")
|
|
629
|
+
|
|
630
|
+
when (type) {
|
|
631
|
+
"core:ui-update" -> {
|
|
632
|
+
// DSL-based hot reload (FAST)
|
|
633
|
+
if (ignoreFirstBuild) {
|
|
634
|
+
Log.d(TAG, "Ignoring first UI update (old build)")
|
|
635
|
+
ignoreFirstBuild = false
|
|
636
|
+
return@onMessage
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
val timestamp = json.optLong("timestamp", 0)
|
|
640
|
+
val dslContent = json.optString("dslContent", "")
|
|
641
|
+
|
|
642
|
+
Log.d(TAG, "UI update received at \$timestamp, connection at \$connectionTime")
|
|
643
|
+
|
|
644
|
+
// Only update if changes happened AFTER we connected
|
|
645
|
+
if (timestamp > connectionTime && dslContent.isNotEmpty()) {
|
|
646
|
+
Log.d(TAG, "New UI update detected, recomposing (\${dslContent.length} bytes)")
|
|
647
|
+
|
|
648
|
+
// Update DSL on main thread
|
|
649
|
+
activity?.runOnUiThread {
|
|
650
|
+
DSLInterpreter.updateDSL(dslContent)
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
Log.d(TAG, "Ignoring old UI update")
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
"core:reload" -> {
|
|
658
|
+
Log.d(TAG, "Reload triggered!")
|
|
659
|
+
// Restart activity on main thread
|
|
660
|
+
activity?.runOnUiThread {
|
|
661
|
+
activity?.recreate()
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
"core:build-complete" -> {
|
|
666
|
+
// Full APK rebuild (SLOW - fallback for non-UI changes)
|
|
667
|
+
if (ignoreFirstBuild) {
|
|
668
|
+
Log.d(TAG, "Ignoring first build-complete (old build)")
|
|
669
|
+
ignoreFirstBuild = false
|
|
670
|
+
return@onMessage
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
val timestamp = json.optLong("timestamp", 0)
|
|
674
|
+
val downloadUrl = json.optString("downloadUrl", "")
|
|
675
|
+
|
|
676
|
+
Log.d(TAG, "Build complete at \$timestamp, connection at \$connectionTime")
|
|
677
|
+
Log.d(TAG, "Download URL: \$downloadUrl")
|
|
678
|
+
|
|
679
|
+
// Only reload if build happened AFTER we connected
|
|
680
|
+
if (timestamp > connectionTime && downloadUrl.isNotEmpty()) {
|
|
681
|
+
Log.d(TAG, "New build detected, downloading and installing APK")
|
|
682
|
+
downloadAndInstallApk(downloadUrl)
|
|
683
|
+
} else {
|
|
684
|
+
Log.d(TAG, "Ignoring old build (timestamp before connection)")
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
} catch (e: Exception) {
|
|
689
|
+
Log.e(TAG, "Failed to parse message: \${e.message}")
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
|
694
|
+
Log.e(TAG, "WebSocket error: \${t.message}")
|
|
695
|
+
// Auto-reconnect after 5 seconds
|
|
696
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
697
|
+
connect(activity!!, serverUrl, sessionId)
|
|
698
|
+
}, 5000)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
|
702
|
+
Log.d(TAG, "WebSocket closed: \$reason")
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
fun disconnect() {
|
|
708
|
+
webSocket?.close(1000, "App closing")
|
|
709
|
+
webSocket = null
|
|
710
|
+
activity = null
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private fun downloadAndInstallApk(downloadUrl: String) {
|
|
714
|
+
Log.d(TAG, "Starting APK download from: \$downloadUrl")
|
|
715
|
+
|
|
716
|
+
val request = Request.Builder()
|
|
717
|
+
.url(downloadUrl)
|
|
718
|
+
.build()
|
|
719
|
+
|
|
720
|
+
httpClient.newCall(request).enqueue(object : Callback {
|
|
721
|
+
override fun onFailure(call: Call, e: IOException) {
|
|
722
|
+
Log.e(TAG, "APK download failed: \${e.message}")
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
override fun onResponse(call: Call, response: Response) {
|
|
726
|
+
if (!response.isSuccessful) {
|
|
727
|
+
Log.e(TAG, "APK download failed with code: \${response.code}")
|
|
728
|
+
return
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
try {
|
|
732
|
+
val apkData = response.body?.bytes()
|
|
733
|
+
if (apkData == null) {
|
|
734
|
+
Log.e(TAG, "APK data is null")
|
|
735
|
+
return
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
Log.d(TAG, "APK downloaded successfully (\${apkData.size} bytes)")
|
|
739
|
+
|
|
740
|
+
// Save APK to cache directory
|
|
741
|
+
val cacheDir = activity?.cacheDir
|
|
742
|
+
if (cacheDir == null) {
|
|
743
|
+
Log.e(TAG, "Cache directory is null")
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
val apkFile = File(cacheDir, "update.apk")
|
|
748
|
+
apkFile.outputStream().use { output ->
|
|
749
|
+
output.write(apkData)
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
Log.d(TAG, "APK saved to: \${apkFile.absolutePath}")
|
|
753
|
+
|
|
754
|
+
// Install APK on main thread
|
|
755
|
+
activity?.runOnUiThread {
|
|
756
|
+
installApk(apkFile)
|
|
757
|
+
}
|
|
758
|
+
} catch (e: Exception) {
|
|
759
|
+
Log.e(TAG, "Failed to save/install APK: \${e.message}")
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
})
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private fun installApk(apkFile: File) {
|
|
766
|
+
try {
|
|
767
|
+
val context = activity ?: return
|
|
768
|
+
|
|
769
|
+
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
770
|
+
FileProvider.getUriForFile(
|
|
771
|
+
context,
|
|
772
|
+
"\${context.packageName}.fileprovider",
|
|
773
|
+
apkFile
|
|
774
|
+
)
|
|
775
|
+
} else {
|
|
776
|
+
Uri.fromFile(apkFile)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
val intent = Intent(Intent.ACTION_VIEW).apply {
|
|
780
|
+
setDataAndType(uri, "application/vnd.android.package-archive")
|
|
781
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
782
|
+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
context.startActivity(intent)
|
|
786
|
+
Log.d(TAG, "Installation intent started")
|
|
787
|
+
} catch (e: Exception) {
|
|
788
|
+
Log.e(TAG, "Failed to install APK: \${e.message}")
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
`;
|
|
793
|
+
|
|
794
|
+
await fs.ensureDir(path.dirname(hotReloadPath));
|
|
795
|
+
await fs.writeFile(hotReloadPath, content);
|
|
585
796
|
}
|
|
586
797
|
|
|
587
798
|
async function generateDSLInterpreter(
|
|
@@ -596,17 +807,280 @@ async function generateDSLInterpreter(
|
|
|
596
807
|
'DSLInterpreter.kt'
|
|
597
808
|
);
|
|
598
809
|
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
810
|
+
const content = `package ${packageName}
|
|
811
|
+
|
|
812
|
+
import android.util.Log
|
|
813
|
+
import androidx.compose.foundation.layout.*
|
|
814
|
+
import androidx.compose.material3.*
|
|
815
|
+
import androidx.compose.runtime.*
|
|
816
|
+
import androidx.compose.ui.Alignment
|
|
817
|
+
import androidx.compose.ui.Modifier
|
|
818
|
+
import androidx.compose.ui.graphics.Color
|
|
819
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
820
|
+
import androidx.compose.ui.unit.dp
|
|
821
|
+
import kotlinx.coroutines.flow.MutableStateFlow
|
|
822
|
+
import kotlinx.coroutines.flow.StateFlow
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* DSL Interpreter
|
|
826
|
+
* Converts JSON DSL to Compose UI at runtime
|
|
827
|
+
*/
|
|
828
|
+
object DSLInterpreter {
|
|
829
|
+
private const val TAG = "DSLInterpreter"
|
|
830
|
+
|
|
831
|
+
private val _currentDSL = MutableStateFlow<UIDefinition?>(null)
|
|
832
|
+
val currentDSL: StateFlow<UIDefinition?> = _currentDSL
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Update the current DSL definition
|
|
836
|
+
*/
|
|
837
|
+
fun updateDSL(jsonString: String) {
|
|
838
|
+
try {
|
|
839
|
+
val definition = parseUIDefinition(jsonString)
|
|
840
|
+
_currentDSL.value = definition
|
|
841
|
+
Log.d(TAG, "DSL updated successfully")
|
|
842
|
+
} catch (e: Exception) {
|
|
843
|
+
Log.e(TAG, "Failed to parse DSL: \${e.message}", e)
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Render DSL as Compose UI
|
|
849
|
+
*/
|
|
850
|
+
@Composable
|
|
851
|
+
fun RenderDSL(definition: UIDefinition) {
|
|
852
|
+
RenderElement(definition.screen)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Render individual DSL element
|
|
857
|
+
*/
|
|
858
|
+
@Composable
|
|
859
|
+
fun RenderElement(element: DSLElement) {
|
|
860
|
+
when (element.type) {
|
|
861
|
+
"Column" -> RenderColumn(element)
|
|
862
|
+
"Row" -> RenderRow(element)
|
|
863
|
+
"Box" -> RenderBox(element)
|
|
864
|
+
"Text" -> RenderText(element)
|
|
865
|
+
"Button" -> RenderButton(element)
|
|
866
|
+
"Spacer" -> RenderSpacer(element)
|
|
867
|
+
else -> {
|
|
868
|
+
Log.w(TAG, "Unknown element type: \${element.type}")
|
|
869
|
+
Text("Unsupported: \${element.type}", color = Color.Red)
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
@Composable
|
|
875
|
+
private fun RenderColumn(element: DSLElement) {
|
|
876
|
+
Column(
|
|
877
|
+
modifier = parseModifier(element.modifier),
|
|
878
|
+
horizontalAlignment = parseHorizontalAlignment(element.horizontalAlignment),
|
|
879
|
+
verticalArrangement = parseVerticalArrangement(element.verticalArrangement)
|
|
880
|
+
) {
|
|
881
|
+
element.children?.forEach { child ->
|
|
882
|
+
RenderElement(child)
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
@Composable
|
|
888
|
+
private fun RenderRow(element: DSLElement) {
|
|
889
|
+
Row(
|
|
890
|
+
modifier = parseModifier(element.modifier),
|
|
891
|
+
verticalAlignment = parseVerticalAlignment(element.horizontalAlignment),
|
|
892
|
+
horizontalArrangement = parseHorizontalArrangement(element.verticalArrangement)
|
|
893
|
+
) {
|
|
894
|
+
element.children?.forEach { child ->
|
|
895
|
+
RenderElement(child)
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
@Composable
|
|
901
|
+
private fun RenderBox(element: DSLElement) {
|
|
902
|
+
Box(
|
|
903
|
+
modifier = parseModifier(element.modifier),
|
|
904
|
+
contentAlignment = parseContentAlignment(element.contentAlignment)
|
|
905
|
+
) {
|
|
906
|
+
element.children?.forEach { child ->
|
|
907
|
+
RenderElement(child)
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
@Composable
|
|
913
|
+
private fun RenderText(element: DSLElement) {
|
|
914
|
+
Text(
|
|
915
|
+
text = element.text ?: "",
|
|
916
|
+
style = parseTextStyle(element.style),
|
|
917
|
+
color = parseColor(element.color) ?: Color.Unspecified,
|
|
918
|
+
modifier = parseModifier(element.modifier)
|
|
919
|
+
)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
@Composable
|
|
923
|
+
private fun RenderButton(element: DSLElement) {
|
|
924
|
+
Button(
|
|
925
|
+
onClick = { handleClick(element.onClick) },
|
|
926
|
+
modifier = parseModifier(element.modifier),
|
|
927
|
+
enabled = element.enabled ?: true
|
|
928
|
+
) {
|
|
929
|
+
Text(element.text ?: "Button")
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
@Composable
|
|
934
|
+
private fun RenderSpacer(element: DSLElement) {
|
|
935
|
+
Spacer(
|
|
936
|
+
modifier = Modifier
|
|
937
|
+
.height(element.height?.dp ?: 0.dp)
|
|
938
|
+
.width(element.width?.dp ?: 0.dp)
|
|
939
|
+
)
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Parse DSL modifier to Compose Modifier
|
|
944
|
+
*/
|
|
945
|
+
private fun parseModifier(dslModifier: DSLModifier?): Modifier {
|
|
946
|
+
var modifier: Modifier = Modifier
|
|
947
|
+
|
|
948
|
+
dslModifier?.let { m ->
|
|
949
|
+
if (m.fillMaxSize == true) modifier = modifier.fillMaxSize()
|
|
950
|
+
if (m.fillMaxWidth == true) modifier = modifier.fillMaxWidth()
|
|
951
|
+
if (m.fillMaxHeight == true) modifier = modifier.fillMaxHeight()
|
|
952
|
+
|
|
953
|
+
m.padding?.let { modifier = modifier.padding(it.dp) }
|
|
954
|
+
m.paddingHorizontal?.let { modifier = modifier.padding(horizontal = it.dp) }
|
|
955
|
+
m.paddingVertical?.let { modifier = modifier.padding(vertical = it.dp) }
|
|
956
|
+
|
|
957
|
+
m.size?.let { modifier = modifier.size(it.dp) }
|
|
958
|
+
m.height?.let { modifier = modifier.height(it.dp) }
|
|
959
|
+
m.width?.let { modifier = modifier.width(it.dp) }
|
|
960
|
+
|
|
961
|
+
// Note: weight() is only available in RowScope/ColumnScope
|
|
962
|
+
// We'll handle it separately when needed
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return modifier
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Parse alignment strings
|
|
970
|
+
*/
|
|
971
|
+
private fun parseHorizontalAlignment(alignment: String?): Alignment.Horizontal {
|
|
972
|
+
return when (alignment?.lowercase()) {
|
|
973
|
+
"start" -> Alignment.Start
|
|
974
|
+
"centerhorizontally", "center" -> Alignment.CenterHorizontally
|
|
975
|
+
"end" -> Alignment.End
|
|
976
|
+
else -> Alignment.Start
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private fun parseVerticalAlignment(alignment: String?): Alignment.Vertical {
|
|
981
|
+
return when (alignment?.lowercase()) {
|
|
982
|
+
"top" -> Alignment.Top
|
|
983
|
+
"centervertically", "center" -> Alignment.CenterVertically
|
|
984
|
+
"bottom" -> Alignment.Bottom
|
|
985
|
+
else -> Alignment.Top
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
private fun parseContentAlignment(alignment: String?): Alignment {
|
|
990
|
+
return when (alignment?.lowercase()) {
|
|
991
|
+
"center" -> Alignment.Center
|
|
992
|
+
"topcenter" -> Alignment.TopCenter
|
|
993
|
+
"topstart" -> Alignment.TopStart
|
|
994
|
+
"topend" -> Alignment.TopEnd
|
|
995
|
+
"bottomcenter" -> Alignment.BottomCenter
|
|
996
|
+
"bottomstart" -> Alignment.BottomStart
|
|
997
|
+
"bottomend" -> Alignment.BottomEnd
|
|
998
|
+
"centerstart" -> Alignment.CenterStart
|
|
999
|
+
"centerend" -> Alignment.CenterEnd
|
|
1000
|
+
else -> Alignment.TopStart
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private fun parseVerticalArrangement(arrangement: String?): Arrangement.Vertical {
|
|
1005
|
+
return when (arrangement?.lowercase()) {
|
|
1006
|
+
"top" -> Arrangement.Top
|
|
1007
|
+
"center" -> Arrangement.Center
|
|
1008
|
+
"bottom" -> Arrangement.Bottom
|
|
1009
|
+
"spacebetween" -> Arrangement.SpaceBetween
|
|
1010
|
+
"spacearound" -> Arrangement.SpaceAround
|
|
1011
|
+
"spaceevenly" -> Arrangement.SpaceEvenly
|
|
1012
|
+
else -> Arrangement.Top
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
private fun parseHorizontalArrangement(arrangement: String?): Arrangement.Horizontal {
|
|
1017
|
+
return when (arrangement?.lowercase()) {
|
|
1018
|
+
"start" -> Arrangement.Start
|
|
1019
|
+
"center" -> Arrangement.Center
|
|
1020
|
+
"end" -> Arrangement.End
|
|
1021
|
+
"spacebetween" -> Arrangement.SpaceBetween
|
|
1022
|
+
"spacearound" -> Arrangement.SpaceAround
|
|
1023
|
+
"spaceevenly" -> Arrangement.SpaceEvenly
|
|
1024
|
+
else -> Arrangement.Start
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Parse text style
|
|
1030
|
+
*/
|
|
1031
|
+
@Composable
|
|
1032
|
+
private fun parseTextStyle(style: String?): androidx.compose.ui.text.TextStyle {
|
|
1033
|
+
return when (style?.lowercase()) {
|
|
1034
|
+
"headlinelarge" -> MaterialTheme.typography.headlineLarge
|
|
1035
|
+
"headlinemedium" -> MaterialTheme.typography.headlineMedium
|
|
1036
|
+
"headlinesmall" -> MaterialTheme.typography.headlineSmall
|
|
1037
|
+
"titlelarge" -> MaterialTheme.typography.titleLarge
|
|
1038
|
+
"titlemedium" -> MaterialTheme.typography.titleMedium
|
|
1039
|
+
"titlesmall" -> MaterialTheme.typography.titleSmall
|
|
1040
|
+
"bodylarge" -> MaterialTheme.typography.bodyLarge
|
|
1041
|
+
"bodymedium" -> MaterialTheme.typography.bodyMedium
|
|
1042
|
+
"bodysmall" -> MaterialTheme.typography.bodySmall
|
|
1043
|
+
"labellarge" -> MaterialTheme.typography.labelLarge
|
|
1044
|
+
"labelmedium" -> MaterialTheme.typography.labelMedium
|
|
1045
|
+
"labelsmall" -> MaterialTheme.typography.labelSmall
|
|
1046
|
+
else -> MaterialTheme.typography.bodyMedium
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Parse color from string
|
|
1052
|
+
*/
|
|
1053
|
+
private fun parseColor(colorString: String?): Color? {
|
|
1054
|
+
if (colorString == null) return null
|
|
1055
|
+
|
|
1056
|
+
return try {
|
|
1057
|
+
when {
|
|
1058
|
+
colorString.startsWith("#") -> {
|
|
1059
|
+
// Hex color
|
|
1060
|
+
Color(android.graphics.Color.parseColor(colorString))
|
|
1061
|
+
}
|
|
1062
|
+
else -> null
|
|
1063
|
+
}
|
|
1064
|
+
} catch (e: Exception) {
|
|
1065
|
+
Log.w(TAG, "Failed to parse color: \$colorString")
|
|
1066
|
+
null
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Handle click events
|
|
1072
|
+
*/
|
|
1073
|
+
private fun handleClick(action: String?) {
|
|
1074
|
+
if (action != null) {
|
|
1075
|
+
Log.d(TAG, "Button clicked: \$action")
|
|
1076
|
+
// TODO: Implement action handling
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
`;
|
|
1081
|
+
|
|
1082
|
+
await fs.ensureDir(path.dirname(interpreterPath));
|
|
1083
|
+
await fs.writeFile(interpreterPath, content);
|
|
610
1084
|
}
|
|
611
1085
|
|
|
612
1086
|
async function generateDSLTypes(
|
|
@@ -621,15 +1095,115 @@ async function generateDSLTypes(
|
|
|
621
1095
|
'DSLTypes.kt'
|
|
622
1096
|
);
|
|
623
1097
|
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1098
|
+
const content = `package ${packageName}
|
|
1099
|
+
|
|
1100
|
+
import org.json.JSONArray
|
|
1101
|
+
import org.json.JSONObject
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* DSL Type Definitions
|
|
1105
|
+
* Represents UI elements in JSON format that can be interpreted at runtime
|
|
1106
|
+
*/
|
|
1107
|
+
|
|
1108
|
+
data class UIDefinition(
|
|
1109
|
+
val version: String = "1.0",
|
|
1110
|
+
val screen: DSLElement
|
|
1111
|
+
)
|
|
1112
|
+
|
|
1113
|
+
data class DSLElement(
|
|
1114
|
+
val type: String,
|
|
1115
|
+
val text: String? = null,
|
|
1116
|
+
val style: String? = null,
|
|
1117
|
+
val color: String? = null,
|
|
1118
|
+
val modifier: DSLModifier? = null,
|
|
1119
|
+
val horizontalAlignment: String? = null,
|
|
1120
|
+
val verticalArrangement: String? = null,
|
|
1121
|
+
val contentAlignment: String? = null,
|
|
1122
|
+
val height: Int? = null,
|
|
1123
|
+
val width: Int? = null,
|
|
1124
|
+
val onClick: String? = null,
|
|
1125
|
+
val enabled: Boolean? = true,
|
|
1126
|
+
val imageVector: String? = null,
|
|
1127
|
+
val tint: String? = null,
|
|
1128
|
+
val contentDescription: String? = null,
|
|
1129
|
+
val children: List<DSLElement>? = null
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1132
|
+
data class DSLModifier(
|
|
1133
|
+
val fillMaxSize: Boolean? = null,
|
|
1134
|
+
val fillMaxWidth: Boolean? = null,
|
|
1135
|
+
val fillMaxHeight: Boolean? = null,
|
|
1136
|
+
val padding: Int? = null,
|
|
1137
|
+
val paddingHorizontal: Int? = null,
|
|
1138
|
+
val paddingVertical: Int? = null,
|
|
1139
|
+
val size: Int? = null,
|
|
1140
|
+
val height: Int? = null,
|
|
1141
|
+
val width: Int? = null,
|
|
1142
|
+
val weight: Float? = null
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Parse JSON string to UIDefinition
|
|
1147
|
+
*/
|
|
1148
|
+
fun parseUIDefinition(json: String): UIDefinition {
|
|
1149
|
+
val obj = JSONObject(json)
|
|
1150
|
+
val version = obj.optString("version", "1.0")
|
|
1151
|
+
val screenObj = obj.getJSONObject("screen")
|
|
1152
|
+
|
|
1153
|
+
return UIDefinition(
|
|
1154
|
+
version = version,
|
|
1155
|
+
screen = parseDSLElement(screenObj)
|
|
1156
|
+
)
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Parse JSONObject to DSLElement
|
|
1161
|
+
*/
|
|
1162
|
+
fun parseDSLElement(obj: JSONObject): DSLElement {
|
|
1163
|
+
val children = if (obj.has("children")) {
|
|
1164
|
+
val childrenArray = obj.getJSONArray("children")
|
|
1165
|
+
List(childrenArray.length()) { i ->
|
|
1166
|
+
parseDSLElement(childrenArray.getJSONObject(i))
|
|
1167
|
+
}
|
|
1168
|
+
} else null
|
|
1169
|
+
|
|
1170
|
+
val modifier = if (obj.has("modifier")) {
|
|
1171
|
+
val modObj = obj.getJSONObject("modifier")
|
|
1172
|
+
DSLModifier(
|
|
1173
|
+
fillMaxSize = modObj.optBoolean("fillMaxSize"),
|
|
1174
|
+
fillMaxWidth = modObj.optBoolean("fillMaxWidth"),
|
|
1175
|
+
fillMaxHeight = modObj.optBoolean("fillMaxHeight"),
|
|
1176
|
+
padding = if (modObj.has("padding")) modObj.getInt("padding") else null,
|
|
1177
|
+
paddingHorizontal = if (modObj.has("paddingHorizontal")) modObj.getInt("paddingHorizontal") else null,
|
|
1178
|
+
paddingVertical = if (modObj.has("paddingVertical")) modObj.getInt("paddingVertical") else null,
|
|
1179
|
+
size = if (modObj.has("size")) modObj.getInt("size") else null,
|
|
1180
|
+
height = if (modObj.has("height")) modObj.getInt("height") else null,
|
|
1181
|
+
width = if (modObj.has("width")) modObj.getInt("width") else null,
|
|
1182
|
+
weight = if (modObj.has("weight")) modObj.getDouble("weight").toFloat() else null
|
|
1183
|
+
)
|
|
1184
|
+
} else null
|
|
1185
|
+
|
|
1186
|
+
return DSLElement(
|
|
1187
|
+
type = obj.getString("type"),
|
|
1188
|
+
text = if (obj.has("text")) obj.getString("text") else null,
|
|
1189
|
+
style = if (obj.has("style")) obj.getString("style") else null,
|
|
1190
|
+
color = if (obj.has("color")) obj.getString("color") else null,
|
|
1191
|
+
modifier = modifier,
|
|
1192
|
+
horizontalAlignment = if (obj.has("horizontalAlignment")) obj.getString("horizontalAlignment") else null,
|
|
1193
|
+
verticalArrangement = if (obj.has("verticalArrangement")) obj.getString("verticalArrangement") else null,
|
|
1194
|
+
contentAlignment = if (obj.has("contentAlignment")) obj.getString("contentAlignment") else null,
|
|
1195
|
+
height = if (obj.has("height")) obj.getInt("height") else null,
|
|
1196
|
+
width = if (obj.has("width")) obj.getInt("width") else null,
|
|
1197
|
+
onClick = if (obj.has("onClick")) obj.getString("onClick") else null,
|
|
1198
|
+
enabled = obj.optBoolean("enabled", true),
|
|
1199
|
+
imageVector = if (obj.has("imageVector")) obj.getString("imageVector") else null,
|
|
1200
|
+
tint = if (obj.has("tint")) obj.getString("tint") else null,
|
|
1201
|
+
contentDescription = if (obj.has("contentDescription")) obj.getString("contentDescription") else null,
|
|
1202
|
+
children = children
|
|
1203
|
+
)
|
|
1204
|
+
}
|
|
1205
|
+
`;
|
|
1206
|
+
|
|
1207
|
+
await fs.ensureDir(path.dirname(typesPath));
|
|
1208
|
+
await fs.writeFile(typesPath, content);
|
|
635
1209
|
}
|