Initial commit: LCK Control Android app

Multi-module Android app (app/shared/sdk) with backend-driven auth,
Quest Platform SDK login, YouTube/Twitch OAuth linking, stream
management via AIDL service. Compose UI with Hilt DI.
This commit is contained in:
2026-02-24 12:03:43 +01:00
commit 82aa207f9a
101 changed files with 4723 additions and 0 deletions

25
shared/build.gradle.kts Normal file
View File

@@ -0,0 +1,25 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "com.omixlab.lckcontrol.shared"
compileSdk = 36
defaultConfig {
minSdk = 32
}
buildFeatures {
aidl = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(libs.androidx.core.ktx)
}

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@@ -0,0 +1,10 @@
package com.omixlab.lckcontrol.shared;
import com.omixlab.lckcontrol.shared.StreamPlan;
interface ILckControlCallback {
void onStreamPlansChanged(in List<StreamPlan> plans);
void onStreamPlanUpdated(in StreamPlan plan);
void onClientRegistered(String clientId);
void onClientUnregistered(String clientId);
}

View File

@@ -0,0 +1,21 @@
package com.omixlab.lckcontrol.shared;
import com.omixlab.lckcontrol.shared.LinkedAccount;
import com.omixlab.lckcontrol.shared.StreamPlan;
import com.omixlab.lckcontrol.shared.StreamPlanConfig;
import com.omixlab.lckcontrol.shared.ILckControlCallback;
interface ILckControlService {
List<LinkedAccount> getLinkedAccounts();
StreamPlan createStreamPlan(in StreamPlanConfig config);
StreamPlan prepareStreamPlan(String planId);
List<StreamPlan> getStreamPlans();
StreamPlan getStreamPlan(String planId);
boolean startStreamPlan(String planId);
boolean endStreamPlan(String planId);
String registerClient(String clientName, String packageName);
void unregisterClient(String clientId);
void setClientActivePlan(String clientId, String planId);
void registerCallback(ILckControlCallback callback);
void unregisterCallback(ILckControlCallback callback);
}

View File

@@ -0,0 +1,3 @@
package com.omixlab.lckcontrol.shared;
parcelable LinkedAccount;

View File

@@ -0,0 +1,3 @@
package com.omixlab.lckcontrol.shared;
parcelable StreamDestination;

View File

@@ -0,0 +1,3 @@
package com.omixlab.lckcontrol.shared;
parcelable StreamPlan;

View File

@@ -0,0 +1,3 @@
package com.omixlab.lckcontrol.shared;
parcelable StreamPlanConfig;

View File

@@ -0,0 +1,36 @@
package com.omixlab.lckcontrol.shared
import android.os.Parcel
import android.os.Parcelable
data class LinkedAccount(
val serviceId: String,
val displayName: String,
val accountId: String,
val avatarUrl: String? = null,
val isAuthenticated: Boolean = false,
) : Parcelable {
constructor(parcel: Parcel) : this(
serviceId = parcel.readString()!!,
displayName = parcel.readString()!!,
accountId = parcel.readString()!!,
avatarUrl = parcel.readString(),
isAuthenticated = parcel.readInt() != 0,
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(serviceId)
parcel.writeString(displayName)
parcel.writeString(accountId)
parcel.writeString(avatarUrl)
parcel.writeInt(if (isAuthenticated) 1 else 0)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<LinkedAccount> {
override fun createFromParcel(parcel: Parcel) = LinkedAccount(parcel)
override fun newArray(size: Int) = arrayOfNulls<LinkedAccount>(size)
}
}

View File

@@ -0,0 +1,51 @@
package com.omixlab.lckcontrol.shared
import android.os.Parcel
import android.os.Parcelable
data class StreamDestination(
val service: String,
val title: String,
val description: String = "",
val privacyStatus: String = "public",
val gameId: String = "",
val tags: List<String> = emptyList(),
val rtmpUrl: String = "",
val streamKey: String = "",
val broadcastId: String = "",
val status: String = "PENDING",
) : Parcelable {
constructor(parcel: Parcel) : this(
service = parcel.readString()!!,
title = parcel.readString()!!,
description = parcel.readString() ?: "",
privacyStatus = parcel.readString() ?: "public",
gameId = parcel.readString() ?: "",
tags = parcel.createStringArrayList() ?: emptyList(),
rtmpUrl = parcel.readString() ?: "",
streamKey = parcel.readString() ?: "",
broadcastId = parcel.readString() ?: "",
status = parcel.readString() ?: "PENDING",
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(service)
parcel.writeString(title)
parcel.writeString(description)
parcel.writeString(privacyStatus)
parcel.writeString(gameId)
parcel.writeStringList(tags)
parcel.writeString(rtmpUrl)
parcel.writeString(streamKey)
parcel.writeString(broadcastId)
parcel.writeString(status)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<StreamDestination> {
override fun createFromParcel(parcel: Parcel) = StreamDestination(parcel)
override fun newArray(size: Int) = arrayOfNulls<StreamDestination>(size)
}
}

View File

@@ -0,0 +1,33 @@
package com.omixlab.lckcontrol.shared
import android.os.Parcel
import android.os.Parcelable
data class StreamPlan(
val planId: String,
val name: String,
val status: String = "DRAFT",
val destinations: List<StreamDestination> = emptyList(),
) : Parcelable {
constructor(parcel: Parcel) : this(
planId = parcel.readString()!!,
name = parcel.readString()!!,
status = parcel.readString() ?: "DRAFT",
destinations = parcel.createTypedArrayList(StreamDestination.CREATOR) ?: emptyList(),
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(planId)
parcel.writeString(name)
parcel.writeString(status)
parcel.writeTypedList(destinations)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<StreamPlan> {
override fun createFromParcel(parcel: Parcel) = StreamPlan(parcel)
override fun newArray(size: Int) = arrayOfNulls<StreamPlan>(size)
}
}

View File

@@ -0,0 +1,27 @@
package com.omixlab.lckcontrol.shared
import android.os.Parcel
import android.os.Parcelable
data class StreamPlanConfig(
val name: String,
val destinations: List<StreamDestination> = emptyList(),
) : Parcelable {
constructor(parcel: Parcel) : this(
name = parcel.readString()!!,
destinations = parcel.createTypedArrayList(StreamDestination.CREATOR) ?: emptyList(),
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeTypedList(destinations)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<StreamPlanConfig> {
override fun createFromParcel(parcel: Parcel) = StreamPlanConfig(parcel)
override fun newArray(size: Int) = arrayOfNulls<StreamPlanConfig>(size)
}
}