diff --git a/app/src/main/java/remix/myplayer/helper/LyricsHelper.kt b/app/src/main/java/remix/myplayer/helper/LyricsHelper.kt index a092f3f6..ca096a32 100644 --- a/app/src/main/java/remix/myplayer/helper/LyricsHelper.kt +++ b/app/src/main/java/remix/myplayer/helper/LyricsHelper.kt @@ -1,9 +1,39 @@ package remix.myplayer.helper +import android.content.Context +import remix.myplayer.R import remix.myplayer.lyrics.LyricsLine +import remix.myplayer.theme.Theme import remix.myplayer.ui.widget.desktop.DesktopLyricsView +import remix.myplayer.util.SPUtil object LyricsHelper { + fun showLocalLyricsTip(context: Context, action: () -> Unit) { + if (!SPUtil.getValue( + context, + SPUtil.LYRICS_KEY.NAME, + SPUtil.LYRICS_KEY.LOCAL_LYRICS_TIP_SHOWN, + false + ) + ) { + Theme.getBaseDialog(context) + .positiveText(R.string.confirm) + .onPositive { _, _ -> + SPUtil.putValue( + context, + SPUtil.LYRICS_KEY.NAME, + SPUtil.LYRICS_KEY.LOCAL_LYRICS_TIP_SHOWN, + true + ) + action.invoke() + } + .content(R.string.local_lyrics_tip) + .show() + } else { + action.invoke() + } + } + fun getDesktopLyricsContent( lyrics: List, offset: Int, progress: Int, duration: Int ): DesktopLyricsView.Content { diff --git a/app/src/main/java/remix/myplayer/lyrics/LrcParser.kt b/app/src/main/java/remix/myplayer/lyrics/LrcParser.kt index d4ea5473..068956e3 100644 --- a/app/src/main/java/remix/myplayer/lyrics/LrcParser.kt +++ b/app/src/main/java/remix/myplayer/lyrics/LrcParser.kt @@ -73,7 +73,6 @@ object LrcParser { // [xxx] if (it.endsWith(']')) { val tag = it.substring(1, it.lastIndex) - println(tag) // [offset:+/-xxx] if (tag.startsWith("offset:")) { try { diff --git a/app/src/main/java/remix/myplayer/lyrics/LyricsLine.kt b/app/src/main/java/remix/myplayer/lyrics/LyricsLine.kt index fbd1210d..69b35d43 100644 --- a/app/src/main/java/remix/myplayer/lyrics/LyricsLine.kt +++ b/app/src/main/java/remix/myplayer/lyrics/LyricsLine.kt @@ -1,9 +1,11 @@ package remix.myplayer.lyrics +import kotlinx.serialization.Serializable import remix.myplayer.App import remix.myplayer.R -abstract class LyricsLine { +@Serializable +sealed class LyricsLine { /** * 这行歌词的开始时间 */ diff --git a/app/src/main/java/remix/myplayer/lyrics/LyricsSearcher.kt b/app/src/main/java/remix/myplayer/lyrics/LyricsSearcher.kt index 274f89b4..251f1bb6 100644 --- a/app/src/main/java/remix/myplayer/lyrics/LyricsSearcher.kt +++ b/app/src/main/java/remix/myplayer/lyrics/LyricsSearcher.kt @@ -87,7 +87,11 @@ object LyricsSearcher { when (song) { is Song.Local -> "local" is Song.Remote -> "remote" - }, if (song is Song.Local) song.id else song.data, song.title, song.artist, song.album + }, + if (song is Song.Local) song.id.toString() else song.data, + song.title, + song.artist, + song.album ) ) // 要作为文件名,安全起见保证输出长度不超过 127 字节,SHA-384 输出 96 字节 diff --git a/app/src/main/java/remix/myplayer/misc/menu/AudioPopupListener.kt b/app/src/main/java/remix/myplayer/misc/menu/AudioPopupListener.kt index e5ec28ad..daf7158d 100644 --- a/app/src/main/java/remix/myplayer/misc/menu/AudioPopupListener.kt +++ b/app/src/main/java/remix/myplayer/misc/menu/AudioPopupListener.kt @@ -13,6 +13,7 @@ import remix.myplayer.bean.mp3.Song import remix.myplayer.db.room.DatabaseRepository import remix.myplayer.helper.DeleteHelper import remix.myplayer.helper.EQHelper +import remix.myplayer.helper.LyricsHelper import remix.myplayer.helper.MusicServiceRemote import remix.myplayer.helper.MusicServiceRemote.getCurrentSong import remix.myplayer.lyrics.provider.EmbeddedProvider @@ -20,7 +21,6 @@ import remix.myplayer.lyrics.provider.IgnoredProvider import remix.myplayer.lyrics.provider.StubProvider import remix.myplayer.service.Command import remix.myplayer.theme.Theme.getBaseDialog -import remix.myplayer.ui.ViewCommon import remix.myplayer.ui.activity.PlayerActivity import remix.myplayer.ui.dialog.AddtoPlayListDialog import remix.myplayer.ui.dialog.TimerDialog @@ -46,7 +46,7 @@ class AudioPopupListener(activity: PlayerActivity, private val song: Song) : val activity = ref.get() ?: return true when (item.itemId) { R.id.menu_lyric -> { - ViewCommon.showLocalLyricTip(activity) { + LyricsHelper.showLocalLyricsTip(activity) { onClickLyric(activity) } return true @@ -169,9 +169,10 @@ class AudioPopupListener(activity: PlayerActivity, private val song: Song) : 7 -> MusicServiceRemote.service?.updateLyrics(IgnoredProvider) // 忽略 8 -> TODO() // 调整字体大小 - 9 -> activity.showLyricOffsetView()// 调整时间轴 + 9 -> activity.showLyricOffsetView() // 调整时间轴 } }.show() + // TODO: update LyricsFragment } companion object { diff --git a/app/src/main/java/remix/myplayer/ui/ViewCommon.kt b/app/src/main/java/remix/myplayer/ui/ViewCommon.kt deleted file mode 100644 index a9a76a67..00000000 --- a/app/src/main/java/remix/myplayer/ui/ViewCommon.kt +++ /dev/null @@ -1,31 +0,0 @@ -package remix.myplayer.ui - -import android.content.Context -import remix.myplayer.R -import remix.myplayer.theme.Theme -import remix.myplayer.util.SPUtil - -object ViewCommon { - fun showLocalLyricTip(context: Context, action: () -> Unit) { -// if (!SPUtil.getValue( -// context, -// SPUtil.LYRIC_KEY.NAME, -// SPUtil.LYRIC_KEY.LYRIC_LOCAL_TIP_SHOWN, -// false -// ) -// ) { -// SPUtil.putValue(context, SPUtil.LYRIC_KEY.NAME, SPUtil.LYRIC_KEY.LYRIC_LOCAL_TIP_SHOWN, true) -// Theme.getBaseDialog(context) -// .negativeText(R.string.cancel) -// .positiveText(R.string.confirm) -// .onPositive { dialog, which -> -// action.invoke() -// } -// .content(R.string.local_lyric_tip) -// .show() -// } else { -// action.invoke() -// } - TODO() - } -} \ No newline at end of file diff --git a/app/src/main/java/remix/myplayer/ui/activity/PlayerActivity.kt b/app/src/main/java/remix/myplayer/ui/activity/PlayerActivity.kt index c4078298..9576a03f 100644 --- a/app/src/main/java/remix/myplayer/ui/activity/PlayerActivity.kt +++ b/app/src/main/java/remix/myplayer/ui/activity/PlayerActivity.kt @@ -26,7 +26,6 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.SeekBar import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.RequiresApi import androidx.appcompat.widget.PopupMenu import androidx.fragment.app.FragmentManager import androidx.palette.graphics.Palette diff --git a/app/src/main/java/remix/myplayer/ui/activity/SettingActivity.kt b/app/src/main/java/remix/myplayer/ui/activity/SettingActivity.kt index 676752ad..f1f441aa 100644 --- a/app/src/main/java/remix/myplayer/ui/activity/SettingActivity.kt +++ b/app/src/main/java/remix/myplayer/ui/activity/SettingActivity.kt @@ -46,6 +46,7 @@ import remix.myplayer.glide.UriFetcher.DOWNLOAD_LASTFM import remix.myplayer.helper.EQHelper import remix.myplayer.helper.LanguageHelper import remix.myplayer.helper.LanguageHelper.AUTO +import remix.myplayer.helper.LyricsHelper import remix.myplayer.helper.M3UHelper.exportPlayListToFile import remix.myplayer.helper.M3UHelper.importLocalPlayList import remix.myplayer.helper.M3UHelper.importM3UFile @@ -70,7 +71,6 @@ import remix.myplayer.theme.Theme import remix.myplayer.theme.Theme.getBaseDialog import remix.myplayer.theme.ThemeStore import remix.myplayer.theme.TintHelper -import remix.myplayer.ui.ViewCommon import remix.myplayer.ui.activity.MainActivity.Companion.EXTRA_LIBRARY import remix.myplayer.ui.activity.MainActivity.Companion.EXTRA_RECREATE import remix.myplayer.ui.activity.MainActivity.Companion.EXTRA_REFRESH_ADAPTER @@ -900,7 +900,7 @@ class SettingActivity : ToolbarActivity(), ColorChooserDialog.ColorCallback, * 歌词搜索优先级 */ private fun configLyricPriority() { - ViewCommon.showLocalLyricTip(this) { + LyricsHelper.showLocalLyricsTip(this) { LyricsOrderDialog().show(supportFragmentManager, "configLyricPriority") } } diff --git a/app/src/main/java/remix/myplayer/ui/fragment/LyricsFragment.kt b/app/src/main/java/remix/myplayer/ui/fragment/LyricsFragment.kt index a371c275..fcd1be65 100644 --- a/app/src/main/java/remix/myplayer/ui/fragment/LyricsFragment.kt +++ b/app/src/main/java/remix/myplayer/ui/fragment/LyricsFragment.kt @@ -118,13 +118,13 @@ class LyricsFragment : BaseMusicFragment(), View.OnClickList override fun onMediaStoreChanged() { super.onMediaStoreChanged() updateLyrics() - TODO("when is it called?") +// TODO("when is it called?") } override fun onMetaChanged() { super.onMetaChanged() updateLyrics() - TODO("when is it called?") +// TODO("when is it called?") } @UiThread diff --git a/app/src/main/java/remix/myplayer/ui/widget/LyricsView.kt b/app/src/main/java/remix/myplayer/ui/widget/LyricsView.kt index d27b65a9..03ead9f1 100644 --- a/app/src/main/java/remix/myplayer/ui/widget/LyricsView.kt +++ b/app/src/main/java/remix/myplayer/ui/widget/LyricsView.kt @@ -2,6 +2,7 @@ package remix.myplayer.ui.widget import android.annotation.SuppressLint import android.content.Context +import android.os.Build import android.util.AttributeSet import android.view.LayoutInflater import android.view.MotionEvent @@ -11,11 +12,13 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.UiThread -import remix.myplayer.R +import androidx.core.view.updatePadding +import remix.myplayer.databinding.LayoutLyricsLineBinding import remix.myplayer.databinding.LayoutLyricsViewBinding import remix.myplayer.lyrics.LyricsLine import remix.myplayer.lyrics.PerWordLyricsLine import remix.myplayer.theme.ThemeStore +import timber.log.Timber import kotlin.math.roundToInt import kotlin.time.Duration.Companion.milliseconds @@ -23,7 +26,10 @@ class LyricsView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : FrameLayout(context, attrs), View.OnTouchListener { companion object { + private const val TAG = "LyricsView" + private val DEACTIVATE_DELAY = 5000.milliseconds + private val AUTO_SCROLL_DELAY = 200.milliseconds private val normalTextColor @ColorInt get() = ThemeStore.textColorSecondary @@ -41,40 +47,29 @@ class LyricsView @JvmOverloads constructor( override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) + Timber.tag(TAG).v("onSizeChanged, h=$h") if (h != oldh) { // 给 container 上下加空白,确保第一行和最后一行歌词可以滚动到 view 中间 - binding.innerContainer.setPadding(0, h / 2, 0, h / 2) + val padding = (h + 1) / 2 + handler.post { + binding.innerContainer.setPadding(0, padding, 0, padding) + } } } - private fun newLayoutForLine(line: LyricsLine): LinearLayout { - val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) - val padding = resources.getDimensionPixelSize(R.dimen.lyrics_view_lrc_block_vertical_padding) - val textColor = normalTextColor - - val layout = LinearLayout(context) - layout.layoutParams = params - layout.setPadding(0, padding, 0, padding) - layout.orientation = LinearLayout.VERTICAL + private fun addLayoutForLine(line: LyricsLine) { + val layout = + LayoutLyricsLineBinding.inflate(LayoutInflater.from(context), binding.innerContainer, true) if (line.content.isNotBlank()) { - val view = TextView(context) - view.layoutParams = params - view.text = if (line is PerWordLyricsLine) { - line.getSpannedString(0f, textColor) + layout.content.text = if (line is PerWordLyricsLine) { + line.getSpannedString(0f, normalTextColor) } else { line.content } - view.setTextColor(textColor) - layout.addView(view) } - if (line.translation?.isNotBlank() == true) { - val view = TextView(context) - view.layoutParams = params - view.text = line.translation - view.setTextColor(textColor) - layout.addView(view) + if (!line.translation.isNullOrBlank()) { + layout.translation.text = line.translation } - return layout } /** @@ -89,7 +84,7 @@ class LyricsView @JvmOverloads constructor( binding.innerContainer.removeAllViews() isClickable = lyrics.isNotEmpty() value.forEach { - binding.innerContainer.addView(newLayoutForLine(it)) + addLayoutForLine(it) } rawProgressAndDuration = null lastHighlightLine = null @@ -107,7 +102,7 @@ class LyricsView @JvmOverloads constructor( } field = value if (isActive) { - showTimeIndicator() + updateTimeIndicator() } rawProgressAndDuration?.run { updateProgress(first, second) @@ -201,7 +196,8 @@ class LyricsView @JvmOverloads constructor( set(value) { field = value if (value) { - showTimeIndicator() + updateTimeIndicator() + binding.timeIndicator.visibility = View.VISIBLE handler.removeCallbacks(deactivateRunnable) handler.postDelayed(deactivateRunnable, DEACTIVATE_DELAY.inWholeMilliseconds) } else { @@ -215,34 +211,69 @@ class LyricsView @JvmOverloads constructor( private val deactivateRunnable = Runnable { isActive = false } + private val scrollToNearestLineRunnable = Runnable { + scrollToLine(getNearestLine()) + } @SuppressLint("SetTextI18n") - private fun showTimeIndicator() { + private fun updateTimeIndicator() { (lyrics[getNearestLine()].time - offset).coerceAtLeast(0).let { binding.time.text = "%02d:%02d.%02d".format(it / 1000 / 60, it / 1000 % 60, (it % 1000 / 10f).roundToInt()) + // TODO: don't set every time binding.playButton.setOnClickListener { _ -> onSeekToListener?.onSeekTo(it) } } - binding.timeIndicator.visibility = View.VISIBLE } + private var isTouching: Boolean = false + override fun onTouch(v: View, event: MotionEvent): Boolean { + check(v == binding.outerContainer) + isActive = true + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + isTouching = true + handler.removeCallbacks(scrollToNearestLineRunnable) + } + + MotionEvent.ACTION_UP -> { + isTouching = false + handler.postDelayed(scrollToNearestLineRunnable, AUTO_SCROLL_DELAY.inWholeMilliseconds) + } + } + return false } + private fun onScrollChange() { + updateTimeIndicator() + + handler.removeCallbacks(scrollToNearestLineRunnable) + if (!isTouching) { + handler.postDelayed(scrollToNearestLineRunnable, AUTO_SCROLL_DELAY.inWholeMilliseconds) + } + } + // 在单独函数以忽略警告 @SuppressLint("ClickableViewAccessibility") - private fun setupOnTouchListener() { + private fun init() { binding.outerContainer.setOnTouchListener(this) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + binding.outerContainer.setOnScrollChangeListener { _, _, _, _, _ -> + onScrollChange() + } + } else { + binding.outerContainer.viewTreeObserver.addOnScrollChangedListener { + onScrollChange() + } + } } init { - binding.outerContainer.onFlingEndListener = ResponsiveScrollView.OnFlingEndListener { - scrollToLine(getNearestLine()) - } - setupOnTouchListener() + init() } } diff --git a/app/src/main/java/remix/myplayer/ui/widget/ResponsiveScrollView.kt b/app/src/main/java/remix/myplayer/ui/widget/ResponsiveScrollView.kt deleted file mode 100644 index 2bce0e2a..00000000 --- a/app/src/main/java/remix/myplayer/ui/widget/ResponsiveScrollView.kt +++ /dev/null @@ -1,30 +0,0 @@ -package remix.myplayer.ui.widget - -import android.content.Context -import android.util.AttributeSet -import android.widget.ScrollView -import kotlin.math.abs - -class ResponsiveScrollView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null -) : ScrollView(context, attrs) { - fun interface OnFlingEndListener { - fun onFlingEnd(v: ResponsiveScrollView) - } - - var onFlingEndListener: OnFlingEndListener? = null - private var isBeingFlung: Boolean = false - - override fun fling(velocityY: Int) { - isBeingFlung = true - super.fling(velocityY) - } - - override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { - if (isBeingFlung && (abs(t - oldt) <= 1 || t >= measuredHeight || t <= 0)) { - isBeingFlung = false - onFlingEndListener?.onFlingEnd(this) - } - super.onScrollChanged(l, t, oldl, oldt) - } -} diff --git a/app/src/main/java/remix/myplayer/util/SPUtil.java b/app/src/main/java/remix/myplayer/util/SPUtil.java index c045a211..aa8583ad 100644 --- a/app/src/main/java/remix/myplayer/util/SPUtil.java +++ b/app/src/main/java/remix/myplayer/util/SPUtil.java @@ -110,6 +110,7 @@ public interface UPDATE_KEY { public interface LYRICS_KEY { String NAME = "Lyrics"; + String LOCAL_LYRICS_TIP_SHOWN = "local_lyrics_tip_shown"; String ORDER = "order"; String OFFSET_PREFIX = "offset_"; // offset_$hashKey } diff --git a/app/src/main/res/layout/fragment_lrc.xml b/app/src/main/res/layout/fragment_lrc.xml index 1161d796..8e7f91a2 100644 --- a/app/src/main/res/layout/fragment_lrc.xml +++ b/app/src/main/res/layout/fragment_lrc.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_margin="16dp" + android:padding="16dp" android:orientation="vertical"> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_lyrics_view.xml b/app/src/main/res/layout/layout_lyrics_view.xml index 4789d09e..ab3065cb 100644 --- a/app/src/main/res/layout/layout_lyrics_view.xml +++ b/app/src/main/res/layout/layout_lyrics_view.xml @@ -6,6 +6,20 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 75910d2f..784a5e28 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -473,6 +473,6 @@ 连接 编辑 2x倍速播放中 - 如果你的Android版本在11以及以上,本地歌词文件请确保放在公共目录中(如Downloads、Music等目录) + 如果你的Android版本在11以及以上,本地歌词文件请确保放在公共目录中(如Downloads、Music等目录) 缓冲中,请稍后 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index b308d17e..1d77733e 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -472,6 +472,6 @@ 連接 編輯 2x倍速播放中 - 如果你的Android版本在11以及以上,本地歌詞檔案請確保放在公共目錄中(如Downloads、Music等目錄) + 如果你的Android版本在11以及以上,本地歌詞檔案請確保放在公共目錄中(如Downloads、Music等目錄) 緩衝中,請稍後 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ed5f0a7a..a4a5cf64 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -473,6 +473,6 @@ 連接 編輯 2x倍速播放中 - 如果你的Android版本在11以及以上,本地歌詞檔案請確保放在公共目錄中(如Downloads、Music等目錄) + 如果你的Android版本在11以及以上,本地歌詞檔案請確保放在公共目錄中(如Downloads、Music等目錄) 緩衝中,請稍後 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 0e769a1a..daf0e829 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -137,6 +137,5 @@ 48dp 11sp - 4dp 24dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a3dbde47..edd64a77 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -474,7 +474,7 @@ Connect Edit Playing at 2x speed - If your Android version is 11 or above, please make sure the local lyrics files are placed in the public directory (such as Downloads, Music, etc. directories) + If your Android version is 11 or above, please make sure the local lyrics files are placed in the public directory (such as Downloads, Music, etc. directories) Buffering, please wait