relayout of train view
This commit is contained in:
@@ -13,7 +13,6 @@ internal const val ASSESSMENT_API_KEY =
|
|||||||
|
|
||||||
internal const val SSO_LOGIN_URL = "https://d1kt.cn/login"
|
internal const val SSO_LOGIN_URL = "https://d1kt.cn/login"
|
||||||
|
|
||||||
internal const val PICK_VIDEO_REQUEST = 1001
|
|
||||||
internal const val RECORD_AUDIO_REQUEST = 1002
|
internal const val RECORD_AUDIO_REQUEST = 1002
|
||||||
internal const val PROGRESS_MAX = 1000
|
internal const val PROGRESS_MAX = 1000
|
||||||
|
|
||||||
|
|||||||
@@ -167,14 +167,6 @@ internal class CourseCatalogController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun useCourseInPlace(video: TrainingVideoSummary) {
|
|
||||||
// Keep the current module (e.g. Train) while syncing course selection.
|
|
||||||
if (screenState.courseEnrollment[video.videoHash] != true) {
|
|
||||||
enrollCourse(video)
|
|
||||||
}
|
|
||||||
loadRemoteVideo(video)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadRemoteVideo(video: TrainingVideoSummary) {
|
fun loadRemoteVideo(video: TrainingVideoSummary) {
|
||||||
screenState.currentCourse = video
|
screenState.currentCourse = video
|
||||||
screenState.activeItemId = video.videoHash
|
screenState.activeItemId = video.videoHash
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import cn.learningpad.oraltrainer.sdk.AuthSession
|
import cn.learningpad.oraltrainer.sdk.AuthSession
|
||||||
import cn.learningpad.oraltrainer.sdk.GestureEvent
|
import cn.learningpad.oraltrainer.sdk.GestureEvent
|
||||||
@@ -17,8 +16,6 @@ import cn.learningpad.oraltrainer.sdk.OralTrainerListener
|
|||||||
import cn.learningpad.oraltrainer.sdk.OralTrainerSdk
|
import cn.learningpad.oraltrainer.sdk.OralTrainerSdk
|
||||||
import cn.learningpad.oraltrainer.sdk.PlaybackSnapshot
|
import cn.learningpad.oraltrainer.sdk.PlaybackSnapshot
|
||||||
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
|
import cn.learningpad.oraltrainer.sdk.SentenceBoundary
|
||||||
import cn.learningpad.oraltrainer.sdk.SentenceBoundaryApiCallback
|
|
||||||
import cn.learningpad.oraltrainer.sdk.SentenceBoundaryApiResult
|
|
||||||
import cn.learningpad.oraltrainer.sdk.TrainingMediaItem
|
import cn.learningpad.oraltrainer.sdk.TrainingMediaItem
|
||||||
import cn.learningpad.oraltrainer.sdk.TrainingVideoSummary
|
import cn.learningpad.oraltrainer.sdk.TrainingVideoSummary
|
||||||
|
|
||||||
@@ -173,53 +170,6 @@ class MainActivity : Activity() {
|
|||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
if (requestCode != PICK_VIDEO_REQUEST || resultCode != RESULT_OK) return
|
|
||||||
val uri = data?.data ?: return
|
|
||||||
val persistableFlags = data.flags and Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
if (persistableFlags != 0) {
|
|
||||||
runCatching { contentResolver.takePersistableUriPermission(uri, persistableFlags) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val item = TrainingMediaItem(
|
|
||||||
id = uri.toString(),
|
|
||||||
title = uri.lastPathSegment ?: "本地视频",
|
|
||||||
uri = uri,
|
|
||||||
)
|
|
||||||
screenState.activeItemId = item.id
|
|
||||||
screenState.currentSentenceCount = 0
|
|
||||||
controller.loadItem(item)
|
|
||||||
if (bindings.hasPlayerChrome) {
|
|
||||||
bindings.lessonTitleText.text = item.title
|
|
||||||
bindings.sentenceMetaText.text = "句子边界分析中"
|
|
||||||
bindings.sentenceText.text = "正在匹配服务端句子边界"
|
|
||||||
bindings.statusText.text = "正在获取本地视频的句子边界..."
|
|
||||||
}
|
|
||||||
screenFactory.renderCatalog()
|
|
||||||
|
|
||||||
sdk.sentenceBoundaryApi.fetchForUri(uri, contentResolver, object : SentenceBoundaryApiCallback {
|
|
||||||
override fun onSuccess(result: SentenceBoundaryApiResult) {
|
|
||||||
if (screenState.activeItemId != item.id) return
|
|
||||||
screenState.currentSentenceCount = result.sentences.size
|
|
||||||
controller.loadItem(item.copy(sentences = result.sentences))
|
|
||||||
if (bindings.hasPlayerChrome) {
|
|
||||||
bindings.statusText.text = "句子边界已加载:${result.sentences.size} 句"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onError(error: Throwable) {
|
|
||||||
if (screenState.activeItemId != item.id) return
|
|
||||||
if (bindings.hasPlayerChrome) {
|
|
||||||
bindings.statusText.text =
|
|
||||||
"句子边界获取失败,使用默认快退/快进:${error.message.orEmpty()}"
|
|
||||||
bindings.sentenceMetaText.text = "暂无句子边界"
|
|
||||||
bindings.sentenceText.text = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
authCoordinator.reset()
|
authCoordinator.reset()
|
||||||
testSession.release()
|
testSession.release()
|
||||||
@@ -268,19 +218,6 @@ class MainActivity : Activity() {
|
|||||||
setRootView()
|
setRootView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openLocalVideoPicker() {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
startActivityForResult(
|
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
type = "video/*"
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
|
||||||
},
|
|
||||||
PICK_VIDEO_REQUEST,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun switchModule(module: Module) {
|
private fun switchModule(module: Module) {
|
||||||
if (module != Module.TEST) {
|
if (module != Module.TEST) {
|
||||||
testSession.stopDubbingIfNeeded()
|
testSession.stopDubbingIfNeeded()
|
||||||
@@ -393,10 +330,6 @@ class MainActivity : Activity() {
|
|||||||
private fun screenActions() = object : SampleScreenActions {
|
private fun screenActions() = object : SampleScreenActions {
|
||||||
override fun onModuleSelected(module: Module) = switchModule(module)
|
override fun onModuleSelected(module: Module) = switchModule(module)
|
||||||
|
|
||||||
override fun onImportLocalVideo() = openLocalVideoPicker()
|
|
||||||
|
|
||||||
override fun onRefreshCatalog() = catalogController.loadCatalog()
|
|
||||||
|
|
||||||
override fun onSeekProgressPreview(progress: Int) {
|
override fun onSeekProgressPreview(progress: Int) {
|
||||||
if (!bindings.hasPlayerChrome) return
|
if (!bindings.hasPlayerChrome) return
|
||||||
val duration = controller.snapshot().durationMs
|
val duration = controller.snapshot().durationMs
|
||||||
@@ -474,10 +407,6 @@ class MainActivity : Activity() {
|
|||||||
|
|
||||||
override fun onRefreshCourses() = catalogController.refreshCourses()
|
override fun onRefreshCourses() = catalogController.refreshCourses()
|
||||||
|
|
||||||
override fun onTrainCatalogVideoClick(video: TrainingVideoSummary) {
|
|
||||||
catalogController.useCourseInPlace(video)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLogout() = logout()
|
override fun onLogout() = logout()
|
||||||
|
|
||||||
override fun nickname(): String =
|
override fun nickname(): String =
|
||||||
|
|||||||
@@ -17,13 +17,10 @@ internal interface SentenceExplainActions {
|
|||||||
|
|
||||||
internal interface TrainScreenActions :
|
internal interface TrainScreenActions :
|
||||||
SentenceExplainActions {
|
SentenceExplainActions {
|
||||||
fun onImportLocalVideo()
|
|
||||||
fun onRefreshCatalog()
|
|
||||||
fun onSeekProgressPreview(progress: Int)
|
fun onSeekProgressPreview(progress: Int)
|
||||||
fun onSeekStart()
|
fun onSeekStart()
|
||||||
fun onSeekStop(progress: Int)
|
fun onSeekStop(progress: Int)
|
||||||
fun onContinuousPlaybackChanged(enabled: Boolean)
|
fun onContinuousPlaybackChanged(enabled: Boolean)
|
||||||
fun onTrainCatalogVideoClick(video: TrainingVideoSummary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface TestScreenActions :
|
internal interface TestScreenActions :
|
||||||
|
|||||||
@@ -33,11 +33,7 @@ internal class SampleScreenFactory(
|
|||||||
private val courseScreen = CourseScreen(activity, bindings, screenState, widgets, actions)
|
private val courseScreen = CourseScreen(activity, bindings, screenState, widgets, actions)
|
||||||
private val trainScreen = TrainScreen(
|
private val trainScreen = TrainScreen(
|
||||||
activity = activity,
|
activity = activity,
|
||||||
bindings = bindings,
|
|
||||||
screenState = screenState,
|
|
||||||
widgets = widgets,
|
|
||||||
playerChrome = playerChrome,
|
playerChrome = playerChrome,
|
||||||
actions = actions,
|
|
||||||
)
|
)
|
||||||
private val testScreen = TestScreen(activity, bindings, screenState, playerChrome, actions)
|
private val testScreen = TestScreen(activity, bindings, screenState, playerChrome, actions)
|
||||||
private val mineScreen = MineScreen(activity, bindings, screenState, widgets, actions)
|
private val mineScreen = MineScreen(activity, bindings, screenState, widgets, actions)
|
||||||
@@ -53,6 +49,10 @@ internal class SampleScreenFactory(
|
|||||||
|
|
||||||
return LinearLayout(activity).apply {
|
return LinearLayout(activity).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
)
|
||||||
setBackgroundColor(SampleTheme.background)
|
setBackgroundColor(SampleTheme.background)
|
||||||
addView(widgets.moduleTabBar(actions))
|
addView(widgets.moduleTabBar(actions))
|
||||||
when (screenState.activeModule) {
|
when (screenState.activeModule) {
|
||||||
@@ -65,24 +65,19 @@ internal class SampleScreenFactory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun renderCatalog() {
|
fun renderCatalog() {
|
||||||
when (screenState.activeModule) {
|
if (screenState.activeModule == Module.COURSE) {
|
||||||
Module.COURSE -> courseScreen.renderCatalog()
|
courseScreen.renderCatalog()
|
||||||
Module.TRAIN -> trainScreen.renderCatalogStrip()
|
|
||||||
else -> Unit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showCatalogLoading() {
|
fun showCatalogLoading() {
|
||||||
when (screenState.activeModule) {
|
if (screenState.activeModule == Module.COURSE) {
|
||||||
Module.COURSE -> courseScreen.showLoading()
|
courseScreen.showLoading()
|
||||||
Module.TRAIN -> trainScreen.showCatalogLoading()
|
|
||||||
else -> Unit
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCatalogStatus(text: String) {
|
fun setCatalogStatus(text: String) {
|
||||||
courseScreen.setStatus(text)
|
courseScreen.setStatus(text)
|
||||||
trainScreen.setCatalogStatus(text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshMyContent(userApi: UserApi, token: String) {
|
fun refreshMyContent(userApi: UserApi, token: String) {
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import android.app.Activity
|
|||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.graphics.drawable.InsetDrawable
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -14,6 +17,7 @@ import android.widget.FrameLayout
|
|||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ScrollView
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.Switch
|
import android.widget.Switch
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@@ -40,26 +44,50 @@ internal class PlayerChrome(
|
|||||||
override val uiContext get() = activity
|
override val uiContext get() = activity
|
||||||
|
|
||||||
fun createPlayerSection(): View =
|
fun createPlayerSection(): View =
|
||||||
FrameLayout(activity).apply {
|
LinearLayout(activity).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
background = rounded(SampleTheme.surface, 8f, SampleTheme.border)
|
background = rounded(SampleTheme.surface, 8f, SampleTheme.border)
|
||||||
clipToOutline = true
|
clipToOutline = true
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
0,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
1f,
|
|
||||||
).withMargins(16.dp, 0, 16.dp, 10.dp)
|
).withMargins(16.dp, 0, 16.dp, 10.dp)
|
||||||
addView(createPlayerView())
|
|
||||||
addView(createPlayerOverlay())
|
|
||||||
addView(
|
addView(
|
||||||
createRotationButton().apply {
|
createPlayerOverlay().apply {
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
44.dp,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
44.dp,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
Gravity.BOTTOM or Gravity.END,
|
)
|
||||||
).apply {
|
},
|
||||||
marginEnd = 12.dp
|
)
|
||||||
bottomMargin = 12.dp
|
addView(
|
||||||
|
object : FrameLayout(activity) {
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
|
val height = (width * 9) / 16
|
||||||
|
super.onMeasure(
|
||||||
|
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
}.apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
)
|
||||||
|
addView(createPlayerView())
|
||||||
|
addView(
|
||||||
|
createRotationButton().apply {
|
||||||
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
44.dp,
|
||||||
|
44.dp,
|
||||||
|
Gravity.BOTTOM or Gravity.END,
|
||||||
|
).apply {
|
||||||
|
marginEnd = 12.dp
|
||||||
|
bottomMargin = 12.dp
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -70,7 +98,8 @@ internal class PlayerChrome(
|
|||||||
setPadding(18.dp, 16.dp, 18.dp, 16.dp)
|
setPadding(18.dp, 16.dp, 18.dp, 16.dp)
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
0,
|
||||||
|
1f,
|
||||||
).withMargins(16.dp, 0, 16.dp, 12.dp)
|
).withMargins(16.dp, 0, 16.dp, 12.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +107,15 @@ internal class PlayerChrome(
|
|||||||
FrameLayout(activity).apply {
|
FrameLayout(activity).apply {
|
||||||
setBackgroundColor(Color.BLACK)
|
setBackgroundColor(Color.BLACK)
|
||||||
addView(createPlayerView())
|
addView(createPlayerView())
|
||||||
addView(createPlayerOverlay())
|
addView(
|
||||||
|
createPlayerOverlay().apply {
|
||||||
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
Gravity.TOP,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
val subtitleContainer = LinearLayout(activity).apply {
|
val subtitleContainer = LinearLayout(activity).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
@@ -106,6 +143,7 @@ internal class PlayerChrome(
|
|||||||
textSize = 17f
|
textSize = 17f
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
typeface = Typeface.DEFAULT_BOLD
|
||||||
maxLines = 1
|
maxLines = 1
|
||||||
|
ellipsize = TextUtils.TruncateAt.END
|
||||||
}
|
}
|
||||||
bindings.sentenceMetaText = TextView(activity).apply {
|
bindings.sentenceMetaText = TextView(activity).apply {
|
||||||
setTextColor(if (translucent) SampleTheme.accentLight else SampleTheme.accentDeep)
|
setTextColor(if (translucent) SampleTheme.accentLight else SampleTheme.accentDeep)
|
||||||
@@ -157,28 +195,50 @@ internal class PlayerChrome(
|
|||||||
background = rounded(SampleTheme.overlayFaint, 0f, null)
|
background = rounded(SampleTheme.overlayFaint, 0f, null)
|
||||||
setPadding(18.dp, 14.dp, 18.dp, 16.dp)
|
setPadding(18.dp, 14.dp, 18.dp, 16.dp)
|
||||||
}
|
}
|
||||||
addView(bindings.lessonTitleText)
|
val topContent = LinearLayout(activity).apply {
|
||||||
if (screenState.activeModule == Module.TRAIN) {
|
orientation = LinearLayout.VERTICAL
|
||||||
addView(createContinuousPlaybackRow(translucent))
|
addView(bindings.lessonTitleText)
|
||||||
|
if (screenState.activeModule == Module.TRAIN) {
|
||||||
|
addView(createContinuousPlaybackRow(translucent))
|
||||||
|
}
|
||||||
|
addView(bindings.sentenceMetaText)
|
||||||
|
addView(LinearLayout(activity).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
gravity = Gravity.TOP
|
||||||
|
addView(
|
||||||
|
bindings.sentenceText,
|
||||||
|
LinearLayout.LayoutParams(
|
||||||
|
0,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
1f,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
bindings.explainSentenceButton = createExplainButton(translucent)
|
||||||
|
addView(
|
||||||
|
bindings.explainSentenceButton,
|
||||||
|
LinearLayout.LayoutParams(32.dp, 32.dp).apply { marginStart = 10.dp },
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
addView(bindings.sentenceMetaText)
|
addView(
|
||||||
addView(LinearLayout(activity).apply {
|
ScrollView(activity).apply {
|
||||||
orientation = LinearLayout.HORIZONTAL
|
isFillViewport = true
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
isVerticalScrollBarEnabled = false
|
||||||
addView(
|
overScrollMode = View.OVER_SCROLL_NEVER
|
||||||
bindings.sentenceText,
|
addView(
|
||||||
LinearLayout.LayoutParams(
|
topContent,
|
||||||
0,
|
LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
1f,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
bindings.explainSentenceButton = createExplainButton(translucent)
|
},
|
||||||
addView(
|
LinearLayout.LayoutParams(
|
||||||
bindings.explainSentenceButton,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
LinearLayout.LayoutParams(32.dp, 32.dp).apply { marginStart = 10.dp },
|
if (translucent) ViewGroup.LayoutParams.WRAP_CONTENT else 0,
|
||||||
)
|
if (translucent) 0f else 1f,
|
||||||
})
|
),
|
||||||
|
)
|
||||||
addView(bindings.progressBar)
|
addView(bindings.progressBar)
|
||||||
addView(bindings.statusText)
|
addView(bindings.statusText)
|
||||||
}
|
}
|
||||||
@@ -197,12 +257,7 @@ internal class PlayerChrome(
|
|||||||
val overlay = LinearLayout(activity).apply {
|
val overlay = LinearLayout(activity).apply {
|
||||||
orientation = LinearLayout.HORIZONTAL
|
orientation = LinearLayout.HORIZONTAL
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
gravity = Gravity.CENTER_VERTICAL
|
||||||
setPadding(12.dp, 12.dp, 12.dp, 12.dp)
|
setPadding(12.dp, 10.dp, 12.dp, 8.dp)
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
||||||
Gravity.TOP,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
bindings.timeText = widgets.overlayPill("--:-- / --:--").apply {
|
bindings.timeText = widgets.overlayPill("--:-- / --:--").apply {
|
||||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
||||||
@@ -217,11 +272,27 @@ internal class PlayerChrome(
|
|||||||
ImageButton(activity).apply {
|
ImageButton(activity).apply {
|
||||||
setImageResource(R.drawable.ic_screen_rotation)
|
setImageResource(R.drawable.ic_screen_rotation)
|
||||||
imageTintList = ColorStateList.valueOf(Color.WHITE)
|
imageTintList = ColorStateList.valueOf(Color.WHITE)
|
||||||
background = rounded(Color.argb(178, 9, 12, 16), 22.dp.toFloat())
|
val backgroundSize = 22.dp
|
||||||
|
val buttonSize = 44.dp
|
||||||
|
val inset = (buttonSize - backgroundSize) / 2
|
||||||
|
background = InsetDrawable(
|
||||||
|
GradientDrawable().apply {
|
||||||
|
shape = GradientDrawable.OVAL
|
||||||
|
setColor(Color.argb(102, 9, 12, 16))
|
||||||
|
setSize(backgroundSize, backgroundSize)
|
||||||
|
},
|
||||||
|
inset,
|
||||||
|
)
|
||||||
scaleType = ImageView.ScaleType.CENTER_INSIDE
|
scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||||
setPadding(10.dp, 10.dp, 10.dp, 10.dp)
|
setPadding(10.dp, 10.dp, 10.dp, 10.dp)
|
||||||
contentDescription = "横屏"
|
contentDescription = "横屏"
|
||||||
layoutParams = LinearLayout.LayoutParams(44.dp, 44.dp)
|
minimumWidth = 0
|
||||||
|
minimumHeight = 0
|
||||||
|
clipToOutline = false
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
|
foreground = null
|
||||||
|
}
|
||||||
|
layoutParams = LinearLayout.LayoutParams(buttonSize, buttonSize)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,40 +153,4 @@ internal class SampleWidgets(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun videoCard(
|
|
||||||
id: String,
|
|
||||||
title: String,
|
|
||||||
meta: String,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
): View {
|
|
||||||
val selected = screenState.activeItemId == id
|
|
||||||
val titleColor = if (selected) SampleTheme.textDark else Color.WHITE
|
|
||||||
val metaColor = if (selected) SampleTheme.accentDeep else SampleTheme.textMuted
|
|
||||||
return LinearLayout(activity).apply {
|
|
||||||
orientation = LinearLayout.VERTICAL
|
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
|
||||||
isClickable = true
|
|
||||||
background = if (selected) {
|
|
||||||
rounded(SampleTheme.selected, 8f, SampleTheme.accent)
|
|
||||||
} else {
|
|
||||||
rounded(SampleTheme.surface, 8f, SampleTheme.border)
|
|
||||||
}
|
|
||||||
setPadding(14.dp, 12.dp, 14.dp, 12.dp)
|
|
||||||
layoutParams = LinearLayout.LayoutParams(218.dp, 84.dp).withMargins(0, 0, 10.dp, 0)
|
|
||||||
setOnClickListener { onClick() }
|
|
||||||
addView(TextView(activity).apply {
|
|
||||||
text = title
|
|
||||||
setTextColor(titleColor)
|
|
||||||
textSize = 15f
|
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
|
||||||
maxLines = 2
|
|
||||||
})
|
|
||||||
addView(TextView(activity).apply {
|
|
||||||
text = meta
|
|
||||||
setTextColor(metaColor)
|
|
||||||
textSize = 12f
|
|
||||||
setPadding(0, 6.dp, 0, 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,14 @@ import android.graphics.Color
|
|||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.HorizontalScrollView
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import cn.learningpad.oraltrainer.sample.SampleBindings
|
|
||||||
import cn.learningpad.oraltrainer.sample.SampleTheme
|
import cn.learningpad.oraltrainer.sample.SampleTheme
|
||||||
import cn.learningpad.oraltrainer.sample.TrainScreenActions
|
|
||||||
import cn.learningpad.oraltrainer.sample.TrainingScreenState
|
|
||||||
import cn.learningpad.oraltrainer.sample.UiStyled
|
import cn.learningpad.oraltrainer.sample.UiStyled
|
||||||
|
|
||||||
internal class TrainScreen(
|
internal class TrainScreen(
|
||||||
private val activity: Activity,
|
private val activity: Activity,
|
||||||
private val bindings: SampleBindings,
|
|
||||||
private val screenState: TrainingScreenState,
|
|
||||||
private val widgets: SampleWidgets,
|
|
||||||
private val playerChrome: PlayerChrome,
|
private val playerChrome: PlayerChrome,
|
||||||
private val actions: TrainScreenActions,
|
|
||||||
) : UiStyled {
|
) : UiStyled {
|
||||||
override val uiContext get() = activity
|
override val uiContext get() = activity
|
||||||
|
|
||||||
@@ -29,47 +20,13 @@ internal class TrainScreen(
|
|||||||
root.addView(createHeader())
|
root.addView(createHeader())
|
||||||
root.addView(playerChrome.createPlayerSection())
|
root.addView(playerChrome.createPlayerSection())
|
||||||
root.addView(playerChrome.createSentenceSection())
|
root.addView(playerChrome.createSentenceSection())
|
||||||
root.addView(createCatalogSection())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun renderCatalogStrip() {
|
|
||||||
if (!bindings.hasCatalogChrome) return
|
|
||||||
bindings.catalogList.removeAllViews()
|
|
||||||
bindings.catalogStatusText.text = screenState.catalogStatus
|
|
||||||
screenState.catalogVideos.forEach { video ->
|
|
||||||
val enrolled = screenState.courseEnrollment[video.videoHash] == true
|
|
||||||
bindings.catalogList.addView(
|
|
||||||
widgets.videoCard(
|
|
||||||
id = video.videoHash,
|
|
||||||
title = video.title,
|
|
||||||
meta = if (enrolled) {
|
|
||||||
"已选修 · ${video.sentenceCount} 句"
|
|
||||||
} else {
|
|
||||||
"未选修 · 点击选修并进入"
|
|
||||||
},
|
|
||||||
onClick = { actions.onTrainCatalogVideoClick(video) },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showCatalogLoading() {
|
|
||||||
if (!bindings.hasCatalogChrome) return
|
|
||||||
bindings.catalogList.removeAllViews()
|
|
||||||
bindings.catalogList.addView(widgets.infoCard("课程加载中..."))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCatalogStatus(text: String) {
|
|
||||||
if (bindings.hasCatalogChrome) {
|
|
||||||
bindings.catalogStatusText.text = text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createHeader(): View {
|
private fun createHeader(): View {
|
||||||
val titleBlock = LinearLayout(activity).apply {
|
return LinearLayout(activity).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.HORIZONTAL
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
gravity = Gravity.CENTER_VERTICAL
|
||||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
setPadding(18.dp, 16.dp, 18.dp, 12.dp)
|
||||||
addView(TextView(activity).apply {
|
addView(TextView(activity).apply {
|
||||||
text = "八哥口语"
|
text = "八哥口语"
|
||||||
setTextColor(Color.WHITE)
|
setTextColor(Color.WHITE)
|
||||||
@@ -80,52 +37,10 @@ internal class TrainScreen(
|
|||||||
addView(TextView(activity).apply {
|
addView(TextView(activity).apply {
|
||||||
text = "外语跟读训练神器"
|
text = "外语跟读训练神器"
|
||||||
setTextColor(SampleTheme.textMuted)
|
setTextColor(SampleTheme.textMuted)
|
||||||
textSize = 20f
|
textSize = 18f
|
||||||
setPadding(1.dp, 4.dp, 0, 0)
|
includeFontPadding = false
|
||||||
|
setPadding(12.dp, 6.dp, 0, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return LinearLayout(activity).apply {
|
|
||||||
orientation = LinearLayout.HORIZONTAL
|
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
|
||||||
setPadding(18.dp, 16.dp, 18.dp, 12.dp)
|
|
||||||
addView(titleBlock)
|
|
||||||
addView(widgets.headerButton("导入") { actions.onImportLocalVideo() })
|
|
||||||
addView(widgets.headerButton("刷新") { actions.onRefreshCatalog() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createCatalogSection(): View {
|
|
||||||
bindings.catalogStatusText = TextView(activity).apply {
|
|
||||||
setTextColor(SampleTheme.textMuted)
|
|
||||||
textSize = 13f
|
|
||||||
gravity = Gravity.END
|
|
||||||
text = "正在同步"
|
|
||||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
|
|
||||||
}
|
|
||||||
bindings.catalogList = LinearLayout(activity).apply {
|
|
||||||
orientation = LinearLayout.HORIZONTAL
|
|
||||||
setPadding(0, 10.dp, 0, 4.dp)
|
|
||||||
}
|
|
||||||
val header = LinearLayout(activity).apply {
|
|
||||||
orientation = LinearLayout.HORIZONTAL
|
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
|
||||||
addView(TextView(activity).apply {
|
|
||||||
text = "课程"
|
|
||||||
setTextColor(Color.WHITE)
|
|
||||||
textSize = 16f
|
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
|
||||||
})
|
|
||||||
addView(bindings.catalogStatusText)
|
|
||||||
}
|
|
||||||
val scroller = HorizontalScrollView(activity).apply {
|
|
||||||
isHorizontalScrollBarEnabled = false
|
|
||||||
addView(bindings.catalogList)
|
|
||||||
}
|
|
||||||
return LinearLayout(activity).apply {
|
|
||||||
orientation = LinearLayout.VERTICAL
|
|
||||||
setPadding(16.dp, 0, 16.dp, 14.dp)
|
|
||||||
addView(header)
|
|
||||||
addView(scroller)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user