feat(shift): inject ShiftPatternStorage into CalendarViewModel
This commit is contained in:
parent
1f1db17384
commit
ef55ffcf64
@ -59,7 +59,8 @@ data class CalendarUiState(
|
||||
* @param clock 时钟源,默认系统时钟;测试时可注入固定时钟
|
||||
*/
|
||||
class CalendarViewModel(
|
||||
private val clock: Clock = Clock.System
|
||||
private val clock: Clock = Clock.System,
|
||||
private val shiftStorage: ShiftPatternStorage? = null
|
||||
) : ViewModel() {
|
||||
private val today: LocalDate = clock.todayIn(TimeZone.currentSystemDefault())
|
||||
|
||||
@ -112,17 +113,30 @@ class CalendarViewModel(
|
||||
|
||||
/**
|
||||
* 个人轮班。与法定节假日完全独立,不受调休影响。
|
||||
* MVP 默认:2026-05-15 起,2 班 2 休循环。后续接入设置页与持久化。
|
||||
* 从 [shiftStorage] 加载;storage 为空或未存时回退到 [DEFAULT_PATTERN]。
|
||||
*/
|
||||
private val _shiftPattern = MutableStateFlow<ShiftPattern?>(
|
||||
ShiftPattern(
|
||||
private val _shiftPattern = MutableStateFlow(loadShiftPattern())
|
||||
val shiftPattern: StateFlow<ShiftPattern?> = _shiftPattern.asStateFlow()
|
||||
|
||||
private fun loadShiftPattern(): ShiftPattern =
|
||||
shiftStorage?.load() ?: DEFAULT_PATTERN
|
||||
|
||||
/**
|
||||
* 设置页返回后调用,从 storage 重新加载,立即生效。
|
||||
*/
|
||||
fun refreshShiftPattern() {
|
||||
_shiftPattern.value = loadShiftPattern()
|
||||
}
|
||||
|
||||
fun shiftKindAt(date: LocalDate): ShiftKind? = shiftPattern.value?.kindAt(date)
|
||||
|
||||
companion object {
|
||||
/** 默认轮班:2026-05-15 起,2 班 2 休循环。 */
|
||||
val DEFAULT_PATTERN = ShiftPattern(
|
||||
anchorDate = LocalDate(2026, 5, 15),
|
||||
cycle = listOf(ShiftKind.WORK, ShiftKind.WORK, ShiftKind.OFF, ShiftKind.OFF)
|
||||
)
|
||||
)
|
||||
val shiftPattern: StateFlow<ShiftPattern?> = _shiftPattern.asStateFlow()
|
||||
|
||||
fun shiftKindAt(date: LocalDate): ShiftKind? = shiftPattern.value?.kindAt(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在右上角显示法定调休角标。默认禁用,此时右上角让位给个人排班。
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package plus.rua.project
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.number
|
||||
import plus.rua.project.ShiftKind
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
@ -127,4 +129,100 @@ class CalendarViewModelTest {
|
||||
val selectedCell = days.first { it.isSelected }
|
||||
assertEquals(15, selectedCell.date.day)
|
||||
}
|
||||
|
||||
// ---- shiftPattern: 默认值与 refresh ----
|
||||
|
||||
@Test
|
||||
fun shiftPattern_noStorage_returnsDefault() {
|
||||
val vm = createViewModel() // 不传 storage
|
||||
assertEquals(ShiftKind.WORK, vm.shiftKindAt(LocalDate(2026, 5, 15)))
|
||||
assertEquals(ShiftKind.OFF, vm.shiftKindAt(LocalDate(2026, 5, 17)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun refreshShiftPattern_reloadsFromStorage() {
|
||||
val prefs = CalendarVmTestPrefs()
|
||||
val storage = ShiftPatternStorage(prefs)
|
||||
// storage 里存一个 1班1休,锚点 2026-01-01
|
||||
storage.save(
|
||||
ShiftPattern(
|
||||
anchorDate = LocalDate(2026, 1, 1),
|
||||
cycle = listOf(ShiftKind.WORK, ShiftKind.OFF)
|
||||
)
|
||||
)
|
||||
val vm = CalendarViewModel(clock = testClock, shiftStorage = storage)
|
||||
// 初始即从 storage 读
|
||||
assertEquals(ShiftKind.WORK, vm.shiftKindAt(LocalDate(2026, 1, 1)))
|
||||
assertEquals(ShiftKind.OFF, vm.shiftKindAt(LocalDate(2026, 1, 2)))
|
||||
}
|
||||
}
|
||||
|
||||
private class CalendarVmTestPrefs : SharedPreferences {
|
||||
|
||||
private val data = mutableMapOf<String, Any?>()
|
||||
|
||||
override fun getAll(): Map<String, *> = data.toMap()
|
||||
|
||||
override fun getString(key: String, defValue: String?): String? =
|
||||
data[key] as? String ?: defValue
|
||||
|
||||
override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? =
|
||||
data[key] as? Set<String> ?: defValues
|
||||
|
||||
override fun getInt(key: String, defValue: Int): Int =
|
||||
data[key] as? Int ?: defValue
|
||||
|
||||
override fun getLong(key: String, defValue: Long): Long =
|
||||
data[key] as? Long ?: defValue
|
||||
|
||||
override fun getFloat(key: String, defValue: Float): Float =
|
||||
data[key] as? Float ?: defValue
|
||||
|
||||
override fun getBoolean(key: String, defValue: Boolean): Boolean =
|
||||
data[key] as? Boolean ?: defValue
|
||||
|
||||
override fun contains(key: String): Boolean = data.containsKey(key)
|
||||
|
||||
override fun edit(): SharedPreferences.Editor = CalendarVmTestPrefsEditor(data)
|
||||
|
||||
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) {}
|
||||
|
||||
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) {}
|
||||
}
|
||||
|
||||
private class CalendarVmTestPrefsEditor(private val data: MutableMap<String, Any?>) : SharedPreferences.Editor {
|
||||
|
||||
private val pending = mutableMapOf<String, Any?>()
|
||||
private var clearPending = false
|
||||
|
||||
override fun putString(key: String, value: String?): SharedPreferences.Editor = apply {
|
||||
pending[key] = value
|
||||
}
|
||||
|
||||
override fun putStringSet(key: String, values: Set<String>?): SharedPreferences.Editor = apply {
|
||||
pending[key] = values
|
||||
}
|
||||
|
||||
override fun putInt(key: String, value: Int): SharedPreferences.Editor = apply { pending[key] = value }
|
||||
override fun putLong(key: String, value: Long): SharedPreferences.Editor = apply { pending[key] = value }
|
||||
override fun putFloat(key: String, value: Float): SharedPreferences.Editor = apply { pending[key] = value }
|
||||
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor = apply { pending[key] = value }
|
||||
|
||||
override fun remove(key: String): SharedPreferences.Editor = apply { pending[key] = null }
|
||||
|
||||
override fun clear(): SharedPreferences.Editor = apply { clearPending = true }
|
||||
|
||||
override fun commit(): Boolean {
|
||||
apply()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
if (clearPending) {
|
||||
data.clear()
|
||||
clearPending = false
|
||||
}
|
||||
data.putAll(pending)
|
||||
pending.clear()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user