From af14e9c73b71c419186393babc4fa6fabadc0b51 Mon Sep 17 00:00:00 2001 From: ztftrue Date: Fri, 2 Feb 2024 00:20:29 +0800 Subject: [PATCH] add: toolbar --- .../com/ztftrue/music/ui/play/LyricsView.kt | 174 +++++++++--------- .../utils/textToolbar/CustomTextToolbar.kt | 35 +++- .../FloatingTextActionModeCallback.kt | 1 + .../textToolbar/TextActionModeCallback.kt | 66 ++++++- 4 files changed, 186 insertions(+), 90 deletions(-) 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 69cfe1b..a593d9d 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 @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.util.TypedValue import android.view.MotionEvent +import android.view.View import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -59,9 +60,9 @@ import androidx.media3.common.util.UnstableApi import com.ztftrue.music.MainActivity import com.ztftrue.music.MusicViewModel import com.ztftrue.music.utils.ListStringCaption -import com.ztftrue.music.utils.textToolbar.CustomTextToolbar import com.ztftrue.music.utils.LyricsType import com.ztftrue.music.utils.Utils +import com.ztftrue.music.utils.textToolbar.CustomTextToolbar import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File @@ -278,110 +279,117 @@ fun LyricsView( ) } else { CompositionLocalProvider( - LocalTextToolbar provides CustomTextToolbar(LocalView.current) + LocalTextToolbar provides CustomTextToolbar( + LocalView.current, + musicViewModel.dictionaryAppList + ) ) { - 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 * 3 - 60.dp.toPx(context) - } else { - it.y + fontSize * 3 + SelectionContainer( + modifier = Modifier, + content = { + 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 * 3 - 60.dp.toPx(context) + } else { + it.y + fontSize * 3 + } + popupOffset = IntOffset(0, a.toInt()) } - popupOffset = IntOffset(0, a.toInt()) } } + false } - false - } - .motionEventSpy { + .motionEventSpy { - } - .onSizeChanged { sizeIt -> - size.value = sizeIt - } - .padding(start = 20.dp, end = 20.dp) - ) { - 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() - } } - ClickableText( - text = annotatedString, - style = TextStyle( - color = if (currentI == listIndex && musicViewModel.autoHighLight.value) { - Color.Blue - } else { - MaterialTheme.colorScheme.onBackground - }, - fontSize = fontSize.sp, - textAlign = musicViewModel.textAlign.value, - lineHeight = (fontSize * 1.5).sp, - textIndent = if (musicViewModel.textAlign.value == TextAlign.Justify || musicViewModel.textAlign.value == TextAlign.Left) { - TextIndent(fontSize.sp * 2) - } else { - TextIndent.None + .onSizeChanged { sizeIt -> + size.value = sizeIt + } + .padding(start = 20.dp, end = 20.dp) + ) { + 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() } - ), - modifier = Modifier - .fillMaxWidth() - .padding(2.dp) - ) { offset -> + } + ClickableText( + text = annotatedString, + style = TextStyle( + color = if (currentI == listIndex && musicViewModel.autoHighLight.value) { + Color.Blue + } else { + MaterialTheme.colorScheme.onBackground + }, + fontSize = fontSize.sp, + textAlign = musicViewModel.textAlign.value, + lineHeight = (fontSize * 1.5).sp, + textIndent = if (musicViewModel.textAlign.value == TextAlign.Justify || musicViewModel.textAlign.value == TextAlign.Left) { + TextIndent(fontSize.sp * 2) + } else { + TextIndent.None + } + ), + 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.startsWith("word")) { - selectedTag = "$listIndex ${itemAnnotations.tag}" - word = itemAnnotations.item - showMenu = true + if (showMenu) { + showMenu = false + } else { + val annotations = + annotatedString.getStringAnnotations(offset, offset) + annotations.firstOrNull()?.let { itemAnnotations -> + if (itemAnnotations.tag.startsWith("word")) { + selectedTag = "$listIndex ${itemAnnotations.tag}" + word = itemAnnotations.item + showMenu = true + } } } - } + } } - } + } } } - } + ) } } } + fun Dp.toPx(context: Context): Int { val displayMetrics = context.resources.displayMetrics return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.value, displayMetrics) diff --git a/app/src/main/java/com/ztftrue/music/utils/textToolbar/CustomTextToolbar.kt b/app/src/main/java/com/ztftrue/music/utils/textToolbar/CustomTextToolbar.kt index afc13c5..6b31567 100644 --- a/app/src/main/java/com/ztftrue/music/utils/textToolbar/CustomTextToolbar.kt +++ b/app/src/main/java/com/ztftrue/music/utils/textToolbar/CustomTextToolbar.kt @@ -1,13 +1,21 @@ package com.ztftrue.music.utils.textToolbar +import android.content.Intent import android.view.ActionMode import android.view.View import androidx.annotation.DoNotInline import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.TextToolbar import androidx.compose.ui.platform.TextToolbarStatus +import androidx.core.view.allViews +import com.ztftrue.music.sqlData.model.DictionaryApp -internal class CustomTextToolbar(private val view: View) : TextToolbar { + +internal class CustomTextToolbar( + private val view: View, + private val customApp: List, +) : TextToolbar { private var actionMode: ActionMode? = null private val textActionModeCallback: TextActionModeCallback = TextActionModeCallback() @@ -40,6 +48,31 @@ internal class CustomTextToolbar(private val view: View) : TextToolbar { textActionModeCallback.onCutRequested = onCutRequested textActionModeCallback.onPasteRequested = onPasteRequested textActionModeCallback.onSelectAllRequested = onSelectAllRequested + textActionModeCallback.customProcessTextApp = customApp + textActionModeCallback.onProcessAppItemClick = { + var min = 0 +// var max: Int = view.getText().length() +// if (mTextView.isFocused()) { +// val selStart: Int = mTextView.getSelectionStart() +// val selEnd: Int = mTextView.getSelectionEnd() +// min = Math.max(0, Math.min(selStart, selEnd)) +// max = Math.max(0, Math.max(selStart, selEnd)) +// } +// // Perform your definition lookup with the selected text +// // Perform your definition lookup with the selected text +// val selectedText: CharSequence = mTextView.getText().subSequence(min, max) + val intent = Intent() + intent.setAction(Intent.ACTION_PROCESS_TEXT) + intent.setClassName( + it.packageName, + it.name + ) + intent.putExtra( + Intent.EXTRA_PROCESS_TEXT, + actionMode?.title + ) + view.context.startActivity(intent) + } if (actionMode == null) { status = TextToolbarStatus.Shown actionMode = diff --git a/app/src/main/java/com/ztftrue/music/utils/textToolbar/FloatingTextActionModeCallback.kt b/app/src/main/java/com/ztftrue/music/utils/textToolbar/FloatingTextActionModeCallback.kt index fc42b6a..2e674af 100644 --- a/app/src/main/java/com/ztftrue/music/utils/textToolbar/FloatingTextActionModeCallback.kt +++ b/app/src/main/java/com/ztftrue/music/utils/textToolbar/FloatingTextActionModeCallback.kt @@ -9,6 +9,7 @@ internal class FloatingTextActionModeCallback( private val callback: TextActionModeCallback ) : ActionMode.Callback2() { override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { + return callback.onActionItemClicked(mode, item) } diff --git a/app/src/main/java/com/ztftrue/music/utils/textToolbar/TextActionModeCallback.kt b/app/src/main/java/com/ztftrue/music/utils/textToolbar/TextActionModeCallback.kt index 0eea31d..f4ad926 100644 --- a/app/src/main/java/com/ztftrue/music/utils/textToolbar/TextActionModeCallback.kt +++ b/app/src/main/java/com/ztftrue/music/utils/textToolbar/TextActionModeCallback.kt @@ -5,6 +5,9 @@ import android.view.Menu import android.view.MenuItem import androidx.annotation.VisibleForTesting import androidx.compose.ui.geometry.Rect +import com.ztftrue.music.sqlData.model.DictionaryApp +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.ui.platform.compositionContext internal class TextActionModeCallback( var rect: Rect = Rect.Zero, @@ -13,6 +16,8 @@ internal class TextActionModeCallback( var onPasteRequested: (() -> Unit)? = null, var onCutRequested: (() -> Unit)? = null, var onSelectAllRequested: (() -> Unit)? = null, + var customProcessTextApp: List? = null, + var onProcessAppItemClick: ((DictionaryApp) -> Unit)? = null ) { fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { requireNotNull(menu) @@ -21,6 +26,9 @@ internal class TextActionModeCallback( onCopyRequested?.let { addMenuItem(menu, MenuItemOption.Copy) } + customProcessTextApp?.forEachIndexed() { index, it -> + addMenuItemDictionaryApp(menu, it, index) + } onPasteRequested?.let { addMenuItem(menu, MenuItemOption.Paste) } @@ -42,12 +50,32 @@ internal class TextActionModeCallback( } fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { - when (item!!.itemId) { - MenuItemOption.Copy.id -> onCopyRequested?.invoke() - MenuItemOption.Paste.id -> onPasteRequested?.invoke() - MenuItemOption.Cut.id -> onCutRequested?.invoke() - MenuItemOption.SelectAll.id -> onSelectAllRequested?.invoke() - else -> return false + if (item != null) { + if (item.groupId == 0) { + when (item.itemId) { + MenuItemOption.Copy.id -> onCopyRequested?.invoke() + MenuItemOption.Paste.id -> onPasteRequested?.invoke() + MenuItemOption.Cut.id -> onCutRequested?.invoke() + MenuItemOption.SelectAll.id -> onSelectAllRequested?.invoke() + } + } else if (item.groupId == 1) { + val app = customProcessTextApp?.get(item.order) + if (app != null) { + +// var min = 0 +// var max: Int = view.getText().length() +// if (mTextView.isFocused()) { +// val selStart: Int = mTextView.getSelectionStart() +// val selEnd: Int = mTextView.getSelectionEnd() +// min = Math.max(0, Math.min(selStart, selEnd)) +// max = Math.max(0, Math.max(selStart, selEnd)) +// } +// // Perform your definition lookup with the selected text +// // Perform your definition lookup with the selected text +// val selectedText: CharSequence = mTextView.getText().subSequence(min, max) + onProcessAppItemClick?.invoke(app) + } + } } mode?.finish() return true @@ -63,6 +91,9 @@ internal class TextActionModeCallback( addOrRemoveMenuItem(menu, MenuItemOption.Paste, onPasteRequested) addOrRemoveMenuItem(menu, MenuItemOption.Cut, onCutRequested) addOrRemoveMenuItem(menu, MenuItemOption.SelectAll, onSelectAllRequested) + customProcessTextApp?.forEachIndexed() { index, it -> + addOrRemoveMenuAppItem(menu, it, index, onProcessAppItemClick) + } } internal fun addMenuItem(menu: Menu, item: MenuItemOption) { @@ -70,6 +101,11 @@ internal class TextActionModeCallback( .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) } + internal fun addMenuItemDictionaryApp(menu: Menu, item: DictionaryApp, index: Int) { + menu.add(1, index + 10, index, item.label) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) + } + private fun addOrRemoveMenuItem( menu: Menu, item: MenuItemOption, @@ -80,8 +116,26 @@ internal class TextActionModeCallback( callback == null && menu.findItem(item.id) != null -> menu.removeItem(item.id) } } + + private fun addOrRemoveMenuAppItem( + menu: Menu, + item: DictionaryApp, + index: Int, + callback: ((d: DictionaryApp) -> Unit)? + ) { + // TODO magic number, because this default max number is 4. + when { + callback != null && menu.findItem(index + 10) == null -> addMenuItemDictionaryApp( + menu, + item, + index + ) + callback == null && menu.findItem(index + 10) != null -> menu.removeItem(index + 10) + } + } } + internal enum class MenuItemOption(val id: Int) { Copy(0), Paste(1),