add a refresh action
This commit is contained in:
@@ -29,20 +29,41 @@ internal class CourseCatalogController(
|
||||
}
|
||||
|
||||
fun loadCatalog() {
|
||||
loadCatalog(refreshBoundaries = false)
|
||||
}
|
||||
|
||||
fun refreshCourses() {
|
||||
if (screenState.catalogIsRefreshing) return
|
||||
loadCatalog(refreshBoundaries = true)
|
||||
}
|
||||
|
||||
private fun loadCatalog(refreshBoundaries: Boolean) {
|
||||
val generation = screenState.beginCatalogRequest()
|
||||
screenState.catalogIsRefreshing = refreshBoundaries
|
||||
screenState.catalogStatus = "正在同步"
|
||||
host.onCatalogStatus(screenState.catalogStatus)
|
||||
host.onCatalogLoadingPlaceholder()
|
||||
sdk.userApi.courses(tokenProvider()) { result ->
|
||||
if (!screenState.isCurrentCatalogRequest(generation)) return@courses
|
||||
result.fold(
|
||||
onSuccess = { courses ->
|
||||
screenState.catalogVideos = courses.map { it.toVideoSummary() }
|
||||
screenState.courseEnrollment.clear()
|
||||
courses.forEach { screenState.courseEnrollment[it.videoHash] = it.enrolled }
|
||||
screenState.catalogStatus = "${screenState.catalogVideos.size} 个课程"
|
||||
host.onCatalogStatus(screenState.catalogStatus)
|
||||
host.onCatalogChanged()
|
||||
if (refreshBoundaries) {
|
||||
screenState.catalogStatus = "${courses.size} 个课程,正在刷新句界"
|
||||
host.onCatalogStatus(screenState.catalogStatus)
|
||||
host.onCatalogChanged()
|
||||
refreshAllCourseBoundaries(generation, index = 0, succeeded = 0, failed = 0)
|
||||
} else {
|
||||
screenState.catalogIsRefreshing = false
|
||||
screenState.catalogStatus = "${screenState.catalogVideos.size} 个课程"
|
||||
host.onCatalogStatus(screenState.catalogStatus)
|
||||
host.onCatalogChanged()
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
screenState.catalogIsRefreshing = false
|
||||
screenState.catalogStatus = "课程加载失败:${it.message}"
|
||||
host.onCatalogStatus(screenState.catalogStatus)
|
||||
screenState.courseEnrollment.clear()
|
||||
@@ -52,6 +73,67 @@ internal class CourseCatalogController(
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshAllCourseBoundaries(
|
||||
generation: Int,
|
||||
index: Int,
|
||||
succeeded: Int,
|
||||
failed: Int,
|
||||
) {
|
||||
if (!screenState.isCurrentCatalogRequest(generation)) return
|
||||
val videos = screenState.catalogVideos
|
||||
if (index >= videos.size) {
|
||||
screenState.catalogIsRefreshing = false
|
||||
screenState.catalogStatus = when {
|
||||
failed == 0 -> "${videos.size} 个课程 · 句界已刷新"
|
||||
succeeded == 0 -> "${videos.size} 个课程 · 句界刷新失败"
|
||||
else -> "${videos.size} 个课程 · 句界部分刷新(失败 $failed)"
|
||||
}
|
||||
host.onCatalogStatus(screenState.catalogStatus)
|
||||
host.onCatalogChanged()
|
||||
return
|
||||
}
|
||||
|
||||
val video = videos[index]
|
||||
screenState.catalogStatus =
|
||||
"正在刷新句界 ${index + 1}/${videos.size}"
|
||||
host.onCatalogStatus(screenState.catalogStatus)
|
||||
sdk.sentenceBoundaryApi.fetch(video.videoHash, object : SentenceBoundaryApiCallback {
|
||||
override fun onSuccess(result: SentenceBoundaryApiResult) {
|
||||
if (!screenState.isCurrentCatalogRequest(generation)) return
|
||||
screenState.courseBoundaries[result.videoHash] = result
|
||||
updateCatalogVideo(video.videoHash, result)
|
||||
if (screenState.activeItemId == result.videoHash) {
|
||||
controller.updateSentences(result.videoHash, result.sentences)
|
||||
screenState.currentSentenceCount = result.sentences.size
|
||||
host.refreshPlayerUi()
|
||||
}
|
||||
host.onCatalogChanged()
|
||||
refreshAllCourseBoundaries(generation, index + 1, succeeded + 1, failed)
|
||||
}
|
||||
|
||||
override fun onError(error: Throwable) {
|
||||
if (!screenState.isCurrentCatalogRequest(generation)) return
|
||||
refreshAllCourseBoundaries(generation, index + 1, succeeded, failed + 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateCatalogVideo(videoHash: String, result: SentenceBoundaryApiResult) {
|
||||
screenState.catalogVideos = screenState.catalogVideos.map { video ->
|
||||
if (video.videoHash != videoHash) {
|
||||
video
|
||||
} else {
|
||||
video.copy(
|
||||
durationMs = result.durationMs,
|
||||
sentenceCount = result.sentences.size,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (screenState.currentCourse?.videoHash == videoHash) {
|
||||
screenState.currentCourse = screenState.catalogVideos.firstOrNull { it.videoHash == videoHash }
|
||||
}
|
||||
}
|
||||
|
||||
fun loadUserData(target: Module, onMine: () -> Unit) {
|
||||
when (target) {
|
||||
Module.COURSE -> loadCatalog()
|
||||
@@ -99,9 +181,15 @@ internal class CourseCatalogController(
|
||||
host.onPlayerLoading(video)
|
||||
host.onCatalogChanged()
|
||||
|
||||
controller.loadItem(video.toTrainingMediaItem())
|
||||
screenState.currentSentenceCount = video.sentenceCount
|
||||
refreshSentenceBoundaries(video, background = false)
|
||||
val cachedBoundary = screenState.courseBoundaries[video.videoHash]
|
||||
controller.loadItem(video.toTrainingMediaItem(cachedBoundary?.sentences.orEmpty()))
|
||||
screenState.currentSentenceCount = cachedBoundary?.sentences?.size ?: video.sentenceCount
|
||||
if (cachedBoundary == null) {
|
||||
refreshSentenceBoundaries(video, background = false)
|
||||
} else {
|
||||
host.refreshPlayerUi()
|
||||
host.onStatus("课程已就绪:${cachedBoundary.sentences.size} 句")
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshSentenceBoundaries(
|
||||
@@ -117,8 +205,11 @@ internal class CourseCatalogController(
|
||||
if (!screenState.isCurrentBoundaryRequest(requestGeneration, video.videoHash)) {
|
||||
return
|
||||
}
|
||||
screenState.courseBoundaries[result.videoHash] = result
|
||||
updateCatalogVideo(video.videoHash, result)
|
||||
screenState.currentSentenceCount = result.sentences.size
|
||||
controller.updateSentences(video.videoHash, result.sentences)
|
||||
host.onCatalogChanged()
|
||||
host.refreshPlayerUi()
|
||||
host.onStatus("课程已就绪:${result.sentences.size} 句")
|
||||
}
|
||||
|
||||
@@ -472,6 +472,8 @@ class MainActivity : Activity() {
|
||||
catalogController.selectCourse(video)
|
||||
}
|
||||
|
||||
override fun onRefreshCourses() = catalogController.refreshCourses()
|
||||
|
||||
override fun onTrainCatalogVideoClick(video: TrainingVideoSummary) {
|
||||
catalogController.useCourseInPlace(video)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ internal class SampleBindings {
|
||||
lateinit var progressBar: SeekBar
|
||||
lateinit var catalogList: LinearLayout
|
||||
lateinit var catalogStatusText: TextView
|
||||
var catalogRefreshButton: Button? = null
|
||||
lateinit var testStatusText: TextView
|
||||
lateinit var scoreSummaryText: TextView
|
||||
lateinit var scoreDetailText: TextView
|
||||
|
||||
@@ -8,6 +8,7 @@ internal interface ModuleTabActions {
|
||||
|
||||
internal interface CourseScreenActions {
|
||||
fun onSelectCourse(video: TrainingVideoSummary)
|
||||
fun onRefreshCourses()
|
||||
}
|
||||
|
||||
internal interface SentenceExplainActions {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.learningpad.oraltrainer.sample
|
||||
|
||||
import cn.learningpad.oraltrainer.sdk.SentenceBoundaryApiResult
|
||||
import cn.learningpad.oraltrainer.sdk.TrainingVideoSummary
|
||||
|
||||
internal class TrainingScreenState {
|
||||
@@ -7,7 +8,9 @@ internal class TrainingScreenState {
|
||||
var currentSentenceCount = 0
|
||||
var catalogVideos: List<TrainingVideoSummary> = emptyList()
|
||||
val courseEnrollment = mutableMapOf<String, Boolean>()
|
||||
val courseBoundaries = mutableMapOf<String, SentenceBoundaryApiResult>()
|
||||
var catalogStatus = "正在同步"
|
||||
var catalogIsRefreshing = false
|
||||
var activeModule = Module.COURSE
|
||||
var currentCourse: TrainingVideoSummary? = null
|
||||
var continuousPlaybackEnabled = false
|
||||
@@ -15,15 +18,26 @@ internal class TrainingScreenState {
|
||||
var landscapeSubtitleVisible = true
|
||||
|
||||
private var boundaryRequestGeneration = 0
|
||||
private var catalogRequestGeneration = 0
|
||||
|
||||
fun beginBoundaryRequest(): Int = ++boundaryRequestGeneration
|
||||
|
||||
fun isCurrentBoundaryRequest(generation: Int, itemId: String): Boolean =
|
||||
boundaryRequestGeneration == generation && activeItemId == itemId
|
||||
|
||||
fun beginCatalogRequest(): Int = ++catalogRequestGeneration
|
||||
|
||||
fun isCurrentCatalogRequest(generation: Int): Boolean =
|
||||
catalogRequestGeneration == generation
|
||||
|
||||
fun clearCourse() {
|
||||
currentCourse = null
|
||||
activeItemId = ""
|
||||
currentSentenceCount = 0
|
||||
catalogVideos = emptyList()
|
||||
courseEnrollment.clear()
|
||||
courseBoundaries.clear()
|
||||
catalogStatus = "正在同步"
|
||||
catalogIsRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.learningpad.oraltrainer.sample.ui
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
@@ -34,18 +35,31 @@ internal class CourseScreen(
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(16.dp, 8.dp, 16.dp, 16.dp)
|
||||
}
|
||||
val header = LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
setPadding(16.dp, 0.dp, 16.dp, 0.dp)
|
||||
addView(TextView(activity).apply {
|
||||
text = "课程首页"
|
||||
textSize = 26f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
setTextColor(Color.WHITE)
|
||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
||||
})
|
||||
bindings.catalogRefreshButton = widgets.headerButton("刷新课程") {
|
||||
actions.onRefreshCourses()
|
||||
}.also { button ->
|
||||
button.isEnabled = !screenState.catalogIsRefreshing
|
||||
button.alpha = if (screenState.catalogIsRefreshing) 0.55f else 1f
|
||||
addView(button)
|
||||
}
|
||||
}
|
||||
return ScrollView(activity).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)
|
||||
addView(LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(0.dp, 12.dp, 0.dp, 16.dp)
|
||||
addView(TextView(activity).apply {
|
||||
text = "课程首页"
|
||||
textSize = 26f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
setTextColor(Color.WHITE)
|
||||
setPadding(16.dp, 0.dp, 16.dp, 0.dp)
|
||||
})
|
||||
addView(header)
|
||||
addView(TextView(activity).apply {
|
||||
text = "选择一门课程开始训练"
|
||||
textSize = 13f
|
||||
@@ -83,5 +97,10 @@ internal class CourseScreen(
|
||||
if (bindings.hasCatalogChrome) {
|
||||
bindings.catalogStatusText.text = text
|
||||
}
|
||||
bindings.catalogRefreshButton?.let { button ->
|
||||
button.isEnabled = !screenState.catalogIsRefreshing
|
||||
button.alpha = if (screenState.catalogIsRefreshing) 0.55f else 1f
|
||||
button.text = if (screenState.catalogIsRefreshing) "刷新中" else "刷新课程"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user