feat: establish Android project foundation

This commit is contained in:
KremeCN
2026-08-22 22:06:50 +08:00
parent e651f008e0
commit 117f87f735
39 changed files with 1653 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
plugins {
alias(libs.plugins.kotlin.jvm)
}
kotlin {
jvmToolchain(21)
}
dependencies {
api(libs.kotlinx.coroutines.core)
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
}
@@ -0,0 +1,57 @@
package com.flagship.abox.manager.domain
sealed interface DomainResult<out T> {
data class Success<T>(val value: T) : DomainResult<T>
data class Failure(val error: DomainError) : DomainResult<Nothing>
}
sealed interface DomainError {
val externalCode: String?
val detail: String?
data class Authentication(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class TokenInvalid(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class Network(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class DeviceNotFound(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class DeviceOffline(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class InvalidRequest(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class OperationFailed(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class NoData(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
data class Unknown(
override val externalCode: String? = null,
override val detail: String? = null,
) : DomainError
}
@@ -0,0 +1,61 @@
package com.flagship.abox.manager.domain
import kotlinx.coroutines.flow.Flow
interface SessionGateway {
fun observeSession(profileId: ProviderProfileId): Flow<Session?>
suspend fun login(
profileId: ProviderProfileId,
credentials: LoginCredentials,
): DomainResult<Session>
suspend fun validateSession(profileId: ProviderProfileId): DomainResult<SessionValidity>
suspend fun logout(profileId: ProviderProfileId): DomainResult<Unit>
}
interface DeviceCatalog {
fun observeDevices(
profileId: ProviderProfileId,
): Flow<DomainResult<List<DeviceDescriptor>>>
}
interface SocketGateway {
suspend fun getStatus(
profileId: ProviderProfileId,
deviceName: DeviceName,
): DomainResult<SocketStatus>
suspend fun setPower(
profileId: ProviderProfileId,
deviceName: DeviceName,
powerState: SocketPowerState,
): DomainResult<SocketStatus>
}
interface TemperatureHumidityGateway {
suspend fun getReading(
profileId: ProviderProfileId,
deviceName: DeviceName,
): DomainResult<TemperatureHumidityReading>
}
interface InfraredGateway {
suspend fun sendKey(
profileId: ProviderProfileId,
deviceName: DeviceName,
key: String,
): DomainResult<Unit>
suspend fun learnKey(
profileId: ProviderProfileId,
deviceName: DeviceName,
key: String,
): DomainResult<Unit>
suspend fun downloadCodes(
profileId: ProviderProfileId,
codes: List<InfraredCode>,
): DomainResult<List<InfraredCodeDownloadResult>>
}
@@ -0,0 +1,86 @@
package com.flagship.abox.manager.domain
@JvmInline
value class ProviderProfileId(val value: String)
@JvmInline
value class DeviceId(val value: String)
@JvmInline
value class DeviceName(val value: String)
@JvmInline
value class Username(val value: String)
@JvmInline
value class Password(val value: String)
enum class ProviderType {
ABOX,
COMPATIBLE_BACKEND,
LOCAL_EXPERIENCE,
}
data class ProviderProfile(
val id: ProviderProfileId,
val type: ProviderType,
val displayName: String,
)
enum class DeviceType {
SOCKET,
TEMPERATURE_HUMIDITY,
INFRARED,
}
data class DeviceDescriptor(
val id: DeviceId,
val profileId: ProviderProfileId,
val name: DeviceName,
val type: DeviceType,
)
data class LoginCredentials(
val username: Username,
val password: Password,
)
data class Session(
val profileId: ProviderProfileId,
val username: Username,
)
enum class SessionValidity {
VALID,
INVALID,
}
enum class SocketPowerState {
OFF,
ON,
}
data class SocketStatus(
val profileId: ProviderProfileId,
val deviceName: DeviceName,
val powerState: SocketPowerState,
)
data class TemperatureHumidityReading(
val profileId: ProviderProfileId,
val deviceName: DeviceName,
val temperature: String,
val humidity: String,
)
data class InfraredCode(
val profileId: ProviderProfileId,
val deviceName: DeviceName,
val key: String,
val code: String,
)
data class InfraredCodeDownloadResult(
val infraredCode: InfraredCode,
val successful: Boolean,
)
@@ -0,0 +1,23 @@
package com.flagship.abox.manager.domain
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class DomainResultTest {
@Test
fun successContainsTypedValue() {
val result: DomainResult<SocketPowerState> = DomainResult.Success(SocketPowerState.ON)
assertEquals(SocketPowerState.ON, (result as DomainResult.Success).value)
}
@Test
fun failureKeepsExternalCodeWithoutSdkTypes() {
val error = DomainError.DeviceOffline(externalCode = "20504")
val result: DomainResult<Unit> = DomainResult.Failure(error)
assertTrue(result is DomainResult.Failure)
assertEquals("20504", (result as DomainResult.Failure).error.externalCode)
}
}