🚀 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:
parent
a830be1403
commit
3fdc289720
6 changed files with 162 additions and 6 deletions
|
|
@ -29,8 +29,10 @@ import ua.acclorite.book_story.R
|
||||||
|
|
||||||
|
|
||||||
private const val MENU_ITEM_COPY = 0
|
private const val MENU_ITEM_COPY = 0
|
||||||
private const val MENU_ITEM_TRANSLATE = 1
|
private const val MENU_ITEM_SHARE = 1
|
||||||
private const val MENU_ITEM_DICTIONARY = 2
|
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.
|
* 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,
|
private val context: Context,
|
||||||
var rect: Rect = Rect.Zero,
|
var rect: Rect = Rect.Zero,
|
||||||
var onCopyRequested: (() -> Unit)? = null,
|
var onCopyRequested: (() -> Unit)? = null,
|
||||||
|
var onShareRequested: (() -> Unit)? = null,
|
||||||
|
var onWebSearchRequested: (() -> Unit)? = null,
|
||||||
var onTranslateRequested: (() -> Unit)? = null,
|
var onTranslateRequested: (() -> Unit)? = null,
|
||||||
var onDictionaryRequested: (() -> Unit)? = null
|
var onDictionaryRequested: (() -> Unit)? = null
|
||||||
) : ActionMode.Callback {
|
) : ActionMode.Callback {
|
||||||
|
|
@ -51,13 +55,23 @@ private class CustomTextActionModeCallback(
|
||||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
.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 {
|
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)
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
onDictionaryRequested?.let {
|
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)
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,6 +85,8 @@ private class CustomTextActionModeCallback(
|
||||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||||
when (item!!.itemId) {
|
when (item!!.itemId) {
|
||||||
MENU_ITEM_COPY -> onCopyRequested?.invoke()
|
MENU_ITEM_COPY -> onCopyRequested?.invoke()
|
||||||
|
MENU_ITEM_SHARE -> onShareRequested?.invoke()
|
||||||
|
MENU_ITEM_WEB -> onWebSearchRequested?.invoke()
|
||||||
MENU_ITEM_TRANSLATE -> onTranslateRequested?.invoke()
|
MENU_ITEM_TRANSLATE -> onTranslateRequested?.invoke()
|
||||||
MENU_ITEM_DICTIONARY -> onDictionaryRequested?.invoke()
|
MENU_ITEM_DICTIONARY -> onDictionaryRequested?.invoke()
|
||||||
else -> return false
|
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 class CustomSelectionToolbar(
|
||||||
private val view: View,
|
private val view: View,
|
||||||
context: Context,
|
context: Context,
|
||||||
private val onCopyRequest: (() -> Unit)?,
|
private val onCopyRequest: (() -> Unit)?,
|
||||||
|
private val onShareRequest: ((String) -> Unit)?,
|
||||||
|
private val onWebSearchRequest: ((String) -> Unit)?,
|
||||||
private val onTranslateRequest: ((String) -> Unit)?,
|
private val onTranslateRequest: ((String) -> Unit)?,
|
||||||
private val onDictionaryRequest: ((String) -> Unit)?
|
private val onDictionaryRequest: ((String) -> Unit)?
|
||||||
) : TextToolbar {
|
) : TextToolbar {
|
||||||
|
|
@ -145,6 +164,36 @@ private class CustomSelectionToolbar(
|
||||||
onCopyRequested?.invoke()
|
onCopyRequested?.invoke()
|
||||||
onCopyRequest?.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 = {
|
callback.onTranslateRequested = {
|
||||||
val previousClipboard = clipboardManager.primaryClip
|
val previousClipboard = clipboardManager.primaryClip
|
||||||
onCopyRequested?.invoke()
|
onCopyRequested?.invoke()
|
||||||
|
|
@ -205,6 +254,8 @@ private class CustomSelectionToolbar(
|
||||||
@Composable
|
@Composable
|
||||||
fun CustomSelectionContainer(
|
fun CustomSelectionContainer(
|
||||||
onCopyRequested: (() -> Unit),
|
onCopyRequested: (() -> Unit),
|
||||||
|
onShareRequested: ((String) -> Unit),
|
||||||
|
onWebSearchRequested: ((String) -> Unit),
|
||||||
onTranslateRequested: ((String) -> Unit),
|
onTranslateRequested: ((String) -> Unit),
|
||||||
onDictionaryRequested: ((String) -> Unit),
|
onDictionaryRequested: ((String) -> Unit),
|
||||||
content: @Composable (toolbarHidden: Boolean) -> Unit
|
content: @Composable (toolbarHidden: Boolean) -> Unit
|
||||||
|
|
@ -220,6 +271,12 @@ fun CustomSelectionContainer(
|
||||||
onCopyRequest = {
|
onCopyRequest = {
|
||||||
onCopyRequested()
|
onCopyRequested()
|
||||||
},
|
},
|
||||||
|
onShareRequest = {
|
||||||
|
onShareRequested(it)
|
||||||
|
},
|
||||||
|
onWebSearchRequest = {
|
||||||
|
onWebSearchRequested(it)
|
||||||
|
},
|
||||||
onTranslateRequest = {
|
onTranslateRequest = {
|
||||||
onTranslateRequested(it)
|
onTranslateRequested(it)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,36 @@ private fun ReaderScreen(
|
||||||
).show()
|
).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 ->
|
onTranslateRequested = { textToTranslate ->
|
||||||
onEvent(
|
onEvent(
|
||||||
ReaderEvent.OnOpenTranslator(
|
ReaderEvent.OnOpenTranslator(
|
||||||
|
|
@ -298,7 +328,7 @@ private fun ReaderScreen(
|
||||||
noAppsFound = {
|
noAppsFound = {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.error_no_browser),
|
context.getString(R.string.error_no_dictionary),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,18 @@ sealed class ReaderEvent {
|
||||||
val noAppsFound: () -> Unit
|
val noAppsFound: () -> Unit
|
||||||
) : ReaderEvent()
|
) : 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(
|
data class OnOpenDictionary(
|
||||||
val textToDefine: String,
|
val textToDefine: String,
|
||||||
val context: ComponentActivity,
|
val context: ComponentActivity,
|
||||||
|
|
|
||||||
|
|
@ -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 -> {
|
is ReaderEvent.OnOpenDictionary -> {
|
||||||
launch {
|
launch {
|
||||||
val dictionaryIntent = Intent()
|
val dictionaryIntent = Intent()
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,9 @@
|
||||||
<string name="error_query">Помилка: %1$s</string>
|
<string name="error_query">Помилка: %1$s</string>
|
||||||
<string name="error_file_empty">Цей файл пустий або пошкоджений.</string>
|
<string name="error_file_empty">Цей файл пустий або пошкоджений.</string>
|
||||||
<string name="error_no_translator">Не знайдено жодного перекладача.</string>
|
<string name="error_no_translator">Не знайдено жодного перекладача.</string>
|
||||||
|
<string name="error_no_dictionary">Не знайдено жодного словника.</string>
|
||||||
<string name="error_no_browser">Не знайдено жодного браузера.</string>
|
<string name="error_no_browser">Не знайдено жодного браузера.</string>
|
||||||
|
<string name="error_no_share_app">Не знайдено застосунка щоб поділитися.</string>
|
||||||
<string name="error_no_text">
|
<string name="error_no_text">
|
||||||
Вміст книги пустий. Будь ласка, перевірте вміст книги та оновіть її.
|
Вміст книги пустий. Будь ласка, перевірте вміст книги та оновіть її.
|
||||||
</string>
|
</string>
|
||||||
|
|
@ -160,6 +162,8 @@
|
||||||
<string name="download">Завантажити</string>
|
<string name="download">Завантажити</string>
|
||||||
<string name="done">Готово</string>
|
<string name="done">Готово</string>
|
||||||
<string name="enable">Увімкнути</string>
|
<string name="enable">Увімкнути</string>
|
||||||
|
<string name="web_search">Веб-пошук</string>
|
||||||
|
<string name="share">Поділитися</string>
|
||||||
|
|
||||||
<!-- Items -->
|
<!-- Items -->
|
||||||
<string name="files">файли</string>
|
<string name="files">файли</string>
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,9 @@
|
||||||
<string name="error_query">Error: %1$s</string>
|
<string name="error_query">Error: %1$s</string>
|
||||||
<string name="error_file_empty">This file is empty or corrupted.</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_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_browser">No browser app found.</string>
|
||||||
|
<string name="error_no_share_app">No app to share found.</string>
|
||||||
<string name="error_no_text">
|
<string name="error_no_text">
|
||||||
The book content is empty. Please, make sure that the book content is not empty and update it.
|
The book content is empty. Please, make sure that the book content is not empty and update it.
|
||||||
</string>
|
</string>
|
||||||
|
|
@ -176,6 +178,8 @@
|
||||||
<string name="download">Download</string>
|
<string name="download">Download</string>
|
||||||
<string name="done">Done</string>
|
<string name="done">Done</string>
|
||||||
<string name="enable">Enable</string>
|
<string name="enable">Enable</string>
|
||||||
|
<string name="web_search">Web search</string>
|
||||||
|
<string name="share">Share</string>
|
||||||
|
|
||||||
<!-- Items -->
|
<!-- Items -->
|
||||||
<string name="files">files</string>
|
<string name="files">files</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue