diff --git a/app/src/main/java/com/ztftrue/music/MusicViewModel.kt b/app/src/main/java/com/ztftrue/music/MusicViewModel.kt index d82b31b..9d6b662 100644 --- a/app/src/main/java/com/ztftrue/music/MusicViewModel.kt +++ b/app/src/main/java/com/ztftrue/music/MusicViewModel.kt @@ -156,12 +156,7 @@ class MusicViewModel : ViewModel() { hasTime = LyricsType.VTT currentCaptionList.addAll(readCaptions(File("$path.vtt"), LyricsType.VTT)) } else { - currentCaptionList.add( - Caption( - text = "No Lyrics, Double click to import lyrics", - 0, - ) - ) + } } 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 5ea8e2c..43b1234 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,16 +1,18 @@ package com.ztftrue.music.ui.play -import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -20,6 +22,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalTextToolbar +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp @@ -27,8 +31,9 @@ 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.CustomTextToolbar import com.ztftrue.music.utils.LyricsType -import com.ztftrue.music.utils.Utils.openFile +import com.ztftrue.music.utils.Utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File @@ -45,7 +50,7 @@ fun LyricsView( val context = LocalContext.current val listState = rememberLazyListState() var currentI by remember { mutableIntStateOf(0) } - + var isSelected by remember { mutableStateOf(true) } LaunchedEffect(musicViewModel.sliderPosition.floatValue) { val timeState = musicViewModel.sliderPosition.floatValue @@ -54,11 +59,11 @@ fun LyricsView( if (entry.timeStart > timeState) { if (currentI != index) { currentI = index - if (musicViewModel.autoScroll.value) { + if (musicViewModel.autoScroll.value && isSelected) { launch(Dispatchers.Main) { // TODO calculate the scroll position by  listState.scrollToItem( - if ((currentI - 6) < 0) 0 else (currentI - 6), + if ((currentI - 1) < 0) 0 else (currentI - 1), 0 ) } @@ -78,7 +83,7 @@ fun LyricsView( } if (cIndex >= 0 && cIndex != currentI) { currentI = cIndex - if (musicViewModel.autoScroll.value) { + if (musicViewModel.autoScroll.value && isSelected) { launch(Dispatchers.Main) { // TODO calculate the scroll position by  listState.scrollToItem(if ((currentI - 1) < 0) 0 else (currentI - 1), 0) @@ -90,41 +95,28 @@ fun LyricsView( if (musicViewModel.currentCaptionList.getOrElse(currentI) { Caption("", 0) }.text.isNotBlank()) { - if (musicViewModel.autoScroll.value) { + if (musicViewModel.autoScroll.value && isSelected) { launch(Dispatchers.Main) { - listState.scrollToItem(if ((currentI - 6) < 0) 0 else (currentI - 6), 0) + listState.scrollToItem(if ((currentI - 1) < 0) 0 else (currentI - 1), 0) } } } } } - - LazyColumn( - state = listState, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .onSizeChanged { sizeIt -> - size.value = sizeIt - } - .padding(start = 20.dp, end = 20.dp) - .combinedClickable( - onLongClick = { - if (musicViewModel.autoScroll.value) { - musicViewModel.autoScroll.value = false - Toast - .makeText(context, "Auto scroll is disabled", Toast.LENGTH_SHORT) - .show() - } else { - musicViewModel.autoScroll.value = true - Toast - .makeText(context, "Auto scroll is enable", Toast.LENGTH_SHORT) - .show() - } - - }, - onDoubleClick = { + if (musicViewModel.currentCaptionList.size == 0) { + Text( + text = "No Lyrics, Click to import lyrics", + color = MaterialTheme.colorScheme.onBackground, + fontSize = MaterialTheme.typography.titleLarge.fontSize, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(2.dp) + .onSizeChanged { sizeIt -> + size.value = sizeIt + } + .clickable() { if (musicViewModel.currentPlay.value != null) { val regexPattern = Regex("[<>\"/~'{}?,+=)(^&*%!@#\$]") val artistsFolder = musicViewModel.currentPlay.value?.artist @@ -137,7 +129,8 @@ fun LyricsView( folderPath ) folder?.mkdirs() - val id = musicViewModel.currentPlay.value?.name?.replace(regexPattern, "_") + val id = + musicViewModel.currentPlay.value?.name?.replace(regexPattern, "_") val pathLyrics: String = context.getExternalFilesDir(folderPath)?.absolutePath + "/$id.lrc" val path: String = @@ -145,35 +138,57 @@ fun LyricsView( val lyrics = File(pathLyrics) val text = File(path) if (lyrics.exists()) { - openFile(lyrics.path, context = context) + Utils.openFile(lyrics.path, context = context) } else if (text.exists()) { - openFile(text.path, context = context) + Utils.openFile(text.path, context = context) } else { val tempPath: String = context.getExternalFilesDir(folderPath)?.absolutePath + "/$id." (context as MainActivity).openFilePicker(tempPath) } } - }, - ) { - // Toast.makeText(context, "Double click to import lyrics", Toast.LENGTH_SHORT).show() - } - ) { - items(musicViewModel.currentCaptionList.size) { - Text( - text = musicViewModel.currentCaptionList[it].text, - color = MaterialTheme.colorScheme.onBackground, - fontSize = if (currentI == it) MaterialTheme.typography.titleLarge.fontSize else - MaterialTheme.typography.titleMedium.fontSize, - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(2.dp) - .onSizeChanged { sizeIt -> - size.value = sizeIt + } + ) + } else { + CompositionLocalProvider( + LocalTextToolbar provides CustomTextToolbar(LocalView.current) + ) { + SelectionContainer { + LazyColumn( + state = listState, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .onSizeChanged { sizeIt -> + size.value = sizeIt + } + .padding(start = 20.dp, end = 20.dp) + .combinedClickable( + ) { + // Toast.makeText(context, "Double click to import lyrics", Toast.LENGTH_SHORT).show() + } + ) { + items(musicViewModel.currentCaptionList.size) { + Text( + text = musicViewModel.currentCaptionList[it].text, + color = MaterialTheme.colorScheme.onBackground, + fontSize = if (currentI == it) MaterialTheme.typography.titleLarge.fontSize else + MaterialTheme.typography.titleMedium.fontSize, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(2.dp) + .onSizeChanged { sizeIt -> + size.value = sizeIt + } + ) } - ) + } + } } + } + + } 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 1b93ce2..a6de546 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 @@ -4,10 +4,12 @@ import android.os.Bundle import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.session.PlaybackStateCompat import android.view.MotionEvent +import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -32,6 +34,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.ScrollableTabRow import androidx.compose.material3.Slider import androidx.compose.material3.Surface +import androidx.compose.material3.Switch import androidx.compose.material3.Tab import androidx.compose.material3.TabRowDefaults import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset @@ -48,6 +51,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput @@ -55,6 +59,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.media3.common.Player @@ -228,7 +234,7 @@ fun PlayingPage( OperateType.RemoveFromQueue -> { val index = viewModel.musicQueue.indexOfFirst { it.id == music.id } - if(index == -1) return@OperateDialog + if (index == -1) return@OperateDialog val bundle = Bundle() bundle.putInt("index", index) viewModel.mediaBrowser?.sendCustomAction( @@ -308,8 +314,49 @@ fun PlayingPage( .fillMaxHeight(), topBar = { Column(Modifier.fillMaxWidth()) { - key(Unit) { + key(Unit, pagerTabState.currentPage) { TopBar(navController, viewModel, content = { + /** + * tabPositions[pagerTabState.currentPage] + * playViewTab + */ + if (playViewTab[pagerTabState.currentPage].id == LyricsID) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .combinedClickable(onLongClick = { + Toast + .makeText( + context, + "Switch auto scroll", + Toast.LENGTH_SHORT + ) + .show() + }) { + viewModel.autoScroll.value = !viewModel.autoScroll.value + } + .padding(0.dp) + .height(50.dp) + ) { + Text( + text = "Scroll", + modifier = Modifier.padding(0.dp), + color = MaterialTheme.colorScheme.onBackground, + fontSize = TextUnit(12f, TextUnitType.Sp), + lineHeight = TextUnit(12f, TextUnitType.Sp), + ) + Switch(checked = viewModel.autoScroll.value, + modifier = Modifier + .scale(0.5f) + .padding(0.dp), + onCheckedChange = { + viewModel.autoScroll.value = it + } + ) + } + } + + IconButton( modifier = Modifier.width(50.dp), onClick = { showDialog = true diff --git a/app/src/main/java/com/ztftrue/music/ui/public/TopBar.kt b/app/src/main/java/com/ztftrue/music/ui/public/TopBar.kt index 5ee11db..557cdc3 100644 --- a/app/src/main/java/com/ztftrue/music/ui/public/TopBar.kt +++ b/app/src/main/java/com/ztftrue/music/ui/public/TopBar.kt @@ -85,7 +85,7 @@ fun TopBar( }) } BackButton(navController) - Row { + Row ( verticalAlignment = Alignment.CenterVertically){ IconButton(onClick = { showDialog = true }) { diff --git a/app/src/main/java/com/ztftrue/music/utils/CustomTextToolbar.kt b/app/src/main/java/com/ztftrue/music/utils/CustomTextToolbar.kt new file mode 100644 index 0000000..ec8f25a --- /dev/null +++ b/app/src/main/java/com/ztftrue/music/utils/CustomTextToolbar.kt @@ -0,0 +1,77 @@ +package com.ztftrue.music.utils + +import android.view.ActionMode +import android.view.View +import androidx.annotation.DoNotInline +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.platform.TextToolbar +import androidx.compose.ui.platform.TextToolbarStatus + +internal class CustomTextToolbar(private val view: View) : TextToolbar { + private var actionMode: ActionMode? = null + private val textActionModeCallback: TextActionModeCallback = TextActionModeCallback() + + override var status: TextToolbarStatus = TextToolbarStatus.Hidden + private set + + override fun hide() { + status = TextToolbarStatus.Hidden + actionMode?.finish() + actionMode = null + } + + override fun showMenu( + rect: Rect, + onCopyRequested: (() -> Unit)?, + onPasteRequested: (() -> Unit)?, + onCutRequested: (() -> Unit)?, + onSelectAllRequested: (() -> Unit)? + +// Before 1.2.0 ActionCallback has to be defined like this: +// typealias ActionCallback = () -> Unit +// +// onCopyRequested: ActionCallback?, +// onPasteRequested: ActionCallback?, +// onCutRequested: ActionCallback?, +// onSelectAllRequested: ActionCallback? + ) { + textActionModeCallback.rect = rect + textActionModeCallback.onCopyRequested = onCopyRequested + textActionModeCallback.onCutRequested = onCutRequested + textActionModeCallback.onPasteRequested = onPasteRequested + textActionModeCallback.onSelectAllRequested = onSelectAllRequested + if (actionMode == null) { + status = TextToolbarStatus.Shown + actionMode = + TextToolbarHelperMethods.startActionMode( + view, + FloatingTextActionModeCallback(textActionModeCallback), + ActionMode.TYPE_FLOATING + ) + } else { + actionMode?.invalidate() + } + } + + +} + +internal object TextToolbarHelperMethods { + @DoNotInline + fun startActionMode( + view: View, + actionModeCallback: ActionMode.Callback, + type: Int + ): ActionMode { + return view.startActionMode( + actionModeCallback, + type + ) + } + + fun invalidateContentRect(actionMode: ActionMode) { + actionMode.invalidateContentRect() + } +} + + diff --git a/app/src/main/java/com/ztftrue/music/utils/FloatingTextActionModeCallback.kt b/app/src/main/java/com/ztftrue/music/utils/FloatingTextActionModeCallback.kt new file mode 100644 index 0000000..7a2dee8 --- /dev/null +++ b/app/src/main/java/com/ztftrue/music/utils/FloatingTextActionModeCallback.kt @@ -0,0 +1,40 @@ +package com.ztftrue.music.utils + +import android.view.ActionMode +import android.view.Menu +import android.view.MenuItem +import android.view.View + +internal class FloatingTextActionModeCallback( + private val callback: TextActionModeCallback +) : ActionMode.Callback2() { + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + return callback.onActionItemClicked(mode, item) + } + + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return callback.onCreateActionMode(mode, menu) + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return callback.onPrepareActionMode(mode, menu) + } + + override fun onDestroyActionMode(mode: ActionMode?) { + callback.onDestroyActionMode() + } + + override fun onGetContentRect( + mode: ActionMode?, + view: View?, + outRect: android.graphics.Rect? + ) { + val rect = callback.rect + outRect?.set( + rect.left.toInt(), + rect.top.toInt(), + rect.right.toInt(), + rect.bottom.toInt() + ) + } +} \ No newline at end of file