*
This commit is contained in:
30
core/common/build.gradle.kts
Normal file
30
core/common/build.gradle.kts
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
Reference in New Issue
Block a user