Abrarul8541 commited on
Commit
d8c0440
·
verified ·
1 Parent(s): ccfda31

Phase 1: Compose UI - capture bar, dashboard, themes, 7-tap dev menu

Browse files
app/src/main/java/io/github/abrarul/modularai/MainActivity.kt ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package io.github.abrarul.modularai
2
+
3
+ import android.content.Intent
4
+ import android.os.Bundle
5
+ import androidx.activity.ComponentActivity
6
+ import androidx.activity.compose.setContent
7
+ import androidx.compose.foundation.clickable
8
+ import androidx.compose.foundation.layout.*
9
+ import androidx.compose.foundation.lazy.LazyColumn
10
+ import androidx.compose.foundation.lazy.items
11
+ import androidx.compose.foundation.isSystemInDarkTheme
12
+ import androidx.compose.material3.*
13
+ import androidx.compose.runtime.*
14
+ import androidx.compose.ui.Modifier
15
+ import androidx.compose.ui.unit.dp
16
+ import io.github.abrarul.modularai.data.AppDb
17
+ import io.github.abrarul.modularai.data.Capture
18
+ import io.github.abrarul.modularai.model.DeviceProfiler
19
+ import io.github.abrarul.modularai.model.Profile
20
+ import io.github.abrarul.modularai.model.Recommendation
21
+ import io.github.abrarul.modularai.security.Vault
22
+ import kotlinx.coroutines.Dispatchers
23
+ import kotlinx.coroutines.launch
24
+ import kotlinx.coroutines.withContext
25
+
26
+ class MainActivity : ComponentActivity() {
27
+ override fun onCreate(savedInstanceState: Bundle?) {
28
+ super.onCreate(savedInstanceState)
29
+ val shared = intent.takeIf { it.action == Intent.ACTION_SEND }?.getStringExtra(Intent.EXTRA_TEXT)
30
+ setContent { App(shared) }
31
+ }
32
+
33
+ @OptIn(ExperimentalMaterial3Api::class)
34
+ @Composable
35
+ fun App(sharedText: String?) {
36
+ var mode by remember { mutableStateOf("system") } // system | dark | light
37
+ val dark = when (mode) { "dark" -> true; "light" -> false; else -> isSystemInDarkTheme() }
38
+ MaterialTheme(colorScheme = if (dark) darkColorScheme() else lightColorScheme()) {
39
+ val db = remember { AppDb.get(this) }
40
+ val items by db.captures().all().collectAsState(initial = emptyList())
41
+ val scope = rememberCoroutineScope()
42
+ var text by remember { mutableStateOf(sharedText.orEmpty()) }
43
+ var taps by remember { mutableIntStateOf(0) }
44
+ var devMenu by remember { mutableStateOf(false) }
45
+ var profile by remember { mutableStateOf<Pair<Profile, Recommendation>?>(null) }
46
+
47
+ LaunchedEffect(Unit) {
48
+ profile = withContext(Dispatchers.Default) {
49
+ val p = DeviceProfiler.scan(this@MainActivity); p to DeviceProfiler.recommend(p)
50
+ }
51
+ }
52
+
53
+ Scaffold(
54
+ topBar = {
55
+ TopAppBar(
56
+ title = {
57
+ Text("ModularAI", Modifier.clickable {
58
+ taps++
59
+ if (taps >= 7) { devMenu = !devMenu; taps = 0 }
60
+ })
61
+ },
62
+ actions = {
63
+ TextButton(onClick = {
64
+ mode = when (mode) { "system" -> "dark"; "dark" -> "light"; else -> "system" }
65
+ }) { Text(mode) }
66
+ }
67
+ )
68
+ },
69
+ bottomBar = {
70
+ Row(Modifier.fillMaxWidth().padding(12.dp).navigationBarsPadding()) {
71
+ OutlinedTextField(
72
+ value = text, onValueChange = { text = it },
73
+ modifier = Modifier.weight(1f), placeholder = { Text("Capture anything...") }
74
+ )
75
+ Spacer(Modifier.width(8.dp))
76
+ Button(onClick = {
77
+ val t = text.trim()
78
+ if (t.isNotEmpty()) scope.launch {
79
+ withContext(Dispatchers.IO) { db.captures().insert(Capture(cipher = Vault.encrypt(t))) }
80
+ text = ""
81
+ }
82
+ }) { Text("Save") }
83
+ }
84
+ }
85
+ ) { pad ->
86
+ LazyColumn(Modifier.padding(pad).padding(horizontal = 12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
87
+ profile?.let { (p, r) ->
88
+ item {
89
+ Card(Modifier.fillMaxWidth()) {
90
+ Column(Modifier.padding(16.dp)) {
91
+ Text("Device: ${p.soc} | ${p.abi} | ${p.cores} cores", style = MaterialTheme.typography.titleSmall)
92
+ Text("RAM ${p.totalRamMb} MB (free ${p.availRamMb}) | Storage free ${p.freeStorageMb} MB | Disk ${"%.0f".format(p.storageMBps)} MB/s | Vulkan ${p.vulkan}")
93
+ Text("Tier: ${p.tier.label} (${p.tier.sizes})")
94
+ Text("Suggested: up to ~${"%.1f".format(r.maxParamsB)}B, ${r.quant}, ctx ${r.contextTokens}, est ${r.estTokensPerSec} tok/s")
95
+ }
96
+ }
97
+ }
98
+ }
99
+ if (devMenu) item {
100
+ Card(Modifier.fillMaxWidth()) {
101
+ Column(Modifier.padding(16.dp)) {
102
+ Text("Developer menu unlocked", style = MaterialTheme.typography.titleSmall)
103
+ Text("Termux bridge, plugins, force-run oversize models, update channel.")
104
+ }
105
+ }
106
+ }
107
+ items(items, key = { it.id }) { c ->
108
+ val plain = remember(c.cipher) { runCatching { Vault.decrypt(c.cipher) }.getOrDefault("[unreadable]") }
109
+ Card(Modifier.fillMaxWidth()) {
110
+ Row(Modifier.padding(16.dp)) {
111
+ Text(plain, Modifier.weight(1f))
112
+ TextButton(onClick = { scope.launch(Dispatchers.IO) { db.captures().delete(c) } }) { Text("Delete") }
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }