Skip to content

Commit

Permalink
improve ui of text to speech
Browse files Browse the repository at this point in the history
  • Loading branch information
kazemcodes committed Apr 5, 2022
1 parent a191851 commit b2909ce
Show file tree
Hide file tree
Showing 12 changed files with 503 additions and 108 deletions.
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/ProjectConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ object ProjectConfig {
const val minSdk = 23
const val targetSdk = 31
const val compileSdk = 31
const val versionName = "0.1.17"
const val versionCode = 18
const val versionName = "0.1.18"
const val versionCode = 19
const val applicationId = "ir.kazemcodes.infinityreader"
}
21 changes: 21 additions & 0 deletions core-ui/src/main/java/org/ireader/core_ui/theme/AppPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class AppPreferences @Inject constructor(
const val SCROLL_INDICATOR_PADDING = "scroll_indicator_padding"
const val SCROLL_INDICATOR_WIDTH = "scroll_indicator_width"


const val TEXT_READER_SPEECH_RATE = "text_reader_speech_rate"
const val TEXT_READER_SPEECH_PITCH = "text_reader_speech_pitch"
const val TEXT_READER_SPEECH_LANGUAGE = "text_reader_speech_language"
const val TEXT_READER_SPEECH_VOICE = "text_reader_speech_voice"

/** Services **/
const val Last_UPDATE_CHECK = "last_update_check"

Expand Down Expand Up @@ -126,6 +132,21 @@ class AppPreferences @Inject constructor(
return preferenceStore.getBoolean(SORT_DESC_LIBRARY_SCREEN, true)
}

fun speechRate(): Preference<Float> {
return preferenceStore.getFloat(TEXT_READER_SPEECH_RATE, .8f)
}

fun speechPitch(): Preference<Float> {
return preferenceStore.getFloat(TEXT_READER_SPEECH_PITCH, .8f)
}

fun speechVoice(): Preference<String> {
return preferenceStore.getString(TEXT_READER_SPEECH_VOICE, "")
}

fun speechLanguage(): Preference<String> {
return preferenceStore.getString(TEXT_READER_SPEECH_LANGUAGE, "")
}

fun lastUpdateCheck(): Preference<Long> {
return preferenceStore.getLong(Last_UPDATE_CHECK, 0)
Expand Down
1 change: 1 addition & 0 deletions core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<string name="channel_crash_logs">Crash logs</string>
<string name="label_backup">restoring from Backup</string>
<string name="label_library">Checking Library</string>
<string name="label_text_reader">Text Reader</string>
<string name="label_recent_updates">Recent Updates</string>
<string name="popular_book">Most Popular</string>
<string name="latest_book">Latest</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ object Notifications {
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
const val ID_LIBRARY_ERROR = -102


private const val GROUP_TEXT_READER = "group_text_reader"
const val CHANNEL_TEXT_READER_PROGRESS = "library_text_reader_channel"
const val ID_TEXT_READER_PROGRESS = -601
const val CHANNEL_TEXT_READER_ERROR = "library_text_reader_error_channel"
const val ID_TEXT_READER_ERROR = -602


/**
* Notification channel and ids used by the downloader.
*/
Expand Down Expand Up @@ -110,6 +118,9 @@ object Notifications {
buildNotificationChannelGroup(GROUP_LIBRARY) {
setName(context.getString(R.string.label_library))
},
buildNotificationChannelGroup(GROUP_TEXT_READER) {
setName(context.getString(R.string.label_text_reader))
},
buildNotificationChannelGroup(GROUP_APK_UPDATES) {
setName(context.getString(R.string.label_recent_updates))
},
Expand All @@ -121,6 +132,16 @@ object Notifications {
buildNotificationChannel(CHANNEL_COMMON, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_common))
},
buildNotificationChannel(CHANNEL_TEXT_READER_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setGroup(GROUP_TEXT_READER)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_TEXT_READER_ERROR, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_errors))
setGroup(GROUP_TEXT_READER)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_LIBRARY_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setGroup(GROUP_LIBRARY)
Expand All @@ -131,6 +152,7 @@ object Notifications {
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},

buildNotificationChannel(CHANNEL_NEW_CHAPTERS, IMPORTANCE_DEFAULT) {
setName(context.getString(R.string.channel_new_chapters))
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,39 @@ fun mapFilterType(input: Int): FilterType {
}


class TextReaderPrefUseCase @Inject constructor(
private val appPreferences: AppPreferences,
) {
fun savePitch(value: Float) {
appPreferences.speechRate().set(value)
}

fun readPitch(): Float {
return appPreferences.speechRate().get()
}

fun saveRate(value: Float) {
appPreferences.speechPitch().set(value)
}

fun readRate(): Float {
return appPreferences.speechPitch().get()
}

fun saveLanguage(value: String) {
appPreferences.speechLanguage().set(value)
}

fun readLanguage(): String {
return appPreferences.speechLanguage().get()
}

fun saveVoice(value: String) {
appPreferences.speechVoice().set(value)
}

fun readVoice(): String {
return appPreferences.speechVoice().get()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ fun FontChip(
viewModel: ReaderScreenViewModel,
) {

Row(modifier = Modifier.fillMaxWidth(),
Row(modifier = Modifier
.fillMaxWidth()
.height(32.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically) {
Text(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.ireader.presentation.feature_reader.presentation.reader.viewmodel


import android.speech.tts.Voice
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
Expand All @@ -11,6 +12,7 @@ import org.ireader.core_ui.theme.FontType
import org.ireader.domain.models.entities.Book
import org.ireader.domain.models.entities.Chapter
import tachiyomi.source.Source
import java.util.*
import javax.inject.Inject

//data class ReaderScreenState(
Expand Down Expand Up @@ -51,14 +53,40 @@ open class ReaderScreenStateImpl @Inject constructor() : ReaderScreenState {
override var stateChapters: List<Chapter> by mutableStateOf<List<Chapter>>(emptyList())
override var stateChapter: Chapter? by mutableStateOf<Chapter?>(null)
override var book: Book? by mutableStateOf<Book?>(null)


}


interface TextReaderScreenState {
var currentReadingParagraph: Int
var isPlaying: Boolean
var voiceMode: Boolean
var autoNextChapter: Boolean
var languages: List<Locale>
var voices: List<Voice>
var currentVoice: String
var currentLanguage: String
var pitch: Float
var speechSpeed: Float
}

class TextReaderScreenStateImpl @Inject constructor() : TextReaderScreenState {
override var currentReadingParagraph: Int by mutableStateOf<Int>(0)
override var languages by mutableStateOf<List<Locale>>(emptyList())
override var voices by mutableStateOf<List<Voice>>(emptyList())

override var currentVoice by mutableStateOf<String>("")
override var currentLanguage by mutableStateOf<String>("")
override var isPlaying by mutableStateOf<Boolean>(false)
override var voiceMode by mutableStateOf<Boolean>(false)


override var autoNextChapter by mutableStateOf<Boolean>(false)
override var pitch by mutableStateOf<Float>(.8f)
override var speechSpeed by mutableStateOf<Float>(.8f)
}



interface ReaderScreenState {
var isLoading: Boolean
var isRemoteLoading: Boolean
Expand All @@ -76,9 +104,6 @@ interface ReaderScreenState {
var stateChapters: List<Chapter>
var stateChapter: Chapter?
var book: Book?
var currentReadingParagraph: Int
var isPlaying: Boolean
var voiceMode: Boolean
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import android.view.WindowManager
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
Expand All @@ -28,6 +30,7 @@ import org.ireader.core_ui.theme.readerScreenBackgroundColors
import org.ireader.core_ui.viewmodel.BaseViewModel
import org.ireader.domain.R
import org.ireader.domain.catalog.service.CatalogStore
import org.ireader.domain.feature_services.notification.Notifications
import org.ireader.domain.models.entities.Book
import org.ireader.domain.models.entities.Chapter
import org.ireader.domain.models.entities.History
Expand All @@ -36,11 +39,11 @@ import org.ireader.domain.use_cases.history.HistoryUseCase
import org.ireader.domain.use_cases.local.LocalGetChapterUseCase
import org.ireader.domain.use_cases.local.LocalInsertUseCases
import org.ireader.domain.use_cases.preferences.reader_preferences.ReaderPrefUseCases
import org.ireader.domain.use_cases.preferences.reader_preferences.TextReaderPrefUseCase
import org.ireader.domain.use_cases.remote.RemoteUseCases
import org.ireader.domain.utils.withIOContext
import tachiyomi.source.Source
import timber.log.Timber
import java.util.*
import javax.inject.Inject


Expand All @@ -55,8 +58,11 @@ class ReaderScreenViewModel @Inject constructor(
private val readerUseCases: ReaderPrefUseCases,
private val prefState: ReaderScreenPreferencesStateImpl,
private val state: ReaderScreenStateImpl,
private val speakerState: TextReaderScreenStateImpl,
val speechPrefUseCases: TextReaderPrefUseCase,
savedStateHandle: SavedStateHandle,
) : BaseViewModel(), ReaderScreenPreferencesState by prefState, ReaderScreenState by state {
) : BaseViewModel(), ReaderScreenPreferencesState by prefState, ReaderScreenState by state,
TextReaderScreenState by speakerState {



Expand Down Expand Up @@ -105,6 +111,10 @@ class ReaderScreenViewModel @Inject constructor(
autoScrollInterval = readerUseCases.autoScrollMode.readInterval()
autoScrollOffset = readerUseCases.autoScrollMode.readOffset()
autoBrightnessMode = readerUseCases.brightnessStateUseCase.readAutoBrightness()
speechSpeed = speechPrefUseCases.readRate()
pitch = speechPrefUseCases.readPitch()
currentVoice = speechPrefUseCases.readVoice()
currentLanguage = speechPrefUseCases.readLanguage()
}

fun onEvent(event: ReaderEvent) {
Expand Down Expand Up @@ -133,7 +143,7 @@ class ReaderScreenViewModel @Inject constructor(
toggleLoading(true)
toggleLocalLoaded(false)
viewModelScope.launch {
speaker?.stop()
onNextVoice()
val resultChapter = getChapterUseCase.findChapterById(
chapterId = chapterId,
state.book?.id,
Expand Down Expand Up @@ -636,12 +646,59 @@ class ReaderScreenViewModel @Inject constructor(

var speaker: TextToSpeech? = null

fun onNextVoice() {
speaker?.stop()
isPlaying = false
currentReadingParagraph = 0
}

fun showTextReaderNotification(context: Context) {
stateChapter?.let { chapter ->
val builder =
NotificationCompat.Builder(context,
Notifications.CHANNEL_TEXT_READER_PROGRESS).apply {
setContentTitle(chapter.title)
setSmallIcon(org.ireader.core.R.drawable.ic_infinity)
setOnlyAlertOnce(true)
priority = NotificationCompat.PRIORITY_LOW
setAutoCancel(true)
setOngoing(true)
setShowWhen(isPlaying)
}
NotificationManagerCompat.from(context).apply {
builder.setProgress(chapter.content.size, 0, false)
notify(Notifications.ID_TEXT_READER_PROGRESS, builder.build())
cancel(Notifications.ID_TEXT_READER_PROGRESS)

}

}

}

fun readText(context: Context) {
speaker = TextToSpeech(context) { status ->

if (status != TextToSpeech.ERROR && speaker != null) {
speaker?.let { ttl ->
ttl.language = Locale.UK
ttl.availableLanguages?.let {
languages = it.toList()
}
ttl.voices?.toList()?.let {
voices = it
}
ttl.voices?.firstOrNull { it.name == currentVoice }?.let {
ttl.voice = it
}
ttl.availableLanguages?.firstOrNull { it.displayName == currentLanguage }
?.let {
ttl.language = it
}

ttl.setPitch(pitch)
ttl.setSpeechRate(speechSpeed)


stateChapter?.let { chapter ->
try {
ttl.speak(chapter.content[currentReadingParagraph],
Expand All @@ -653,19 +710,36 @@ class ReaderScreenViewModel @Inject constructor(
UtteranceProgressListener() {
override fun onStop(utteranceId: String?, interrupted: Boolean) {
super.onStop(utteranceId, interrupted)
Timber.e("onStop....")
isPlaying = false
}

override fun onStart(p0: String?) {
Timber.e("OnStart....")
// showTextReaderNotification(context)
isPlaying = true
}

override fun onDone(p0: String?) {
currentReadingParagraph += 1
Timber.e(currentReadingParagraph.toString())
readText(context)
if (currentReadingParagraph < chapter.content.size) {
currentReadingParagraph += 1
readText(context)
}
if (currentReadingParagraph == chapter.content.size - 1) {
isPlaying = false
if (autoNextChapter) {
source?.let {
updateChapterSliderIndex(currentChapterIndex + 1)
viewModelScope.launch {
getChapter(getCurrentChapterByIndex().id,
source = it)
isPlaying = true
readText(context = context)
}

}


}
}
}

override fun onError(p0: String?) {
Expand Down
Loading

0 comments on commit b2909ce

Please sign in to comment.