diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0f4c0ac --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/com/thisux/droidclaw/DroidClawApp.kt b/android/app/src/main/java/com/thisux/droidclaw/DroidClawApp.kt new file mode 100644 index 0000000..31752b6 --- /dev/null +++ b/android/app/src/main/java/com/thisux/droidclaw/DroidClawApp.kt @@ -0,0 +1,14 @@ +package com.thisux.droidclaw + +import android.app.Application +import com.thisux.droidclaw.data.SettingsStore + +class DroidClawApp : Application() { + lateinit var settingsStore: SettingsStore + private set + + override fun onCreate() { + super.onCreate() + settingsStore = SettingsStore(this) + } +} diff --git a/android/app/src/main/java/com/thisux/droidclaw/data/SettingsStore.kt b/android/app/src/main/java/com/thisux/droidclaw/data/SettingsStore.kt new file mode 100644 index 0000000..f9da10b --- /dev/null +++ b/android/app/src/main/java/com/thisux/droidclaw/data/SettingsStore.kt @@ -0,0 +1,55 @@ +package com.thisux.droidclaw.data + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +val Context.dataStore: DataStore by preferencesDataStore(name = "settings") + +object SettingsKeys { + val API_KEY = stringPreferencesKey("api_key") + val SERVER_URL = stringPreferencesKey("server_url") + val DEVICE_NAME = stringPreferencesKey("device_name") + val AUTO_CONNECT = booleanPreferencesKey("auto_connect") +} + +class SettingsStore(private val context: Context) { + + val apiKey: Flow = context.dataStore.data.map { prefs -> + prefs[SettingsKeys.API_KEY] ?: "" + } + + val serverUrl: Flow = context.dataStore.data.map { prefs -> + prefs[SettingsKeys.SERVER_URL] ?: "wss://localhost:8080" + } + + val deviceName: Flow = context.dataStore.data.map { prefs -> + prefs[SettingsKeys.DEVICE_NAME] ?: android.os.Build.MODEL + } + + val autoConnect: Flow = context.dataStore.data.map { prefs -> + prefs[SettingsKeys.AUTO_CONNECT] ?: false + } + + suspend fun setApiKey(value: String) { + context.dataStore.edit { it[SettingsKeys.API_KEY] = value } + } + + suspend fun setServerUrl(value: String) { + context.dataStore.edit { it[SettingsKeys.SERVER_URL] = value } + } + + suspend fun setDeviceName(value: String) { + context.dataStore.edit { it[SettingsKeys.DEVICE_NAME] = value } + } + + suspend fun setAutoConnect(value: Boolean) { + context.dataStore.edit { it[SettingsKeys.AUTO_CONNECT] = value } + } +} diff --git a/android/app/src/main/java/com/thisux/droidclaw/model/AppState.kt b/android/app/src/main/java/com/thisux/droidclaw/model/AppState.kt new file mode 100644 index 0000000..bfa1001 --- /dev/null +++ b/android/app/src/main/java/com/thisux/droidclaw/model/AppState.kt @@ -0,0 +1,30 @@ +package com.thisux.droidclaw.model + +enum class ConnectionState { + Disconnected, + Connecting, + Connected, + Error +} + +enum class GoalStatus { + Idle, + Running, + Completed, + Failed +} + +data class AgentStep( + val step: Int, + val action: String, + val reasoning: String, + val timestamp: Long = System.currentTimeMillis() +) + +data class GoalSession( + val sessionId: String, + val goal: String, + val steps: List, + val status: GoalStatus, + val timestamp: Long = System.currentTimeMillis() +) diff --git a/android/app/src/main/java/com/thisux/droidclaw/model/Protocol.kt b/android/app/src/main/java/com/thisux/droidclaw/model/Protocol.kt new file mode 100644 index 0000000..8d1d4f8 --- /dev/null +++ b/android/app/src/main/java/com/thisux/droidclaw/model/Protocol.kt @@ -0,0 +1,75 @@ +package com.thisux.droidclaw.model + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +@Serializable +data class AuthMessage( + val type: String = "auth", + val apiKey: String, + val deviceInfo: DeviceInfoMsg? = null +) + +@Serializable +data class DeviceInfoMsg( + val model: String, + val androidVersion: String, + val screenWidth: Int, + val screenHeight: Int +) + +@Serializable +data class ScreenResponse( + val type: String = "screen", + val requestId: String, + val elements: List, + val screenshot: String? = null, + val packageName: String? = null +) + +@Serializable +data class ResultResponse( + val type: String = "result", + val requestId: String, + val success: Boolean, + val error: String? = null, + val data: String? = null +) + +@Serializable +data class GoalMessage( + val type: String = "goal", + val text: String +) + +@Serializable +data class PongMessage( + val type: String = "pong" +) + +@Serializable +data class ServerMessage( + val type: String, + val requestId: String? = null, + val deviceId: String? = null, + val message: String? = null, + val sessionId: String? = null, + val goal: String? = null, + val success: Boolean? = null, + val stepsUsed: Int? = null, + val step: Int? = null, + val action: JsonObject? = null, + val reasoning: String? = null, + val screenHash: String? = null, + val x: Int? = null, + val y: Int? = null, + val x1: Int? = null, + val y1: Int? = null, + val x2: Int? = null, + val y2: Int? = null, + val duration: Int? = null, + val text: String? = null, + val packageName: String? = null, + val url: String? = null, + val code: Int? = null +) diff --git a/android/app/src/main/java/com/thisux/droidclaw/model/UIElement.kt b/android/app/src/main/java/com/thisux/droidclaw/model/UIElement.kt new file mode 100644 index 0000000..68ac65d --- /dev/null +++ b/android/app/src/main/java/com/thisux/droidclaw/model/UIElement.kt @@ -0,0 +1,26 @@ +package com.thisux.droidclaw.model + +import kotlinx.serialization.Serializable + +@Serializable +data class UIElement( + val id: String = "", + val text: String = "", + val type: String = "", + val bounds: String = "", + val center: List = listOf(0, 0), + val size: List = listOf(0, 0), + val clickable: Boolean = false, + val editable: Boolean = false, + val enabled: Boolean = false, + val checked: Boolean = false, + val focused: Boolean = false, + val selected: Boolean = false, + val scrollable: Boolean = false, + val longClickable: Boolean = false, + val password: Boolean = false, + val hint: String = "", + val action: String = "read", + val parent: String = "", + val depth: Int = 0 +)