🚀 Add new text selection actions (Share/Web)

Added new actions to text selection toolbar. Share action shares selected text to the app you want to. Web Search action searches selected text in your browser.

Partly resolves: #46
This commit is contained in:
Acclorite 2024-08-14 21:12:29 +03:00
parent a830be1403
commit 3fdc289720
6 changed files with 162 additions and 6 deletions

View file

@ -29,8 +29,10 @@ import ua.acclorite.book_story.R
private const val MENU_ITEM_COPY = 0
private const val MENU_ITEM_TRANSLATE = 1
private const val MENU_ITEM_DICTIONARY = 2
private const val MENU_ITEM_SHARE = 1
private const val MENU_ITEM_WEB = 2
private const val MENU_ITEM_TRANSLATE = 3
private const val MENU_ITEM_DICTIONARY = 4
/**
* Custom Text ActionMode callback. Used in pair with [CustomSelectionToolbar]. Follow [TextToolbar] for more info.
@ -39,6 +41,8 @@ private class CustomTextActionModeCallback(
private val context: Context,
var rect: Rect = Rect.Zero,
var onCopyRequested: (() -> Unit)? = null,
var onShareRequested: (() -> Unit)? = null,
var onWebSearchRequested: (() -> Unit)? = null,
var onTranslateRequested: (() -> Unit)? = null,
var onDictionaryRequested: (() -> Unit)? = null
) : ActionMode.Callback {
@ -51,13 +55,23 @@ private class CustomTextActionModeCallback(
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
}
onShareRequested?.let {
menu.add(0, MENU_ITEM_SHARE, 1, context.getString(R.string.share))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
}
onWebSearchRequested?.let {
menu.add(0, MENU_ITEM_WEB, 2, context.getString(R.string.web_search))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
}
onTranslateRequested?.let {
menu.add(0, MENU_ITEM_TRANSLATE, 1, context.getString(R.string.translate))
menu.add(0, MENU_ITEM_TRANSLATE, 3, context.getString(R.string.translate))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
}
onDictionaryRequested?.let {
menu.add(0, MENU_ITEM_DICTIONARY, 2, context.getString(R.string.dictionary))
menu.add(0, MENU_ITEM_DICTIONARY, 4, context.getString(R.string.dictionary))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
}
@ -71,6 +85,8 @@ private class CustomTextActionModeCallback(
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
when (item!!.itemId) {
MENU_ITEM_COPY -> onCopyRequested?.invoke()
MENU_ITEM_SHARE -> onShareRequested?.invoke()
MENU_ITEM_WEB -> onWebSearchRequested?.invoke()
MENU_ITEM_TRANSLATE -> onTranslateRequested?.invoke()
MENU_ITEM_DICTIONARY -> onDictionaryRequested?.invoke()
else -> return false
@ -116,12 +132,15 @@ private class FloatingTextActionModeCallback(
}
/**
* Custom Selection Toolbar. Used in pair with [CustomSelectionContainer] to display custom toolbar.
* Custom Selection Toolbar.
* Used in pair with [CustomSelectionContainer] to display custom toolbar.
*/
private class CustomSelectionToolbar(
private val view: View,
context: Context,
private val onCopyRequest: (() -> Unit)?,
private val onShareRequest: ((String) -> Unit)?,
private val onWebSearchRequest: ((String) -> Unit)?,
private val onTranslateRequest: ((String) -> Unit)?,
private val onDictionaryRequest: ((String) -> Unit)?
) : TextToolbar {
@ -145,6 +164,36 @@ private class CustomSelectionToolbar(
onCopyRequested?.invoke()
onCopyRequest?.invoke()
}
callback.onShareRequested = {
val previousClipboard = clipboardManager.primaryClip
onCopyRequested?.invoke()
val currentClipboard = clipboardManager.text
onShareRequest?.invoke(currentClipboard.toString())
if (previousClipboard != null) {
clipboardManager.setPrimaryClip(
previousClipboard
)
} else {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, " "))
}
}
callback.onWebSearchRequested = {
val previousClipboard = clipboardManager.primaryClip
onCopyRequested?.invoke()
val currentClipboard = clipboardManager.text
onWebSearchRequest?.invoke(currentClipboard.toString())
if (previousClipboard != null) {
clipboardManager.setPrimaryClip(
previousClipboard
)
} else {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, " "))
}
}
callback.onTranslateRequested = {
val previousClipboard = clipboardManager.primaryClip
onCopyRequested?.invoke()
@ -205,6 +254,8 @@ private class CustomSelectionToolbar(
@Composable
fun CustomSelectionContainer(
onCopyRequested: (() -> Unit),
onShareRequested: ((String) -> Unit),
onWebSearchRequested: ((String) -> Unit),
onTranslateRequested: ((String) -> Unit),
onDictionaryRequested: ((String) -> Unit),
content: @Composable (toolbarHidden: Boolean) -> Unit
@ -220,6 +271,12 @@ fun CustomSelectionContainer(
onCopyRequest = {
onCopyRequested()
},
onShareRequest = {
onShareRequested(it)
},
onWebSearchRequest = {
onWebSearchRequested(it)
},
onTranslateRequest = {
onTranslateRequested(it)
},

View file

@ -274,6 +274,36 @@ private fun ReaderScreen(
).show()
}
},
onShareRequested = { textToShare ->
onEvent(
ReaderEvent.OnOpenShareApp(
textToShare = textToShare,
context,
noAppsFound = {
Toast.makeText(
context,
context.getString(R.string.error_no_share_app),
Toast.LENGTH_SHORT
).show()
}
)
)
},
onWebSearchRequested = { textToSearch ->
onEvent(
ReaderEvent.OnOpenWebBrowser(
textToSearch = textToSearch,
context,
noAppsFound = {
Toast.makeText(
context,
context.getString(R.string.error_no_browser),
Toast.LENGTH_SHORT
).show()
}
)
)
},
onTranslateRequested = { textToTranslate ->
onEvent(
ReaderEvent.OnOpenTranslator(
@ -298,7 +328,7 @@ private fun ReaderScreen(
noAppsFound = {
Toast.makeText(
context,
context.getString(R.string.error_no_browser),
context.getString(R.string.error_no_dictionary),
Toast.LENGTH_SHORT
).show()
}

View file

@ -51,6 +51,18 @@ sealed class ReaderEvent {
val noAppsFound: () -> Unit
) : ReaderEvent()
data class OnOpenShareApp(
val textToShare: String,
val context: ComponentActivity,
val noAppsFound: () -> Unit
) : ReaderEvent()
data class OnOpenWebBrowser(
val textToSearch: String,
val context: ComponentActivity,
val noAppsFound: () -> Unit
) : ReaderEvent()
data class OnOpenDictionary(
val textToDefine: String,
val context: ComponentActivity,

View file

@ -360,6 +360,55 @@ class ReaderViewModel @Inject constructor(
}
}
is ReaderEvent.OnOpenShareApp -> {
launch {
val shareIntent = Intent()
shareIntent.action = Intent.ACTION_SEND
shareIntent.type = "text/plain"
shareIntent.putExtra(
Intent.EXTRA_SUBJECT,
event.context.getString(R.string.app_name)
)
shareIntent.putExtra(
Intent.EXTRA_TEXT,
event.textToShare.trim()
)
var shareFailure = false
shareIntent.launchActivity(event.context, createChooser = true) {
shareFailure = true
}
if (!shareFailure) {
return@launch
}
event.noAppsFound()
}
}
is ReaderEvent.OnOpenWebBrowser -> {
launch {
val browserIntent = Intent()
browserIntent.action = Intent.ACTION_WEB_SEARCH
browserIntent.putExtra(
SearchManager.QUERY,
event.textToSearch
)
var browserFailure = false
browserIntent.launchActivity(event.context) {
browserFailure = true
}
if (!browserFailure) {
return@launch
}
event.noAppsFound()
}
}
is ReaderEvent.OnOpenDictionary -> {
launch {
val dictionaryIntent = Intent()

View file

@ -101,7 +101,9 @@
<string name="error_query">Помилка: %1$s</string>
<string name="error_file_empty">Цей файл пустий або пошкоджений.</string>
<string name="error_no_translator">Не знайдено жодного перекладача.</string>
<string name="error_no_dictionary">Не знайдено жодного словника.</string>
<string name="error_no_browser">Не знайдено жодного браузера.</string>
<string name="error_no_share_app">Не знайдено застосунка щоб поділитися.</string>
<string name="error_no_text">
Вміст книги пустий. Будь ласка, перевірте вміст книги та оновіть її.
</string>
@ -160,6 +162,8 @@
<string name="download">Завантажити</string>
<string name="done">Готово</string>
<string name="enable">Увімкнути</string>
<string name="web_search">Веб-пошук</string>
<string name="share">Поділитися</string>
<!-- Items -->
<string name="files">файли</string>

View file

@ -118,7 +118,9 @@
<string name="error_query">Error: %1$s</string>
<string name="error_file_empty">This file is empty or corrupted.</string>
<string name="error_no_translator">No translator app found.</string>
<string name="error_no_dictionary">No dictionary app found.</string>
<string name="error_no_browser">No browser app found.</string>
<string name="error_no_share_app">No app to share found.</string>
<string name="error_no_text">
The book content is empty. Please, make sure that the book content is not empty and update it.
</string>
@ -176,6 +178,8 @@
<string name="download">Download</string>
<string name="done">Done</string>
<string name="enable">Enable</string>
<string name="web_search">Web search</string>
<string name="share">Share</string>
<!-- Items -->
<string name="files">files</string>