feat: 新建记录标题预填拍摄日期并支持联动
This commit is contained in:
parent
94e81bffb1
commit
0d83fef3a9
@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.number
|
||||||
import kotlinx.datetime.todayIn
|
import kotlinx.datetime.todayIn
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
@ -58,6 +59,9 @@ class RecordEditViewModel(
|
|||||||
private val _uiState = MutableStateFlow(RecordEditUiState())
|
private val _uiState = MutableStateFlow(RecordEditUiState())
|
||||||
val uiState: StateFlow<RecordEditUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<RecordEditUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
/** 用户是否手动编辑过标题;为 false 时标题随拍摄日期联动 */
|
||||||
|
private var titleManuallyEdited = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (recordId != null) {
|
if (recordId != null) {
|
||||||
loadExistingRecord(recordId)
|
loadExistingRecord(recordId)
|
||||||
@ -69,11 +73,14 @@ class RecordEditViewModel(
|
|||||||
private fun initNewRecord() {
|
private fun initNewRecord() {
|
||||||
requireNotNull(photoPath) { "新建模式必须提供 photoPath" }
|
requireNotNull(photoPath) { "新建模式必须提供 photoPath" }
|
||||||
val file = File(photoPath)
|
val file = File(photoPath)
|
||||||
|
val today = Clock.System.todayIn(TimeZone.currentSystemDefault())
|
||||||
_uiState.value = RecordEditUiState(
|
_uiState.value = RecordEditUiState(
|
||||||
loading = false,
|
loading = false,
|
||||||
|
title = formatLocalDate(today),
|
||||||
|
shootDate = today,
|
||||||
photoUri = "file://${file.absolutePath}",
|
photoUri = "file://${file.absolutePath}",
|
||||||
photoAbsolutePath = file.absolutePath,
|
photoAbsolutePath = file.absolutePath,
|
||||||
canSave = false,
|
canSave = true,
|
||||||
isExistingRecord = false
|
isExistingRecord = false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -83,6 +90,7 @@ class RecordEditViewModel(
|
|||||||
val record = repository.observeById(id).first()
|
val record = repository.observeById(id).first()
|
||||||
if (record != null) {
|
if (record != null) {
|
||||||
val absFile = repository.absoluteFileOf(record.photoPath)
|
val absFile = repository.absoluteFileOf(record.photoPath)
|
||||||
|
titleManuallyEdited = true
|
||||||
_uiState.value = RecordEditUiState(
|
_uiState.value = RecordEditUiState(
|
||||||
loading = false,
|
loading = false,
|
||||||
title = record.title,
|
title = record.title,
|
||||||
@ -102,6 +110,7 @@ class RecordEditViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onTitleChange(value: String) {
|
fun onTitleChange(value: String) {
|
||||||
|
titleManuallyEdited = true
|
||||||
_uiState.update { it.copy(title = value, canSave = value.isNotBlank() && it.photoAbsolutePath != null) }
|
_uiState.update { it.copy(title = value, canSave = value.isNotBlank() && it.photoAbsolutePath != null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +119,13 @@ class RecordEditViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onShootDateChange(date: LocalDate) {
|
fun onShootDateChange(date: LocalDate) {
|
||||||
_uiState.update { it.copy(shootDate = date) }
|
_uiState.update {
|
||||||
|
if (titleManuallyEdited) {
|
||||||
|
it.copy(shootDate = date)
|
||||||
|
} else {
|
||||||
|
it.copy(shootDate = date, title = formatLocalDate(date))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLinkedDateChange(date: LocalDate) {
|
fun onLinkedDateChange(date: LocalDate) {
|
||||||
@ -155,3 +170,12 @@ class RecordEditViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将日期格式化为 `yyyy-MM-dd`,用于新建记录的默认标题和编辑页的日期显示。
|
||||||
|
*
|
||||||
|
* @param date 待格式化的日期
|
||||||
|
*/
|
||||||
|
internal fun formatLocalDate(date: LocalDate): String {
|
||||||
|
return "${date.year}-${date.month.number.toString().padStart(2, '0')}-${date.day.toString().padStart(2, '0')}"
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
package plus.rua.project
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新建记录标题预填与拍摄日期联动的单元测试。
|
||||||
|
*
|
||||||
|
* 新建模式的被测路径不会触发 DAO 调用,[FakeDateRecordDao] 返回空数据即可。
|
||||||
|
*/
|
||||||
|
class RecordEditViewModelTest {
|
||||||
|
|
||||||
|
private fun newViewModel(): RecordEditViewModel {
|
||||||
|
val repository = DateRecorderRepository(FakeDateRecordDao(), File("build/tmp/record_edit_test"))
|
||||||
|
return RecordEditViewModel(repository, photoPath = "/tmp/fake_photo.jpg", recordId = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun newRecord_prefillsTitleWithShootDate_andCanSave() {
|
||||||
|
val vm = newViewModel()
|
||||||
|
val state = vm.uiState.value
|
||||||
|
assertEquals(formatLocalDate(state.shootDate), state.title)
|
||||||
|
assertTrue(state.canSave)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shootDateChange_updatesTitle_whenTitleNotManuallyEdited() {
|
||||||
|
val vm = newViewModel()
|
||||||
|
vm.onShootDateChange(LocalDate(2026, 3, 5))
|
||||||
|
assertEquals("2026-03-05", vm.uiState.value.title)
|
||||||
|
assertTrue(vm.uiState.value.canSave)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shootDateChange_keepsTitle_whenTitleManuallyEdited() {
|
||||||
|
val vm = newViewModel()
|
||||||
|
vm.onTitleChange("我的记录")
|
||||||
|
vm.onShootDateChange(LocalDate(2026, 3, 5))
|
||||||
|
assertEquals("我的记录", vm.uiState.value.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun titleCleared_disablesSave() {
|
||||||
|
val vm = newViewModel()
|
||||||
|
vm.onTitleChange("")
|
||||||
|
assertFalse(vm.uiState.value.canSave)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FakeDateRecordDao : DateRecordDao {
|
||||||
|
override fun getByIdFlow(id: Long): Flow<DateRecord?> = flowOf(null)
|
||||||
|
override fun getAllFlow(): Flow<List<DateRecord>> = flowOf(emptyList())
|
||||||
|
override suspend fun insert(record: DateRecord): Long = 0
|
||||||
|
override suspend fun update(record: DateRecord) = Unit
|
||||||
|
override suspend fun deleteByIds(ids: List<Long>): Int = 0
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user