Initial commit
CI / schema-and-codegen (push) Failing after 29s

This commit is contained in:
root
2026-06-20 03:54:40 -04:00
commit 9494f85b78
57 changed files with 3324 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
id("com.apollographql.apollo3")
}
android {
namespace = "com.tlm.android"
compileSdk = 34
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
applicationId = "com.tlm.android"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "0.0.1"
val gqlUrl = project.findProperty("TLM_GRAPHQL_URL") as String? ?: "http://10.0.2.2:4000/graphql"
buildConfigField("String", "GRAPHQL_URL", "\"$gqlUrl\"")
}
buildTypes {
release {
isMinifyEnabled = false
}
}
buildFeatures {
compose = true
buildConfig = true
}
kotlinOptions {
jvmTarget = "17"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
apollo {
service("tlm") {
packageName.set("com.tlm.graphql")
schemaFile.set(file("../../../packages/graphql-schema/schema.graphql"))
srcDir("src/main/graphql")
}
}
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2024.09.03")
implementation(composeBom)
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation("androidx.work:work-runtime-ktx:2.9.1")
implementation("com.apollographql.apollo3:apollo-runtime:3.8.5")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.google.android.material:material:1.12.0")
}
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.TLM"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,19 @@
query Chats {
chats {
__typename
id
... on DirectThread {
peer {
id
displayName
}
}
... on Group {
title
}
... on Channel {
title
}
unreadCount
}
}
@@ -0,0 +1,10 @@
mutation Login($email: String!, $password: String!) {
login(email: $email, password: $password) {
accessToken
refreshToken
user {
id
displayName
}
}
}
@@ -0,0 +1,7 @@
mutation SendMessage($chatId: ID!, $body: String) {
sendMessage(chatId: $chatId, body: $body) {
id
seq
body
}
}
@@ -0,0 +1,193 @@
package com.tlm.android
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import com.apollographql.apollo3.exception.ApolloException
import com.tlm.android.data.OutboxRepository
import com.tlm.graphql.ChatsQuery
import com.tlm.graphql.LoginMutation
import com.tlm.graphql.SendMessageMutation
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val prefs = getSharedPreferences("tlm", Context.MODE_PRIVATE)
val ok =
OkHttpClient.Builder()
.addInterceptor { chain ->
val t = prefs.getString("accessToken", null)
val b = chain.request().newBuilder()
if (!t.isNullOrBlank()) b.addHeader("Authorization", "Bearer $t")
chain.proceed(b.build())
}
.build()
val apollo =
ApolloClient.Builder()
.serverUrl(BuildConfig.GRAPHQL_URL)
.okHttpClient(ok)
.build()
val outbox = OutboxRepository()
setContent {
MaterialTheme {
val scope = rememberCoroutineScope()
val email = remember { mutableStateOf("alice@example.com") }
val password = remember { mutableStateOf("password123") }
val tokenPresent = remember { mutableStateOf(!prefs.getString("accessToken", null).isNullOrBlank()) }
val chats = remember { mutableStateOf<List<ChatsQuery.Chat>>(emptyList()) }
val activeId = remember { mutableStateOf<String?>(null) }
val draft = remember { mutableStateOf("") }
val status = remember { mutableStateOf("") }
fun loadChats() {
scope.launch {
try {
val res = apollo.query(ChatsQuery()).execute()
if (res.data != null) {
chats.value = res.data!!.chats.filterNotNull()
} else {
status.value = res.errors?.joinToString { it.message } ?: "chats error"
}
} catch (e: ApolloException) {
status.value = e.message ?: "network"
}
}
}
LaunchedEffect(tokenPresent.value) {
if (tokenPresent.value) loadChats()
}
Column(Modifier.fillMaxSize().padding(12.dp)) {
Text("TLM (dev)", style = MaterialTheme.typography.headlineSmall)
if (!tokenPresent.value) {
OutlinedTextField(
value = email.value,
onValueChange = { email.value = it },
label = { Text("Email") },
modifier = Modifier.fillMaxWidth(),
)
OutlinedTextField(
value = password.value,
onValueChange = { password.value = it },
label = { Text("Password") },
modifier = Modifier.fillMaxWidth(),
)
Button(
onClick = {
scope.launch {
try {
val res =
apollo
.mutation(
LoginMutation(
email = email.value,
password = password.value,
),
)
.execute()
val t = res.data?.login?.accessToken
if (!t.isNullOrBlank()) {
prefs.edit().putString("accessToken", t).apply()
tokenPresent.value = true
status.value = "ok"
} else {
status.value = res.errors?.joinToString { it.message } ?: "login failed"
}
} catch (e: ApolloException) {
status.value = e.message ?: "network"
}
}
},
) {
Text("Login")
}
} else {
Row {
Button(onClick = { loadChats() }) { Text("Refresh chats") }
Button(
onClick = {
prefs.edit().remove("accessToken").apply()
tokenPresent.value = false
},
) {
Text("Logout")
}
}
LazyColumn(modifier = Modifier.weight(1f)) {
items(chats.value) { c ->
Button(
onClick = { activeId.value = c.id },
modifier = Modifier.fillMaxWidth(),
) {
Text("${c.__typename} ${c.id} (${c.unreadCount} unread)")
}
}
}
OutlinedTextField(
value = draft.value,
onValueChange = { draft.value = it },
label = { Text("Message") },
modifier = Modifier.fillMaxWidth(),
)
Button(
onClick = {
val id = activeId.value
if (id == null) {
status.value = "pick a chat"
return@Button
}
val body = draft.value
outbox.enqueue("send:$id:${body.length}")
scope.launch {
try {
apollo
.mutation(
SendMessageMutation(
chatId = id,
body =
if (body.isBlank()) Optional.absent()
else Optional.present(body),
),
)
.execute()
draft.value = ""
loadChats()
} catch (e: ApolloException) {
status.value = e.message ?: "send failed"
}
}
},
) {
Text("Send")
}
}
Text(status.value, modifier = Modifier.padding(top = 8.dp))
}
}
}
}
}
@@ -0,0 +1,23 @@
package com.tlm.android.data
import java.util.concurrent.ConcurrentLinkedQueue
/**
* Minimal outbox for optimistic sends; drain with WorkManager in a real build.
*/
class OutboxRepository {
private val q = ConcurrentLinkedQueue<String>()
fun enqueue(payload: String) {
q.add(payload)
}
fun drain(max: Int = 50): List<String> {
val out = ArrayList<String>()
repeat(max) {
val v = q.poll() ?: return out
out.add(v)
}
return out
}
}
@@ -0,0 +1,15 @@
package com.tlm.android.push
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
/**
* Placeholder for FCM token registration against your GraphQL `registerDevice` mutation.
*/
class FcmRegistrationWorker(
appContext: Context,
params: WorkerParameters,
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result = Result.success()
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">TLM</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.TLM" parent="Theme.Material3.DayNight.NoActionBar" />
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">localhost</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
</network-security-config>
+6
View File
@@ -0,0 +1,6 @@
plugins {
id("com.android.application") version "8.7.2" apply false
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false
id("com.apollographql.apollo3") version "3.8.5" apply false
}
+3
View File
@@ -0,0 +1,3 @@
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+18
View File
@@ -0,0 +1,18 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "tlm-android"
include(":app")