diff --git a/app/build.gradle b/app/build.gradle index 8c8fa6c5..9c31a8f6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ android { applicationId "com.lagradost.quicknovel" minSdkVersion 21 targetSdkVersion 34 - versionCode 54 - versionName "3.1.7" + versionCode 55 + versionName "3.1.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/lagradost/quicknovel/DataStore.kt b/app/src/main/java/com/lagradost/quicknovel/DataStore.kt index 99e19e2f..93f15ce0 100644 --- a/app/src/main/java/com/lagradost/quicknovel/DataStore.kt +++ b/app/src/main/java/com/lagradost/quicknovel/DataStore.kt @@ -20,6 +20,7 @@ const val DOWNLOAD_NORMAL_SORTING_METHOD: String = "download_normal_sorting" const val DOWNLOAD_SETTINGS: String = "download_settings" const val EPUB_LOCK_ROTATION: String = "reader_epub_rotation" const val EPUB_TEXT_SIZE: String = "reader_epub_text_size" +const val EPUB_TEXT_BIONIC: String = "reader_epub_bionic_reading" const val EPUB_SCROLL_VOL: String = "reader_epub_scroll_volume" const val EPUB_TTS_LOCK: String = "reader_epub_scroll_lock" const val EPUB_BG_COLOR: String = "reader_epub_bg_color" diff --git a/app/src/main/java/com/lagradost/quicknovel/ReadActivity2.kt b/app/src/main/java/com/lagradost/quicknovel/ReadActivity2.kt index a0f936d2..62d5d31a 100644 --- a/app/src/main/java/com/lagradost/quicknovel/ReadActivity2.kt +++ b/app/src/main/java/com/lagradost/quicknovel/ReadActivity2.kt @@ -553,7 +553,6 @@ class ReadActivity2 : AppCompatActivity(), ColorPickerDialogListener { config.setArgs(binding.loadingText, CONFIG_FONT or CONFIG_COLOR) config.setArgs(binding.readBattery, CONFIG_FONT or CONFIG_COLOR or CONFIG_FONT_BOLD) config.setArgs(binding.readTimeClock, CONFIG_FONT or CONFIG_COLOR or CONFIG_FONT_BOLD) - config.setArgs(binding.readLoadingBar) } @@ -715,7 +714,8 @@ class ReadActivity2 : AppCompatActivity(), ColorPickerDialogListener { textColor = viewModel.textColor, textSize = viewModel.textSize, textFont = viewModel.textFont, - backgroundColor = viewModel.backgroundColor + backgroundColor = viewModel.backgroundColor, + bionicReading = viewModel.bionicReading ).also { config -> updateOtherTextConfig(config) } @@ -754,6 +754,12 @@ class ReadActivity2 : AppCompatActivity(), ColorPickerDialogListener { } } + observe(viewModel.bionicReadingLive) { color -> + if (textAdapter.changeBionicReading(color)) { + updateTextAdapterConfig() + } + } + observe(viewModel.showBatteryLive) { show -> binding.readBattery.isVisible = show binding.readOverlay.isVisible = show && viewModel.showTime @@ -1266,6 +1272,11 @@ class ReadActivity2 : AppCompatActivity(), ColorPickerDialogListener { viewModel.scrollWithVolume = isChecked } + readSettingsShowBionic.isChecked = viewModel.bionicReading + readSettingsShowBionic.setOnCheckedChangeListener { _, isChecked -> + viewModel.bionicReading = isChecked + } + readSettingsLockTts.isChecked = viewModel.ttsLock readSettingsLockTts.setOnCheckedChangeListener { _, isChecked -> viewModel.ttsLock = isChecked diff --git a/app/src/main/java/com/lagradost/quicknovel/ReadActivityViewModel.kt b/app/src/main/java/com/lagradost/quicknovel/ReadActivityViewModel.kt index 94d776a9..f18c7220 100644 --- a/app/src/main/java/com/lagradost/quicknovel/ReadActivityViewModel.kt +++ b/app/src/main/java/com/lagradost/quicknovel/ReadActivityViewModel.kt @@ -1202,6 +1202,14 @@ class ReadActivityViewModel : ViewModel() { textSizeLive ) + val bionicReadingLive: MutableLiveData = MutableLiveData(null) + var bionicReading by PreferenceDelegateLiveView( + EPUB_TEXT_BIONIC, + false, + Boolean::class, + bionicReadingLive + ) + val orientationLive: MutableLiveData = MutableLiveData(null) var orientation by PreferenceDelegateLiveView( EPUB_LOCK_ROTATION, diff --git a/app/src/main/java/com/lagradost/quicknovel/TTSHelper.kt b/app/src/main/java/com/lagradost/quicknovel/TTSHelper.kt index bcdd1654..7628c1e4 100644 --- a/app/src/main/java/com/lagradost/quicknovel/TTSHelper.kt +++ b/app/src/main/java/com/lagradost/quicknovel/TTSHelper.kt @@ -4,6 +4,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.graphics.Typeface import android.media.AudioAttributes import android.media.AudioFocusRequest import android.media.AudioManager @@ -12,21 +13,26 @@ import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import android.speech.tts.Voice import android.support.v4.media.session.MediaSessionCompat +import android.text.Spannable +import android.text.SpannableString import android.text.Spanned +import android.text.style.StyleSpan import android.view.KeyEvent import androidx.media.session.MediaButtonReceiver -import com.lagradost.quicknovel.ui.UiText -import com.lagradost.quicknovel.ui.txt import com.lagradost.quicknovel.BaseApplication.Companion.removeKey import com.lagradost.quicknovel.BaseApplication.Companion.setKey import com.lagradost.quicknovel.mvvm.debugAssert import com.lagradost.quicknovel.receivers.BecomingNoisyReceiver +import com.lagradost.quicknovel.ui.UiText +import com.lagradost.quicknovel.ui.txt import com.lagradost.quicknovel.util.UIHelper.requestAudioFocus import io.noties.markwon.Markwon import kotlinx.coroutines.delay import org.jsoup.Jsoup import java.util.Locale import java.util.Stack +import kotlin.math.roundToInt + class TTSSession(val context: Context, event: (TTSHelper.TTSActionType) -> Boolean) { private val intentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY) @@ -266,6 +272,30 @@ data class TextSpan( override val index: Int, override var innerIndex: Int, ) : SpanDisplay() { + val bionicText : Spanned by lazy { + val wordToSpan: Spannable = SpannableString(text) + val length = wordToSpan.length + Regex("([a-zà-ýA-ZÀ-ÝåäöÅÄÖ].*?)[^a-zà-ýA-ZÀ-ÝåäöÅÄÖ'’]").findAll(text).forEach { match -> + val range = match.groups[1]!!.range + // https://github.com/gBloxy/Bionic-Reader/blob/main/bionic-reader.py#L167 + val correctLength = when (val rangeLength = range.last + 1 - range.first) { + 0 -> return@forEach // this should never happened + 1, 2, 3 -> 1 + 4 -> 2 + else -> { + (rangeLength.toFloat() * 0.4).roundToInt() + } + } + wordToSpan.setSpan( + StyleSpan(Typeface.BOLD), + minOf(maxOf(match.range.first, 0), length), + minOf(maxOf(match.range.first + correctLength, 0), length), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + + wordToSpan + } override fun id(): Long { return generateId(0, index, start, end) } diff --git a/app/src/main/java/com/lagradost/quicknovel/ui/TextAdapter.kt b/app/src/main/java/com/lagradost/quicknovel/ui/TextAdapter.kt index 76af852b..99429d51 100644 --- a/app/src/main/java/com/lagradost/quicknovel/ui/TextAdapter.kt +++ b/app/src/main/java/com/lagradost/quicknovel/ui/TextAdapter.kt @@ -174,6 +174,7 @@ data class TextConfig( val textFont: String, val defaultFont: Typeface, val backgroundColor : Int, + val bionicReading : Boolean ) { private val fontFile: File? by lazy { if (textFont == "") null else systemFonts.firstOrNull { it.name == textFont } @@ -251,6 +252,12 @@ class TextAdapter(private val viewModel: ReadActivityViewModel, var config: Text return true } + fun changeBionicReading(to : Boolean) : Boolean { + if (config.bionicReading == to) return false + config = config.copy(bionicReading = to) + return true + } + fun changeColor(color: Int): Boolean { if (config.textColor == color) return false config = config.copy(textColor = color) @@ -547,7 +554,7 @@ class TextAdapter(private val viewModel: ReadActivityViewModel, var config: Text UIHelper.bindImage(binding.root, img) } - private fun bindText(obj: TextSpan) { + private fun bindText(obj: TextSpan, config: TextConfig) { when (binding) { is SingleImageBinding -> { val img = obj.text.getSpans(0, obj.text.length)[0] @@ -586,7 +593,11 @@ class TextAdapter(private val viewModel: ReadActivityViewModel, var config: Text binding.root.apply { // this is set to fix the nonclick https://stackoverflow.com/questions/8641343/android-clickablespan-not-calling-onclick movementMethod = LinkMovementMethod.getInstance() - text = obj.text + text = if (config.bionicReading) { + obj.bionicText + } else { + obj.text + } //val links = obj.text.getSpans() //if (links.isNotEmpty()) { @@ -671,7 +682,7 @@ class TextAdapter(private val viewModel: ReadActivityViewModel, var config: Text span = obj when (obj) { is TextSpan -> { - this.bindText(obj) + this.bindText(obj, config) // because we bind text here we know that it will be cleared and thus // we do not have to update it with null if (ttsLine != null) diff --git a/app/src/main/res/layout/read_bottom_settings.xml b/app/src/main/res/layout/read_bottom_settings.xml index a036bc65..1d9e6670 100644 --- a/app/src/main/res/layout/read_bottom_settings.xml +++ b/app/src/main/res/layout/read_bottom_settings.xml @@ -99,6 +99,17 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> + + More Info download_path_key Download path + Bionic Reading