diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c921208..e675966 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Unit,
- isMainTtsActive: Boolean = false
+ isMainTtsActive: Boolean = false,
+ onOpenExternalDictionary: () -> Unit
) {
val ttsController = rememberTtsController()
val ttsState by ttsController.ttsState.collectAsState()
@@ -642,6 +644,13 @@ fun AiDefinitionPopup(
contentDescription = "Copy"
)
}
+ Spacer(modifier = Modifier.width(8.dp))
+ IconButton(onClick = onOpenExternalDictionary) {
+ Icon(
+ painter = painterResource(id = R.drawable.dictionary),
+ contentDescription = "Open in Dictionary App"
+ )
+ }
}
}
diff --git a/app/src/main/java/com/aryan/reader/epubreader/ChapterWebView.kt b/app/src/main/java/com/aryan/reader/epubreader/ChapterWebView.kt
index 5ecba14..92eb347 100644
--- a/app/src/main/java/com/aryan/reader/epubreader/ChapterWebView.kt
+++ b/app/src/main/java/com/aryan/reader/epubreader/ChapterWebView.kt
@@ -975,7 +975,7 @@ fun ChapterWebView(
}
// 3. Dictionary Option (Preserving Logic)
- if (!isOss && state.selectedText.length <= 2000) {
+ if (state.selectedText.length <= 2000) {
HorizontalDivider()
Row(
modifier = Modifier
@@ -983,12 +983,7 @@ fun ChapterWebView(
.clickable {
val textToDefine = state.selectedText
if (textToDefine.isNotBlank()) {
- val wordCount = countWords(textToDefine)
- if (isProUser || wordCount <= 1) {
- onWordSelectedForAiDefinition(textToDefine)
- } else {
- onShowDictionaryUpsellDialog()
- }
+ onWordSelectedForAiDefinition(textToDefine)
}
customMenuState = null
}
diff --git a/app/src/main/java/com/aryan/reader/epubreader/DictionarySettingsDialog.kt b/app/src/main/java/com/aryan/reader/epubreader/DictionarySettingsDialog.kt
new file mode 100644
index 0000000..9974729
--- /dev/null
+++ b/app/src/main/java/com/aryan/reader/epubreader/DictionarySettingsDialog.kt
@@ -0,0 +1,299 @@
+// DictionarySettingsDialog.kt
+package com.aryan.reader.epubreader
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.core.graphics.drawable.toBitmap
+import com.aryan.reader.BuildConfig
+import com.aryan.reader.R
+
+@Suppress("KotlinConstantConditions")
+@Composable
+fun DictionarySettingsDialog(
+ isVisible: Boolean,
+ onDismiss: () -> Unit,
+ isProUser: Boolean,
+ useOnlineDictionary: Boolean,
+ onToggleOnlineDictionary: (Boolean) -> Unit,
+ selectedPackageName: String?,
+ onSelectPackage: (String) -> Unit
+) {
+ if (!isVisible) return
+
+ val context = LocalContext.current
+ var availableApps by remember { mutableStateOf>(emptyList()) }
+
+ LaunchedEffect(Unit) {
+ availableApps = ExternalDictionaryHelper.getAvailableDictionaries(context)
+ }
+
+ Dialog(onDismissRequest = onDismiss) {
+ Surface(
+ shape = RoundedCornerShape(16.dp),
+ color = MaterialTheme.colorScheme.surface,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp, vertical = 24.dp)
+ ) {
+ // Header
+ Text(
+ text = "Dictionary Settings",
+ style = MaterialTheme.typography.headlineSmall,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+
+ if (BuildConfig.FLAVOR != "oss") {
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ Surface(
+ shape = RoundedCornerShape(12.dp),
+ color = if (useOnlineDictionary) MaterialTheme.colorScheme.primaryContainer else Color.Transparent,
+ border = BorderStroke(
+ 1.dp,
+ if (useOnlineDictionary) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onToggleOnlineDictionary(true) }
+ ) {
+ Row(
+ modifier = Modifier.padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ai),
+ contentDescription = null,
+ tint = if (useOnlineDictionary) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(modifier = Modifier.width(16.dp))
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = "AI Smart Dictionary",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold,
+ color = if (useOnlineDictionary) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface
+ )
+ Text(
+ text = "Contextual definitions powered by AI.",
+ style = MaterialTheme.typography.bodySmall,
+ color = if (useOnlineDictionary) MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f) else MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ if (useOnlineDictionary) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Selected",
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ }
+
+ // External App Option
+ Surface(
+ shape = RoundedCornerShape(12.dp),
+ color = if (!useOnlineDictionary) MaterialTheme.colorScheme.primaryContainer else Color.Transparent,
+ border = BorderStroke(
+ 1.dp,
+ if (!useOnlineDictionary) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onToggleOnlineDictionary(false) }
+ ) {
+ Row(
+ modifier = Modifier.padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.dictionary),
+ contentDescription = null,
+ tint = if (!useOnlineDictionary) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.size(24.dp)
+ )
+ Spacer(modifier = Modifier.width(16.dp))
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = "External App",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold,
+ color = if (!useOnlineDictionary) MaterialTheme.colorScheme.onPrimaryContainer else MaterialTheme.colorScheme.onSurface
+ )
+ Text(
+ text = "Launch an offline dictionary or search app.",
+ style = MaterialTheme.typography.bodySmall,
+ color = if (!useOnlineDictionary) MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f) else MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ if (!useOnlineDictionary) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Selected",
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Text(
+ text = if (useOnlineDictionary) "Fallback External App (Used when offline)" else "Select External App",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+
+ } else {
+ // OSS FLAVOR UI (Dedicated to external apps)
+ Surface(
+ color = MaterialTheme.colorScheme.secondaryContainer,
+ shape = RoundedCornerShape(12.dp),
+ modifier = Modifier.padding(bottom = 16.dp)
+ ) {
+ Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ painter = painterResource(id = R.drawable.dictionary),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ modifier = Modifier.size(32.dp)
+ )
+ Spacer(Modifier.width(12.dp))
+ Text(
+ text = "Choose an external app to define selected words.",
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSecondaryContainer
+ )
+ }
+ }
+ }
+
+ // App List
+ if (availableApps.isEmpty()) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(24.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ "No supported dictionary apps found.",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.error,
+ textAlign = TextAlign.Center
+ )
+ }
+ } else {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(max = 300.dp) // Bound height so it doesn't take over screen
+ .background(MaterialTheme.colorScheme.surfaceContainerLowest, RoundedCornerShape(12.dp))
+ ) {
+ items(availableApps) { app ->
+ val isSelected = app.packageName == selectedPackageName
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onSelectPackage(app.packageName) }
+ .padding(vertical = 12.dp, horizontal = 8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // Dynamic Icon handling
+ if (app.packageName == ExternalDictionaryHelper.GOOGLE_SEARCH_PKG) {
+ Box(
+ modifier = Modifier
+ .size(40.dp)
+ .background(MaterialTheme.colorScheme.secondaryContainer, RoundedCornerShape(8.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = "Search",
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ } else if (app.icon != null) {
+ Image(
+ bitmap = app.icon.toBitmap().asImageBitmap(),
+ contentDescription = null,
+ modifier = Modifier.size(40.dp)
+ )
+ } else {
+ Box(
+ modifier = Modifier
+ .size(40.dp)
+ .background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(8.dp))
+ )
+ }
+
+ Spacer(modifier = Modifier.width(16.dp))
+ Text(
+ text = app.label,
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
+ modifier = Modifier.weight(1f)
+ )
+
+ if (isSelected) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Selected",
+ tint = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderAi.kt b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderAi.kt
index c04f1b7..4ad992f 100644
--- a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderAi.kt
+++ b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderAi.kt
@@ -227,21 +227,16 @@ suspend fun executeRecapLogic(
*/
@Composable
fun EpubReaderAiOverlays(
- // Summarization State
showSummarizationPopup: Boolean,
summarizationResult: SummarizationResult?,
isSummarizationLoading: Boolean,
onDismissSummarization: () -> Unit,
showSummarizationUpsellDialog: Boolean,
onDismissSummarizationUpsell: () -> Unit,
-
- // Recap State
showRecapPopup: Boolean,
recapResult: SummarizationResult?,
isRecapLoading: Boolean,
onDismissRecap: () -> Unit,
-
- // Dictionary State
showAiDefinitionPopup: Boolean,
selectedTextForAi: String?,
aiDefinitionResult: AiDefinitionResult?,
@@ -249,10 +244,9 @@ fun EpubReaderAiOverlays(
onDismissAiDefinition: () -> Unit,
showDictionaryUpsellDialog: Boolean,
onDismissDictionaryUpsell: () -> Unit,
-
- // Navigation
onNavigateToPro: () -> Unit,
- isTtsSessionActive: Boolean
+ isTtsSessionActive: Boolean,
+ onOpenExternalDictionary: (String) -> Unit
) {
if (showSummarizationPopup) {
SummarizationPopup(
@@ -298,7 +292,11 @@ fun EpubReaderAiOverlays(
result = aiDefinitionResult,
isLoading = isAiDefinitionLoading,
onDismiss = onDismissAiDefinition,
- isMainTtsActive = isTtsSessionActive
+ isMainTtsActive = isTtsSessionActive,
+ // Pass it down
+ onOpenExternalDictionary = {
+ selectedTextForAi?.let { text -> onOpenExternalDictionary(text) }
+ }
)
}
diff --git a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt
index 36da00b..bff4365 100644
--- a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt
+++ b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderControls.kt
@@ -67,6 +67,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.automirrored.filled.MenuBook
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Check
@@ -148,6 +149,7 @@ fun EpubReaderTopBar(
onStartAutoScroll: () -> Unit,
onOpenTtsSettings: () -> Unit,
onOpenDeviceVoiceSettings: () -> Unit,
+ onOpenDictionarySettings: () -> Unit,
searchFocusRequester: androidx.compose.ui.focus.FocusRequester,
modifier: Modifier = Modifier,
onToggleReflow: (() -> Unit)? = null,
@@ -190,6 +192,12 @@ fun EpubReaderTopBar(
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
+ IconButton(onClick = onOpenDictionarySettings) {
+ Icon(
+ painter = painterResource(id = R.drawable.dictionary),
+ contentDescription = "Dictionary Settings"
+ )
+ }
Box {
var showMoreMenu by remember { mutableStateOf(false) }
IconButton(onClick = { showMoreMenu = true }) {
diff --git a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt
index fd8f0d0..5ffb17e 100644
--- a/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt
+++ b/app/src/main/java/com/aryan/reader/epubreader/EpubReaderScreen.kt
@@ -32,6 +32,7 @@ import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.webkit.WebView
+import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -280,6 +281,30 @@ private fun saveTtsMode(context: Context, modeName: String) {
prefs.edit { putString(TTS_MODE_KEY, modeName) }
}
+private const val PREF_USE_ONLINE_DICT = "use_online_dictionary"
+private const val PREF_EXTERNAL_DICT_PKG = "external_dictionary_package"
+
+private fun loadUseOnlineDict(context: Context): Boolean {
+ if (BuildConfig.FLAVOR == "oss") return false
+ val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
+ return prefs.getBoolean(PREF_USE_ONLINE_DICT, true)
+}
+
+private fun saveUseOnlineDict(context: Context, useOnline: Boolean) {
+ val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
+ prefs.edit { putBoolean(PREF_USE_ONLINE_DICT, useOnline) }
+}
+
+private fun loadExternalDictPackage(context: Context): String? {
+ val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
+ return prefs.getString(PREF_EXTERNAL_DICT_PKG, null)
+}
+
+private fun saveExternalDictPackage(context: Context, packageName: String) {
+ val prefs = context.getSharedPreferences("reader_prefs", Context.MODE_PRIVATE)
+ prefs.edit { putString(PREF_EXTERNAL_DICT_PKG, packageName) }
+}
+
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@Composable
fun EpubReaderScreen(
@@ -334,6 +359,7 @@ fun EpubReaderScreen(
)
}
+@Suppress("ControlFlowWithEmptyBody")
@SuppressLint("UnusedBoxWithConstraintsScope", "ObsoleteSdkInt")
@androidx.annotation.OptIn(UnstableApi::class)
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -516,6 +542,61 @@ fun EpubReaderHost(
saveHighlightsToPrefs(context, epubBook.title, userHighlights)
}
+ // Dictionary
+ var showAiDefinitionPopup by remember { mutableStateOf(false) }
+ var selectedTextForAi by remember { mutableStateOf(null) }
+ var aiDefinitionResult by remember { mutableStateOf(null) }
+ var isAiDefinitionLoading by remember { mutableStateOf(false) }
+
+ var showDictionarySettingsSheet by remember { mutableStateOf(false) }
+
+ var useOnlineDictionary by remember {
+ mutableStateOf(loadUseOnlineDict(context))
+ }
+ var selectedDictPackage by remember {
+ mutableStateOf(loadExternalDictPackage(context))
+ }
+
+ var showDictionaryUpsellDialog by remember { mutableStateOf(false) }
+ var showSummarizationUpsellDialog by remember { mutableStateOf(false) }
+
+ val onDictionaryLookup = { word: String ->
+ val isOss = BuildConfig.FLAVOR == "oss"
+ val effectiveUseOnline = !isOss && useOnlineDictionary
+
+ if (effectiveUseOnline) {
+ val wordCount = countWords(word)
+ if (isProUser || wordCount <= 1) {
+ selectedTextForAi = word
+ showAiDefinitionPopup = true
+ scope.launch {
+ isAiDefinitionLoading = true
+ aiDefinitionResult = null
+ fetchAiDefinition(
+ text = word,
+ onUpdate = { chunk ->
+ val currentDefinition = aiDefinitionResult?.definition ?: ""
+ aiDefinitionResult = AiDefinitionResult(definition = currentDefinition + chunk)
+ },
+ onError = { error ->
+ aiDefinitionResult = AiDefinitionResult(error = error)
+ },
+ onFinish = { isAiDefinitionLoading = false }
+ )
+ }
+ } else {
+ showDictionaryUpsellDialog = true
+ }
+ } else {
+ if (selectedDictPackage != null) {
+ ExternalDictionaryHelper.launchDictionary(context, selectedDictPackage!!, word)
+ } else {
+ Toast.makeText(context, "Please select a dictionary app first.", Toast.LENGTH_SHORT).show()
+ showDictionarySettingsSheet = true
+ }
+ }
+ }
+
val summaryCacheManager = remember(context) { SummaryCacheManager(context) }
var showRecapPopup by remember { mutableStateOf(false) }
var recapResult by remember { mutableStateOf(null) }
@@ -585,17 +666,9 @@ fun EpubReaderHost(
var webViewRefForTts by remember { mutableStateOf(null) }
- // Dictionary
- var showAiDefinitionPopup by remember { mutableStateOf(false) }
- var selectedTextForAi by remember { mutableStateOf(null) }
- var aiDefinitionResult by remember { mutableStateOf(null) }
- var isAiDefinitionLoading by remember { mutableStateOf(false) }
-
var showSummarizationPopup by remember { mutableStateOf(false) }
var summarizationResult by remember { mutableStateOf(null) }
var isSummarizationLoading by remember { mutableStateOf(false) }
- var showDictionaryUpsellDialog by remember { mutableStateOf(false) }
- var showSummarizationUpsellDialog by remember { mutableStateOf(false) }
val epubSearcher = remember(epubBook) { createEpubSearcher(epubBook) }
@@ -2101,32 +2174,7 @@ fun EpubReaderHost(
}
},
onWordSelectedForAiDefinition = { text ->
- val wordCount = countWords(text)
- if (isProUser || wordCount <= 1) {
- Timber.d("Text selected for AI definition: $text"
- )
- selectedTextForAi = text
- showAiDefinitionPopup = true
- scope.launch {
- isAiDefinitionLoading = true
- aiDefinitionResult = null
- fetchAiDefinition(
- text = text,
- onUpdate = { chunk ->
- val currentDefinition = aiDefinitionResult?.definition ?: ""
- aiDefinitionResult = AiDefinitionResult(definition = currentDefinition + chunk)
- },
- onError = { error ->
- aiDefinitionResult = AiDefinitionResult(error = error)
- },
- onFinish = {
- isAiDefinitionLoading = false
- }
- )
- }
- } else {
- showDictionaryUpsellDialog = true
- }
+ onDictionaryLookup(text)
},
onContentReadyForSummarization = { content ->
Timber.d("Content received for summarization")
@@ -2461,32 +2509,7 @@ fun EpubReaderHost(
showDictionaryUpsellDialog = true
},
onWordSelectedForAiDefinition = { text ->
- val wordCount = countWords(text)
- if (isProUser || wordCount <= 1) {
- Timber.d("Text selected for AI definition: $text"
- )
- selectedTextForAi = text
- showAiDefinitionPopup = true
- scope.launch {
- isAiDefinitionLoading = true
- aiDefinitionResult = null
- fetchAiDefinition(
- text = text,
- onUpdate = { chunk ->
- val currentDefinition = aiDefinitionResult?.definition ?: ""
- aiDefinitionResult = AiDefinitionResult(definition = currentDefinition + chunk)
- },
- onError = { error ->
- aiDefinitionResult = AiDefinitionResult(error = error)
- },
- onFinish = {
- isAiDefinitionLoading = false
- }
- )
- }
- } else {
- showDictionaryUpsellDialog = true
- }
+ onDictionaryLookup(text)
},
userHighlights = userHighlights.filter { it.chapterIndex == (currentChapterInPaginatedMode ?: -1) },
onHighlightCreated = { cfi, text, colorId ->
@@ -2992,6 +3015,7 @@ fun EpubReaderHost(
searchFocusRequester = searchFocusRequester,
modifier = Modifier.align(Alignment.TopCenter),
onOpenTtsSettings = { showTtsSettingsSheet = true },
+ onOpenDictionarySettings = { showDictionarySettingsSheet = true },
onOpenDeviceVoiceSettings = { showDeviceVoiceSettingsSheet = true },
onToggleReflow = onToggleReflow,
)
@@ -3322,9 +3346,16 @@ fun EpubReaderHost(
},
showDictionaryUpsellDialog = showDictionaryUpsellDialog,
onDismissDictionaryUpsell = { showDictionaryUpsellDialog = false },
-
onNavigateToPro = onNavigateToPro,
- isTtsSessionActive = isTtsSessionActive
+ isTtsSessionActive = isTtsSessionActive,
+ onOpenExternalDictionary = { text ->
+ if (selectedDictPackage != null) {
+ ExternalDictionaryHelper.launchDictionary(context, selectedDictPackage!!, text)
+ } else {
+ Toast.makeText(context, "Select an offline dictionary first.", Toast.LENGTH_SHORT).show()
+ showDictionarySettingsSheet = true
+ }
+ }
)
if (isNavigatingToBookmark) {
@@ -3483,6 +3514,24 @@ fun EpubReaderHost(
)
}
+ if (showDictionarySettingsSheet) {
+ DictionarySettingsDialog(
+ isVisible = true,
+ onDismiss = { showDictionarySettingsSheet = false },
+ isProUser = isProUser,
+ useOnlineDictionary = useOnlineDictionary,
+ onToggleOnlineDictionary = { newState ->
+ useOnlineDictionary = newState
+ saveUseOnlineDict(context, newState)
+ },
+ selectedPackageName = selectedDictPackage,
+ onSelectPackage = { pkg ->
+ selectedDictPackage = pkg
+ saveExternalDictPackage(context, pkg)
+ }
+ )
+ }
+
if (showDeviceVoiceSettingsSheet) {
DeviceVoiceSettingsSheet(
isVisible = true,
diff --git a/app/src/main/java/com/aryan/reader/epubreader/ExternalDictionaryHelper.kt b/app/src/main/java/com/aryan/reader/epubreader/ExternalDictionaryHelper.kt
new file mode 100644
index 0000000..cf08a92
--- /dev/null
+++ b/app/src/main/java/com/aryan/reader/epubreader/ExternalDictionaryHelper.kt
@@ -0,0 +1,154 @@
+// ExternalDictionaryHelper.kt
+package com.aryan.reader.epubreader
+
+import android.app.SearchManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.widget.Toast
+import timber.log.Timber
+
+data class ExternalDictionaryApp(
+ val label: String,
+ val packageName: String,
+ val icon: Drawable?
+)
+
+object ExternalDictionaryHelper {
+ const val GOOGLE_SEARCH_PKG = "app.internal.google_search"
+
+ private val PACKAGE_BLOCKLIST = setOf(
+ "com.samsung.android.samsungpassautofill",
+ "com.samsung.android.samsungpass",
+ "com.samsung.android.app.pass",
+ "com.google.android.gms",
+ "com.truecaller",
+ "com.adobe.reader",
+ "com.reddit.frontpage"
+ )
+
+ fun getAvailableDictionaries(context: Context): List {
+ val pm = context.packageManager
+ val apps = mutableListOf()
+ val addedPackages = mutableSetOf()
+
+ val processTextIntent = Intent(Intent.ACTION_PROCESS_TEXT).setType("text/plain")
+ val textResolvers = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ pm.queryIntentActivities(processTextIntent, PackageManager.ResolveInfoFlags.of(0))
+ } else {
+ @Suppress("DEPRECATION") pm.queryIntentActivities(processTextIntent, 0)
+ }
+
+ textResolvers.forEach { ri ->
+ val pkg = ri.activityInfo.packageName
+ if (!PACKAGE_BLOCKLIST.contains(pkg) && addedPackages.add(pkg)) {
+ apps.add(ExternalDictionaryApp(
+ label = ri.loadLabel(pm).toString(),
+ packageName = pkg,
+ icon = ri.loadIcon(pm)
+ ))
+ }
+ }
+
+ val colorDictIntent = Intent("colordict.intent.action.SEARCH")
+ val colorResolvers = pm.queryIntentActivities(colorDictIntent, 0)
+ colorResolvers.forEach { ri ->
+ val pkg = ri.activityInfo.packageName
+ if (!PACKAGE_BLOCKLIST.contains(pkg) && addedPackages.add(pkg)) {
+ apps.add(ExternalDictionaryApp(
+ label = ri.loadLabel(pm).toString(),
+ packageName = pkg,
+ icon = ri.loadIcon(pm)
+ ))
+ }
+ }
+
+ val sortedApps = apps.sortedBy { it.label }.toMutableList()
+
+ // Inject Google Search at the top
+ sortedApps.add(
+ 0,
+ ExternalDictionaryApp(
+ label = "Search",
+ packageName = GOOGLE_SEARCH_PKG,
+ icon = null
+ )
+ )
+
+ return sortedApps
+ }
+
+ fun launchDictionary(context: Context, packageName: String, query: String) {
+ val pm = context.packageManager
+ try {
+ if (packageName == GOOGLE_SEARCH_PKG) {
+ val searchIntent = Intent(Intent.ACTION_WEB_SEARCH).apply {
+ putExtra(SearchManager.QUERY, query)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ context.startActivity(searchIntent)
+ return
+ }
+
+ val processTextIntent = Intent(Intent.ACTION_PROCESS_TEXT).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_PROCESS_TEXT, query)
+ putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true)
+ setPackage(packageName)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ if (processTextIntent.resolveActivity(pm) != null) {
+ context.startActivity(processTextIntent)
+ return
+ }
+
+ val dictIntent = Intent("colordict.intent.action.SEARCH").apply {
+ putExtra("EXTRA_QUERY", query)
+ setPackage(packageName)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ if (dictIntent.resolveActivity(pm) != null) {
+ context.startActivity(dictIntent)
+ return
+ }
+
+ if (packageName == "it.t_arn.aard2") {
+ val aardIntent = Intent("aard2.lookup").apply {
+ putExtra("query", query)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ context.startActivity(aardIntent)
+ return
+ }
+
+ launchGenericSend(context, packageName, query)
+
+ } catch (e: Exception) {
+ Timber.e(e, "Failed to launch dictionary app: $packageName")
+ Toast.makeText(context, "Error opening dictionary", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private fun launchGenericSend(context: Context, packageName: String, query: String) {
+ val sendIntent = Intent(Intent.ACTION_SEND)
+ sendIntent.type = "text/plain"
+ sendIntent.putExtra(Intent.EXTRA_TEXT, query)
+ sendIntent.setPackage(packageName)
+ sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ try {
+ context.startActivity(sendIntent)
+ } catch (e: Exception) {
+ val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
+ if (launchIntent != null) {
+ context.startActivity(launchIntent)
+ } else {
+ throw e
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aryan/reader/paginatedreader/PaginatedReader.kt b/app/src/main/java/com/aryan/reader/paginatedreader/PaginatedReader.kt
index 0b3a032..cee987d 100644
--- a/app/src/main/java/com/aryan/reader/paginatedreader/PaginatedReader.kt
+++ b/app/src/main/java/com/aryan/reader/paginatedreader/PaginatedReader.kt
@@ -1467,15 +1467,9 @@ internal fun PaginatedReaderContent(
if (isForDictionary) {
if (!text.isNullOrBlank()) {
- if (isProUser || countWords(text) <= 1) {
- if (text.length <= 2000) {
- onWordSelectedForAiDefinition(text)
- }
- } else {
- onShowDictionaryUpsellDialog()
- }
+ onWordSelectedForAiDefinition(text)
}
- } else if (isForHighlight) {
+ } else if (isForHighlight) {
// Do not copy to real clipboard
} else {
realClipboard.setClipEntry(clipEntry)
@@ -3022,28 +3016,26 @@ private fun PaginatedTextSelectionMenu(
}
// 5. Dictionary Option
- if (!isOss) {
- HorizontalDivider()
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .clickable(onClick = onDictionary)
- .padding(horizontal = 16.dp, vertical = 12.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(12.dp)
- ) {
- Icon(
- painter = painterResource(id = R.drawable.dictionary),
- contentDescription = null,
- tint = MaterialTheme.colorScheme.onSurface,
- modifier = Modifier.size(20.dp)
- )
- Text(
- "Dictionary",
- style = MaterialTheme.typography.bodyMedium,
- color = MaterialTheme.colorScheme.onSurface
- )
- }
+ HorizontalDivider()
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(onClick = onDictionary)
+ .padding(horizontal = 16.dp, vertical = 12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.dictionary),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.size(20.dp)
+ )
+ Text(
+ "Dictionary",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface
+ )
}
}
}
diff --git a/app/src/main/java/com/aryan/reader/pdf/PdfHelper.kt b/app/src/main/java/com/aryan/reader/pdf/PdfHelper.kt
index 854f2f8..63039a4 100644
--- a/app/src/main/java/com/aryan/reader/pdf/PdfHelper.kt
+++ b/app/src/main/java/com/aryan/reader/pdf/PdfHelper.kt
@@ -129,9 +129,7 @@ internal fun PdfSelectionMenuPopup(
popupPositionProvider: PopupPositionProvider,
onCopy: (String) -> Unit,
onAiDefine: (String) -> Unit,
- onSelectAll: () -> Unit,
- isProUser: Boolean,
- onShowUpsellDialog: () -> Unit,
+ onSelectAll: () -> Unit
) {
Popup(
popupPositionProvider = popupPositionProvider,
@@ -157,11 +155,7 @@ internal fun PdfSelectionMenuPopup(
}
if (menuState.selectedText.length <= 2000) {
TextButton(onClick = {
- if (isProUser || countWords(menuState.selectedText) <= 1) {
- onAiDefine(menuState.selectedText)
- } else {
- onShowUpsellDialog()
- }
+ onAiDefine(menuState.selectedText)
}) {
Text("Dictionary")
}
diff --git a/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt b/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt
index f3dc497..4f6c1ea 100644
--- a/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt
+++ b/app/src/main/java/com/aryan/reader/pdf/PdfPageComposable.kt
@@ -4557,9 +4557,7 @@ private fun PdfPageRenderer(
popupPositionProvider = popupPositionProvider,
onCopy = onCopy,
onAiDefine = onAiDefine,
- onSelectAll = onSelectAll,
- isProUser = isProUser,
- onShowUpsellDialog = onShowUpsellDialog
+ onSelectAll = onSelectAll
)
}
}
diff --git a/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt b/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt
index 461969e..5b52fd6 100644
--- a/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt
+++ b/app/src/main/java/com/aryan/reader/pdf/PdfViewerScreen.kt
@@ -29,14 +29,12 @@ import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.RectF
-import androidx.compose.material3.SnackbarDuration
-import androidx.compose.material3.SnackbarResult
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import android.provider.OpenableColumns
-import androidx.compose.foundation.border
import android.util.Base64
+import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -55,6 +53,7 @@ import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
@@ -90,6 +89,7 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.automirrored.filled.MenuBook
import androidx.compose.material.icons.filled.ArrowDownward
import androidx.compose.material.icons.filled.ArrowUpward
import androidx.compose.material.icons.filled.Brush
@@ -133,8 +133,10 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
+import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
@@ -236,7 +238,10 @@ import com.aryan.reader.SearchTopBar
import com.aryan.reader.SummarizationPopup
import com.aryan.reader.SummarizationResult
import com.aryan.reader.TtsSettingsSheet
+import com.aryan.reader.countWords
import com.aryan.reader.epubreader.AutoScrollControls
+import com.aryan.reader.epubreader.DictionarySettingsDialog
+import com.aryan.reader.epubreader.ExternalDictionaryHelper
import com.aryan.reader.fetchAiDefinition
import com.aryan.reader.paginatedreader.TtsChunk
import com.aryan.reader.pdf.data.AnnotationSettingsRepository
@@ -304,6 +309,29 @@ private const val PDF_AUTO_SCROLL_LOCAL_MAX_PREFIX = "pdf_as_local_max_"
private const val PDF_SCROLL_LOCKED_PREFIX = "pdf_sl_local_"
private const val PDF_FULL_SCREEN_PREFIX = "pdf_fs_local_"
private const val PDF_MUSICIAN_MODE_KEY = "pdf_musician_mode_enabled"
+private const val PREF_USE_ONLINE_DICT = "use_online_dictionary"
+private const val PREF_EXTERNAL_DICT_PKG = "external_dictionary_package"
+
+private fun loadUseOnlineDict(context: Context): Boolean {
+ @Suppress("KotlinConstantConditions") if (BuildConfig.FLAVOR == "oss") return false
+ val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
+ return prefs.getBoolean(PREF_USE_ONLINE_DICT, true)
+}
+
+private fun saveUseOnlineDict(context: Context, useOnline: Boolean) {
+ val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
+ prefs.edit { putBoolean(PREF_USE_ONLINE_DICT, useOnline) }
+}
+
+private fun loadExternalDictPackage(context: Context): String? {
+ val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
+ return prefs.getString(PREF_EXTERNAL_DICT_PKG, null)
+}
+
+private fun saveExternalDictPackage(context: Context, packageName: String) {
+ val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
+ prefs.edit { putString(PREF_EXTERNAL_DICT_PKG, packageName) }
+}
private fun savePdfMusicianMode(context: Context, isEnabled: Boolean) {
val prefs = context.getSharedPreferences(SETTINGS_PREFS_NAME, Context.MODE_PRIVATE)
@@ -736,6 +764,10 @@ fun PdfViewerScreen(
var currentTtsMode by remember { mutableStateOf(loadTtsMode(context)) }
var showTtsSettingsSheet by remember { mutableStateOf(false) }
+ var showDictionarySettingsSheet by remember { mutableStateOf(false) }
+ var useOnlineDictionary by remember { mutableStateOf(loadUseOnlineDict(context)) }
+ var selectedDictPackage by remember { mutableStateOf(loadExternalDictPackage(context)) }
+
var showDeviceVoiceSettingsSheet by remember { mutableStateOf(false) }
fun triggerAutoScrollTempPause(durationMs: Long) {
@@ -1840,24 +1872,54 @@ fun PdfViewerScreen(
{ isLoading: Boolean -> isHighlightingLoading = isLoading }
}
- val onShowDictionaryUpsellDialogStable = remember { { showDictionaryUpsellDialog = true } }
+ val onShowDictionaryUpsellDialogStable = remember(useOnlineDictionary) {
+ {
+ if (useOnlineDictionary) {
+ showDictionaryUpsellDialog = true
+ }
+ }
+ }
- val onWordSelectedForAiDefinitionStable = remember(isProUser, executeWithOcrCheck) {
+ val onDictionaryLookupStable = remember(isProUser, executeWithOcrCheck, useOnlineDictionary, selectedDictPackage) {
{ text: String ->
executeWithOcrCheck {
- selectedTextForAi = text
- showAiDefinitionPopup = true
- coroutineScope.launch {
- isAiDefinitionLoading = true
- aiDefinitionResult = null
- fetchAiDefinition(text = text, onUpdate = { chunk ->
- val currentDefinition = aiDefinitionResult?.definition ?: ""
- aiDefinitionResult = AiDefinitionResult(
- definition = currentDefinition + chunk
- )
- }, onError = { error ->
- aiDefinitionResult = AiDefinitionResult(error = error)
- }, onFinish = { isAiDefinitionLoading = false })
+ val isOss = BuildConfig.FLAVOR == "oss"
+ val effectiveUseOnline = !isOss && useOnlineDictionary
+
+ if (effectiveUseOnline) {
+ val wordCount = countWords(text)
+ if (isProUser || wordCount <= 1) {
+ selectedTextForAi = text
+ showAiDefinitionPopup = true
+ coroutineScope.launch {
+ isAiDefinitionLoading = true
+ aiDefinitionResult = null
+ fetchAiDefinition(
+ text = text,
+ onUpdate = { chunk ->
+ val currentDefinition = aiDefinitionResult?.definition ?: ""
+ aiDefinitionResult = AiDefinitionResult(
+ definition = currentDefinition + chunk
+ )
+ },
+ onError = { error ->
+ aiDefinitionResult = AiDefinitionResult(error = error)
+ },
+ onFinish = {
+ isAiDefinitionLoading = false
+ }
+ )
+ }
+ } else {
+ showDictionaryUpsellDialog = true
+ }
+ } else {
+ if (selectedDictPackage != null) {
+ ExternalDictionaryHelper.launchDictionary(context, selectedDictPackage!!, text)
+ } else {
+ Toast.makeText(context, "Please select a dictionary app first.", Toast.LENGTH_SHORT).show()
+ showDictionarySettingsSheet = true
+ }
}
}
}
@@ -3458,33 +3520,11 @@ fun PdfViewerScreen(
onSingleTap = onSingleTapStable,
isProUser = isProUser,
onShowDictionaryUpsellDialog = {
- showDictionaryUpsellDialog = true
- },
- onWordSelectedForAiDefinition = { text ->
- selectedTextForAi = text
- showAiDefinitionPopup = true
- coroutineScope.launch {
- isAiDefinitionLoading = true
- aiDefinitionResult = null
- fetchAiDefinition(
- text = text,
- onUpdate = { chunk ->
- val currentDefinition =
- aiDefinitionResult?.definition ?: ""
- aiDefinitionResult = AiDefinitionResult(
- definition = currentDefinition + chunk
- )
- },
- onError = { error ->
- aiDefinitionResult = AiDefinitionResult(
- error = error
- )
- },
- onFinish = {
- isAiDefinitionLoading = false
- })
+ if (useOnlineDictionary) {
+ showDictionaryUpsellDialog = true
}
},
+ onWordSelectedForAiDefinition = onDictionaryLookupStable,
onOcrStateChange = onOcrStateChange,
onLinkClicked = { url -> clickedLinkUrl = url },
onInternalLinkClicked = onInternalLinkNav,
@@ -3846,7 +3886,7 @@ fun PdfViewerScreen(
searchResultToHighlight = searchHighlightTarget,
isProUser = isProUser,
onShowDictionaryUpsellDialog = onShowDictionaryUpsellDialogStable,
- onWordSelectedForAiDefinition = onWordSelectedForAiDefinitionStable,
+ onWordSelectedForAiDefinition = onDictionaryLookupStable,
ttsHighlightData = ttsHighlightData,
ttsReadingPage = ttsPageData?.pageIndex,
onLinkClicked = onLinkClickedStable,
@@ -3940,7 +3980,7 @@ fun PdfViewerScreen(
}
if (isMusicianMode && isAutoScrollModeActive) {
- val density = LocalDensity.current
+ @Suppress("UnusedVariable", "Unused") val density = LocalDensity.current
var leftPulseTrigger by remember { mutableLongStateOf(0L) }
var rightPulseTrigger by remember { mutableLongStateOf(0L) }
@@ -4345,6 +4385,14 @@ fun PdfViewerScreen(
)
}
+ IconButton(onClick = { showDictionarySettingsSheet = true }) {
+ Icon(
+ painter = painterResource(id = R.drawable.dictionary),
+ contentDescription = "Dictionary Settings",
+ tint = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+
if (BuildConfig.DEBUG) {
IconButton(onClick = { showPenPlayground = true }) {
Icon(
@@ -4453,11 +4501,7 @@ fun PdfViewerScreen(
showMoreMenu = false
isAutoScrollModeActive = true
isAutoScrollPlaying = true
- showBars = if (isMusicianMode) {
- false
- } else {
- true
- }
+ showBars = !isMusicianMode
}
)
@@ -5741,7 +5785,17 @@ fun PdfViewerScreen(
selectedTextForAi = null
aiDefinitionResult = null
},
- isMainTtsActive = isTtsSessionActive
+ isMainTtsActive = isTtsSessionActive,
+ onOpenExternalDictionary = {
+ selectedTextForAi?.let { text ->
+ if (selectedDictPackage != null) {
+ ExternalDictionaryHelper.launchDictionary(context, selectedDictPackage!!, text)
+ } else {
+ Toast.makeText(context, "Select an offline dictionary first.", Toast.LENGTH_SHORT).show()
+ showDictionarySettingsSheet = true
+ }
+ }
+ }
)
}
if (showDictionaryUpsellDialog) {
@@ -5873,6 +5927,24 @@ fun PdfViewerScreen(
)
}
+ if (showDictionarySettingsSheet) {
+ DictionarySettingsDialog(
+ isVisible = true,
+ onDismiss = { showDictionarySettingsSheet = false },
+ isProUser = isProUser,
+ useOnlineDictionary = useOnlineDictionary,
+ onToggleOnlineDictionary = { newState ->
+ useOnlineDictionary = newState
+ saveUseOnlineDict(context, newState)
+ },
+ selectedPackageName = selectedDictPackage,
+ onSelectPackage = { pkg ->
+ selectedDictPackage = pkg
+ saveExternalDictPackage(context, pkg)
+ }
+ )
+ }
+
if (showDeviceVoiceSettingsSheet) {
DeviceVoiceSettingsSheet(
isVisible = true,