diff --git a/app/src/main/java/org/cis_india/wsreader/MyneApp.kt b/app/src/main/java/org/cis_india/wsreader/MyneApp.kt index 75e0093..e78b8b1 100644 --- a/app/src/main/java/org/cis_india/wsreader/MyneApp.kt +++ b/app/src/main/java/org/cis_india/wsreader/MyneApp.kt @@ -33,6 +33,7 @@ import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.MainScope import okhttp3.OkHttpClient +import org.cis_india.wsreader.api.BookAPI import java.io.File import java.util.concurrent.TimeUnit @@ -58,6 +59,9 @@ class MyneApp : Application(), ImageLoaderFactory { lateinit var bookRepository: BookRepository private set + lateinit var bookApi: BookAPI + private set + lateinit var bookshelf: Bookshelf private set @@ -81,6 +85,8 @@ class MyneApp : Application(), ImageLoaderFactory { bookRepository = BookRepository(database.booksDao()) + bookApi = BookAPI(this) + val downloadsDir = File(cacheDir, "downloads") // Cleans the download dir. @@ -108,7 +114,8 @@ class MyneApp : Application(), ImageLoaderFactory { this@MyneApp, readium, bookRepository, - navigatorPreferences + navigatorPreferences, + bookApi ) } diff --git a/app/src/main/java/org/cis_india/wsreader/data/BookRepository.kt b/app/src/main/java/org/cis_india/wsreader/data/BookRepository.kt index 41d53bd..1320adb 100644 --- a/app/src/main/java/org/cis_india/wsreader/data/BookRepository.kt +++ b/app/src/main/java/org/cis_india/wsreader/data/BookRepository.kt @@ -83,7 +83,8 @@ class BookRepository( mediaType: MediaType, publication: Publication, cover: File, - wdIdentifier: String? = null + wdIdentifier: String? = null, + bookLanguageCode: String? = null ): Long { val book = Book( creation = Date().time, @@ -93,11 +94,19 @@ class BookRepository( identifier = wdIdentifier?: publication.metadata.identifier ?: "", mediaType = mediaType, progression = "{}", - cover = cover.path + cover = cover.path, + languageCode = bookLanguageCode ?: "en" ) return booksDao.insertBook(book) } + suspend fun updateBookLanguage(bookId: Long, languageCode: String) = + booksDao.updateBookLanguage(bookId, languageCode) + + fun getBookLanguage(bookId: Long): Flow { + return booksDao.getBookLanguage(bookId) + } + suspend fun deleteBook(id: Long) = booksDao.deleteBook(id) } diff --git a/app/src/main/java/org/cis_india/wsreader/data/db/AppDatabase.kt b/app/src/main/java/org/cis_india/wsreader/data/db/AppDatabase.kt index cf990c4..a48e242 100644 --- a/app/src/main/java/org/cis_india/wsreader/data/db/AppDatabase.kt +++ b/app/src/main/java/org/cis_india/wsreader/data/db/AppDatabase.kt @@ -19,7 +19,7 @@ import org.cis_india.wsreader.data.model.Highlight @Database( entities = [Book::class, Bookmark::class, Highlight::class, Catalog::class], - version = 1, + version = 2, exportSchema = false ) @TypeConverters( @@ -45,7 +45,9 @@ abstract class AppDatabase : RoomDatabase() { context.applicationContext, AppDatabase::class.java, "database" - ).build() + ) + .addMigrations(MIGRATION_1_2) + .build() INSTANCE = instance return instance } diff --git a/app/src/main/java/org/cis_india/wsreader/data/db/BooksDao.kt b/app/src/main/java/org/cis_india/wsreader/data/db/BooksDao.kt index 737a49f..3e58e17 100644 --- a/app/src/main/java/org/cis_india/wsreader/data/db/BooksDao.kt +++ b/app/src/main/java/org/cis_india/wsreader/data/db/BooksDao.kt @@ -123,4 +123,10 @@ interface BooksDao { "UPDATE " + Book.TABLE_NAME + " SET " + Book.PROGRESSION + " = :locator WHERE " + Book.ID + "= :id" ) suspend fun saveProgression(locator: String, id: Long) + + @Query("UPDATE " + Book.TABLE_NAME + " SET language_code = :languageCode WHERE " + Book.ID + " = :bookId") + suspend fun updateBookLanguage(bookId: Long, languageCode: String) + + @Query("SELECT language_code FROM books WHERE id = :bookId LIMIT 1") + fun getBookLanguage(bookId: Long): Flow } diff --git a/app/src/main/java/org/cis_india/wsreader/data/db/Migrations.kt b/app/src/main/java/org/cis_india/wsreader/data/db/Migrations.kt new file mode 100644 index 0000000..8edd479 --- /dev/null +++ b/app/src/main/java/org/cis_india/wsreader/data/db/Migrations.kt @@ -0,0 +1,11 @@ +package org.cis_india.wsreader.data.db + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +val MIGRATION_1_2 = object : Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + // Add the new column language_code with default value + database.execSQL("ALTER TABLE books ADD COLUMN language_code TEXT NOT NULL DEFAULT 'auto'") + } +} \ No newline at end of file diff --git a/app/src/main/java/org/cis_india/wsreader/data/model/Book.kt b/app/src/main/java/org/cis_india/wsreader/data/model/Book.kt index 2be5558..9746738 100644 --- a/app/src/main/java/org/cis_india/wsreader/data/model/Book.kt +++ b/app/src/main/java/org/cis_india/wsreader/data/model/Book.kt @@ -35,6 +35,8 @@ data class Book( val rawMediaType: String, @ColumnInfo(name = COVER) val cover: String, + @ColumnInfo(name = LANGUAGE_CODE) + val languageCode: String ) { constructor( @@ -47,6 +49,7 @@ data class Book( progression: String? = null, mediaType: MediaType, cover: String, + languageCode: String, ) : this( id = id, creation = creation, @@ -56,7 +59,8 @@ data class Book( identifier = identifier, progression = progression, rawMediaType = mediaType.toString(), - cover = cover + cover = cover, + languageCode = languageCode ) val url: AbsoluteUrl get() = AbsoluteUrl(href)!! @@ -81,5 +85,6 @@ data class Book( const val PROGRESSION = "progression" const val MEDIA_TYPE = "media_type" const val COVER = "cover" + const val LANGUAGE_CODE = "language_code" } } diff --git a/app/src/main/java/org/cis_india/wsreader/domain/Bookshelf.kt b/app/src/main/java/org/cis_india/wsreader/domain/Bookshelf.kt index 152c91f..06e3f18 100644 --- a/app/src/main/java/org/cis_india/wsreader/domain/Bookshelf.kt +++ b/app/src/main/java/org/cis_india/wsreader/domain/Bookshelf.kt @@ -52,10 +52,11 @@ class Bookshelf( fun importPublicationFromStorage( uri: Uri, - wdIdentifier: String? = null + wdIdentifier: String? = null, + bookLanguageCode: String? = null ) { coroutineScope.launch { - addBookFeedback(publicationRetriever.retrieveFromStorage(uri),wdIdentifier) + addBookFeedback(publicationRetriever.retrieveFromStorage(uri),wdIdentifier, bookLanguageCode) } } @@ -93,10 +94,11 @@ class Bookshelf( private suspend fun addBookFeedback( retrieverResult: Try, - wdIdentifier: String? = null + wdIdentifier: String? = null, + bookLanguageCode: String? = null ) { retrieverResult - .map { addBook(it.publication.toUrl(), it.format, it.coverUrl, wdIdentifier) } + .map { addBook(it.publication.toUrl(), it.format, it.coverUrl, wdIdentifier, bookLanguageCode) } .onSuccess { channel.send(Event.ImportPublicationSuccess) } .onFailure { channel.send(Event.ImportPublicationError(it)) } } @@ -116,7 +118,8 @@ class Bookshelf( url: AbsoluteUrl, format: Format? = null, coverUrl: AbsoluteUrl? = null, - wdIdentifier: String? = null + wdIdentifier: String? = null, + bookLanguageCode: String? = null ): Try { val asset = if (format == null) { @@ -148,7 +151,8 @@ class Bookshelf( asset.format.mediaType, publication, coverFile, - wdIdentifier + wdIdentifier, + bookLanguageCode, ) if (id == -1L) { coverFile.delete() diff --git a/app/src/main/java/org/cis_india/wsreader/reader/ReaderRepository.kt b/app/src/main/java/org/cis_india/wsreader/reader/ReaderRepository.kt index 53dfd59..6136211 100644 --- a/app/src/main/java/org/cis_india/wsreader/reader/ReaderRepository.kt +++ b/app/src/main/java/org/cis_india/wsreader/reader/ReaderRepository.kt @@ -24,6 +24,7 @@ import org.readium.r2.shared.util.DebugError import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.getOrElse import org.cis_india.wsreader.Readium +import org.cis_india.wsreader.api.BookAPI import org.cis_india.wsreader.data.BookRepository import org.cis_india.wsreader.domain.PublicationError import org.cis_india.wsreader.reader.preferences.AndroidTtsPreferencesManagerFactory @@ -46,6 +47,7 @@ class ReaderRepository( private val readium: Readium, private val bookRepository: BookRepository, private val preferencesDataStore: DataStore, + private val bookApi: BookAPI ) { private val coroutineQueue: CoroutineQueue = @@ -73,6 +75,9 @@ class ReaderRepository( val book = checkNotNull(bookRepository.get(bookId)) { "Cannot find book in database." } + //Update book language if not set in books entity in room db + updateBookLanguageIfAuto(bookId, book.identifier, book.languageCode) + val asset = readium.assetRetriever.retrieve( book.url, book.mediaType @@ -124,6 +129,19 @@ class ReaderRepository( } } + private suspend fun updateBookLanguageIfAuto(bookId: Long, identifier: String, currentLang: String) { + if(currentLang != "auto") return + + try { + val bookSet = bookApi.getBookById(identifier).getOrNull()!! + val language = bookSet.books.firstOrNull()?.languages?.firstOrNull()?:"en" + + bookRepository.updateBookLanguage(bookId, language) + } catch (e: Exception) { + + } + } + private suspend fun openAudio( bookId: Long, publication: Publication, diff --git a/app/src/main/java/org/cis_india/wsreader/reader/ReaderViewModel.kt b/app/src/main/java/org/cis_india/wsreader/reader/ReaderViewModel.kt index aea20bb..3f0a1ef 100644 --- a/app/src/main/java/org/cis_india/wsreader/reader/ReaderViewModel.kt +++ b/app/src/main/java/org/cis_india/wsreader/reader/ReaderViewModel.kt @@ -125,6 +125,11 @@ class ReaderViewModel( bookRepository.deleteBookmark(id) } + + val bookLanguage: Flow = bookRepository + .getBookLanguage(bookId) + .map { it ?: "en" } + // Highlights val highlights: Flow> by lazy { diff --git a/app/src/main/java/org/cis_india/wsreader/reader/VisualReaderFragment.kt b/app/src/main/java/org/cis_india/wsreader/reader/VisualReaderFragment.kt index 0b1c167..7a92afc 100644 --- a/app/src/main/java/org/cis_india/wsreader/reader/VisualReaderFragment.kt +++ b/app/src/main/java/org/cis_india/wsreader/reader/VisualReaderFragment.kt @@ -8,14 +8,17 @@ package org.cis_india.wsreader.reader import android.app.AlertDialog import android.app.SearchManager +import android.content.ActivityNotFoundException import android.content.Context import android.content.ClipData import android.content.ClipboardManager import android.content.Intent import android.graphics.Color import android.graphics.RectF +import android.net.Uri import android.os.Bundle import android.os.Build +import android.util.Log import android.view.ActionMode import android.view.Gravity import android.view.LayoutInflater @@ -51,6 +54,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import androidx.core.view.MenuHost import androidx.core.view.MenuProvider +import androidx.fragment.app.activityViewModels import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -84,6 +88,7 @@ import org.cis_india.wsreader.reader.tts.TtsViewModel import org.cis_india.wsreader.utils.clearPadding import org.cis_india.wsreader.utils.extensions.confirmDialog import org.cis_india.wsreader.utils.extensions.throttleLatest +import org.cis_india.wsreader.utils.getDictionaryUrl import org.cis_india.wsreader.utils.hideSystemUi import org.cis_india.wsreader.utils.observeWhenStarted import org.cis_india.wsreader.utils.padSystemUi @@ -101,8 +106,12 @@ abstract class VisualReaderFragment : BaseReaderFragment() { protected var binding: FragmentReaderBinding by viewLifecycle() + private val viewModel: ReaderViewModel by activityViewModels() + private lateinit var navigatorFragment: Fragment + private var bookLanguage: String = "en" + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -120,6 +129,14 @@ abstract class VisualReaderFragment : BaseReaderFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.bookLanguage.collect { language -> + bookLanguage = language + } + } + } + navigatorFragment = navigator as Fragment (navigator as OverflowableNavigator).apply { @@ -395,6 +412,8 @@ abstract class VisualReaderFragment : BaseReaderFragment() { menu.findItem(R.id.note).isVisible = true menu.findItem(R.id.copy).isVisible = true menu.findItem(R.id.web_search).isVisible = true + menu.findItem(R.id.dictionary).isVisible = true + menu.findItem(R.id.translate).isVisible = true } return true } @@ -406,6 +425,8 @@ abstract class VisualReaderFragment : BaseReaderFragment() { R.id.note -> showAnnotationPopup() R.id.copy -> copySelectionToClipboard() R.id.web_search -> searchSelectionOnWeb() + R.id.dictionary -> openDictionary() + R.id.translate -> openTranslate() else -> return false } @@ -586,7 +607,7 @@ abstract class VisualReaderFragment : BaseReaderFragment() { val clipboard = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip = ClipData.newPlainText("selected_text", selection.locator.text.highlight.toString()) clipboard.setPrimaryClip(clip) - + //Only show Toast in Android 12L (API level 32) and lower. Android 13 and higher has standard feedback when content enters the clipboard if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast.makeText(context, R.string.copied, Toast.LENGTH_SHORT).show() @@ -614,6 +635,77 @@ abstract class VisualReaderFragment : BaseReaderFragment() { } } + private fun openDictionary() { + viewLifecycleOwner.lifecycleScope.launch { + val navigator = navigator as? SelectableNavigator ?: return@launch + val selection = navigator.currentSelection() ?: return@launch + + val selectedText = selection.locator.text.highlight.toString().trim() + + if(selectedText.isNotEmpty()) { + val lang = bookLanguage.lowercase() + + val dictionaryUrl = getDictionaryUrl(lang, selectedText) + + val intent = if (dictionaryUrl != null) { + Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(dictionaryUrl) + } + } else { + Intent(Intent.ACTION_WEB_SEARCH).apply { + putExtra(SearchManager.QUERY, selectedText) + } + } + + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText( + context, + getString(R.string.no_app_found_dictionary), + Toast.LENGTH_SHORT + ).show() + } + + navigator.clearSelection() + } else { + Toast.makeText( + context, + getString(R.string.no_text_selected_for_dictionary), + Toast.LENGTH_SHORT + ).show() + } + } + } + + private fun openTranslate() { + viewLifecycleOwner.lifecycleScope.launch { + val navigator = navigator as? SelectableNavigator ?: return@launch + val selection = navigator.currentSelection() ?: return@launch + + val selectedText = selection.locator.text.highlight.toString().trim() + + if (selectedText.isNotEmpty()) { + val translateUrl = "https://translate.google.com/?sl=$bookLanguage&tl=en&text=${Uri.encode(selectedText)}" + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(translateUrl)) + + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText( + requireContext(), + getString(R.string.no_app_found_for_translation), + Toast.LENGTH_SHORT + ).show() + } + + navigator.clearSelection() + } else { + Toast.makeText(context, getString(R.string.no_text_selected_for_translation), Toast.LENGTH_SHORT).show() + } + } + } + private fun showFootnotePopup( text: CharSequence, ) { diff --git a/app/src/main/java/org/cis_india/wsreader/ui/screens/detail/viewmodels/BookDetailViewModel.kt b/app/src/main/java/org/cis_india/wsreader/ui/screens/detail/viewmodels/BookDetailViewModel.kt index 9fe5543..e5ac9c8 100644 --- a/app/src/main/java/org/cis_india/wsreader/ui/screens/detail/viewmodels/BookDetailViewModel.kt +++ b/app/src/main/java/org/cis_india/wsreader/ui/screens/detail/viewmodels/BookDetailViewModel.kt @@ -131,6 +131,8 @@ class BookDetailViewModel @Inject constructor( fun downloadBook( book: Book, downloadProgressListener: (Float, Int) -> Unit ) { + val languageCode = book.languages.firstOrNull()?.lowercase() ?: "en" + bookDownloader.downloadBook(book = book, downloadProgressListener = downloadProgressListener, onDownloadSuccess = { filePath -> @@ -138,7 +140,8 @@ class BookDetailViewModel @Inject constructor( val uri = Uri.fromFile(file) app.bookshelf.importPublicationFromStorage( uri = uri, - wdIdentifier = book.id.toString() + wdIdentifier = book.id.toString(), + languageCode ) //state = state.copy(bookLibraryItem = libraryDao.getItemByBookId(book.id)) } diff --git a/app/src/main/java/org/cis_india/wsreader/utils/DictionaryUtils.kt b/app/src/main/java/org/cis_india/wsreader/utils/DictionaryUtils.kt new file mode 100644 index 0000000..99a8a4a --- /dev/null +++ b/app/src/main/java/org/cis_india/wsreader/utils/DictionaryUtils.kt @@ -0,0 +1,31 @@ +package org.cis_india.wsreader.utils + +import android.net.Uri + +fun getDictionaryUrl(lang: String, word: String): String? { + val encodedWord = Uri.encode(word) + return when (lang.lowercase()) { + "as" -> "https://www.xobdo.org/dic/$encodedWord" + "fr" -> "https://www.larousse.fr/dictionnaires/francais/$encodedWord" + "es" -> "https://www.wordreference.com/definicion/$encodedWord" + "en" -> "https://www.onelook.com/?w=$encodedWord" + "bn", + "ca", + "cs", + "hi", + "id", + "it", + "kn", + "ml", + "mr", + "pl", + "sv", + "ta", + "te", + "uk", + "vi" -> + "https://$lang.wiktionary.org/wiki/$encodedWord" + + else -> null + } +} \ No newline at end of file diff --git a/app/src/main/res/menu/menu_action_mode.xml b/app/src/main/res/menu/menu_action_mode.xml index b9c243a..2388271 100644 --- a/app/src/main/res/menu/menu_action_mode.xml +++ b/app/src/main/res/menu/menu_action_mode.xml @@ -37,4 +37,15 @@ app:showAsAction="ifRoom"> + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 81c6cf7..a1af12e 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -43,6 +43,8 @@ تم النسخ! بحث ويب مشاركة + ترجمة + قاموس الاعدادات عام فتح الكتب الإلكترونية مع … diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index 530663b..56dae86 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -42,6 +42,8 @@ অনুলিপি করা হয়েছে! ওয়েব অনুসন্ধান শেয়ার করুন + অনুবাদ + অভিধান সেটিং সাধারণ এর মাধ্যমে ইবুক খুলুন... diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1a0ba67..88f4fb7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -37,6 +37,8 @@ Zkopírováno! Webový vyhledávač Sdílet + Přeložit + Slovník Nastavení Obecné Otevírat e-knihy pomocí… diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e36361c..1ed4183 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -41,6 +41,8 @@ Kopiert! Websuche Teilen + Übersetzen + Wörterbuch Einstellungen Allgemein E-Books öffnen mit … diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4d7a264..1d9edd3 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -37,6 +37,8 @@ ¡Copiado! Búsqueda web Compartir + Traducir + Diccionario Ajustes General Abrir ebooks con… diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 689af21..c621f10 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -41,6 +41,8 @@ कॉपी किया गया! वेब खोज साझा करें + अनुवाद करें + शब्दकोष सेटिंग्स सामान्य ईबुक्स को इसके साथ खोलें… diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4a60bf0..8160bac 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -42,6 +42,8 @@ Copiato! Ricerca web Condividi + Traduci + Dizionario Impostazioni Generale Apri ebooks con… diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index e51a5c8..118266c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -43,6 +43,8 @@ 已複製! 網頁搜尋 分享 + 翻譯 + 字典 設定 一般 開啟電子書以… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 99e3a3d..9369955 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -56,6 +56,14 @@ Copied! Web Search Share + Translate + Dictionary + + No app found for translation + No app found for dictionary lookup + No text selected for translation + No text selected for dictionary lookup + Settings