regenerate something when the sentences changed

This commit is contained in:
2026-10-03 21:12:12 +08:00
parent d5609c5c42
commit 4fd0fd36a4
11 changed files with 245 additions and 13 deletions

View File

@@ -2,6 +2,7 @@ package cn.learningpad.oraltrainer.sample
import cn.learningpad.oraltrainer.sdk.OralTrainerController
import cn.learningpad.oraltrainer.sdk.OralTrainerSdk
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
import cn.learningpad.oraltrainer.sdk.SentenceBoundaryApiCallback
import cn.learningpad.oraltrainer.sdk.SentenceBoundaryApiResult
import cn.learningpad.oraltrainer.sdk.TrainingVideoSummary
@@ -26,6 +27,7 @@ internal class CourseCatalogController(
fun onSentencePlaceholder(meta: String, text: String)
fun refreshPlayerUi()
fun openTrainingModule()
fun syncSentenceExplanations(videoHash: String, sentences: List<SentenceBoundary>)
}
fun loadCatalog() {
@@ -101,6 +103,7 @@ internal class CourseCatalogController(
override fun onSuccess(result: SentenceBoundaryApiResult) {
if (!screenState.isCurrentCatalogRequest(generation)) return
screenState.courseBoundaries[result.videoHash] = result
host.syncSentenceExplanations(result.videoHash, result.sentences)
updateCatalogVideo(video.videoHash, result)
if (screenState.activeItemId == result.videoHash) {
controller.updateSentences(result.videoHash, result.sentences)
@@ -174,6 +177,9 @@ internal class CourseCatalogController(
host.onCatalogChanged()
val cachedBoundary = screenState.courseBoundaries[video.videoHash]
if (cachedBoundary != null) {
host.syncSentenceExplanations(cachedBoundary.videoHash, cachedBoundary.sentences)
}
controller.loadItem(video.toTrainingMediaItem(cachedBoundary?.sentences.orEmpty()))
screenState.currentSentenceCount = cachedBoundary?.sentences?.size ?: video.sentenceCount
if (cachedBoundary == null) {
@@ -198,6 +204,7 @@ internal class CourseCatalogController(
return
}
screenState.courseBoundaries[result.videoHash] = result
host.syncSentenceExplanations(result.videoHash, result.sentences)
updateCatalogVideo(video.videoHash, result)
screenState.currentSentenceCount = result.sentences.size
controller.updateSentences(video.videoHash, result.sentences)

View File

@@ -480,10 +480,12 @@ class MainActivity : Activity() {
val sentence = controller.currentSentence() ?: return
if (sentence.text.isNullOrBlank()) return
controller.pause()
val item = controller.currentTrainingItem()
explanationOverlay.show(
sentence,
controller.currentTrainingItem()?.title.orEmpty(),
controller.currentTrainingItem()?.id.orEmpty(),
item?.title.orEmpty(),
item?.id.orEmpty(),
item?.sentences.orEmpty(),
)
}
@@ -561,6 +563,13 @@ class MainActivity : Activity() {
screenFactory.renderCatalog()
refreshCurrentUi()
}
override fun syncSentenceExplanations(
videoHash: String,
sentences: List<SentenceBoundary>,
) {
SentenceExplanationStore(this@MainActivity).sync(videoHash, sentences)
}
}
private fun testHost() = object : TestSessionController.Host {

View File

@@ -23,7 +23,15 @@ internal class SentenceExplanationOverlay(
private var currentRequest: CancellableExplanationRequest? = null
private var requestGeneration = 0
fun show(sentence: SentenceBoundary, courseTitle: String, mediaId: String) {
fun show(
sentence: SentenceBoundary,
courseTitle: String,
mediaId: String,
sentences: List<SentenceBoundary> = emptyList(),
) {
if (sentences.isNotEmpty()) {
store.sync(mediaId, sentences)
}
close()
val generation = ++requestGeneration
val root = FrameLayout(activity).apply {

View File

@@ -1,6 +1,7 @@
package cn.learningpad.oraltrainer.sample
import android.content.Context
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
import java.io.File
import java.util.Locale
@@ -23,12 +24,34 @@ internal class SentenceExplanationStore(context: Context) {
return file.isFile && file.length() > 0L
}
fun sync(mediaId: String, sentences: List<SentenceBoundary>) {
if (sentences.isEmpty()) return
val directoryName = sanitize(mediaId) ?: return
val dir = File(rootDir, directoryName)
val revision = revisionFor(sentences)
val marker = File(dir, REVISION_FILE)
val previous = marker.takeIf { it.isFile }?.readText()?.trim().orEmpty()
if (previous == revision) return
if (dir.exists()) {
dir.deleteRecursively()
}
dir.mkdirs()
marker.writeText(revision)
}
private fun fileFor(mediaId: String, sentenceIndex: Int): File? {
val directoryName = sanitize(mediaId) ?: return null
return File(File(rootDir, directoryName), "$sentenceIndex.txt")
}
companion object {
private const val REVISION_FILE = "revision.txt"
fun revisionFor(sentences: List<SentenceBoundary>): String =
sentences.joinToString("\n") { sentence ->
"${sentence.index}\t${sentence.text.orEmpty().trim()}"
}
fun sanitize(mediaId: String): String? {
val filtered = mediaId.trim().lowercase(Locale.US)
.replace(Regex("[^a-z0-9._-]"), "_")