This commit is contained in:
阿猫
2026-09-03 12:38:16 +08:00
parent 09669fe4a2
commit e96e92d7ab
68 changed files with 2810 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
// core:common 通用基础层AppResult / UiState / 日志门面
// 代码保持纯 Kotlin不引 Android API便于 JVM 单测与被所有 core 模块复用
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.stec.cmd.core.common"
compileSdk = 34
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
api(libs.kotlinx.coroutines.core)
testImplementation(libs.junit)
}

View File

@@ -0,0 +1,36 @@
package com.stec.cmd.core.common
/**
* 轻量日志门面core 层不依赖 android.util.Log
* app 壳启动时注入 LogCat 实现AppLog.sink = ...),测试环境可不注入或注入内存实现。
*/
object AppLog {
enum class Level { DEBUG, INFO, WARN, ERROR }
fun interface Sink {
fun log(level: Level, tag: String, message: String, throwable: Throwable?)
}
@Volatile
private var sink: Sink? = null
/** app 启动时调用一次;传 null 恢复为静默(适用于单元测试)。 */
fun install(sink: Sink?) {
this.sink = sink
}
fun d(tag: String, message: String) = dispatch(Level.DEBUG, tag, message, null)
fun i(tag: String, message: String) = dispatch(Level.INFO, tag, message, null)
fun w(tag: String, message: String, throwable: Throwable? = null) =
dispatch(Level.WARN, tag, message, throwable)
fun e(tag: String, message: String, throwable: Throwable? = null) =
dispatch(Level.ERROR, tag, message, throwable)
private fun dispatch(level: Level, tag: String, message: String, throwable: Throwable?) {
sink?.log(level, tag, message, throwable)
}
}

View File

@@ -0,0 +1,48 @@
package com.stec.cmd.core.common
/**
* 全工程统一的结果封装UI 层与数据层之间只通过 [AppResult] 传递成败,
* 具体业务异常(网络/数据库/协议)由各 core 模块定义并在此统一消费。
*/
sealed interface AppResult<out T> {
/** 成功,携带业务数据。 */
data class Success<T>(val data: T) : AppResult<T>
/** 失败,携带原始异常与可选的人类可读说明。 */
data class Error(
val error: Throwable,
val message: String? = error.message,
) : AppResult<Nothing>
/** 加载中(首屏/刷新语义由 UI 层区分)。 */
data object Loading : AppResult<Nothing>
companion object {
/** 同步块结果捕获:块内抛出的任意异常转为 [Error]。 */
inline fun <T> of(block: () -> T): AppResult<T> = try {
Success(block())
} catch (t: Throwable) {
Error(t)
}
}
}
/** 成功值映射,失败/加载态原样透传。 */
inline fun <T, R> AppResult<T>.map(transform: (T) -> R): AppResult<R> = when (this) {
is AppResult.Success -> AppResult.Success(transform(data))
is AppResult.Error -> this
is AppResult.Loading -> this
}
/** 成功时的副作用钩子,返回值不变。 */
inline fun <T> AppResult<T>.onSuccess(block: (T) -> Unit): AppResult<T> {
if (this is AppResult.Success) block(data)
return this
}
/** 失败时的副作用钩子,返回值不变。 */
inline fun <T> AppResult<T>.onError(block: (AppResult.Error) -> Unit): AppResult<T> {
if (this is AppResult.Error) block(this)
return this
}

View File

@@ -0,0 +1,28 @@
package com.stec.cmd.core.common
/**
* 页面级视图状态MVVM 中 ViewModel 对 UI 暴露的最小状态机。
* 与 [AppResult] 解耦——AppResult 描述一次调用UiState 描述一个页面。
*/
sealed interface UiState<out T> {
/** 初始空闲态(尚未发起加载)。 */
data object Idle : UiState<Nothing>
/** 加载中。 */
data object Loading : UiState<Nothing>
/** 内容就绪。 */
data class Success<T>(val data: T) : UiState<T>
/**
* 失败态。
*
* @property message 用户可读的错误说明
* @property retryable 是否允许重试429/403 等限流场景由业务层定)
*/
data class Error(
val message: String,
val retryable: Boolean = true,
) : UiState<Nothing>
}