麦动KTV: 移除Gitee数据库(改官方Muse同步), 歌曲源切w.w345.my, 远程日志+广告屏蔽

- DatabaseBootstrapper: 删除 Gitee 分片/manifest/blob 下载逻辑, 曲库只走官方 MuseDbSync
- build.gradle: 移除 GITEE_DATABASE_* 构建参数
- MainActivity: 重置数据库文案改为官方同步
- ktv_api.js: 歌曲源仅 w.w345.my, 缓存标记 7, 广告/错号资源拦截+重试
- 远程日志: 点歌记录 编号/标题/请求源/状态
This commit is contained in:
zijunchiang
2026-09-04 21:23:21 +08:00
commit 0c26f6da68
1287 changed files with 25218 additions and 0 deletions
@@ -0,0 +1,97 @@
package com.local.ktv
import android.content.Context
import android.graphics.Color
import android.graphics.Outline
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import kotlin.math.ceil
/** Four-column singer grid matching the original OTT singer page. */
class SingerGridAdapter(
private val context: Context,
private val singers: List<Array<String?>>,
private val onSingerClick: (Array<String?>) -> Unit,
) : BaseAdapter() {
private val density = context.resources.displayMetrics.density
override fun getCount(): Int = ceil(singers.size / COLUMN_COUNT.toDouble()).toInt()
override fun getItem(position: Int): Any = position
override fun getItemId(position: Int): Long = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val row = (convertView as? LinearLayout) ?: LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(145))
}
row.removeAllViews()
repeat(COLUMN_COUNT) { column ->
val index = position * COLUMN_COUNT + column
val singer = singers.getOrNull(index)
row.addView(
singer?.let(::createSingerTile) ?: View(context),
LinearLayout.LayoutParams(0, dp(145), 1f),
)
}
return row
}
private fun createSingerTile(singer: Array<String?>): View {
val name = singer.getOrNull(1).orEmpty()
return LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
isFocusable = true
isClickable = true
setAccessibleFocus("focus:singer:${singer.getOrNull(0) ?: name}", "歌星,$name")
val avatar = ImageView(context).apply {
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
scaleType = ImageView.ScaleType.CENTER_CROP
SingerAvatarLoader.load(this, singer.getOrNull(5), R.drawable.ic_singer_placeholder)
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setOval(0, 0, view.width, view.height)
}
}
clipToOutline = true
}
addView(avatar, LinearLayout.LayoutParams(dp(88), dp(88)).apply { topMargin = dp(21) })
addView(TextView(context).apply {
text = name
textSize = 16f
setTextColor(Color.WHITE)
gravity = Gravity.CENTER
isSingleLine = true
ellipsize = android.text.TextUtils.TruncateAt.END
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(30)).apply { topMargin = dp(4) })
setOnTouchListener { view, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> view.animate().scaleX(0.94f).scaleY(0.94f).setDuration(90L).start()
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
view.animate().scaleX(1f).scaleY(1f).setDuration(120L).start()
}
false
}
setOnClickListener { onSingerClick(singer) }
TvFocusStyler.install(this)
}
}
private fun dp(value: Int): Int = (value * density + 0.5f).toInt()
companion object {
private const val COLUMN_COUNT = 4
}
}