add readming mode
This commit is contained in:
@@ -3,6 +3,7 @@ package cn.learningpad.oraltrainer.sample
|
||||
enum class Module {
|
||||
COURSE,
|
||||
TRAIN,
|
||||
READING,
|
||||
TEST,
|
||||
MINE,
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ class MainActivity : Activity() {
|
||||
private lateinit var testSession: TestSessionController
|
||||
private lateinit var explanationOverlay: SentenceExplanationOverlay
|
||||
private lateinit var translationApi: SubtitleTranslationApi
|
||||
private lateinit var sentenceFavoriteStore: SentenceFavoriteStore
|
||||
private var translationRequest: CancellableTranslationRequest? = null
|
||||
|
||||
private var progressBarDragging = false
|
||||
@@ -80,6 +81,7 @@ class MainActivity : Activity() {
|
||||
translationApi = SubtitleTranslationApi(this, SERVER_BASE_URL) {
|
||||
authSessionStore.session?.token.orEmpty()
|
||||
}
|
||||
sentenceFavoriteStore = SentenceFavoriteStore(this)
|
||||
|
||||
screenFactory = SampleScreenFactory(
|
||||
activity = this,
|
||||
@@ -264,7 +266,17 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
private fun refreshCurrentUi() {
|
||||
if (screenState.activeModule != Module.TRAIN && screenState.activeModule != Module.TEST) {
|
||||
if (screenState.activeModule != Module.TRAIN &&
|
||||
screenState.activeModule != Module.TEST &&
|
||||
screenState.activeModule != Module.READING
|
||||
) {
|
||||
screenFactory.setCatalogStatus(screenState.catalogStatus)
|
||||
return
|
||||
}
|
||||
if (screenState.activeModule == Module.READING) {
|
||||
screenState.currentSentenceCount = controller.currentTrainingItem()?.sentences?.size ?: 0
|
||||
screenFactory.refreshReadingSentences()
|
||||
ensureChineseTranslations()
|
||||
screenFactory.setCatalogStatus(screenState.catalogStatus)
|
||||
return
|
||||
}
|
||||
@@ -284,11 +296,14 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
override fun onMediaChanged(item: TrainingMediaItem?) {
|
||||
screenState.currentSentenceCount = item?.sentences?.size ?: 0
|
||||
if (bindings.hasPlayerChrome) {
|
||||
bindings.lessonTitleText.text = item?.title ?: "未选择课程"
|
||||
screenState.currentSentenceCount = item?.sentences?.size ?: 0
|
||||
}
|
||||
ensureChineseTranslations()
|
||||
if (screenState.activeModule == Module.READING) {
|
||||
screenFactory.refreshReadingSentences()
|
||||
}
|
||||
testSession.ensureHistoryForCurrentCourse()
|
||||
if (screenState.activeModule == Module.TEST) {
|
||||
testSession.refreshTestUi()
|
||||
@@ -398,10 +413,19 @@ class MainActivity : Activity() {
|
||||
screenState.courseBoundaries[current.id] = cached.copy(sentences = updated)
|
||||
}
|
||||
applyChineseSubtitle(controller.currentSentence())
|
||||
screenFactory.refreshReadingTranslations()
|
||||
}
|
||||
|
||||
private fun favoriteUserKey(): String =
|
||||
SentenceFavoriteStore.userKey(authSessionStore.session?.token.orEmpty())
|
||||
|
||||
private fun ensureChineseTranslations() {
|
||||
if (screenState.activeModule != Module.TRAIN || !screenState.chineseSubtitlesEnabled) {
|
||||
val translationEnabled = when (screenState.activeModule) {
|
||||
Module.TRAIN -> screenState.chineseSubtitlesEnabled
|
||||
Module.READING -> screenState.readingTranslationVisible
|
||||
else -> false
|
||||
}
|
||||
if (!translationEnabled) {
|
||||
return
|
||||
}
|
||||
val item = controller.currentTrainingItem() ?: return
|
||||
@@ -411,12 +435,14 @@ class MainActivity : Activity() {
|
||||
screenState.chineseTranslationLoading = false
|
||||
screenState.chineseTranslationError = ""
|
||||
applyChineseSubtitle(controller.currentSentence())
|
||||
screenFactory.refreshReadingTranslations()
|
||||
return
|
||||
}
|
||||
val generation = screenState.beginTranslationRequest()
|
||||
screenState.chineseTranslationLoading = true
|
||||
screenState.chineseTranslationError = ""
|
||||
applyChineseSubtitle(controller.currentSentence())
|
||||
screenFactory.refreshReadingTranslations()
|
||||
translationRequest?.cancel()
|
||||
val expectedIndexes = sentences.mapNotNull { sentence ->
|
||||
sentence.index.takeIf { !sentence.text.isNullOrBlank() }
|
||||
@@ -505,6 +531,27 @@ class MainActivity : Activity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReadingTranslationVisible(visible: Boolean) {
|
||||
screenState.readingTranslationVisible = visible
|
||||
if (!visible) {
|
||||
translationRequest?.cancel()
|
||||
screenState.chineseTranslationLoading = false
|
||||
} else {
|
||||
ensureChineseTranslations()
|
||||
}
|
||||
screenFactory.refreshReadingTranslations()
|
||||
}
|
||||
|
||||
override fun isSentenceFavorite(sentence: SentenceBoundary): Boolean {
|
||||
val item = controller.currentTrainingItem() ?: return false
|
||||
return sentence.index in sentenceFavoriteStore.indexes(favoriteUserKey(), item.id)
|
||||
}
|
||||
|
||||
override fun toggleSentenceFavorite(sentence: SentenceBoundary): Boolean {
|
||||
val item = controller.currentTrainingItem() ?: return false
|
||||
return sentenceFavoriteStore.toggle(favoriteUserKey(), item.id, sentence.index)
|
||||
}
|
||||
|
||||
override fun onExplainCurrentSentence() {
|
||||
val sentence = controller.currentSentence() ?: return
|
||||
if (sentence.text.isNullOrBlank()) return
|
||||
@@ -558,6 +605,12 @@ class MainActivity : Activity() {
|
||||
|
||||
override fun nickname(): String =
|
||||
authSessionStore.session?.user?.nickname.orEmpty()
|
||||
|
||||
override fun favoriteSentences(): List<SentenceBoundary> {
|
||||
val item = controller.currentTrainingItem() ?: return emptyList()
|
||||
val indexes = sentenceFavoriteStore.indexes(favoriteUserKey(), item.id)
|
||||
return item.sentences.filter { it.index in indexes }
|
||||
}
|
||||
}
|
||||
|
||||
private fun catalogHost() = object : CourseCatalogController.Host {
|
||||
|
||||
@@ -31,6 +31,7 @@ internal class SampleBindings {
|
||||
var dubbingStatusText: TextView? = null
|
||||
var explainSentenceButton: ImageButton? = null
|
||||
var myContentList: LinearLayout? = null
|
||||
var favoriteSentenceList: LinearLayout? = null
|
||||
|
||||
val hasPlayerChrome: Boolean
|
||||
get() = ::lessonTitleText.isInitialized &&
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.learningpad.oraltrainer.sample
|
||||
|
||||
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
|
||||
import cn.learningpad.oraltrainer.sdk.TrainingVideoSummary
|
||||
|
||||
internal interface ModuleTabActions {
|
||||
@@ -24,6 +25,12 @@ internal interface TrainScreenActions :
|
||||
fun onChineseSubtitlesChanged(enabled: Boolean)
|
||||
}
|
||||
|
||||
internal interface ReadingScreenActions {
|
||||
fun onReadingTranslationVisible(visible: Boolean)
|
||||
fun isSentenceFavorite(sentence: SentenceBoundary): Boolean
|
||||
fun toggleSentenceFavorite(sentence: SentenceBoundary): Boolean
|
||||
}
|
||||
|
||||
internal interface TestScreenActions :
|
||||
SentenceExplainActions {
|
||||
fun onSeekProgressPreview(progress: Int)
|
||||
@@ -42,11 +49,14 @@ internal interface TestScreenActions :
|
||||
internal interface MineScreenActions {
|
||||
fun onLogout()
|
||||
fun nickname(): String
|
||||
fun favoriteSentences(): List<SentenceBoundary>
|
||||
fun toggleSentenceFavorite(sentence: SentenceBoundary): Boolean
|
||||
}
|
||||
|
||||
internal interface SampleScreenActions :
|
||||
ModuleTabActions,
|
||||
CourseScreenActions,
|
||||
TrainScreenActions,
|
||||
ReadingScreenActions,
|
||||
TestScreenActions,
|
||||
MineScreenActions
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.widget.LinearLayout
|
||||
import cn.learningpad.oraltrainer.sample.ui.CourseScreen
|
||||
import cn.learningpad.oraltrainer.sample.ui.MineScreen
|
||||
import cn.learningpad.oraltrainer.sample.ui.PlayerChrome
|
||||
import cn.learningpad.oraltrainer.sample.ui.ReadingScreen
|
||||
import cn.learningpad.oraltrainer.sample.ui.SampleWidgets
|
||||
import cn.learningpad.oraltrainer.sample.ui.TestScreen
|
||||
import cn.learningpad.oraltrainer.sample.ui.TrainScreen
|
||||
@@ -36,6 +37,13 @@ internal class SampleScreenFactory(
|
||||
playerChrome = playerChrome,
|
||||
)
|
||||
private val testScreen = TestScreen(activity, bindings, screenState, playerChrome, actions)
|
||||
private val readingScreen = ReadingScreen(
|
||||
activity = activity,
|
||||
screenState = screenState,
|
||||
controller = controller,
|
||||
widgets = widgets,
|
||||
actions = actions,
|
||||
)
|
||||
private val mineScreen = MineScreen(activity, bindings, screenState, widgets, actions)
|
||||
|
||||
fun createContentView(): View {
|
||||
@@ -59,11 +67,24 @@ internal class SampleScreenFactory(
|
||||
Module.COURSE -> addView(courseScreen.createSection())
|
||||
Module.MINE -> addView(mineScreen.createSection())
|
||||
Module.TRAIN -> trainScreen.attachPortraitSections(this)
|
||||
Module.READING -> addView(readingScreen.createSection())
|
||||
Module.TEST -> testScreen.attachSections(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshReadingSentences() {
|
||||
if (screenState.activeModule == Module.READING) {
|
||||
readingScreen.renderSentences()
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshReadingTranslations() {
|
||||
if (screenState.activeModule == Module.READING) {
|
||||
readingScreen.refreshTranslations()
|
||||
}
|
||||
}
|
||||
|
||||
fun renderCatalog() {
|
||||
if (screenState.activeModule == Module.COURSE) {
|
||||
courseScreen.renderCatalog()
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.learningpad.oraltrainer.sample
|
||||
|
||||
import android.content.Context
|
||||
import java.security.MessageDigest
|
||||
|
||||
/** Persists reading-mode favorites locally, scoped by signed-in user and video. */
|
||||
internal class SentenceFavoriteStore(context: Context) {
|
||||
private val preferences =
|
||||
context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
fun toggle(userKey: String, mediaId: String, sentenceIndex: Int): Boolean {
|
||||
val key = storageKey(userKey, mediaId) ?: return false
|
||||
val favorites = readIndexes(key)
|
||||
val isFavorite = sentenceIndex in favorites
|
||||
val updated = if (isFavorite) {
|
||||
favorites - sentenceIndex
|
||||
} else {
|
||||
favorites + sentenceIndex
|
||||
}
|
||||
preferences.edit()
|
||||
.putString(key, updated.sorted().joinToString(SEPARATOR))
|
||||
.apply()
|
||||
return !isFavorite
|
||||
}
|
||||
|
||||
fun indexes(userKey: String, mediaId: String): Set<Int> {
|
||||
return storageKey(userKey, mediaId)?.let(::readIndexes).orEmpty()
|
||||
}
|
||||
|
||||
private fun readIndexes(key: String): Set<Int> {
|
||||
return preferences.getString(key, null).orEmpty()
|
||||
.split(SEPARATOR)
|
||||
.mapNotNull { it.trim().toIntOrNull() }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun storageKey(userKey: String, mediaId: String): String? {
|
||||
val normalizedUser = userKey.trim()
|
||||
val normalizedMedia = mediaId.trim()
|
||||
if (normalizedUser.isEmpty() || normalizedMedia.isEmpty()) return null
|
||||
return "$KEY_PREFIX$normalizedUser:$normalizedMedia"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFERENCES_NAME = "sentence_reading_favorites"
|
||||
private const val KEY_PREFIX = "indexes"
|
||||
private const val SEPARATOR = ","
|
||||
|
||||
fun userKey(token: String): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
.digest(token.toByteArray(Charsets.UTF_8))
|
||||
return digest.joinToString("") { "%02x".format(it) }.take(24)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ internal class TrainingScreenState {
|
||||
var currentCourse: TrainingVideoSummary? = null
|
||||
var continuousPlaybackEnabled = false
|
||||
var chineseSubtitlesEnabled = false
|
||||
var readingTranslationVisible = true
|
||||
var chineseTranslationLoading = false
|
||||
var chineseTranslationError = ""
|
||||
var testSubtitleVisible = false
|
||||
|
||||
@@ -16,6 +16,7 @@ import cn.learningpad.oraltrainer.sample.SampleBindings
|
||||
import cn.learningpad.oraltrainer.sample.SampleTheme
|
||||
import cn.learningpad.oraltrainer.sample.TrainingScreenState
|
||||
import cn.learningpad.oraltrainer.sample.UiStyled
|
||||
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
|
||||
import cn.learningpad.oraltrainer.sdk.UserApi
|
||||
|
||||
internal class MineScreen(
|
||||
@@ -33,6 +34,9 @@ internal class MineScreen(
|
||||
bindings.myContentList = LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
}
|
||||
bindings.favoriteSentenceList = LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
}
|
||||
return ScrollView(activity).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)
|
||||
addView(LinearLayout(activity).apply {
|
||||
@@ -52,6 +56,13 @@ internal class MineScreen(
|
||||
44.dp,
|
||||
).withMargins(0, 12.dp, 0, 20.dp)
|
||||
})
|
||||
addView(TextView(activity).apply {
|
||||
text = "本视频收藏句子"
|
||||
textSize = 18f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
setTextColor(Color.WHITE)
|
||||
})
|
||||
addView(bindings.favoriteSentenceList)
|
||||
addView(TextView(activity).apply {
|
||||
text = "我的测试结果"
|
||||
textSize = 18f
|
||||
@@ -64,6 +75,7 @@ internal class MineScreen(
|
||||
}
|
||||
|
||||
fun refreshContent(userApi: UserApi, token: String) {
|
||||
renderFavoriteSentences()
|
||||
val list = bindings.myContentList ?: return
|
||||
list.removeAllViews()
|
||||
val course = screenState.currentCourse
|
||||
@@ -125,6 +137,74 @@ internal class MineScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderFavoriteSentences() {
|
||||
val list = bindings.favoriteSentenceList ?: return
|
||||
list.removeAllViews()
|
||||
if (screenState.currentCourse == null) {
|
||||
list.addView(widgets.infoCard("请先在「课程」中选择一门课程"))
|
||||
return
|
||||
}
|
||||
val favorites = actions.favoriteSentences()
|
||||
if (favorites.isEmpty()) {
|
||||
list.addView(widgets.infoCard("当前课程还没有收藏句子"))
|
||||
return
|
||||
}
|
||||
favorites.forEach { sentence ->
|
||||
list.addView(favoriteCard(sentence) {
|
||||
if (!actions.toggleSentenceFavorite(sentence)) {
|
||||
renderFavoriteSentences()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun favoriteCard(
|
||||
sentence: SentenceBoundary,
|
||||
onRemove: () -> Unit,
|
||||
): View {
|
||||
val foreign = sentence.text?.takeIf { it.isNotBlank() } ?: "未提供文本"
|
||||
val translation = sentence.zhText?.takeIf { it.isNotBlank() }
|
||||
val cardText = buildString {
|
||||
append("第 ${sentence.index + 1} 句")
|
||||
append("\n$foreign")
|
||||
translation?.let { append("\n$it") }
|
||||
}
|
||||
return LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
background = rounded(SampleTheme.selected, 8f)
|
||||
setPadding(12.dp, 10.dp, 10.dp, 10.dp)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
).withMargins(0, 0, 0, 8.dp)
|
||||
addView(TextView(activity).apply {
|
||||
text = cardText
|
||||
setTextColor(SampleTheme.textDark)
|
||||
textSize = 13f
|
||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
||||
})
|
||||
addView(Button(activity).apply {
|
||||
text = "移除"
|
||||
isAllCaps = false
|
||||
setTextColor(Color.WHITE)
|
||||
textSize = 12f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
minWidth = 0
|
||||
minHeight = 0
|
||||
minimumWidth = 0
|
||||
minimumHeight = 0
|
||||
setPadding(10.dp, 0, 10.dp, 0)
|
||||
background = rounded(SampleTheme.danger, 8f)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
32.dp,
|
||||
).withMargins(8.dp, 0, 0, 0)
|
||||
setOnClickListener { onRemove() }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteResult(userApi: UserApi, token: String, attemptId: String) {
|
||||
userApi.deleteResult(token, attemptId) { result ->
|
||||
result.fold(
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
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.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import cn.learningpad.oraltrainer.sample.ReadingScreenActions
|
||||
import cn.learningpad.oraltrainer.sample.SampleTheme
|
||||
import cn.learningpad.oraltrainer.sample.TrainingScreenState
|
||||
import cn.learningpad.oraltrainer.sample.UiStyled
|
||||
import cn.learningpad.oraltrainer.sdk.OralTrainerController
|
||||
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
|
||||
|
||||
/** Scrollable full-text reading view for the selected course. */
|
||||
internal class ReadingScreen(
|
||||
private val activity: Activity,
|
||||
private val screenState: TrainingScreenState,
|
||||
private val controller: OralTrainerController,
|
||||
private val widgets: SampleWidgets,
|
||||
private val actions: ReadingScreenActions,
|
||||
) : UiStyled {
|
||||
override val uiContext get() = activity
|
||||
|
||||
private data class SentenceRow(
|
||||
val translation: TextView,
|
||||
val favoriteButton: Button,
|
||||
)
|
||||
|
||||
private val sentenceRows = mutableMapOf<Int, SentenceRow>()
|
||||
private var sentenceList: LinearLayout? = null
|
||||
private var titleText: TextView? = null
|
||||
|
||||
fun createSection(): View {
|
||||
val content = LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
setPadding(16.dp, 12.dp, 16.dp, 16.dp)
|
||||
}
|
||||
val list = LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
}
|
||||
sentenceList = list
|
||||
|
||||
val title = TextView(activity).apply {
|
||||
setTextColor(Color.WHITE)
|
||||
textSize = 24f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
setPadding(0, 0, 0, 6.dp)
|
||||
}
|
||||
titleText = title
|
||||
content.addView(title)
|
||||
|
||||
content.addView(createTranslationToggle())
|
||||
content.addView(list)
|
||||
renderSentences()
|
||||
|
||||
return ScrollView(activity).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
0,
|
||||
1f,
|
||||
)
|
||||
addView(content)
|
||||
}
|
||||
}
|
||||
|
||||
fun renderSentences() {
|
||||
val list = sentenceList ?: return
|
||||
val item = controller.currentTrainingItem()
|
||||
titleText?.text = screenState.currentCourse?.title.orEmpty()
|
||||
.ifBlank { item?.title ?: "阅读模式" }
|
||||
sentenceRows.clear()
|
||||
list.removeAllViews()
|
||||
if (item == null || item.sentences.isEmpty()) {
|
||||
list.addView(widgets.infoCard("当前课程还没有可阅读的句子文本"))
|
||||
return
|
||||
}
|
||||
val currentTitle = titleText?.text?.toString().orEmpty()
|
||||
titleText?.text = "$currentTitle · ${item.sentences.size} 句"
|
||||
item.sentences.forEach { sentence ->
|
||||
list.addView(sentenceRow(sentence))
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshTranslations() {
|
||||
val item = controller.currentTrainingItem() ?: return
|
||||
item.sentences.forEach { sentence ->
|
||||
val row = sentenceRows[sentence.index] ?: return@forEach
|
||||
row.translation.visibility = if (screenState.readingTranslationVisible) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
row.translation.text = translationText(sentence)
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshFavorites() {
|
||||
val item = controller.currentTrainingItem() ?: return
|
||||
item.sentences.forEach { sentence ->
|
||||
sentenceRows[sentence.index]?.favoriteButton?.apply {
|
||||
val isFavorite = actions.isSentenceFavorite(sentence)
|
||||
setText(if (isFavorite) "已收藏" else "收藏")
|
||||
background = rounded(
|
||||
if (isFavorite) SampleTheme.accent else SampleTheme.button,
|
||||
8f,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTranslationToggle(): View =
|
||||
LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
setPadding(0, 4.dp, 0, 10.dp)
|
||||
addView(TextView(activity).apply {
|
||||
text = "中文翻译"
|
||||
setTextColor(Color.WHITE)
|
||||
textSize = 15f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
||||
})
|
||||
addView(Switch(activity).apply {
|
||||
isChecked = screenState.readingTranslationVisible
|
||||
buttonTintList = android.content.res.ColorStateList.valueOf(SampleTheme.accent)
|
||||
setOnCheckedChangeListener { _, checked ->
|
||||
actions.onReadingTranslationVisible(checked)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun sentenceRow(sentence: SentenceBoundary): View {
|
||||
val translation = TextView(activity).apply {
|
||||
text = translationText(sentence)
|
||||
setTextColor(SampleTheme.textSubtle)
|
||||
textSize = 14f
|
||||
setPadding(0, 6.dp, 0, 0)
|
||||
visibility = if (screenState.readingTranslationVisible) View.VISIBLE else View.GONE
|
||||
}
|
||||
val favoriteButton = Button(activity).apply {
|
||||
setText("收藏")
|
||||
isAllCaps = false
|
||||
setTextColor(Color.WHITE)
|
||||
textSize = 12f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
minWidth = 0
|
||||
minHeight = 0
|
||||
minimumWidth = 0
|
||||
minimumHeight = 0
|
||||
setPadding(12.dp, 0, 12.dp, 0)
|
||||
background = rounded(SampleTheme.button, 8f)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
32.dp,
|
||||
).withMargins(10.dp, 0, 0, 0)
|
||||
setOnClickListener {
|
||||
val isFavorite = actions.toggleSentenceFavorite(sentence)
|
||||
setText(if (isFavorite) "已收藏" else "收藏")
|
||||
background = rounded(
|
||||
if (isFavorite) SampleTheme.accent else SampleTheme.button,
|
||||
8f,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val isFavorite = actions.isSentenceFavorite(sentence)
|
||||
favoriteButton.setText(if (isFavorite) "已收藏" else "收藏")
|
||||
favoriteButton.background = rounded(
|
||||
if (isFavorite) SampleTheme.accent else SampleTheme.button,
|
||||
8f,
|
||||
)
|
||||
val textColumn = LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
||||
addView(TextView(activity).apply {
|
||||
text = "${sentence.index + 1}. ${sentence.text?.takeIf { it.isNotBlank() } ?: "未提供文本"}"
|
||||
setTextColor(Color.WHITE)
|
||||
textSize = 16f
|
||||
})
|
||||
addView(translation)
|
||||
}
|
||||
|
||||
return LinearLayout(activity).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
background = rounded(SampleTheme.surface, 10f, SampleTheme.border)
|
||||
setPadding(14.dp, 12.dp, 10.dp, 12.dp)
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
).withMargins(0, 0, 0, 10.dp)
|
||||
addView(textColumn)
|
||||
addView(favoriteButton)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translationText(sentence: SentenceBoundary): String = when {
|
||||
!sentence.zhText.isNullOrBlank() -> sentence.zhText.orEmpty()
|
||||
screenState.chineseTranslationLoading -> "正在翻译中文…"
|
||||
screenState.chineseTranslationError.isNotBlank() -> screenState.chineseTranslationError
|
||||
else -> "暂无中文翻译"
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ internal class SampleWidgets(
|
||||
setPadding(16.dp, 40.dp, 16.dp, 0)
|
||||
addView(moduleTab("课程", Module.COURSE, actions))
|
||||
addView(moduleTab("训练", Module.TRAIN, actions))
|
||||
addView(moduleTab("阅读", Module.READING, actions))
|
||||
addView(moduleTab("测试", Module.TEST, actions))
|
||||
addView(moduleTab("我的", Module.MINE, actions))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user