From 87a095fd4b2138813c1b4a29feb8f623b3ae83f6 Mon Sep 17 00:00:00 2001 From: kazemcodes Date: Thu, 7 Apr 2022 16:00:29 +0430 Subject: [PATCH] add search feature to reader viewmodel --- .idea/deploymentTargetDropDown.xml | 13 +- .../infinity/presentation/MainActivity.kt | 6 + .../ireader/core_ui/theme/AppPreferences.kt | 10 + .../notification/DefaultNotificationHelper.kt | 11 +- .../notification/Notifications.kt | 4 +- .../reader_preferences/OrientationUseCase.kt | 8 + .../SelectedFontStateUseCase.kt | 13 +- .../presentation/reader/ReaderScreen.kt | 24 +- .../presentation/reader/ReaderScreenTopBar.kt | 177 +++++++++++-- .../presentation/reader/ReaderText.kt | 67 +++-- .../components/ReaderSettingComposable.kt | 7 + .../reader/viewmodel/ReaderMainFunc.kt | 27 +- .../reader/viewmodel/ReaderPrefCodes.kt | 16 +- .../reader/viewmodel/ReaderScreenState.kt | 14 ++ .../reader/viewmodel/ReaderScreenViewModel.kt | 33 +++ .../reader/viewmodel/ReaderUiFunc.kt | 15 ++ .../reader/viewmodel/TextReaderManager.kt | 14 +- .../presentation/setting/SettingViewModel.kt | 2 +- .../presentation/webview/WebPageTopBar.kt | 16 +- .../presentation/feature_ttl/TTSScreen.kt | 237 +++++++++++------- .../TopAppBarReusableComposables.kt | 2 + .../presentation/ui/ReaderScreenSpec.kt | 74 ++++-- 22 files changed, 604 insertions(+), 186 deletions(-) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 5fad538e7..793204587 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -1,6 +1,17 @@ + + + + + + + + + + + @@ -12,6 +23,6 @@ - + \ No newline at end of file diff --git a/app/src/main/java/org/ireader/infinity/presentation/MainActivity.kt b/app/src/main/java/org/ireader/infinity/presentation/MainActivity.kt index e16461a97..494439211 100644 --- a/app/src/main/java/org/ireader/infinity/presentation/MainActivity.kt +++ b/app/src/main/java/org/ireader/infinity/presentation/MainActivity.kt @@ -5,6 +5,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.core.app.NotificationManagerCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import dagger.hilt.android.AndroidEntryPoint @@ -32,5 +33,10 @@ class MainActivity : ComponentActivity() { } + override fun onDestroy() { + NotificationManagerCompat.from(this).cancelAll() + super.onDestroy() + } + } diff --git a/core-ui/src/main/java/org/ireader/core_ui/theme/AppPreferences.kt b/core-ui/src/main/java/org/ireader/core_ui/theme/AppPreferences.kt index abed6eba7..eb27feb2f 100644 --- a/core-ui/src/main/java/org/ireader/core_ui/theme/AppPreferences.kt +++ b/core-ui/src/main/java/org/ireader/core_ui/theme/AppPreferences.kt @@ -31,12 +31,14 @@ class AppPreferences @Inject constructor( const val AUTO_SCROLL_MODE_OFFSET = "auto_scroll_mode_offset" const val SCROLL_INDICATOR_PADDING = "scroll_indicator_padding" const val SCROLL_INDICATOR_WIDTH = "scroll_indicator_width" + const val SELECTABLE_TEXT = "selectable_text" 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" + const val TEXT_READER_AUTO_NEXT = "text_reader_auto_next" /** Services **/ const val Last_UPDATE_CHECK = "last_update_check" @@ -108,6 +110,10 @@ class AppPreferences @Inject constructor( return preferenceStore.getBoolean(SCROLL_MODE, true) } + fun selectableText(): Preference { + return preferenceStore.getBoolean(SELECTABLE_TEXT, true) + } + fun autoScrollInterval(): Preference { return preferenceStore.getLong(AUTO_SCROLL_MODE_INTERVAL, 5000L) } @@ -136,6 +142,10 @@ class AppPreferences @Inject constructor( return preferenceStore.getFloat(TEXT_READER_SPEECH_RATE, .8f) } + fun readerAutoNext(): Preference { + return preferenceStore.getBoolean(TEXT_READER_AUTO_NEXT, false) + } + fun speechPitch(): Preference { return preferenceStore.getFloat(TEXT_READER_SPEECH_PITCH, .8f) } diff --git a/domain/src/main/java/org/ireader/domain/feature_services/notification/DefaultNotificationHelper.kt b/domain/src/main/java/org/ireader/domain/feature_services/notification/DefaultNotificationHelper.kt index 0d95ecf3a..377fb1f20 100644 --- a/domain/src/main/java/org/ireader/domain/feature_services/notification/DefaultNotificationHelper.kt +++ b/domain/src/main/java/org/ireader/domain/feature_services/notification/DefaultNotificationHelper.kt @@ -5,10 +5,10 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.media.MediaMetadata -import android.media.session.PlaybackState import android.os.Build import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.net.toUri @@ -176,9 +176,10 @@ class DefaultNotificationHelper @Inject constructor( setMetadata(MediaMetadataCompat.Builder().apply { putText(MediaMetadata.METADATA_KEY_TITLE, chapter.title) }.build()) - val actions = - PlaybackState.ACTION_PLAY_PAUSE or PlaybackState.ACTION_STOP or PlaybackState.ACTION_SKIP_TO_NEXT or PlaybackState.ACTION_SKIP_TO_PREVIOUS + setPlaybackState(PlaybackStateCompat.Builder().apply { + setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_STOP or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) + }.build()) } return NotificationCompat.Builder(applicationContext, Notifications.CHANNEL_TEXT_READER_PROGRESS).apply { @@ -186,9 +187,8 @@ class DefaultNotificationHelper @Inject constructor( setContentText("${progress}/${chapter.content.size}") setSmallIcon(org.ireader.core.R.drawable.ic_infinity) setOnlyAlertOnce(true) - // setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + setVisibility(NotificationCompat.VISIBILITY_PUBLIC) setLargeIcon(applicationContext, book.cover) - //setShowWhen(false) priority = NotificationCompat.PRIORITY_LOW addAction(R.drawable.ic_baseline_skip_previous, @@ -213,7 +213,6 @@ class DefaultNotificationHelper @Inject constructor( .setShowActionsInCompactView(1, 2, 3) ) setSubText(book.title) - color = applicationContext.resources.getColor(R.color.blue_200) //setColorized(true) //setAutoCancel(true) diff --git a/domain/src/main/java/org/ireader/domain/feature_services/notification/Notifications.kt b/domain/src/main/java/org/ireader/domain/feature_services/notification/Notifications.kt index e3fa48c3e..fcad908ba 100644 --- a/domain/src/main/java/org/ireader/domain/feature_services/notification/Notifications.kt +++ b/domain/src/main/java/org/ireader/domain/feature_services/notification/Notifications.kt @@ -9,7 +9,6 @@ import androidx.core.app.NotificationCompat import androidx.core.graphics.drawable.toBitmap import coil.ImageLoader import coil.request.ImageRequest -import coil.transform.CircleCropTransformation data class Channel( val name: String, @@ -41,10 +40,9 @@ fun createChannel(context: Context, channel: Channel) { if (data != null) { val request = ImageRequest.Builder(context) .data(data) - .transformations(CircleCropTransformation()) .allowHardware(true) .target { setLargeIcon(it.toBitmap()) } - .size(200) + .size(512) .build() ImageLoader(context).execute(request) } diff --git a/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/OrientationUseCase.kt b/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/OrientationUseCase.kt index cf0618b4e..f414535ba 100644 --- a/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/OrientationUseCase.kt +++ b/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/OrientationUseCase.kt @@ -123,4 +123,12 @@ class TextReaderPrefUseCase @Inject constructor( return appPreferences.speechVoice().get() } + fun saveAutoNext(value: Boolean) { + appPreferences.readerAutoNext().set(value) + } + + fun readAutoNext(): Boolean { + return appPreferences.readerAutoNext().get() + } + } \ No newline at end of file diff --git a/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/SelectedFontStateUseCase.kt b/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/SelectedFontStateUseCase.kt index e0d013ea2..4668623ee 100644 --- a/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/SelectedFontStateUseCase.kt +++ b/domain/src/main/java/org/ireader/domain/use_cases/preferences/reader_preferences/SelectedFontStateUseCase.kt @@ -8,16 +8,25 @@ import javax.inject.Inject class SelectedFontStateUseCase @Inject constructor( private val appPreferences: AppPreferences, ) { - fun save(fontIndex: Int) { + fun saveFont(fontIndex: Int) { appPreferences.font().set(fontIndex) } /** * fontIndex is the index of font which is in fonts list inside the Type package */ - fun read(): FontType { + fun readFont(): FontType { val fontType = appPreferences.font().get() return fonts[fontType] } + fun saveSelectableText(value: Boolean) { + appPreferences.selectableText().set(value) + } + + fun readSelectableText(): Boolean { + return appPreferences.selectableText().get() + } + + } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreen.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreen.kt index 033f59f57..312c6c852 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreen.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -63,13 +62,13 @@ fun ReadingScreen( val context = LocalContext.current - DisposableEffect(key1 = true) { - onDispose { - vm.uiFunc.apply { - vm.restoreSetting(context, scrollState) - } - } - } +// DisposableEffect(key1 = true) { +// onDispose { +// vm.uiFunc.apply { +// vm.restoreSetting(context, scrollState) +// } +// } +// } LaunchedEffect(key1 = scaffoldState.drawerState.targetValue) { if (chapter != null && scaffoldState.drawerState.targetValue == DrawerValue.Open && vm.stateChapters.isNotEmpty()) { vm.uiFunc.apply { @@ -195,6 +194,15 @@ fun ReadingScreen( vm.showSnackBar(UiText.ExceptionString(e)) } } + }, + vm = vm, + state = vm, + scrollState = scrollState, + onBookMark = { + vm.uiFunc.apply { + vm.bookmarkChapter() + + } } ) }, diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreenTopBar.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreenTopBar.kt index e06bde909..c91b3f492 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreenTopBar.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderScreenTopBar.kt @@ -5,23 +5,28 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Text import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Autorenew -import androidx.compose.material.icons.filled.Public +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import kotlinx.coroutines.launch import org.ireader.domain.models.entities.Chapter +import org.ireader.presentation.feature_reader.presentation.reader.viewmodel.ReaderScreenPreferencesState +import org.ireader.presentation.feature_reader.presentation.reader.viewmodel.ReaderScreenState import org.ireader.presentation.presentation.Toolbar import org.ireader.presentation.presentation.reusable_composable.AppIconButton +import org.ireader.presentation.presentation.reusable_composable.AppTextField import org.ireader.presentation.presentation.reusable_composable.TopAppBarBackButton import tachiyomi.source.Source @@ -30,15 +35,18 @@ import tachiyomi.source.Source fun ReaderScreenTopBar( isReaderModeEnable: Boolean, isLoaded: Boolean, + vm: ReaderScreenPreferencesState, + state: ReaderScreenState, modalBottomSheetValue: ModalBottomSheetValue, chapter: Chapter?, navController: NavController, onRefresh: () -> Unit, source: Source, onWebView: () -> Unit, - - - ) { + onBookMark: () -> Unit, + scrollState: LazyListState, +) { + val scope = rememberCoroutineScope() if (!isReaderModeEnable && isLoaded) { AnimatedVisibility( visible = !isReaderModeEnable && isLoaded, @@ -48,34 +56,153 @@ fun ReaderScreenTopBar( Toolbar( modifier = Modifier.systemBarsPadding(), title = { - Text( - text = chapter?.title ?: "", - color = MaterialTheme.colors.onBackground, - style = MaterialTheme.typography.subtitle1, - fontWeight = FontWeight.Bold, - overflow = TextOverflow.Ellipsis, - maxLines = 1 - ) + if (!vm.searchMode) { + Text( + text = chapter?.title ?: "", + color = MaterialTheme.colors.onBackground, + style = MaterialTheme.typography.subtitle1, + fontWeight = FontWeight.Bold, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } else { + AppTextField( + query = vm.searchQuery, + onValueChange = { query -> + vm.searchQuery = query + vm.queriedTextIndex.clear() + state.stateChapter?.let { chapter -> + chapter.content.filter { cont -> + cont.contains(query, + ignoreCase = true) + }.forEach { str -> + val index = chapter.content.indexOf(str) + if (index != -1) { + vm.queriedTextIndex.add(index) + } + + } + } + + }, + onConfirm = { + + }, + ) + } + }, backgroundColor = MaterialTheme.colors.background, contentColor = MaterialTheme.colors.onBackground, elevation = 0.dp, navigationIcon = { - TopAppBarBackButton(navController = navController) - }, - actions = { - if (chapter != null) { - AppIconButton(imageVector = Icons.Default.Autorenew, - title = "Refresh", + if (!vm.searchMode) { + TopAppBarBackButton(navController = navController) + } else { + AppIconButton(imageVector = Icons.Default.ArrowBack, + title = "Exit search mode", onClick = { - onRefresh() + vm.searchQuery = "" + vm.searchMode = false + vm.queriedTextIndex.clear() }) } - AppIconButton(imageVector = Icons.Default.Public, - title = "WebView", - onClick = { - onWebView() - }) + }, + actions = { + when (vm.searchMode) { + true -> { + AppIconButton( + imageVector = Icons.Default.Close, + title = "Close", + onClick = { + vm.searchQuery = "" + vm.searchMode = false + vm.queriedTextIndex.clear() + }, + ) + AppIconButton( + imageVector = Icons.Default.ExpandMore, + title = "previous result", + onClick = { + vm.currentViewingSearchResultIndex.let { index -> + chapter?.let { + if (index < chapter.content.lastIndex) { + scope.launch { + try { + vm.currentViewingSearchResultIndex += 1 + scrollState.scrollToItem(vm.queriedTextIndex[index]) + } catch (e: Exception) { + vm.currentViewingSearchResultIndex = 0 + } + } + + } + + } + } + + + }, + ) + AppIconButton( + imageVector = Icons.Default.ExpandLess, + title = "next result", + onClick = { + vm.currentViewingSearchResultIndex.let { index -> + if (index > 0) { + + scope.launch { + try { + vm.currentViewingSearchResultIndex -= 1 + scrollState.scrollToItem(vm.queriedTextIndex[index]) + } catch (e: Exception) { + vm.currentViewingSearchResultIndex = 0 + } + } + } + + + } + }, + ) + } + else -> { + if (chapter != null) { + AppIconButton(imageVector = if (vm.expandTopMenu) Icons.Default.ChevronRight else Icons.Default.ChevronLeft, + title = "Expand Menu", + onClick = { + vm.expandTopMenu = !vm.expandTopMenu + }) + if (vm.expandTopMenu) { + AppIconButton(imageVector = if (chapter.bookmark) Icons.Filled.Bookmark else Icons.Default.Bookmark, + title = "Bookmark", + tint = if (chapter.bookmark) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground, + onClick = { + onBookMark() + }) + AppIconButton(imageVector = Icons.Default.Search, + title = "Search", + onClick = { + vm.searchMode = true + }) + AppIconButton(imageVector = Icons.Default.Public, + title = "WebView", + onClick = { + onWebView() + }) + } + AppIconButton(imageVector = Icons.Default.Autorenew, + title = "Refresh", + onClick = { + onRefresh() + }) + + } + } + + } + + } ) } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderText.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderText.kt index 69604cd9a..f58f5f004 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderText.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/ReaderText.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.material.ModalBottomSheetState @@ -19,6 +20,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -100,21 +102,42 @@ fun ReaderText( state = scrollState, modifier = Modifier ) { - item { - Text( - modifier = modifier - .fillMaxSize() - .padding(horizontal = vm.paragraphsIndent.dp, - vertical = 4.dp), - text = "\n\n" + chapter.content.map { it.trimStart() } - .joinToString("\n".repeat(vm.distanceBetweenParagraphs)), - fontSize = vm.fontSize.sp, - fontFamily = vm.font.fontFamily, - textAlign = TextAlign.Start, - color = vm.textColor, - lineHeight = vm.lineHeight.sp, - ) + items(count = chapter.content.size) { index -> + TextSelectionContainer(selectable = vm.selectableMode) { + Text( + modifier = modifier + .padding(horizontal = vm.paragraphsIndent.dp, vertical = 4.dp) + .background(if (index in vm.queriedTextIndex) vm.textColor.copy( + .1f) else Color.Transparent), + text = if (index == 0) "\n\n" + chapter.content[index].plus("\n".repeat( + vm.distanceBetweenParagraphs)) else chapter.content[index].plus( + "\n".repeat(vm.distanceBetweenParagraphs)), + fontSize = vm.fontSize.sp, + fontFamily = vm.font.fontFamily, + textAlign = TextAlign.Start, + color = vm.textColor, + lineHeight = vm.lineHeight.sp, + ) + } } +// item { +// TextSelectionContainer(selectable = vm.selectableMode) { +// Text( +// modifier = modifier +// .fillMaxSize() +// .padding(horizontal = vm.paragraphsIndent.dp, +// vertical = 4.dp), +// text = "\n\n" + chapter.content.map { it.trimStart() } +// .joinToString("\n".repeat(vm.distanceBetweenParagraphs)), +// fontSize = vm.fontSize.sp, +// fontFamily = vm.font.fontFamily, +// textAlign = TextAlign.Start, +// color = vm.textColor, +// lineHeight = vm.lineHeight.sp, +// ) +// } +// +// } } @@ -196,4 +219,20 @@ fun ReaderText( fun LazyListState.isScrolledToTheEnd(): Boolean { val lastItem = layoutInfo.visibleItemsInfo.lastOrNull() return lastItem == null || lastItem.size + lastItem.offset <= layoutInfo.viewportEndOffset +} + +@Composable +fun TextSelectionContainer( + modifier: Modifier = Modifier, + selectable: Boolean, + content: @Composable () -> Unit, +) { + when (selectable) { + true -> SelectionContainer { + content() + } + else -> { + content() + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderSettingComposable.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderSettingComposable.kt index 2bad43210..2c7131a13 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderSettingComposable.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderSettingComposable.kt @@ -70,6 +70,13 @@ fun ReaderSettingComposable(modifier: Modifier = Modifier, viewModel: ReaderScre viewModel.toggleImmersiveMode(context) } }) + SettingItemToggleComposable(text = "Selectable mode", + value = viewModel.selectableMode, + onToggle = { + func.apply { + viewModel.toggleSelectableMode() + } + }) SettingItemComposable(text = "Font Size", value = viewModel.fontSize.toString(), onAdd = { diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderMainFunc.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderMainFunc.kt index 0c933442f..4aa410f93 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderMainFunc.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderMainFunc.kt @@ -19,6 +19,7 @@ interface ReaderMainFunctions { suspend fun ReaderScreenViewModel.getChapter( chapterId: Long, source: Source, + onSuccess: () -> Unit = {}, ) suspend fun ReaderScreenViewModel.getLocalBookById( @@ -30,13 +31,22 @@ interface ReaderMainFunctions { suspend fun ReaderScreenViewModel.insertChapter(chapter: Chapter) suspend fun ReaderScreenViewModel.insertBook(book: Book) - fun ReaderScreenViewModel.getReadingContentRemotely(chapter: Chapter, source: Source) + fun ReaderScreenViewModel.getReadingContentRemotely( + chapter: Chapter, + source: Source, + onSuccess: () -> Unit = {}, + ) + fun ReaderScreenViewModel.updateLastReadTime(chapter: Chapter) fun ReaderScreenViewModel.getLocalChaptersByPaging(bookId: Long) } class ReaderMainFunctionsImpl @Inject constructor() : ReaderMainFunctions { - override suspend fun ReaderScreenViewModel.getChapter(chapterId: Long, source: Source) { + override suspend fun ReaderScreenViewModel.getChapter( + chapterId: Long, + source: Source, + onSuccess: () -> Unit, + ) { toggleLoading(true) toggleLocalLoaded(false) viewModelScope.launch { @@ -61,13 +71,16 @@ class ReaderMainFunctionsImpl @Inject constructor() : ReaderMainFunctions { !state.isRemoteLoading && !state.isLoading ) { - getReadingContentRemotely(chapter = chapter, source = source) + getReadingContentRemotely(chapter = chapter, source = source) { + + } } updateLastReadTime(resultChapter) updateChapterSliderIndex(getCurrentIndexOfChapter(resultChapter)) if (!initialized) { initialized = true } + onSuccess() } else { toggleLoading(false) toggleLocalLoaded(false) @@ -121,7 +134,11 @@ class ReaderMainFunctionsImpl @Inject constructor() : ReaderMainFunctions { insertUseCases.insertBook(book) } - override fun ReaderScreenViewModel.getReadingContentRemotely(chapter: Chapter, source: Source) { + override fun ReaderScreenViewModel.getReadingContentRemotely( + chapter: Chapter, + source: Source, + onSuccess: () -> Unit, + ) { clearError() toggleLocalLoaded(false) toggleRemoteLoading(true) @@ -144,7 +161,7 @@ class ReaderMainFunctionsImpl @Inject constructor() : ReaderMainFunctions { toggleRemoteLoaded(true) clearError() getChapter(chapter.id, source = source) - + onSuccess() } else { showSnackBar(UiText.StringResource(R.string.something_is_wrong_with_this_chapter)) } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderPrefCodes.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderPrefCodes.kt index 3ac818d45..9c42d42cd 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderPrefCodes.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderPrefCodes.kt @@ -27,6 +27,7 @@ interface ReaderPrefFunctions { fun ReaderScreenViewModel.saveOrientation(context: Context) fun ReaderScreenViewModel.readImmersiveMode(context: Context) fun ReaderScreenViewModel.toggleImmersiveMode(context: Context) + fun ReaderScreenViewModel.toggleSelectableMode() fun ReaderScreenViewModel.toggleAutoScrollMode() fun ReaderScreenViewModel.toggleScrollMode() fun ReaderScreenViewModel.saveParagraphDistance(isIncreased: Boolean) @@ -49,8 +50,8 @@ interface ReaderPrefFunctions { class ReaderPrefFunctionsImpl @Inject constructor() : ReaderPrefFunctions { override fun ReaderScreenViewModel.readPreferences() { - font = readerUseCases.selectedFontStateUseCase.read() - readerUseCases.selectedFontStateUseCase.save(0) + font = readerUseCases.selectedFontStateUseCase.readFont() + readerUseCases.selectedFontStateUseCase.saveFont(0) this.fontSize = readerUseCases.fontSizeStateUseCase.read() @@ -71,6 +72,8 @@ class ReaderPrefFunctionsImpl @Inject constructor() : ReaderPrefFunctions { pitch = speechPrefUseCases.readPitch() currentVoice = speechPrefUseCases.readVoice() currentLanguage = speechPrefUseCases.readLanguage() + selectableMode = readerUseCases.selectedFontStateUseCase.readSelectableText() + autoNextChapter = speechPrefUseCases.readAutoNext() } override fun ReaderScreenViewModel.toggleReaderMode(enable: Boolean?) { @@ -93,7 +96,7 @@ class ReaderPrefFunctionsImpl @Inject constructor() : ReaderPrefFunctions { override fun ReaderScreenViewModel.saveFont(index: Int) { this.font = fonts[index] - readerUseCases.selectedFontStateUseCase.save(index) + readerUseCases.selectedFontStateUseCase.saveFont(index) } @@ -194,6 +197,11 @@ class ReaderPrefFunctionsImpl @Inject constructor() : ReaderPrefFunctions { } } + override fun ReaderScreenViewModel.toggleSelectableMode() { + selectableMode = !selectableMode + readerUseCases.selectedFontStateUseCase.saveSelectableText(selectableMode) + } + override fun ReaderScreenViewModel.toggleAutoScrollMode() { autoScrollMode = !autoScrollMode } @@ -210,7 +218,7 @@ class ReaderPrefFunctionsImpl @Inject constructor() : ReaderPrefFunctions { readerUseCases.paragraphDistanceUseCase.save(currentDistance + 1) distanceBetweenParagraphs = currentDistance + 1 - } else if (currentDistance > 1 && !isIncreased) { + } else if (currentDistance > 0 && !isIncreased) { readerUseCases.paragraphDistanceUseCase.save(currentDistance - 1) distanceBetweenParagraphs = currentDistance - 1 diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenState.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenState.kt index 1917ef204..d806afb81 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenState.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenState.kt @@ -4,8 +4,10 @@ package org.ireader.presentation.feature_reader.presentation.reader.viewmodel import android.speech.tts.TextToSpeech import android.speech.tts.Voice import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.graphics.Color import org.ireader.core.utils.UiText import org.ireader.core_ui.theme.BackgroundColor @@ -134,7 +136,13 @@ open class ReaderScreenPreferencesStateImpl @Inject constructor() : ReaderScreen override var autoScrollMode by mutableStateOf(false) override var autoBrightnessMode by mutableStateOf(false) override var immersiveMode by mutableStateOf(false) + override var selectableMode by mutableStateOf(false) override var initialized by mutableStateOf(false) + override var searchMode by mutableStateOf(false) + override var expandTopMenu by mutableStateOf(false) + override var searchQuery by mutableStateOf("") + override var queriedTextIndex: SnapshotStateList = mutableStateListOf() + override var currentViewingSearchResultIndex by mutableStateOf(0) } @@ -161,7 +169,13 @@ interface ReaderScreenPreferencesState { var autoScrollMode: Boolean var autoBrightnessMode: Boolean var immersiveMode: Boolean + var selectableMode: Boolean var initialized: Boolean + var searchMode: Boolean + var expandTopMenu: Boolean + var searchQuery: String + var queriedTextIndex: SnapshotStateList + var currentViewingSearchResultIndex: Int } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenViewModel.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenViewModel.kt index 985dfdd60..7466af140 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenViewModel.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderScreenViewModel.kt @@ -3,16 +3,19 @@ package org.ireader.presentation.feature_reader.presentation.reader.viewmodel import android.content.Context import android.support.v4.media.session.MediaSessionCompat +import androidx.compose.foundation.lazy.LazyListState import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import org.ireader.core.R import org.ireader.core.utils.UiText import org.ireader.core_ui.viewmodel.BaseViewModel import org.ireader.domain.catalog.service.CatalogStore import org.ireader.domain.feature_services.notification.DefaultNotificationHelper import org.ireader.domain.feature_services.notification.NotificationStates +import org.ireader.domain.models.entities.Chapter import org.ireader.domain.ui.NavigationArgs import org.ireader.domain.use_cases.history.HistoryUseCase import org.ireader.domain.use_cases.local.LocalGetChapterUseCase @@ -20,6 +23,7 @@ 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 tachiyomi.source.Source import javax.inject.Inject @@ -70,6 +74,7 @@ class ReaderScreenViewModel @Inject constructor( } + } else { viewModelScope.launch { showSnackBar(UiText.StringResource(org.ireader.core.R.string.the_source_is_not_found)) @@ -125,5 +130,33 @@ class ReaderScreenViewModel @Inject constructor( currentReadingParagraph = 0 } + fun onNext( + scrollState: LazyListState, + chapters: List, + source: Source, + currentIndex: Int, + ) { + if (currentIndex < chapters.lastIndex) { + uiFunc.apply { + updateChapterSliderIndex(currentIndex + 1) + } + mainFunc.apply { + uiFunc.apply { + scope.launch { + getChapter(getCurrentChapterByIndex().id, + source = source) + scrollState.animateScrollToItem(0, 0) + } + } + } + + } else { + scope.launch { + showSnackBar(UiText.StringResource(R.string.this_is_last_chapter)) + + } + } + } + } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderUiFunc.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderUiFunc.kt index 61dd75b59..206d7dab9 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderUiFunc.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderUiFunc.kt @@ -5,6 +5,8 @@ import android.content.pm.ActivityInfo import android.view.WindowManager import androidx.compose.foundation.lazy.LazyListState import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.ireader.core.utils.UiText import org.ireader.core.utils.findComponentActivity @@ -29,6 +31,8 @@ interface ReaderUiFunctions { fun ReaderScreenViewModel.getCurrentIndex(): Int fun ReaderScreenViewModel.getCurrentChapterByIndex(): Chapter fun ReaderScreenViewModel.reverseChapters() + fun ReaderScreenViewModel.bookmarkChapter() + } @@ -130,4 +134,15 @@ class ReaderUiFunctionsImpl @Inject constructor() : ReaderUiFunctions { override fun ReaderScreenViewModel.reverseChapters() { toggleIsAsc(!prefState.isAsc) } + + override fun ReaderScreenViewModel.bookmarkChapter() { + stateChapter?.let { chapter -> + viewModelScope.launch(Dispatchers.IO) { + stateChapter = chapter.copy(bookmark = !chapter.bookmark) + insertUseCases.insertChapter(chapter.copy(bookmark = !chapter.bookmark)) + + } + + } + } } \ No newline at end of file diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderManager.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderManager.kt index c56b4e271..55a49627f 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderManager.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderManager.kt @@ -69,7 +69,7 @@ class TextReaderManager @Inject constructor( defaultNotificationHelper.basicPlayingTextReaderNotification( chapter, book, - true, + isPlaying, currentReadingParagraph, mediaSessionCompat) @@ -105,16 +105,20 @@ class TextReaderManager @Inject constructor( // notify(Notifications.ID_TEXT_READER_PROGRESS, builder.build()) readText(context, mediaSessionCompat) } - if (currentReadingParagraph == chapter.content.size) { + if (currentReadingParagraph == chapter.content.size && speaker != null && !isLoading && !isRemoteLoading) { isPlaying = false + speaker?.stop() if (autoNextChapter) { source?.let { updateChapterSliderIndex(currentChapterIndex + 1) viewModelScope.launch { getChapter(getCurrentChapterByIndex().id, - source = it) - readText(context = context, mediaSessionCompat) - isPlaying = true + source = it) { + if (chapter.content.isNotEmpty() && !isLoading && !isRemoteLoading) { + readText(context = context, + mediaSessionCompat) + } + } } } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/setting/SettingViewModel.kt b/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/setting/SettingViewModel.kt index 5a0aeed3e..fcbf27a2b 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/setting/SettingViewModel.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/setting/SettingViewModel.kt @@ -56,7 +56,7 @@ class SettingViewModel @Inject constructor( fun deleteDefaultSettings() { viewModelScope.launchIO { - prefUseCases.selectedFontStateUseCase.save(0) + prefUseCases.selectedFontStateUseCase.saveFont(0) prefUseCases.fontHeightUseCase.save(25) prefUseCases.fontSizeStateUseCase.save(18) prefUseCases.paragraphDistanceUseCase.save(2) diff --git a/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/webview/WebPageTopBar.kt b/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/webview/WebPageTopBar.kt index 52ff920b3..1589428fb 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/webview/WebPageTopBar.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_settings/presentation/webview/WebPageTopBar.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController +import org.ireader.core.ChapterParse import org.ireader.core.ChaptersParse import org.ireader.core.DetailParse import org.ireader.domain.FetchType @@ -103,7 +104,8 @@ fun WebPageTopBar( }) { MidSizeTextComposable(text = stringResource(R.string.go_forward)) } - if (source is HttpSource && source.getListings().contains(DetailParse())) { + if (source is HttpSource && source.getListings().map { it.name } + .contains(DetailParse().name)) { DropdownMenuItem(onClick = { isMenuExpanded = false fetchBook() @@ -111,7 +113,17 @@ fun WebPageTopBar( MidSizeTextComposable(text = "Fetch Book") } } - if (source is HttpSource && source.getListings().contains(ChaptersParse())) { + if (source is HttpSource && source.getListings().map { it.name } + .contains(ChaptersParse().name)) { + DropdownMenuItem(onClick = { + isMenuExpanded = false + fetchChapter() + }) { + MidSizeTextComposable(text = "Fetch Chapter") + } + } + if (source is HttpSource && source.getListings().map { it.name } + .contains(ChapterParse().name)) { DropdownMenuItem(onClick = { isMenuExpanded = false fetchChapter() diff --git a/presentation/src/main/java/org/ireader/presentation/feature_ttl/TTSScreen.kt b/presentation/src/main/java/org/ireader/presentation/feature_ttl/TTSScreen.kt index 16208709e..c425ee7a9 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_ttl/TTSScreen.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_ttl/TTSScreen.kt @@ -1,12 +1,15 @@ package org.ireader.presentation.feature_ttl +import android.speech.tts.TextToSpeech import androidx.activity.compose.BackHandler import androidx.compose.animation.core.TweenSpec import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* @@ -20,6 +23,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.app.NotificationManagerCompat @@ -27,6 +31,7 @@ import androidx.navigation.NavController import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.ireader.core.R +import org.ireader.core.utils.toast import org.ireader.domain.feature_services.io.BookCover import org.ireader.domain.feature_services.notification.Notifications import org.ireader.domain.models.entities.Chapter @@ -59,6 +64,8 @@ fun TTSScreen( onNext: () -> Unit, source: Source, onChapter: (Chapter) -> Unit, + onValueChange: (Float) -> Unit, + onValueChangeFinished: () -> Unit, ) { @@ -83,6 +90,38 @@ fun TTSScreen( } } } + LaunchedEffect(key1 = true) { + vm.apply { + if (speaker == null) { + speaker = TextToSpeech(context) { status -> + isLoading = true + if (status == TextToSpeech.ERROR) { + context.toast("Text-to-Speech Not Available") + isLoading = false + return@TextToSpeech + } + isLoading = false + } + } + } + } + LaunchedEffect(key1 = vm.stateChapter, vm.isPlaying, vm.currentReadingParagraph) { + vm.state.stateChapter?.let { chapter -> + vm.state.book?.let { book -> + val notification = vm.defaultNotificationHelper.basicPlayingTextReaderNotification( + chapter, + book, + vm.isPlaying, + vm.currentReadingParagraph, + vm.mediaSessionCompat(context)) + NotificationManagerCompat.from(context) + .notify(Notifications.ID_TEXT_READER_PROGRESS, notification.build()) + } + + } + } + + LaunchedEffect(key1 = true) { vm.notificationStates.mediaPlayerNotification.collectLatest { Timber.e(it.toString()) @@ -97,28 +136,17 @@ fun TTSScreen( onPlay() chapter?.let { vm.book?.let { book -> - val notification = when { - vm.isPlaying -> { - vm.defaultNotificationHelper.basicPlayingTextReaderNotification( - chapter, - book, - false, - vm.currentReadingParagraph, - vm.mediaSessionCompat(context)) - } - else -> { - vm.defaultNotificationHelper.basicPlayingTextReaderNotification( - chapter, - book, - true, - vm.currentReadingParagraph, - vm.mediaSessionCompat(context)) - - } - } - NotificationManagerCompat.from(context).apply { - notify(Notifications.ID_TEXT_READER_PROGRESS, notification.build()) - } +// val notification = vm.defaultNotificationHelper.basicPlayingTextReaderNotification( +// chapter, +// book, +// !vm.isPlaying, +// vm.currentReadingParagraph, +// vm.mediaSessionCompat(context)) +// +// +// NotificationManagerCompat.from(context).apply { +// notify(Notifications.ID_TEXT_READER_PROGRESS, notification.build()) +// } } } @@ -150,7 +178,10 @@ fun TTSScreen( LanguageChip(viewModel = vm, modifier = Modifier.height(32.dp)) SettingItemToggleComposable(text = "Auto Next Chapter", value = vm.autoNextChapter, - onToggle = { vm.autoNextChapter = !vm.autoNextChapter }) + onToggle = { + vm.autoNextChapter = !vm.autoNextChapter + vm.speechPrefUseCases.saveAutoNext(vm.autoNextChapter) + }) SettingItemComposable(text = "Speech Rate", value = vm.speechSpeed.toString(), onAdd = { @@ -249,7 +280,7 @@ fun TTSScreen( image = BookCover.from(book), modifier = Modifier .padding(8.dp) - .height(150.dp) + .height(180.dp) .width(120.dp) .clip(MaterialTheme.shapes.medium) .border(2.dp, @@ -257,8 +288,14 @@ fun TTSScreen( contentScale = ContentScale.Crop, ) - BigSizeTextComposable(text = chapter.title, align = TextAlign.Center) - MidSizeTextComposable(text = book.title, align = TextAlign.Center) + BigSizeTextComposable(text = chapter.title, + align = TextAlign.Center, + maxLine = 1, + overflow = TextOverflow.Ellipsis) + MidSizeTextComposable(text = book.title, + align = TextAlign.Center, + maxLine = 1, + overflow = TextOverflow.Ellipsis) vm.stateChapter?.let { chapter -> SuperSmallTextComposable(text = "${vm.currentReadingParagraph}/${chapter.content.size}") @@ -266,11 +303,11 @@ fun TTSScreen( } Text( modifier = modifier - + .padding(horizontal = vm.paragraphsIndent.dp, vertical = 4.dp) .fillMaxWidth() - .align(Alignment.CenterHorizontally) - .padding(horizontal = vm.paragraphsIndent.dp, - vertical = 4.dp), + .height(160.dp) + .verticalScroll(rememberScrollState()) + .align(Alignment.CenterHorizontally), text = if (chapter.content.isNotEmpty() && vm.currentReadingParagraph != chapter.content.size) chapter.content[vm.currentReadingParagraph] else "", fontSize = vm.fontSize.sp, fontFamily = vm.font.fontFamily, @@ -297,13 +334,15 @@ fun TTSScreen( } ) TTLScreenPlay( - modifier = Modifier.padding(bottom = 46.dp, top = 32.dp), onPlay = onPlay, onNext = onNext, onPrev = onPrev, vm = vm, onNextPar = onNextPar, - onPrevPar = onPrevPar + onPrevPar = onPrevPar, + chapter = chapter, + onValueChange = onValueChange, + onValueChangeFinished = onValueChangeFinished ) } @@ -326,8 +365,8 @@ private fun TTLScreenSetting( onContent: () -> Unit, ) { Row(modifier = Modifier + .padding(horizontal = 8.dp) .fillMaxWidth() - // .padding(16.dp) .height(80.dp) .border(width = 1.dp, color = MaterialTheme.colors.onBackground.copy(.1f)), horizontalArrangement = Arrangement.SpaceAround, @@ -364,69 +403,99 @@ private fun TTLScreenSetting( private fun TTLScreenPlay( modifier: Modifier = Modifier, vm: ReaderScreenViewModel, + chapter: Chapter?, onPrev: () -> Unit, onPlay: () -> Unit, onNext: () -> Unit, onNextPar: () -> Unit, onPrevPar: () -> Unit, + onValueChange: (Float) -> Unit, + onValueChangeFinished: () -> Unit, ) { - Row(modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceAround, - verticalAlignment = Alignment.CenterVertically) { - Row(modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceAround) { - AppIconButton(modifier = Modifier.size(50.dp), - imageVector = Icons.Filled.SkipPrevious, - title = "Previous Paragraph", - onClick = onPrev, - tint = MaterialTheme.colors.onBackground) - AppIconButton(modifier = Modifier.size(50.dp), - imageVector = Icons.Filled.FastRewind, - title = "Previous", - onClick = onPrevPar, - tint = MaterialTheme.colors.onBackground) - Box(modifier = Modifier - .size(100.dp) - .border(1.dp, MaterialTheme.colors.onBackground.copy(.4f), CircleShape), - contentAlignment = Alignment.Center) { - when { - vm.isLoading || vm.isRemoteLoading -> { - showLoading() - } - vm.isPlaying -> { - AppIconButton(modifier = Modifier.size(80.dp), - imageVector = Icons.Filled.Pause, - title = "Play", - onClick = onPlay, - tint = MaterialTheme.colors.onBackground) - } - else -> { - AppIconButton(modifier = Modifier.size(80.dp), - imageVector = Icons.Filled.PlayArrow, - title = "Play", - onClick = onPlay, - tint = MaterialTheme.colors.onBackground) + Column(modifier = Modifier.fillMaxWidth()) { + chapter?.let { chapter -> + Slider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + value = if (chapter.content.isEmpty()) 0F else vm.currentReadingParagraph.toFloat(), + onValueChange = { + onValueChange(it) + }, + onValueChangeFinished = { + onValueChangeFinished() + }, + valueRange = 0f..(if (chapter.content.isNotEmpty()) chapter.content.size - 1 else 0).toFloat(), + colors = SliderDefaults.colors( + thumbColor = MaterialTheme.colors.primary, + activeTrackColor = MaterialTheme.colors.primary.copy(alpha = .6f), + inactiveTickColor = MaterialTheme.colors.onBackground.copy(alpha = .6f), + inactiveTrackColor = MaterialTheme.colors.onBackground.copy(alpha = .6f), + activeTickColor = MaterialTheme.colors.primary.copy(alpha = .6f) + ) + ) + } + + Row(modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically) { + Row(modifier = Modifier + .padding(bottom = 16.dp, top = 4.dp) + .fillMaxWidth() + .height(80.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceAround) { + AppIconButton(modifier = Modifier.size(50.dp), + imageVector = Icons.Filled.SkipPrevious, + title = "Previous Paragraph", + onClick = onPrev, + tint = MaterialTheme.colors.onBackground) + AppIconButton(modifier = Modifier.size(50.dp), + imageVector = Icons.Filled.FastRewind, + title = "Previous", + onClick = onPrevPar, + tint = MaterialTheme.colors.onBackground) + Box(modifier = Modifier + .size(80.dp) + .border(1.dp, MaterialTheme.colors.onBackground.copy(.4f), CircleShape), + contentAlignment = Alignment.Center) { + when { + vm.isLoading || vm.isRemoteLoading -> { + showLoading() + } + vm.isPlaying -> { + AppIconButton(modifier = Modifier.size(80.dp), + imageVector = Icons.Filled.Pause, + title = "Play", + onClick = onPlay, + tint = MaterialTheme.colors.onBackground) + } + else -> { + AppIconButton(modifier = Modifier.size(80.dp), + imageVector = Icons.Filled.PlayArrow, + title = "Play", + onClick = onPlay, + tint = MaterialTheme.colors.onBackground) + } } - } - } + } - AppIconButton(modifier = Modifier.size(50.dp), - imageVector = Icons.Filled.FastForward, - title = "Next Paragraph", - onClick = onNextPar, - tint = MaterialTheme.colors.onBackground) - AppIconButton(modifier = Modifier.size(50.dp), - imageVector = Icons.Filled.SkipNext, - title = "Next", - onClick = onNext, - tint = MaterialTheme.colors.onBackground) + AppIconButton(modifier = Modifier.size(50.dp), + imageVector = Icons.Filled.FastForward, + title = "Next Paragraph", + onClick = onNextPar, + tint = MaterialTheme.colors.onBackground) + AppIconButton(modifier = Modifier.size(50.dp), + imageVector = Icons.Filled.SkipNext, + title = "Next", + onClick = onNext, + tint = MaterialTheme.colors.onBackground) + } } } + } diff --git a/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt b/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt index 7df927e5c..d6fe9105a 100644 --- a/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt +++ b/presentation/src/main/java/org/ireader/presentation/presentation/reusable_composable/TopAppBarReusableComposables.kt @@ -35,6 +35,7 @@ fun BigSizeTextComposable( fontWeight: FontWeight? = null, overflow: TextOverflow? = null, align: TextAlign? = null, + maxLine: Int = Int.MAX_VALUE, ) { Text( modifier = modifier, @@ -44,6 +45,7 @@ fun BigSizeTextComposable( fontWeight = fontWeight ?: FontWeight.Bold, overflow = overflow ?: TextOverflow.Ellipsis, textAlign = align ?: TextAlign.Start, + maxLines = maxLine ) } diff --git a/presentation/src/main/java/org/ireader/presentation/ui/ReaderScreenSpec.kt b/presentation/src/main/java/org/ireader/presentation/ui/ReaderScreenSpec.kt index cdc0b9bbd..808ce1d70 100644 --- a/presentation/src/main/java/org/ireader/presentation/ui/ReaderScreenSpec.kt +++ b/presentation/src/main/java/org/ireader/presentation/ui/ReaderScreenSpec.kt @@ -55,6 +55,7 @@ object ReaderScreenSpec : ScreenSpec { val currentIndex = vm.currentChapterIndex val source = vm.source val chapters = vm.stateChapters + val chapter = vm.stateChapter val scope = rememberCoroutineScope() val scrollState = rememberLazyListState() val drawerScrollState = rememberLazyListState() @@ -67,8 +68,13 @@ object ReaderScreenSpec : ScreenSpec { vm.mediaSessionCompat(context).release() NotificationManagerCompat.from(context) .cancel(Notifications.ID_TEXT_READER_PROGRESS) + vm.uiFunc.apply { + vm.restoreSetting(context, scrollState) + } } } + + if (source != null) { when { vm.voiceMode -> { @@ -76,22 +82,15 @@ object ReaderScreenSpec : ScreenSpec { TTSScreen( vm = vm, onPrev = { - if (currentIndex > 0) { - vm.uiFunc.apply { - vm.updateChapterSliderIndex(currentIndex - 1) - - } - vm.mainFunc.apply { - vm.uiFunc.apply { - scope.launch { - vm.getChapter(vm.getCurrentChapterByIndex().id, - source = source) - scrollState.animateScrollToItem(0, 0) - } + if (vm.currentChapterIndex > 0) { + vm.apply { + currentChapterIndex -= 1 + scope.launch { + vm.getChapter(vm.getCurrentChapterByIndex().id, + source = source) + scrollState.animateScrollToItem(0, 0) } } - - } else { scope.launch { vm.showSnackBar(UiText.StringResource(org.ireader.core.R.string.this_is_first_chapter)) @@ -109,22 +108,33 @@ object ReaderScreenSpec : ScreenSpec { } } } - + scope.launch { + vm.state.stateChapter?.let { chapter -> + vm.state.book?.let { book -> + val notification = + vm.defaultNotificationHelper.basicPlayingTextReaderNotification( + chapter, + book, + vm.isPlaying, + vm.currentReadingParagraph, + vm.mediaSessionCompat(context)) + NotificationManagerCompat.from(context) + .notify(Notifications.ID_TEXT_READER_PROGRESS, + notification.build()) + } + } + } }, onNext = { if (currentIndex < chapters.lastIndex) { - vm.uiFunc.apply { - - vm.updateChapterSliderIndex(currentIndex + 1) - } - vm.mainFunc.apply { - vm.uiFunc.apply { - scope.launch { - vm.getChapter(vm.getCurrentChapterByIndex().id, - source = source) - scrollState.animateScrollToItem(0, 0) - } + vm.apply { + currentChapterIndex += 1 + scope.launch { + vm.getChapter(vm.getCurrentChapterByIndex().id, + source = source) + scrollState.animateScrollToItem(0, 0) } + } } else { @@ -177,6 +187,18 @@ object ReaderScreenSpec : ScreenSpec { } } } + }, + onValueChange = { + vm.speaker?.stop() + vm.currentReadingParagraph = it.toInt() + }, + onValueChangeFinished = { + if (vm.isPlaying) { + vm.textReaderManager.apply { + vm.readText(context, vm.mediaSessionCompat(context)) + } + } + } ) }