Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f53dd8d
Update Gradle wrapper to version 9.3.1
kitswas Mar 3, 2026
cdee86e
Update Android Gradle plugin to 9 from 8
kitswas Mar 3, 2026
7ce4b2a
Migrate deprecated properties
kitswas Mar 3, 2026
1674b6e
Update dependencies
kitswas Mar 3, 2026
5a239d0
Refactor tests
kitswas Mar 3, 2026
e436487
Mise config to manage java version
kitswas Mar 3, 2026
563ff2e
Automated tests
kitswas Mar 3, 2026
331e990
Migrate to Java 21
kitswas Mar 3, 2026
bb50cff
Update GitHub Actions to use specific versions for checkout and setup…
kitswas Mar 3, 2026
16e1647
Fix workflow
kitswas Mar 3, 2026
a0fcd26
Improved testing workflow
kitswas Mar 3, 2026
8859904
Add step to make gradlew executable in build workflow
kitswas Mar 3, 2026
6d2d2af
Show stacktrace for better debugging
kitswas Mar 3, 2026
6d778fc
Fix tests
kitswas Mar 3, 2026
f606af2
Got rid of Robolectric
kitswas Mar 3, 2026
19bd9c4
Extract UI strings
kitswas Mar 3, 2026
80a95be
Format and optimize imports
kitswas Mar 3, 2026
f76660a
Network diagnostics on connection failure
kitswas Mar 4, 2026
3f94071
Tests
kitswas Mar 5, 2026
82d3a1c
Switch to sourcesets for VGP_Data_Exchange integration
kitswas Mar 5, 2026
65dccae
Handle SettingsScreen state changes better
kitswas Mar 5, 2026
5011d00
rememberSaveable for diagnostics dialog
kitswas Mar 5, 2026
a859ffc
Code formatting
kitswas Mar 5, 2026
d1af7dc
Lint fix
kitswas Mar 5, 2026
1f4b76c
Update Android Gradle Plugin to version 9.1.0
kitswas Apr 2, 2026
67de351
Update dependencies
kitswas Apr 2, 2026
b7b3357
Setting to save last IP and Port
kitswas Apr 2, 2026
9a44dc4
Add DiagnosticsDialog preview and update colour usage
kitswas Apr 2, 2026
efc8999
Full screen mode
kitswas Apr 2, 2026
4bf4eab
Make Sonar happy
kitswas Apr 2, 2026
45ee559
Minor fix
kitswas Apr 2, 2026
9f90190
Update Gradle wrapper to version 9.4.1
kitswas Apr 2, 2026
b54dfd7
Bumped version to v0.4.2
kitswas Apr 2, 2026
59923d2
Cleanup
kitswas Apr 2, 2026
72d23f4
Explicitly mark usesCleartextTraffic as false
kitswas Apr 2, 2026
a04b404
Refactor AnalogStick to use a dedicated state class
kitswas Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/build_and_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Build and Test
on: [pull_request, push, workflow_dispatch]

jobs:
# Job 1: Build and fast checks
build-and-unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: true

- name: Set up JDK
uses: actions/setup-java@v5
with:
java-version: "21"
distribution: "temurin"
cache: gradle

- name: Build, lint and run Unit Tests in parallel
run: |
chmod +x gradlew
./gradlew build lint test

# Job 2: Parallel Instrumented Tests
instrumented-tests:
needs: build-and-unit-test # Only run if build/unit tests pass
runs-on: ubuntu-latest
strategy:
fail-fast: false # Don't cancel API 34 if API 26 fails
matrix:
api-level:
- 26 # Minimum supported API level
- 34 # Target API level for testing
- 36 # Latest API level

steps:
- uses: actions/checkout@v6
with:
submodules: true

- name: set up JDK
uses: actions/setup-java@v5
with:
java-version: "21"
distribution: "temurin"
cache: gradle

- name: Make gradlew executable
run: chmod +x gradlew

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Run Instrumented Tests (API ${{ matrix.api-level }})
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ matrix.api-level >= 31 && 'google_apis' || 'default' }}
arch: x86_64
disable-animations: true
script: ./gradlew connectedCheck --stacktrace

- name: Upload Test Report (API ${{ matrix.api-level }})
if: always()
uses: actions/upload-artifact@v7
with:
name: reports-api-${{ matrix.api-level }}
path: app/build/reports/androidTests/connected/
retention-days: 7
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,5 @@ replay_pid*
# Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects
.kotlin/

# Generated files
/app/src/main/java/io/github/kitswas/VGP_Data_Exchange

# Ignore the following file to keep keystore information private
signing.properties
44 changes: 19 additions & 25 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.dokka)
alias(libs.plugins.kotlin.parcelize)
Expand All @@ -21,8 +20,8 @@ android {
minSdk = 26
// Specifies the API level used to test the app.
targetSdk = 34
versionCode = 10
versionName = "0.4.1"
versionCode = 11
versionName = "0.4.2"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down Expand Up @@ -52,18 +51,13 @@ android {

compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("17")
}
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}

buildFeatures {
compose = true
resValues = true
}

composeOptions {
Expand All @@ -82,10 +76,22 @@ android {
includeInApk = false
includeInBundle = false
}

sourceSets {
getByName("main") {
java.directories.add("../VGP_Data_Exchange")
}
}
}

kotlin {
compilerOptions {
jvmTarget = JvmTarget.fromTarget("21")
}
}

val props = Properties()
val propFile = File("signing.properties")
val propFile: File = rootProject.file("signing.properties")

if (propFile.canRead()) {
props.load(FileInputStream(propFile))
Expand Down Expand Up @@ -117,6 +123,7 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.window)
implementation(libs.compose.ui)
implementation(libs.compose.material.icons.extended)
implementation(libs.compose.ui.tooling.preview)
implementation(libs.kotlinx.serialization.json)
implementation(libs.material3)
Expand All @@ -127,7 +134,6 @@ dependencies {
implementation(libs.zxing.android.embedded)
implementation(libs.zxing.core)
implementation(platform(libs.compose.bom))
testImplementation(libs.robolectric)
coreLibraryDesugaring(libs.desugar.jdk.libs)

// Test dependencies
Expand All @@ -144,15 +150,3 @@ dependencies {
debugImplementation(libs.compose.ui.test.manifest)
debugImplementation(libs.compose.ui.tooling)
}

val updateVGPDataExchangePackage by tasks.registering(ProcessResources::class) {
println("Updating VGP_Data_Exchange package...")
from(rootDir.toPath().resolve("VGP_Data_Exchange/io/github/kitswas/VGP_Data_Exchange"))
into(rootDir.toPath().resolve("app/src/main/java/io/github/kitswas/VGP_Data_Exchange"))
}

tasks.matching {
it != updateVGPDataExchangePackage.get()
}.configureEach {
dependsOn(updateVGPDataExchangePackage)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,7 @@ import java.util.concurrent.atomic.AtomicBoolean
object TestGamepadServer {
private val running = AtomicBoolean(false)
private var serverSocket: ServerSocket? = null

@JvmStatic
fun main(args: Array<String>) {
val port = if (args.isNotEmpty()) args[0].toInt() else 7878
start(port)

// Add shutdown hook for clean server shutdown
Runtime.getRuntime().addShutdownHook(Thread {
println("[TestGamepadServer] Shutting down...")
stop()
})

// Keep main thread alive
try {
while (running.get()) {
Thread.sleep(1000)
}
} catch (_: InterruptedException) {
stop()
}
}
private val readings = Collections.synchronizedList(mutableListOf<GamepadReading>())

fun start(port: Int) {
if (running.getAndSet(true)) {
Expand All @@ -41,40 +21,32 @@ object TestGamepadServer {

try {
serverSocket = ServerSocket(port)
println("[TestGamepadServer] Listening on port $port on interfaces:")
println("[TestGamepadServer] Listening on port ${serverSocket?.localPort} on interfaces:")
// List all network interfaces and addresses
Collections.list(NetworkInterface.getNetworkInterfaces())
.filter { it.isUp && !it.isLoopback }.forEach { nif ->
Collections.list(nif.inetAddresses).forEach { address ->
println("[TestGamepadServer] ${nif.displayName}: $address")
}
}
println("[TestGamepadServer] Connect to 10.0.2.2:$port from your emulator")

// Start server loop in a separate thread
Thread {
try {
while (running.get()) {
try {
// Block waiting for client connection (like production server)
// Block waiting for client connection
val client = serverSocket?.accept()
if (client != null && running.get()) {
println("[TestGamepadServer] Client connected: ${client.remoteSocketAddress}")

// Configure socket like production server
client.tcpNoDelay = true

// Handle client in current thread (blocking, like production server)
// This mimics tcpServer->pauseAccepting() behavior
handleClient(client)

println("[TestGamepadServer] Client disconnected, ready for new connections")
}
} catch (e: Exception) {
if (running.get()) {
System.err.println("[TestGamepadServer] Error accepting client: $e")
// Small delay before retrying
Thread.sleep(1000)
Thread.sleep(100)
}
}
}
Expand All @@ -97,17 +69,24 @@ object TestGamepadServer {
}

println("[TestGamepadServer] Stopping server...")

// Close server socket
try {
serverSocket?.close()
} catch (e: Exception) {
System.err.println("[TestGamepadServer] Error closing server socket: $e")
}

println("[TestGamepadServer] Server stopped")
}

fun getPort(): Int = serverSocket?.localPort ?: -1

fun clearReadings() {
readings.clear()
}

fun getReadings(): List<GamepadReading> {
return readings.toList()
}

private fun handleClient(client: Socket) {
try {
client.use { socket ->
Expand All @@ -117,30 +96,22 @@ object TestGamepadServer {
try {
val len = inputStream.read(buf)
if (len == -1) {
println("[TestGamepadServer] Client closed connection (EOF)")
break
}

// Process the received data
try {
val reading = GamepadReading()
reading.unmarshal(buf, 0, len)
logGamepadReading(reading)
} catch (e: Exception) {
// Handle partial reads or invalid data gracefully
System.err.println("[TestGamepadServer] Failed to unmarshal: $e")
System.err.println(
"[TestGamepadServer] Raw bytes (len=$len): ${
buf.copyOf(
len
).joinToString(" ") { "%02x".format(it) }
}"
)
var offset = 0
while (offset < len) {
try {
val reading = GamepadReading()
offset = reading.unmarshal(buf, offset, len)
readings.add(reading)
logGamepadReading(reading)
} catch (e: Exception) {
System.err.println("[TestGamepadServer] Failed to unmarshal at offset $offset: $e")
break
}
}
} catch (e: Exception) {
if (running.get() && !socket.isClosed) {
System.err.println("[TestGamepadServer] Read error: $e")
}
break
}
}
Expand Down
Loading
Loading