diff --git a/app/src/main/java/com/ztftrue/music/MusicViewModel.kt b/app/src/main/java/com/ztftrue/music/MusicViewModel.kt index ff87dd4..6559cf9 100644 --- a/app/src/main/java/com/ztftrue/music/MusicViewModel.kt +++ b/app/src/main/java/com/ztftrue/music/MusicViewModel.kt @@ -20,6 +20,7 @@ import androidx.navigation.NavHostController import com.ztftrue.music.sqlData.model.MainTab import com.ztftrue.music.sqlData.model.MusicItem import com.ztftrue.music.ui.play.Lyrics +import com.ztftrue.music.utils.AnnotatedStringCaption import com.ztftrue.music.utils.AnyListBase import com.ztftrue.music.utils.Caption import com.ztftrue.music.utils.EqualizerBand @@ -88,7 +89,7 @@ class MusicViewModel : ViewModel() { // lyrics var itemDuration: Long = 1 var hasTime: LyricsType = LyricsType.TEXT - var currentCaptionList = mutableStateListOf() + var currentCaptionList = mutableStateListOf() // sleep time var sleepTime = mutableLongStateOf(0L) @@ -157,24 +158,29 @@ class MusicViewModel : ViewModel() { hasTime = LyricsType.VTT currentCaptionList.addAll(readCaptions(File("$path.vtt"), LyricsType.VTT)) } else { - + return } - } val duration = currentPlay.duration // every lyrics line duration - itemDuration = duration / if(currentCaptionList.size == 0) 1 else currentCaptionList.size + itemDuration = duration / if (currentCaptionList.size == 0) 1 else currentCaptionList.size } - private fun readLyricsOrText(file: File, context: Context): ArrayList { - val arrayList = arrayListOf() + private fun readLyricsOrText(file: File, context: Context): ArrayList { + val arrayList = arrayListOf() val inputStream: InputStream = file.inputStream() val inputString = inputStream.bufferedReader().use { it.readText() } inputString.split("\n").forEach { if (it.startsWith("offset:")) { // TODO } else { - arrayList.add(Utils.parseLyricLine(it, context)) + val captions = Utils.parseLyricLine(it, context) + val an = AnnotatedStringCaption( + text = captions.text.split(Regex("[\\n\\r\\s]+")), + timeStart = captions.timeStart, + timeEnd = captions.timeEnd + ) + arrayList.add(an) } } return arrayList @@ -183,13 +189,23 @@ class MusicViewModel : ViewModel() { private fun readCaptions( file: File, captionType: LyricsType, - ): ArrayList { - val arrayList = arrayListOf() + ): ArrayList { + val captions = arrayListOf() if (captionType == LyricsType.SRT) { - arrayList.addAll(Utils.parseSrtFile(file)) + captions.addAll(Utils.parseSrtFile(file)) } else if (captionType == LyricsType.VTT) { - arrayList.addAll(Utils.parseVttFile(file)) + captions.addAll(Utils.parseVttFile(file)) } + val arrayList = arrayListOf() + captions.forEach { + val an = AnnotatedStringCaption( + text = it.text.split(Regex("[\\n\\r\\s]+")), + timeStart = it.timeStart, + timeEnd = it.timeEnd + ) + arrayList.add(an) + } + return arrayList } diff --git a/app/src/main/java/com/ztftrue/music/ui/play/CoverView.kt b/app/src/main/java/com/ztftrue/music/ui/play/CoverView.kt index 949e5be..e563aee 100644 --- a/app/src/main/java/com/ztftrue/music/ui/play/CoverView.kt +++ b/app/src/main/java/com/ztftrue/music/ui/play/CoverView.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -35,7 +36,7 @@ import com.ztftrue.music.R @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) fun CoverView(musicViewModel: MusicViewModel) { val listState = rememberLazyListState() - var paint by remember { mutableStateOf(null) } + var paint by remember { mutableStateOf(null) } val showOtherMessage = remember { mutableStateOf(false) } LaunchedEffect(musicViewModel.currentPlay.value) { @@ -77,6 +78,16 @@ fun CoverView(musicViewModel: MusicViewModel) { color = MaterialTheme.colorScheme.onBackground,// Set the text color here fontSize = MaterialTheme.typography.titleSmall.fontSize ) + Text( + text = "artist: ${it1.artist}", modifier = + Modifier + .padding(0.dp) + .height(30.dp) + .horizontalScroll(rememberScrollState(0)) + .fillMaxWidth(), + color = MaterialTheme.colorScheme.onBackground,// Set the text color here + fontSize = MaterialTheme.typography.titleSmall.fontSize + ) Text( text = "album: ${it1.album}", modifier = Modifier diff --git a/app/src/main/java/com/ztftrue/music/ui/play/LyricsView.kt b/app/src/main/java/com/ztftrue/music/ui/play/LyricsView.kt index d09e61f..f365e01 100644 --- a/app/src/main/java/com/ztftrue/music/ui/play/LyricsView.kt +++ b/app/src/main/java/com/ztftrue/music/ui/play/LyricsView.kt @@ -1,7 +1,9 @@ package com.ztftrue.music.ui.play +import android.content.Context import android.content.Intent -import androidx.compose.foundation.ExperimentalFoundationApi +import android.util.TypedValue +import android.view.MotionEvent import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -24,13 +26,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.motionEventSpy +import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext @@ -40,7 +46,10 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -49,7 +58,7 @@ import androidx.compose.ui.window.PopupProperties import androidx.media3.common.util.UnstableApi import com.ztftrue.music.MainActivity import com.ztftrue.music.MusicViewModel -import com.ztftrue.music.utils.Caption +import com.ztftrue.music.utils.AnnotatedStringCaption import com.ztftrue.music.utils.CustomTextToolbar import com.ztftrue.music.utils.LyricsType import com.ztftrue.music.utils.Utils @@ -63,7 +72,7 @@ const val Lyrics = "lyrics" var size = mutableStateOf(IntSize.Zero) @UnstableApi -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalComposeUiApi::class) @Composable fun LyricsView( musicViewModel: MusicViewModel, @@ -115,8 +124,8 @@ fun LyricsView( } else { currentI = (timeState / musicViewModel.itemDuration).toInt() if (musicViewModel.currentCaptionList.getOrElse(currentI) { - Caption("", 0) - }.text.isNotBlank()) { + AnnotatedStringCaption(arrayListOf(), 0) + }.text.isNotEmpty()) { if (musicViewModel.autoScroll.value && isSelected && !showMenu) { launch(Dispatchers.Main) { listState.scrollToItem(if ((currentI - 1) < 0) 0 else (currentI - 1), 0) @@ -126,27 +135,44 @@ fun LyricsView( } } - + var fontSize by remember { + mutableIntStateOf(18) + } var word by remember { mutableStateOf("") } + var selectedTag by remember { + mutableStateOf("") + } + + var popupOffset by remember { + mutableStateOf(IntOffset(0, 0)) + } + if (showMenu) { val list = getAllCitivity(context) - if(list.isEmpty()) { - showMenu=false - }else{ + if (list.isEmpty()) { + showMenu = false + } else { Popup( // on below line we are adding // alignment and properties. alignment = Alignment.TopCenter, - properties = PopupProperties() + properties = PopupProperties(), + offset = popupOffset, + onDismissRequest = { + showMenu = false + isSelected = false + selectedTag = "" + word = "" + } ) { val rowListSate = rememberLazyListState() val configuration = LocalConfiguration.current // on the below line we are creating a box. Column( Modifier - .size((configuration.screenWidthDp - 40).dp, 50.dp) + .size((configuration.screenWidthDp - 40).dp, 60.dp) .padding(top = 5.dp) // on below line we are adding background color .background( @@ -182,6 +208,9 @@ fun LyricsView( word ) showMenu = false + isSelected = false + selectedTag = "" + word = "" context.startActivity(intent) } // color = MaterialTheme.colorScheme.onBackground, @@ -207,10 +236,7 @@ fun LyricsView( modifier = Modifier .fillMaxWidth() .padding(2.dp) - .onSizeChanged { sizeIt -> - size.value = sizeIt - } - .clickable() { + .clickable { if (musicViewModel.currentPlay.value != null) { val regexPattern = Regex("[<>\"/~'{}?,+=)(^&*%!@#\$]") val artistsFolder = musicViewModel.currentPlay.value?.artist @@ -247,56 +273,86 @@ fun LyricsView( CompositionLocalProvider( LocalTextToolbar provides CustomTextToolbar(LocalView.current) ) { - SelectionContainer { LazyColumn( state = listState, modifier = Modifier .fillMaxWidth() .fillMaxHeight() + .pointerInteropFilter { + when (it.action) { + MotionEvent.ACTION_DOWN -> { + if (it.action == MotionEvent.ACTION_DOWN) { + val a = if (it.y > size.value.height / 2) { + it.y - fontSize * 2.5 - 60.dp.toPx(context) + } else { + it.y + fontSize * 2.5 + } + popupOffset = IntOffset(0, a.toInt()) + } + } + } + false + } + .motionEventSpy { + + } .onSizeChanged { sizeIt -> size.value = sizeIt } .padding(start = 20.dp, end = 20.dp) ) { - items(musicViewModel.currentCaptionList.size) { - val tex = musicViewModel.currentCaptionList[it].text - val annotatedString = buildAnnotatedString { - for (text in tex.split(Regex("[\\n\\r\\s]+"))) { - val pattern = Regex("[,:;.\"]") - pushStringAnnotation("word", text.replace(pattern, "")) - withStyle(style = SpanStyle()) { - append(text) + items(musicViewModel.currentCaptionList.size) { listIndex -> + key(Unit) { + val tex = musicViewModel.currentCaptionList[listIndex].text + val annotatedString = buildAnnotatedString { + for ((index, text) in tex.withIndex()) { + val pattern = Regex("[,:;.\"]") + val tItem = text.replace(pattern, "") + pushStringAnnotation("word$tItem$index", tItem) + withStyle( + style = SpanStyle( + textDecoration = if (selectedTag == "$listIndex word$tItem$index") { + TextDecoration.Underline + } else { + TextDecoration.None + } + ) + ) { + append(text) + } + pop() + pushStringAnnotation("space", "") + append(" ") + pop() } - pop() - pushStringAnnotation("space", "") - append(" ") - pop() } - } - ClickableText( - text = annotatedString, - style = TextStyle( - color = MaterialTheme.colorScheme.onBackground, - fontSize = if (currentI == it && musicViewModel.autoHighLight.value) 24.sp else - 18.sp, - textAlign = TextAlign.Center, - lineHeight = MaterialTheme.typography.titleLarge.fontSize - ), - modifier = Modifier - .fillMaxWidth() - .padding(2.dp) - .onSizeChanged { sizeIt -> - size.value = sizeIt - }, - onClick = { offset -> + ClickableText( + text = annotatedString, + style = TextStyle( + color = if (currentI == listIndex && musicViewModel.autoHighLight.value) { +// MaterialTheme.colorScheme.onSecondary + Color.Blue + } else { + MaterialTheme.colorScheme.onBackground + }, + fontSize = fontSize.sp, + textAlign = TextAlign.Center, + lineHeight = (fontSize * 1.5).sp + ), + modifier = Modifier + .fillMaxWidth() + .padding(2.dp) + ) { offset -> + if (showMenu) { showMenu = false } else { val annotations = annotatedString.getStringAnnotations(offset, offset) annotations.firstOrNull()?.let { itemAnnotations -> - if (itemAnnotations.tag == "word") { + if (itemAnnotations.tag.startsWith("word")) { + selectedTag = "$listIndex ${itemAnnotations.tag}" word = itemAnnotations.item showMenu = true } else { @@ -305,7 +361,9 @@ fun LyricsView( } } - }) + } + } + } } } @@ -315,3 +373,8 @@ fun LyricsView( } +private fun Dp.toPx(context: Context): Int { + val displayMetrics = context.resources.displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.value, displayMetrics).toInt() +} + diff --git a/app/src/main/java/com/ztftrue/music/ui/play/PlayingPage.kt b/app/src/main/java/com/ztftrue/music/ui/play/PlayingPage.kt index 30e1406..3bb0ce3 100644 --- a/app/src/main/java/com/ztftrue/music/ui/play/PlayingPage.kt +++ b/app/src/main/java/com/ztftrue/music/ui/play/PlayingPage.kt @@ -384,20 +384,9 @@ fun PlayingPage( text = it1.name, modifier = Modifier .padding(0.dp) - .height(40.dp) .horizontalScroll(rememberScrollState(0)) .fillMaxWidth(), color = MaterialTheme.colorScheme.onBackground, - fontSize = MaterialTheme.typography.titleMedium.fontSize - ) - Text( - text = it1.artist, modifier = - Modifier - .padding(0.dp) - .height(30.dp) - .horizontalScroll(rememberScrollState(0)) - .fillMaxWidth(), - color = MaterialTheme.colorScheme.onBackground,// Set the text color here fontSize = MaterialTheme.typography.titleSmall.fontSize ) } diff --git a/app/src/main/java/com/ztftrue/music/utils/Model.kt b/app/src/main/java/com/ztftrue/music/utils/Model.kt index f09ac59..37637ac 100644 --- a/app/src/main/java/com/ztftrue/music/utils/Model.kt +++ b/app/src/main/java/com/ztftrue/music/utils/Model.kt @@ -1,6 +1,7 @@ package com.ztftrue.music.utils import android.os.Parcelable +import androidx.compose.ui.text.AnnotatedString import kotlinx.parcelize.Parcelize import java.io.Serializable @@ -82,4 +83,10 @@ data class Caption( val text: String, val timeStart: Long, val timeEnd: Long=0 +) + +data class AnnotatedStringCaption( + val text: List, + val timeStart: Long, + val timeEnd: Long=0 ) \ No newline at end of file