commit 66242f9443ae55fc57cdc78da35253a304b81f65 Author: Acid Date: Sun Mar 22 20:31:26 2026 -0400 initial gui implemented diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c8b76a --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Build artifacts +.gradle/ +build/ +app/build/ +*.apk +*.aar +*.jks + +# IDE settings +.idea/ +*.iml +.DS_Store +captures/ + +# Sensitive info +local.properties + +# Generated files +.cxx/ +.externalNativeBuild/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..7f73dc8 --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml new file mode 100644 index 0000000..8d6eef7 --- /dev/null +++ b/.idea/caches/deviceStreaming.xml @@ -0,0 +1,1538 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..de8dbb5 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..2504dc6 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..efd1031 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml new file mode 100644 index 0000000..539e3b8 --- /dev/null +++ b/.idea/studiobot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..e965aa7 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + namespace 'com.example.stims' + compileSdk 34 + + defaultConfig { + applicationId "com.example.stims" + minSdk 24 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion '1.5.8' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' + implementation 'androidx.activity:activity-compose:1.8.2' + implementation platform('androidx.compose:compose-bom:2023.10.01') + implementation 'androidx.compose.ui:ui' + implementation 'androidx.compose.ui:ui-graphics' + implementation 'androidx.compose.ui:ui-tooling-preview' + implementation 'androidx.compose.material3:material3' + implementation 'androidx.compose.material:material-icons-extended' + + // Added for XML theme support + implementation 'com.google.android.material:material:1.11.0' + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation platform('androidx.compose:compose-bom:2023.10.01') + androidTestImplementation 'androidx.compose.ui:ui-test-junit4' + debugImplementation 'androidx.compose.ui:ui-tooling' + debugImplementation 'androidx.compose.ui:ui-test-manifest' +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6cee9e4 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/stims/MainActivity.kt b/app/src/main/java/com/example/stims/MainActivity.kt new file mode 100644 index 0000000..4379b1c --- /dev/null +++ b/app/src/main/java/com/example/stims/MainActivity.kt @@ -0,0 +1,206 @@ +package com.example.stims + +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.core.graphics.drawable.toBitmap +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +data class AppInfo( + val name: String, + val packageName: String, + val icon: Drawable? = null +) + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + AppListScreen() + } + } + } +} + +@Composable +fun AppListScreen() { + val context = LocalContext.current + val packageManager = context.packageManager + + var allApps by remember { mutableStateOf>(emptyList()) } + var isLoading by remember { mutableStateOf(true) } + + LaunchedEffect(Unit) { + allApps = withContext(Dispatchers.IO) { + val intent = Intent(Intent.ACTION_MAIN, null).apply { + addCategory(Intent.CATEGORY_LAUNCHER) + } + // Query for all activities that can be launched + val resolveInfos = packageManager.queryIntentActivities(intent, 0) + + resolveInfos.map { resolveInfo -> + AppInfo( + name = resolveInfo.loadLabel(packageManager).toString(), + packageName = resolveInfo.activityInfo.packageName, + icon = resolveInfo.loadIcon(packageManager) + ) + }.distinctBy { it.packageName } // Avoid duplicates if an app has multiple launchers + .sortedBy { it.name } + } + isLoading = false + } + + var searchQuery by remember { mutableStateOf("") } + val selectedApps = remember { mutableStateListOf() } + + val filteredApps = remember(searchQuery, allApps) { + if (searchQuery.isBlank()) { + allApps + } else { + allApps.filter { + it.name.contains(searchQuery, ignoreCase = true) || + it.packageName.contains(searchQuery, ignoreCase = true) + } + } + } + + if (isLoading) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } else { + Column { + OutlinedTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + placeholder = { Text("Search apps...") }, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + singleLine = true + ) + + if (selectedApps.isNotEmpty()) { + Text( + text = "${selectedApps.size} apps selected", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + color = MaterialTheme.colorScheme.primary + ) + } + + LazyColumn(modifier = Modifier.weight(1f)) { + items(filteredApps) { app -> + val isSelected = selectedApps.any { it.packageName == app.packageName } + AppItem( + app = app, + isSelected = isSelected, + onToggleSelection = { + if (isSelected) { + selectedApps.removeAll { it.packageName == app.packageName } + } else { + selectedApps.add(app) + } + } + ) + } + } + + if (selectedApps.isNotEmpty()) { + Button( + onClick = { + val names = selectedApps.joinToString { it.name } + Toast.makeText(context, "Selected: $names", Toast.LENGTH_LONG).show() + }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text("Confirm Selection") + } + } + } + } +} + +@Composable +fun AppItem( + app: AppInfo, + isSelected: Boolean, + onToggleSelection: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onToggleSelection) + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (app.icon != null) { + Image( + bitmap = app.icon.toBitmap().asImageBitmap(), + contentDescription = null, + modifier = Modifier.size(48.dp) + ) + } else { + Surface( + modifier = Modifier.size(48.dp), + color = MaterialTheme.colorScheme.primaryContainer, + shape = MaterialTheme.shapes.small + ) {} + } + + Spacer(modifier = Modifier.width(16.dp)) + + Column(modifier = Modifier.weight(1f)) { + Text(text = app.name, style = MaterialTheme.typography.bodyLarge) + Text( + text = app.packageName, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Checkbox( + checked = isSelected, + onCheckedChange = { onToggleSelection() } + ) + } +} + +@Preview(showBackground = true) +@Composable +fun AppItemPreview() { + MaterialTheme { + AppItem( + app = AppInfo("Example App", "com.example.app", null), + isSelected = true, + onToggleSelection = {} + ) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..5ae43b0 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Stims + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..838b9ce --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..48bc87e --- /dev/null +++ b/build.gradle @@ -0,0 +1,18 @@ +// Top-level build file +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.2.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..f55b0ed --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +android.useAndroidX=true +android.enableJetifier=true +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1af9e09 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f374a79 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = "stims" +include ':app'