diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ce32ad799..b905d41bf 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -7,7 +7,7 @@ - + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ecc0028a4..e067e675b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ + + @@ -63,6 +65,7 @@ android:host="deeplink" android:scheme="ireader" /> + + diff --git a/app/src/main/java/org/ireader/infinity/di/LocalModule.kt b/app/src/main/java/org/ireader/infinity/di/LocalModule.kt index 98f3cc420..d8e54399d 100644 --- a/app/src/main/java/org/ireader/infinity/di/LocalModule.kt +++ b/app/src/main/java/org/ireader/infinity/di/LocalModule.kt @@ -1,6 +1,7 @@ package org.ireader.infinity.di import android.content.Context +import android.speech.tts.TextToSpeech import androidx.room.Room import dagger.Module import dagger.Provides @@ -46,6 +47,15 @@ class LocalModule { .build() } + + @Provides + @Singleton + fun provideTextToSpeech(context: Context): TextToSpeech { + return TextToSpeech(context) { + + } + } + @Provides @Singleton fun providesBookDao(db: AppDatabase): LibraryBookDao { 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 00b5a0fdd..e16461a97 100644 --- a/app/src/main/java/org/ireader/infinity/presentation/MainActivity.kt +++ b/app/src/main/java/org/ireader/infinity/presentation/MainActivity.kt @@ -19,6 +19,7 @@ class MainActivity : ComponentActivity() { installSplashScreen() WindowCompat.setDecorFitsSystemWindows(window, false) super.onCreate(savedInstanceState) + setContent { AppTheme { Surface( diff --git a/core-ui/src/main/java/org/ireader/core_ui/ui/PreferenceMutableState.kt b/core-ui/src/main/java/org/ireader/core_ui/ui/PreferenceMutableState.kt index aa3a5a13c..671facbf0 100644 --- a/core-ui/src/main/java/org/ireader/core_ui/ui/PreferenceMutableState.kt +++ b/core-ui/src/main/java/org/ireader/core_ui/ui/PreferenceMutableState.kt @@ -15,9 +15,13 @@ class PreferenceMutableState( private val state = mutableStateOf(preference.get()) init { - preference.changes() - .onEach { state.value = it } - .launchIn(scope) + try { + preference.changes() + .onEach { state.value = it } + .launchIn(scope) + } catch (e: Exception) { + } + } override var value: T diff --git a/core/src/main/res/drawable/ic_baseline_fast_forward.xml b/core/src/main/res/drawable/ic_baseline_fast_forward.xml new file mode 100644 index 000000000..8beaa2e35 --- /dev/null +++ b/core/src/main/res/drawable/ic_baseline_fast_forward.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/src/main/res/drawable/ic_baseline_fast_rewind.xml b/core/src/main/res/drawable/ic_baseline_fast_rewind.xml new file mode 100644 index 000000000..337f569e3 --- /dev/null +++ b/core/src/main/res/drawable/ic_baseline_fast_rewind.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/src/main/res/drawable/ic_baseline_pause.xml b/core/src/main/res/drawable/ic_baseline_pause.xml new file mode 100644 index 000000000..645e31d2c --- /dev/null +++ b/core/src/main/res/drawable/ic_baseline_pause.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/src/main/res/drawable/ic_baseline_play_arrow.xml b/core/src/main/res/drawable/ic_baseline_play_arrow.xml new file mode 100644 index 000000000..da896608b --- /dev/null +++ b/core/src/main/res/drawable/ic_baseline_play_arrow.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/src/main/res/drawable/ic_baseline_skip_next.xml b/core/src/main/res/drawable/ic_baseline_skip_next.xml new file mode 100644 index 000000000..79972afe5 --- /dev/null +++ b/core/src/main/res/drawable/ic_baseline_skip_next.xml @@ -0,0 +1,10 @@ + + + diff --git a/core/src/main/res/drawable/ic_baseline_skip_previous.xml b/core/src/main/res/drawable/ic_baseline_skip_previous.xml new file mode 100644 index 000000000..00db3759c --- /dev/null +++ b/core/src/main/res/drawable/ic_baseline_skip_previous.xml @@ -0,0 +1,10 @@ + + + diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index d2c345f20..546c63c64 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("kotlin-kapt") id("kotlin-parcelize") id("kotlinx-serialization") + id("dagger.hilt.android.plugin") } android { @@ -23,7 +24,7 @@ dependencies { // implementation(Deps.tachiyomi.api) implementation(libs.tachiyomi) - + implementation(androidx.media) implementation(kotlinx.datetime) implementation(compose.compose.activity) 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 2aa8b0bcb..0d95ecf3a 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 @@ -1,16 +1,34 @@ package org.ireader.domain.feature_services.notification import android.app.PendingIntent +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 androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.net.toUri +import androidx.work.WorkManager +import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import org.ireader.core.R +import org.ireader.domain.models.entities.Book +import org.ireader.domain.models.entities.Chapter import org.ireader.infinity.feature_services.flags +import java.util.* import javax.inject.Inject import javax.inject.Singleton + @Singleton class DefaultNotificationHelper @Inject constructor( @ApplicationContext private val applicationContext: Context, @@ -56,5 +74,178 @@ class DefaultNotificationHelper @Inject constructor( applicationContext, 0, openDownloadIntent, flags ) + fun baseNotificationDownloader( + chapter: Chapter, + book: Book, + workManagerId: UUID, + ): NotificationCompat.Builder { + val cancelDownloadIntent = WorkManager.getInstance(applicationContext) + .createCancelPendingIntent(workManagerId) + return NotificationCompat.Builder(applicationContext, + Notifications.CHANNEL_DOWNLOADER_PROGRESS).apply { + setContentTitle("Downloading ${book.title}") + setSmallIcon(R.drawable.ic_downloading) + setOnlyAlertOnce(true) + priority = NotificationCompat.PRIORITY_LOW + setAutoCancel(true) + setOngoing(true) + addAction(R.drawable.baseline_close_24, "Cancel", cancelDownloadIntent) + setContentIntent(openDownloadsPendingIntent) + } + } + + fun updateDownloaderNotification( + chapter: Chapter, + book: Book, + workManagerId: UUID, + index: Int, + maxIndex: Int, + ) { + val notification = baseNotificationDownloader(chapter, book, workManagerId).apply { + setContentText(chapter.title) + setSubText(index.toString()) + setProgress(maxIndex, index, false) + }.build() + + NotificationManagerCompat.from(applicationContext) + .notify(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, notification) + + } + + + val skipPrev = PendingIntent.getBroadcast( + applicationContext.applicationContext, + 0, + Intent(applicationContext, TextReaderBroadcastReceiver::class.java).apply { + putExtra("PLAYER", 1) + }, + pendingIntentFlags + ) + val rewind = PendingIntent.getBroadcast( + applicationContext.applicationContext, + 1, + Intent(applicationContext.applicationContext, + TextReaderBroadcastReceiver::class.java).apply { + putExtra("PLAYER", 2) + }, + pendingIntentFlags + ) + val pause = PendingIntent.getBroadcast( + applicationContext.applicationContext, + 2, + Intent(applicationContext, TextReaderBroadcastReceiver::class.java).apply { + putExtra("PLAYER", 3) + }, + pendingIntentFlags + ) + val next = PendingIntent.getBroadcast( + applicationContext.applicationContext, + 3, + Intent(applicationContext, TextReaderBroadcastReceiver::class.java).apply { + putExtra("PLAYER", 4) + }, + pendingIntentFlags + ) + val skipNext = PendingIntent.getBroadcast( + applicationContext.applicationContext, + 4, + Intent(applicationContext, TextReaderBroadcastReceiver::class.java).apply { + putExtra("PLAYER", 5) + }, + pendingIntentFlags + ) + val cancelMediaPlater = PendingIntent.getBroadcast( + applicationContext.applicationContext, + 5, + Intent(applicationContext, TextReaderBroadcastReceiver::class.java).apply { + putExtra("PLAYER", 6) + }, + pendingIntentFlags + ) + + + suspend fun basicPlayingTextReaderNotification( + chapter: Chapter, + book: Book, + playing: Boolean, + progress: Int, + mediaSessionCompat: MediaSessionCompat, + ): NotificationCompat.Builder { + mediaSessionCompat.apply { + isActive = true + 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 + + } + return NotificationCompat.Builder(applicationContext, + Notifications.CHANNEL_TEXT_READER_PROGRESS).apply { + setContentTitle(chapter.title) + setContentText("${progress}/${chapter.content.size}") + setSmallIcon(org.ireader.core.R.drawable.ic_infinity) + setOnlyAlertOnce(true) + // setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + setLargeIcon(applicationContext, book.cover) + //setShowWhen(false) + priority = NotificationCompat.PRIORITY_LOW + + addAction(R.drawable.ic_baseline_skip_previous, + "Previous Chapter", + skipPrev) + addAction( + R.drawable.ic_baseline_fast_rewind, + "Previous Paragraph", + rewind) + + if (playing) { + addAction(R.drawable.ic_baseline_pause, "Pause", pause) + } else { + addAction(R.drawable.ic_baseline_play_arrow, "Start", pause) + + } + addAction(R.drawable.ic_baseline_fast_forward, "Next Paragraph", next) + addAction(R.drawable.ic_baseline_skip_next, "Next Chapter", skipNext) + setStyle(androidx.media.app.NotificationCompat.MediaStyle() + .setCancelButtonIntent(cancelMediaPlater) + .setMediaSession(mediaSessionCompat.sessionToken) + .setShowActionsInCompactView(1, 2, 3) + ) + setSubText(book.title) + color = applicationContext.resources.getColor(R.color.blue_200) + + //setColorized(true) + //setAutoCancel(true) + setOngoing(false) + } + + + } +} + +@AndroidEntryPoint() +class TextReaderBroadcastReceiver : BroadcastReceiver() { + + @Inject + lateinit var state: NotificationStates + + val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) + + override fun onReceive(p0: Context?, intent: Intent?) { + intent?.getIntExtra("PLAYER", -1)?.let { + scope.launch { + state.mediaPlayerNotification.emit(it) + } + } + + + } + +} + +@Singleton +class NotificationStates @Inject constructor() { + val mediaPlayerNotification = MutableSharedFlow() +} -} \ No newline at end of file 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 c9f3b547d..e3fa48c3e 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 @@ -33,19 +33,24 @@ fun createChannel(context: Context, channel: Channel) { } } -suspend fun NotificationCompat.Builder.setLargeIcon( - context: Context, - data: Any?, -): NotificationCompat.Builder { - if (data != null) { - val request = ImageRequest.Builder(context) - .data(data) - .transformations(CircleCropTransformation()) - .target { setLargeIcon(it.toBitmap()) } - .build() + suspend fun NotificationCompat.Builder.setLargeIcon( + context: Context, + data: Any?, + ): NotificationCompat.Builder { + return try { + if (data != null) { + val request = ImageRequest.Builder(context) + .data(data) + .transformations(CircleCropTransformation()) + .allowHardware(true) + .target { setLargeIcon(it.toBitmap()) } + .size(200) + .build() + ImageLoader(context).execute(request) + } + this - ImageLoader(context).execute(request) - } - - return this -} + } catch (e: Exception) { + this + } + } diff --git a/domain/src/main/java/org/ireader/domain/feature_services/notification/NotificatonWoker.kt b/domain/src/main/java/org/ireader/domain/feature_services/notification/NotificatonWoker.kt deleted file mode 100644 index 32f27f161..000000000 --- a/domain/src/main/java/org/ireader/domain/feature_services/notification/NotificatonWoker.kt +++ /dev/null @@ -1,147 +0,0 @@ -package org.ireader.domain.feature_services.notification - -import android.app.Notification -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.hilt.work.HiltWorker -import androidx.work.CoroutineWorker -import androidx.work.WorkerParameters -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOn -import org.ireader.domain.R -import org.ireader.domain.models.entities.Book -import org.ireader.domain.models.entities.Chapter -import org.ireader.domain.repository.LocalBookRepository -import org.ireader.domain.repository.LocalChapterRepository -import org.ireader.infinity.feature_services.flags -import timber.log.Timber - -private const val CHANNEL_ID: String = "42000" - -@HiltWorker -class NotificationWorker @AssistedInject constructor( - @Assisted context: Context, - @Assisted params: WorkerParameters, - private val bookRepo: LocalBookRepository, - private val chaptersRepo: LocalChapterRepository, -) : CoroutineWorker(context, params) { - - private val notificationId = 42069 - override suspend fun doWork(): Result { - Notifications.createChannels(context = applicationContext) - createChannel( - applicationContext, Channel( - name = applicationContext.getString(R.string.library_updates_notification), - id = CHANNEL_ID - ) - ) - getNotifications().collect { - with(NotificationManagerCompat.from(applicationContext)) { - notify(it.book.id.toInt(), it.createNotification()) - } - } - return Result.success() - } - - private fun getNotifications() = flow { - val books = bookRepo.findAllInLibraryBooks() - - progressNotification(books) { book -> - try { - val lastGroup = chaptersRepo.subscribeChaptersByBookId(book.id).first() - val refreshedGroups = - chaptersRepo.subscribeChaptersByBookId(book.id, true).first() - val updateCount = refreshedGroups.lastChapter() - lastGroup.lastChapter() - if (updateCount > 0) - emit( - BookNotification( - applicationContext, book, refreshedGroups, updateCount - ) - ) - } catch (e: Exception) { - Timber.e("getNotifications: Failed to query $book") - } - } - }.flowOn(Dispatchers.IO) - - private fun List.lastChapter(): Int { - return maxByOrNull { it.id }?.id?.toInt() ?: 0 - } - - private suspend fun progressNotification( - books: List, - action: suspend (Book) -> Unit, - ) { - val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID).apply { - setContentTitle("Checking for updates") - setSmallIcon(R.drawable.ic_infinity) - setOnlyAlertOnce(true) - priority = NotificationCompat.PRIORITY_LOW - } - - NotificationManagerCompat.from(applicationContext).apply { - builder.setProgress(books.size, 0, false) - notify(notificationId, builder.build()) - - books.onEachIndexed { counter, book -> - builder.apply { - setContentText(book.title) - setProgress(books.size, counter, false) - notify(notificationId, build()) - } - - action(book) - } - - builder.setProgress(0, 0, false) - cancel(notificationId) - } - } - - private data class BookNotification( - val context: Context, - val book: Book, - val chapters: List, - val updateCount: Int, - ) { - - private val lastChapter = chapters.maxByOrNull { it.id }!! - private val text = "$updateCount new chapter${if (updateCount > 1) "s" else ""} available" - - private val groupKey = "com.ubadahj.qidianunderground.CHAPTER_UPDATES" - - - suspend fun createNotification(): Notification = - NotificationCompat.Builder(context, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_infinity) - .setContentTitle(book.title) - .setContentText(text) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setContentIntent(createIntent(lastChapter)) - .addAction(R.drawable.ic_add, "Open Book", createIntent()) - .setAutoCancel(true) - .setLargeIcon(context, book.cover) - .setGroup(groupKey) - .build() - - private fun createIntent(chapter: Chapter? = null) = - PendingIntent.getActivity( - context, - hashCode() + chapter.hashCode(), - Intent(context, context.applicationContext::class.java).apply { - putExtra("book", book.id) - putExtra("group", chapter?.link) - }, - flags - )!! - - } - -} \ No newline at end of file diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 207fdf8b1..707c7bb65 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -5,6 +5,7 @@ work = "2.7.1" [libraries] agp = "com.android.tools.build:gradle:7.1.2" core = "androidx.core:core-ktx:1.8.0-alpha05" +media = "androidx.media:media:1.5.0" appCompat = "androidx.appcompat:appcompat:1.6.0-alpha01" browser = "androidx.browser:browser:1.4.0" webkit = "androidx.webkit:webkit:1.4.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cfc51fd0b..eb4b06628 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Mar 05 17:51:03 IRST 2022 +#Wed Apr 06 15:33:52 IRDT 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index cecaa6985..b0f117004 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -3,6 +3,7 @@ plugins { id("kotlin-android") id("kotlin-kapt") id("kotlinx-serialization") + } android { @@ -65,6 +66,7 @@ dependencies { implementation(libs.tachiyomi) implementation(androidx.core) + implementation(androidx.media) implementation(androidx.material) implementation(androidx.emoji) implementation(androidx.appCompat) diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/ScrollIndicatorSetting.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/ScrollIndicatorSetting.kt index 1eb2bd262..07c89cdc9 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/ScrollIndicatorSetting.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/ScrollIndicatorSetting.kt @@ -25,10 +25,17 @@ fun ScrollIndicatorSetting( enable: Boolean = false, vm: ReaderScreenViewModel, onDismiss: () -> Unit = { vm.scrollIndicatorDialogShown = false - vm.scrollIndicatorPadding = vm.readScrollIndicatorPadding() - vm.scrollIndicatorWith = vm.readScrollIndicatorWidth() - vm.readBackgroundColor() - vm.readTextColor() + vm.prefFunc.apply { + vm.readScrollIndicatorPadding() + vm.readScrollIndicatorWidth() + } + //vm.scrollIndicatorPadding = vm.readScrollIndicatorPadding() + //vm.scrollIndicatorWith = vm.readScrollIndicatorWidth() + vm.prefFunc.apply { + vm.readBackgroundColor() + vm.readTextColor() + + } }, ) { @@ -59,7 +66,10 @@ fun ScrollIndicatorSetting( try { vm.backgroundColor = Color(it.toColorInt()) } catch (e: Exception) { - vm.readBackgroundColor() + vm.prefFunc.apply { + vm.readBackgroundColor() + + } } }, @@ -79,7 +89,10 @@ fun ScrollIndicatorSetting( try { vm.textColor = Color(it.toColorInt()) } catch (e: Exception) { - vm.readTextColor() + vm.prefFunc.apply { + vm.readTextColor() + + } } }, onConfirm = { @@ -116,14 +129,20 @@ fun ScrollIndicatorSetting( try { if (bgValue.isNotBlank()) { - vm.setReaderBackgroundColor(vm.backgroundColor) + vm.prefFunc.apply { + vm.setReaderBackgroundColor(vm.backgroundColor) + } + } } catch (e: Exception) { } try { if (txtValue.isNotBlank()) { - vm.setReaderTextColor(vm.textColor) + vm.prefFunc.apply { + vm.setReaderTextColor(vm.textColor) + } + } } catch (e: Exception) { } 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 35cf1af31..033f59f57 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 @@ -52,6 +52,7 @@ fun ReadingScreen( swipeState: SwipeRefreshState, ) { + val chapters = vm.stateChapters val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) val modalState = @@ -64,13 +65,16 @@ fun ReadingScreen( DisposableEffect(key1 = true) { onDispose { - vm.speaker?.shutdown() - vm.restoreSetting(context, scrollState) + vm.uiFunc.apply { + vm.restoreSetting(context, scrollState) + } } } LaunchedEffect(key1 = scaffoldState.drawerState.targetValue) { if (chapter != null && scaffoldState.drawerState.targetValue == DrawerValue.Open && vm.stateChapters.isNotEmpty()) { - drawerScrollState.scrollToItem(vm.getCurrentIndexOfChapter(chapter)) + vm.uiFunc.apply { + drawerScrollState.scrollToItem(vm.getCurrentIndexOfChapter(chapter)) + } } } LaunchedEffect(key1 = vm.currentChapterIndex) { @@ -97,7 +101,9 @@ fun ReadingScreen( false -> { scope.launch { if (chapter != null) { - vm.getLocalChaptersByPaging(chapter.bookId) + vm.mainFunc.apply { + vm.getLocalChaptersByPaging(chapter.bookId) + } } modalState.snapTo(ModalBottomSheetValue.Expanded) } @@ -110,22 +116,24 @@ fun ReadingScreen( } } LaunchedEffect(key1 = vm.autoBrightnessMode) { - vm.readBrightness(context) + vm.prefFunc.apply { + vm.readBrightness(context) + } + } LaunchedEffect(key1 = vm.initialized) { if (chapter != null) { scrollState.scrollBy(chapter.progress.toFloat()) } } -// LaunchedEffect(key1 = vm.stateChapter) { -// if (chapter != null) { -// scrollState.scrollBy(chapter.progress.toFloat()) -// } -// } + LaunchedEffect(key1 = true) { - vm.readOrientation(context) - vm.readBrightness(context) - vm.readImmersiveMode(context) + + vm.prefFunc.apply { + vm.readOrientation(context) + vm.readBrightness(context) + vm.readImmersiveMode(context) + } vm.eventFlow.collectLatest { event -> @@ -140,7 +148,9 @@ fun ReadingScreen( } LaunchedEffect(key1 = chapter) { if (chapter != null) { - vm.updateChapterSliderIndex(vm.getCurrentIndexOfChapter(chapter)) + vm.uiFunc.apply { + vm.updateChapterSliderIndex(vm.getCurrentIndexOfChapter(chapter)) + } } } @@ -153,8 +163,10 @@ fun ReadingScreen( modalBottomSheetValue = modalState.targetValue, onRefresh = { if (chapter != null) { - vm.getReadingContentRemotely(chapter = chapter, - source = source) + vm.mainFunc.apply { + vm.getReadingContentRemotely(chapter = chapter, + source = source) + } } }, source = source, @@ -213,7 +225,9 @@ fun ReadingScreen( chapters = vm.stateChapters, currentChapterIndex = vm.currentChapterIndex, onSetting = { - vm.toggleSettingMode(true) + vm.uiFunc.apply { + vm.toggleSettingMode(true) + } }, source = source, onNext = { @@ -249,9 +263,13 @@ fun ReadingScreen( modifier = Modifier.statusBarsPadding(), onReverseIcon = { if (chapter != null) { - vm.reverseChapters() - scope.launch { - vm.getLocalChaptersByPaging(chapter.bookId) + vm.uiFunc.apply { + vm.reverseChapters() + } + vm.mainFunc.apply { + scope.launch { + vm.getLocalChaptersByPaging(chapter.bookId) + } } } }, diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/BrightnessSliderComposable.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/BrightnessSliderComposable.kt index 4bf85865c..58b9e4806 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/BrightnessSliderComposable.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/BrightnessSliderComposable.kt @@ -58,7 +58,11 @@ fun BrightnessSliderComposable( imageVector = Icons.Default.Brightness7, contentDescription = "less brightness") } - OutlinedButton(onClick = { viewModel.toggleAutoBrightness() }, + OutlinedButton(onClick = { + viewModel.prefFunc.apply { + viewModel.toggleAutoBrightness() + } + }, modifier = Modifier .weight(1F) .padding(8.dp), diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/MainBottonSettingComposable.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/MainBottonSettingComposable.kt index bc4bfaf2d..e1574fb03 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/MainBottonSettingComposable.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/MainBottonSettingComposable.kt @@ -59,11 +59,12 @@ fun MainBottomSettingComposable( AppIconButton(imageVector = Icons.Default.Menu, title = "Chapter List Drawer", onClick = { scope.launch { scaffoldState.drawerState.open() } }) - AppIconButton(imageVector = Icons.Default.Settings, - title = "Setting Drawer", - onClick = { onSetting() }) AppIconButton(imageVector = Icons.Default.Headphones, title = "Play Text To Speech", onClick = { onPlay() }) + AppIconButton(imageVector = Icons.Default.Settings, + title = "Setting Drawer", + onClick = { onSetting() }) + } } \ No newline at end of file diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderBackgroundComposable.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderBackgroundComposable.kt index 527736224..bc6fefbf3 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderBackgroundComposable.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/components/ReaderBackgroundComposable.kt @@ -40,7 +40,12 @@ fun ReaderBackgroundComposable(modifier: Modifier = Modifier, viewModel: ReaderS .border(2.dp, MaterialTheme.colors.primary, CircleShape) - .clickable { viewModel.changeBackgroundColor(index) }, + .clickable { + viewModel.prefFunc.apply { + viewModel.changeBackgroundColor(index) + } + + }, contentAlignment = Alignment.Center ) { if (viewModel.backgroundColor == readerScreenBackgroundColors[index].color) { 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 41f33efb0..2bad43210 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 @@ -25,6 +25,7 @@ import org.ireader.presentation.presentation.reusable_composable.AppIconButton fun ReaderSettingComposable(modifier: Modifier = Modifier, viewModel: ReaderScreenViewModel) { val context = LocalContext.current val scrollState = rememberScrollState() + val func = viewModel.prefFunc Column( modifier = modifier .fillMaxSize() @@ -42,17 +43,33 @@ fun ReaderSettingComposable(modifier: Modifier = Modifier, viewModel: ReaderScre Spacer(modifier = Modifier.height(12.dp)) SettingItemToggleComposable(text = "Scroll Mode", value = viewModel.verticalScrolling, - onToggle = { viewModel.toggleScrollMode() }) + onToggle = { + func.apply { + viewModel.toggleScrollMode() + } + }) SettingItemToggleComposable(text = "Orientation", value = viewModel.orientation == Orientation.Landscape, - onToggle = { viewModel.saveOrientation(context) }) + onToggle = { + func.apply { + viewModel.saveOrientation(context) + } + }) SettingItemToggleComposable(text = "AutoScroll", value = viewModel.autoScrollMode, - onToggle = { viewModel.toggleAutoScrollMode() }) + onToggle = { + func.apply { + viewModel.toggleAutoScrollMode() + } + }) SettingItemToggleComposable(text = "Immersive mode", value = viewModel.immersiveMode, - onToggle = { viewModel.toggleImmersiveMode(context) }) + onToggle = { + func.apply { + viewModel.toggleImmersiveMode(context) + } + }) SettingItemComposable(text = "Font Size", value = viewModel.fontSize.toString(), onAdd = { @@ -66,65 +83,94 @@ fun ReaderSettingComposable(modifier: Modifier = Modifier, viewModel: ReaderScre SettingItemComposable(text = "Paragraph Indent", value = viewModel.paragraphsIndent.toString(), onAdd = { - viewModel.saveParagraphIndent(true) + func.apply { + viewModel.saveParagraphIndent(true) + } }, onMinus = { - viewModel.saveParagraphIndent(false) + func.apply { + viewModel.saveParagraphIndent(false) + } }) SettingItemComposable(text = "Paragraph Distance", value = viewModel.distanceBetweenParagraphs.toString(), onAdd = { - viewModel.saveParagraphDistance(true) + func.apply { + viewModel.saveParagraphDistance(true) + } }, onMinus = { - viewModel.saveParagraphDistance(false) - + func.apply { + viewModel.saveParagraphDistance(false) + } }) SettingItemComposable(text = "Line Height", value = viewModel.lineHeight.toString(), onAdd = { - viewModel.saveFontHeight(true) + func.apply { + viewModel.saveFontHeight(true) + } }, onMinus = { - viewModel.saveFontHeight(false) + func.apply { + viewModel.saveFontHeight(false) + } + }) SettingItemComposable(text = "Autoscroll Interval", value = "${viewModel.autoScrollInterval / 1000} second", onAdd = { - viewModel.setAutoScrollIntervalReader(true) + func.apply { + viewModel.setAutoScrollIntervalReader(true) + } }, onMinus = { - viewModel.setAutoScrollIntervalReader(false) + func.apply { + viewModel.setAutoScrollIntervalReader(false) + } }) SettingItemComposable(text = "Autoscroll Offset", value = viewModel.autoScrollOffset.toString(), onAdd = { - viewModel.setAutoScrollOffsetReader(true) + func.apply { + viewModel.setAutoScrollIntervalReader(true) + } }, onMinus = { - viewModel.setAutoScrollOffsetReader(false) + func.apply { + viewModel.setAutoScrollIntervalReader(false) + } + }) SettingItemComposable(text = "ScrollIndicator Padding", value = viewModel.scrollIndicatorPadding.toString(), onAdd = { - viewModel.saveScrollIndicatorPadding(true) + func.apply { + viewModel.saveScrollIndicatorPadding(true) + } }, onMinus = { - viewModel.saveScrollIndicatorPadding(false) + func.apply { + viewModel.saveScrollIndicatorPadding(false) + } }) SettingItemComposable(text = "ScrollIndicator Width", value = viewModel.scrollIndicatorWith.toString(), onAdd = { - viewModel.saveScrollIndicatorWidth(true) + func.apply { + viewModel.saveScrollIndicatorWidth(true) + } }, onMinus = { - viewModel.saveScrollIndicatorWidth(false) + func.apply { + viewModel.saveScrollIndicatorWidth(false) + } }) 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 new file mode 100644 index 000000000..0c933442f --- /dev/null +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderMainFunc.kt @@ -0,0 +1,189 @@ +package org.ireader.presentation.feature_reader.presentation.reader.viewmodel + +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.datetime.Clock +import org.ireader.core.utils.Constants +import org.ireader.core.utils.UiText +import org.ireader.core.utils.currentTimeToLong +import org.ireader.domain.R +import org.ireader.domain.models.entities.Book +import org.ireader.domain.models.entities.Chapter +import org.ireader.domain.models.entities.History +import org.ireader.domain.utils.withIOContext +import tachiyomi.source.Source +import javax.inject.Inject + +interface ReaderMainFunctions { + suspend fun ReaderScreenViewModel.getChapter( + chapterId: Long, + source: Source, + ) + + suspend fun ReaderScreenViewModel.getLocalBookById( + bookId: Long, + chapterId: Long, + source: Source, + ) + + suspend fun ReaderScreenViewModel.insertChapter(chapter: Chapter) + suspend fun ReaderScreenViewModel.insertBook(book: Book) + + fun ReaderScreenViewModel.getReadingContentRemotely(chapter: Chapter, source: Source) + fun ReaderScreenViewModel.updateLastReadTime(chapter: Chapter) + fun ReaderScreenViewModel.getLocalChaptersByPaging(bookId: Long) +} + +class ReaderMainFunctionsImpl @Inject constructor() : ReaderMainFunctions { + override suspend fun ReaderScreenViewModel.getChapter(chapterId: Long, source: Source) { + toggleLoading(true) + toggleLocalLoaded(false) + viewModelScope.launch { + onNextVoice() + val resultChapter = getChapterUseCase.findChapterById( + chapterId = chapterId, + state.book?.id, + ) + if (resultChapter != null) { + + clearError() + toggleLoading(false) + toggleLocalLoaded(true) + setChapter(resultChapter.copy(content = resultChapter.content, + read = true, + readAt = Clock.System.now().toEpochMilliseconds())) + val chapter = state.stateChapter + if ( + chapter != null && + chapter.content.joinToString() + .isBlank() && + !state.isRemoteLoading && + !state.isLoading + ) { + getReadingContentRemotely(chapter = chapter, source = source) + } + updateLastReadTime(resultChapter) + updateChapterSliderIndex(getCurrentIndexOfChapter(resultChapter)) + if (!initialized) { + initialized = true + } + } else { + toggleLoading(false) + toggleLocalLoaded(false) + } + } + } + + override suspend fun ReaderScreenViewModel.getLocalBookById( + bookId: Long, + chapterId: Long, + source: Source, + ) { + viewModelScope.launch { + val book = getBookUseCases.findBookById(id = bookId) + if (book != null) { + setStateChapter(book) + toggleBookLoaded(true) + getLocalChaptersByPaging(bookId) + val last = historyUseCase.findHistoryByBookId(bookId) + if (chapterId != Constants.LAST_CHAPTER && chapterId != Constants.NO_VALUE) { + getChapter(chapterId, source = source) + } else if (last != null) { + getChapter(chapterId = last.chapterId, source = source) + } else { + val chapters = getChapterUseCase.findChaptersByBookId(bookId) + if (chapters.isNotEmpty()) { + getChapter(chapters.first().id, source = source) + } + } + if (stateChapters.isNotEmpty()) { + state.currentChapterIndex = + stateChapters.indexOfFirst { state.stateChapter?.id == it.id } + +// if (stateChapter == null && state.stateChapters.isNotEmpty()) { +// getChapter(state.stateChapters.first().id, source = source) +// } + } + + + } + } + } + + override suspend fun ReaderScreenViewModel.insertChapter(chapter: Chapter) { + withIOContext { + insertUseCases.insertChapter(chapter) + } + } + + override suspend fun ReaderScreenViewModel.insertBook(book: Book) { + insertUseCases.insertBook(book) + } + + override fun ReaderScreenViewModel.getReadingContentRemotely(chapter: Chapter, source: Source) { + clearError() + toggleLocalLoaded(false) + toggleRemoteLoading(true) + toggleLoading(true) + getContentJob?.cancel() + getContentJob = viewModelScope.launch(Dispatchers.IO) { + remoteUseCases.getRemoteReadingContent( + chapter, + source = source, + onSuccess = { content -> + if (content != null) { + insertChapter(content.copy( + dateFetch = Clock.System.now() + .toEpochMilliseconds(), + )) + setChapter(content) + toggleLoading(false) + toggleRemoteLoading(false) + toggleLocalLoaded(true) + toggleRemoteLoaded(true) + clearError() + getChapter(chapter.id, source = source) + + } else { + showSnackBar(UiText.StringResource(R.string.something_is_wrong_with_this_chapter)) + } + }, + onError = { message -> + toggleRemoteLoading(false) + toggleLoading(false) + toggleLocalLoaded(false) + toggleRemoteLoading(false) + if (message != null) { + showSnackBar(message) + } + } + ) + } + } + + override fun ReaderScreenViewModel.updateLastReadTime(chapter: Chapter) { + viewModelScope.launch(Dispatchers.IO) { + insertUseCases.insertChapter( + chapter = chapter.copy(read = true, + readAt = Clock.System.now().toEpochMilliseconds()) + ) + historyUseCase.insertHistory(History( + bookId = chapter.bookId, + chapterId = chapter.id, + readAt = currentTimeToLong())) + } + } + + override fun ReaderScreenViewModel.getLocalChaptersByPaging(bookId: Long) { + getChapterJob?.cancel() + getChapterJob = viewModelScope.launch { + getChapterUseCase.subscribeChaptersByBookId(bookId = bookId, + isAsc = prefState.isAsc, "") + .collect { + stateChapters = it + } + } + } + +} \ No newline at end of file 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 new file mode 100644 index 000000000..3ac818d45 --- /dev/null +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderPrefCodes.kt @@ -0,0 +1,363 @@ +package org.ireader.presentation.feature_reader.presentation.reader.viewmodel + +import android.content.Context +import android.content.pm.ActivityInfo +import android.view.WindowManager +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import org.ireader.core.utils.findComponentActivity +import org.ireader.core_ui.theme.OrientationMode +import org.ireader.core_ui.theme.fonts +import org.ireader.core_ui.theme.readerScreenBackgroundColors +import javax.inject.Inject + +interface ReaderPrefFunctions { + fun ReaderScreenViewModel.readPreferences() + fun ReaderScreenViewModel.toggleReaderMode(enable: Boolean? = null) + fun ReaderScreenViewModel.saveBrightness(brightness: Float, context: Context) + fun ReaderScreenViewModel.saveFont(index: Int) + fun ReaderScreenViewModel.saveFontSize(event: FontSizeEvent) + fun ReaderScreenViewModel.saveParagraphIndent(isIncreased: Boolean) + fun ReaderScreenViewModel.saveScrollIndicatorWidth(increase: Boolean) + fun ReaderScreenViewModel.saveScrollIndicatorPadding(increase: Boolean) + fun ReaderScreenViewModel.saveFontHeight(isIncreased: Boolean) + fun ReaderScreenViewModel.saveOrientation(context: Context) + fun ReaderScreenViewModel.readImmersiveMode(context: Context) + fun ReaderScreenViewModel.toggleImmersiveMode(context: Context) + fun ReaderScreenViewModel.toggleAutoScrollMode() + fun ReaderScreenViewModel.toggleScrollMode() + fun ReaderScreenViewModel.saveParagraphDistance(isIncreased: Boolean) + fun ReaderScreenViewModel.readScrollIndicatorPadding(): Int + fun ReaderScreenViewModel.readScrollIndicatorWidth(): Int + fun ReaderScreenViewModel.readBrightness(context: Context) + fun ReaderScreenViewModel.readBackgroundColor(): Color + fun ReaderScreenViewModel.readTextColor(): Color + fun ReaderScreenViewModel.readOrientation(context: Context) + fun ReaderScreenViewModel.changeBackgroundColor(colorIndex: Int) + fun ReaderScreenViewModel.setReaderBackgroundColor(color: Color) + fun ReaderScreenViewModel.setReaderTextColor(color: Color) + fun ReaderScreenViewModel.setAutoScrollIntervalReader(increase: Boolean) + fun ReaderScreenViewModel.setAutoScrollOffsetReader(increase: Boolean) + fun ReaderScreenViewModel.toggleAutoBrightness() + fun ReaderScreenViewModel.toggleRemoteLoaded(loaded: Boolean) + fun ReaderScreenViewModel.showSystemBars(context: Context) + fun ReaderScreenViewModel.hideSystemBars(context: Context) +} + +class ReaderPrefFunctionsImpl @Inject constructor() : ReaderPrefFunctions { + override fun ReaderScreenViewModel.readPreferences() { + font = readerUseCases.selectedFontStateUseCase.read() + readerUseCases.selectedFontStateUseCase.save(0) + + + this.fontSize = readerUseCases.fontSizeStateUseCase.read() + + readBackgroundColor() + readTextColor() + + lineHeight = readerUseCases.fontHeightUseCase.read() + distanceBetweenParagraphs = readerUseCases.paragraphDistanceUseCase.read() + paragraphsIndent = readerUseCases.paragraphIndentUseCase.read() + verticalScrolling = readerUseCases.scrollModeUseCase.read() + scrollIndicatorPadding = readerUseCases.scrollIndicatorUseCase.readPadding() + scrollIndicatorWith = readerUseCases.scrollIndicatorUseCase.readWidth() + autoScrollInterval = readerUseCases.autoScrollMode.readInterval() + autoScrollOffset = readerUseCases.autoScrollMode.readOffset() + autoBrightnessMode = readerUseCases.brightnessStateUseCase.readAutoBrightness() + speechSpeed = speechPrefUseCases.readRate() + pitch = speechPrefUseCases.readPitch() + currentVoice = speechPrefUseCases.readVoice() + currentLanguage = speechPrefUseCases.readLanguage() + } + + override fun ReaderScreenViewModel.toggleReaderMode(enable: Boolean?) { + isReaderModeEnable = enable ?: !state.isReaderModeEnable + isMainBottomModeEnable = true + isSettingModeEnable = false + } + + override fun ReaderScreenViewModel.saveBrightness(brightness: Float, context: Context) { + val activity = context.findComponentActivity() + if (activity != null) { + val window = activity.window + this.brightness = brightness + val layoutParams: WindowManager.LayoutParams = window.attributes + layoutParams.screenBrightness = brightness + window.attributes = layoutParams + readerUseCases.brightnessStateUseCase.saveBrightness(brightness) + } + } + + override fun ReaderScreenViewModel.saveFont(index: Int) { + this.font = fonts[index] + readerUseCases.selectedFontStateUseCase.save(index) + + } + + override fun ReaderScreenViewModel.saveFontSize(event: FontSizeEvent) { + if (event == FontSizeEvent.Increase) { + this.fontSize = this.fontSize + 1 + readerUseCases.fontSizeStateUseCase.save(prefState.fontSize) + } else { + if (prefState.fontSize > 0) { + this.fontSize = this.fontSize - 1 + readerUseCases.fontSizeStateUseCase.save(prefState.fontSize) + } + } + } + + override fun ReaderScreenViewModel.saveParagraphIndent(isIncreased: Boolean) { + val paragraphsIndent = prefState.paragraphsIndent + if (isIncreased) { + readerUseCases.paragraphIndentUseCase.save(paragraphsIndent + 1) + this.paragraphsIndent = paragraphsIndent + 1 + + + } else if (paragraphsIndent > 1 && !isIncreased) { + readerUseCases.paragraphIndentUseCase.save(paragraphsIndent - 1) + this.paragraphsIndent = paragraphsIndent - 1 + } + } + + override fun ReaderScreenViewModel.saveScrollIndicatorWidth(increase: Boolean) { + if (increase) { + scrollIndicatorWith += 1 + readerUseCases.scrollIndicatorUseCase.saveWidth(scrollIndicatorWith + 1) + } else { + scrollIndicatorWith -= 1 + readerUseCases.scrollIndicatorUseCase.saveWidth(scrollIndicatorWith - 1) + } + } + + override fun ReaderScreenViewModel.saveScrollIndicatorPadding(increase: Boolean) { + if (increase) { + scrollIndicatorPadding += 1 + + readerUseCases.scrollIndicatorUseCase.savePadding(scrollIndicatorPadding + 1) + } else { + scrollIndicatorPadding -= 1 + readerUseCases.scrollIndicatorUseCase.savePadding(scrollIndicatorPadding - 1) + } + } + + override fun ReaderScreenViewModel.saveFontHeight(isIncreased: Boolean) { + val currentFontHeight = prefState.lineHeight + if (isIncreased) { + readerUseCases.fontHeightUseCase.save(currentFontHeight + 1) + lineHeight = currentFontHeight + 1 + + } else if (currentFontHeight > 20 && !isIncreased) { + readerUseCases.fontHeightUseCase.save(currentFontHeight - 1) + lineHeight = currentFontHeight - 1 + + } + } + + override fun ReaderScreenViewModel.saveOrientation(context: Context) { + val activity = context.findComponentActivity() + if (activity != null) { + when (prefState.orientation) { + is Orientation.Landscape -> { + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + orientation = Orientation.Portrait + readerUseCases.orientationUseCase.save(OrientationMode.Portrait) + + } + is Orientation.Portrait -> { + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + orientation = Orientation.Landscape + readerUseCases.orientationUseCase.save(OrientationMode.Landscape) + } + } + } + } + + override fun ReaderScreenViewModel.readImmersiveMode(context: Context) { + immersiveMode = readerUseCases.immersiveModeUseCase.read() + if (immersiveMode) { + hideSystemBars(context = context) + } else { + showSystemBars(context) + } + } + + override fun ReaderScreenViewModel.toggleImmersiveMode(context: Context) { + immersiveMode = !immersiveMode + readerUseCases.immersiveModeUseCase.save(immersiveMode) + if (immersiveMode) { + hideSystemBars(context = context) + } else { + showSystemBars(context) + } + } + + override fun ReaderScreenViewModel.toggleAutoScrollMode() { + autoScrollMode = !autoScrollMode + } + + override fun ReaderScreenViewModel.toggleScrollMode() { + verticalScrolling = !verticalScrolling + readerUseCases.scrollModeUseCase.save(verticalScrolling) + + } + + override fun ReaderScreenViewModel.saveParagraphDistance(isIncreased: Boolean) { + val currentDistance = prefState.distanceBetweenParagraphs + if (isIncreased) { + readerUseCases.paragraphDistanceUseCase.save(currentDistance + 1) + distanceBetweenParagraphs = currentDistance + 1 + + } else if (currentDistance > 1 && !isIncreased) { + readerUseCases.paragraphDistanceUseCase.save(currentDistance - 1) + distanceBetweenParagraphs = currentDistance - 1 + + } + } + + override fun ReaderScreenViewModel.readScrollIndicatorPadding(): Int { + return readerUseCases.scrollIndicatorUseCase.readPadding() + } + + override fun ReaderScreenViewModel.readScrollIndicatorWidth(): Int { + return readerUseCases.scrollIndicatorUseCase.readWidth() + } + + override fun ReaderScreenViewModel.readBrightness(context: Context) { + val brightness = readerUseCases.brightnessStateUseCase.readBrightness() + val activity = context.findComponentActivity() + if (activity != null) { + val window = activity.window + if (!autoBrightnessMode) { + val layoutParams: WindowManager.LayoutParams = window.attributes + layoutParams.screenBrightness = brightness + window.attributes = layoutParams + this.brightness = brightness + } else { + val layoutParams: WindowManager.LayoutParams = window.attributes + showSystemBars(context = context) + layoutParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + window.attributes = layoutParams + } + } + } + + override fun ReaderScreenViewModel.readBackgroundColor(): Color { + val color = Color(readerUseCases.backgroundColorUseCase.read()) + this.backgroundColor = color + return color + } + + override fun ReaderScreenViewModel.readTextColor(): Color { + val textColor = Color(readerUseCases.textColorUseCase.read()) + this.textColor = textColor + return textColor + } + + override fun ReaderScreenViewModel.readOrientation(context: Context) { + val activity = context.findComponentActivity() + if (activity != null) { + when (readerUseCases.orientationUseCase.read()) { + OrientationMode.Portrait -> { + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + orientation = Orientation.Portrait + + } + OrientationMode.Landscape -> { + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + orientation = Orientation.Landscape + } + } + } + } + + override fun ReaderScreenViewModel.changeBackgroundColor(colorIndex: Int) { + val bgColor = readerScreenBackgroundColors[colorIndex].color + val textColor = readerScreenBackgroundColors[colorIndex].onTextColor + backgroundColor = bgColor + this.textColor = textColor + setReaderBackgroundColor(bgColor) + setReaderTextColor(textColor) + } + + override fun ReaderScreenViewModel.setReaderBackgroundColor(color: Color) { + readerUseCases.backgroundColorUseCase.save(color.toArgb()) + } + + override fun ReaderScreenViewModel.setReaderTextColor(color: Color) { + readerUseCases.textColorUseCase.save(color.toArgb()) + } + + override fun ReaderScreenViewModel.setAutoScrollIntervalReader(increase: Boolean) { + if (increase) { + autoScrollInterval += 500 + readerUseCases.autoScrollMode.saveInterval(autoScrollInterval + 500) + } else { + autoScrollInterval -= 500 + readerUseCases.autoScrollMode.saveInterval(autoScrollInterval - 500) + } + } + + override fun ReaderScreenViewModel.setAutoScrollOffsetReader(increase: Boolean) { + if (increase) { + autoScrollOffset += 50 + readerUseCases.autoScrollMode.saveOffset(autoScrollOffset + 50) + } else { + autoScrollOffset -= 50 + readerUseCases.autoScrollMode.saveOffset(autoScrollOffset - 50) + } + } + + override fun ReaderScreenViewModel.toggleAutoBrightness() { + if (autoBrightnessMode) { + autoBrightnessMode = false + readerUseCases.brightnessStateUseCase.saveAutoBrightness(false) + } else { + autoBrightnessMode = true + readerUseCases.brightnessStateUseCase.saveAutoBrightness(true) + } + + } + + + override fun ReaderScreenViewModel.toggleRemoteLoaded(loaded: Boolean) { + state.isRemoteLoaded = loaded + } + + override fun ReaderScreenViewModel.showSystemBars(context: Context) { + val activity = context.findComponentActivity() + if (activity != null) { + val window = activity.window + val windowInsetsController = + ViewCompat.getWindowInsetsController(window.decorView) ?: return + // Configure the behavior of the hidden system bars + windowInsetsController.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + + + // Hide both the status bar and the navigation bar + //windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) + windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) + } + } + + override fun ReaderScreenViewModel.hideSystemBars(context: Context) { + val activity = context.findComponentActivity() + if (activity != null && immersiveMode) { + val window = activity.window + val windowInsetsController = + ViewCompat.getWindowInsetsController(window.decorView) ?: return + // Configure the behavior of the hidden system bars + windowInsetsController.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + // Hide both the status bar and the navigation bar + windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) + + // windowInsetsController.hide(WindowInsetsCompat.Type.navigationBars()) + } + } + +} \ No newline at end of file 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 51d54db6c..1917ef204 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 @@ -1,6 +1,7 @@ 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.mutableStateOf @@ -55,6 +56,7 @@ open class ReaderScreenStateImpl @Inject constructor() : ReaderScreenState { override var book: Book? by mutableStateOf(null) + } @@ -69,6 +71,7 @@ interface TextReaderScreenState { var currentLanguage: String var pitch: Float var speechSpeed: Float + var speaker: TextToSpeech? } class TextReaderScreenStateImpl @Inject constructor() : TextReaderScreenState { @@ -83,6 +86,7 @@ class TextReaderScreenStateImpl @Inject constructor() : TextReaderScreenState { override var autoNextChapter by mutableStateOf(false) override var pitch by mutableStateOf(.8f) override var speechSpeed by mutableStateOf(.8f) + override var speaker by mutableStateOf(null) } @@ -104,6 +108,7 @@ interface ReaderScreenState { var stateChapters: List var stateChapter: Chapter? var book: Book? + } 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 fce9c13de..985dfdd60 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 @@ -1,39 +1,18 @@ package org.ireader.presentation.feature_reader.presentation.reader.viewmodel -import android.annotation.SuppressLint import android.content.Context -import android.content.pm.ActivityInfo -import android.speech.tts.TextToSpeech -import android.speech.tts.UtteranceProgressListener -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 +import android.support.v4.media.session.MediaSessionCompat import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import kotlinx.datetime.Clock -import org.ireader.core.utils.* -import org.ireader.core_ui.theme.OrientationMode -import org.ireader.core_ui.theme.fonts -import org.ireader.core_ui.theme.readerScreenBackgroundColors +import org.ireader.core.utils.UiText 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 +import org.ireader.domain.feature_services.notification.DefaultNotificationHelper +import org.ireader.domain.feature_services.notification.NotificationStates import org.ireader.domain.ui.NavigationArgs import org.ireader.domain.use_cases.history.HistoryUseCase import org.ireader.domain.use_cases.local.LocalGetChapterUseCase @@ -41,32 +20,43 @@ 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 javax.inject.Inject @HiltViewModel class ReaderScreenViewModel @Inject constructor( - private val getBookUseCases: org.ireader.domain.use_cases.local.LocalGetBookUseCases, - private val getChapterUseCase: LocalGetChapterUseCase, - private val remoteUseCases: RemoteUseCases, - private val insertUseCases: LocalInsertUseCases, - private val historyUseCase: HistoryUseCase, + val getBookUseCases: org.ireader.domain.use_cases.local.LocalGetBookUseCases, + val getChapterUseCase: LocalGetChapterUseCase, + val remoteUseCases: RemoteUseCases, + val insertUseCases: LocalInsertUseCases, + val historyUseCase: HistoryUseCase, private val catalogStore: CatalogStore, - private val readerUseCases: ReaderPrefUseCases, - private val prefState: ReaderScreenPreferencesStateImpl, - private val state: ReaderScreenStateImpl, - private val speakerState: TextReaderScreenStateImpl, + val readerUseCases: ReaderPrefUseCases, + val prefState: ReaderScreenPreferencesStateImpl, + val state: ReaderScreenStateImpl, + val textReaderManager: TextReaderManager, + val speakerState: TextReaderScreenStateImpl, + val prefFunc: ReaderPrefFunctionsImpl, + val uiFunc: ReaderUiFunctionsImpl, + val mainFunc: ReaderMainFunctionsImpl, val speechPrefUseCases: TextReaderPrefUseCase, - savedStateHandle: SavedStateHandle, -) : BaseViewModel(), ReaderScreenPreferencesState by prefState, ReaderScreenState by state, + val savedStateHandle: SavedStateHandle, + val defaultNotificationHelper: DefaultNotificationHelper, + val notificationStates: NotificationStates, +) : BaseViewModel(), + ReaderScreenPreferencesState by prefState, + ReaderScreenState by state, + ReaderPrefFunctions by prefFunc, + ReaderUiFunctions by uiFunc, + ReaderMainFunctions by mainFunc, TextReaderScreenState by speakerState { + fun mediaSessionCompat(context: Context) = + MediaSessionCompat(context, "mediaPlayer", null, null) init { + val sourceId = savedStateHandle.get(NavigationArgs.sourceId.name) val chapterId = savedStateHandle.get(NavigationArgs.chapterId.name) val bookId = savedStateHandle.get(NavigationArgs.bookId.name) @@ -92,30 +82,6 @@ class ReaderScreenViewModel @Inject constructor( } } - private fun readPreferences() { - font = readerUseCases.selectedFontStateUseCase.read() - readerUseCases.selectedFontStateUseCase.save(0) - - - this.fontSize = readerUseCases.fontSizeStateUseCase.read() - - readBackgroundColor() - readTextColor() - - lineHeight = readerUseCases.fontHeightUseCase.read() - distanceBetweenParagraphs = readerUseCases.paragraphDistanceUseCase.read() - paragraphsIndent = readerUseCases.paragraphIndentUseCase.read() - verticalScrolling = readerUseCases.scrollModeUseCase.read() - scrollIndicatorPadding = readerUseCases.scrollIndicatorUseCase.readPadding() - scrollIndicatorWith = readerUseCases.scrollIndicatorUseCase.readWidth() - 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) { when (event) { @@ -135,506 +101,13 @@ class ReaderScreenViewModel @Inject constructor( } } - - suspend fun getChapter( - chapterId: Long, - source: Source, - ) { - toggleLoading(true) - toggleLocalLoaded(false) - viewModelScope.launch { - onNextVoice() - val resultChapter = getChapterUseCase.findChapterById( - chapterId = chapterId, - state.book?.id, - ) - if (resultChapter != null) { - - clearError() - this@ReaderScreenViewModel.toggleLoading(false) - toggleLocalLoaded(true) - setChapter(resultChapter.copy(content = resultChapter.content, - read = true, - readAt = Clock.System.now().toEpochMilliseconds())) - val chapter = state.stateChapter - if ( - chapter != null && - chapter.content.joinToString() - .isBlank() && - !state.isRemoteLoading && - !state.isLoading - ) { - getReadingContentRemotely(chapter = chapter, source = source) - } - updateLastReadTime(resultChapter) - updateChapterSliderIndex(getCurrentIndexOfChapter(resultChapter)) - if (!initialized) { - initialized = true - } - } else { - toggleLoading(false) - toggleLocalLoaded(false) - } - } - - } - - var getContentJob: Job? = null - fun getReadingContentRemotely(chapter: Chapter, source: Source) { - clearError() - toggleLocalLoaded(false) - toggleRemoteLoading(true) - toggleLoading(true) - getContentJob?.cancel() - getContentJob = viewModelScope.launch(Dispatchers.IO) { - remoteUseCases.getRemoteReadingContent( - chapter, - source = source, - onSuccess = { content -> - if (content != null) { - insertChapter(content.copy( - dateFetch = Clock.System.now() - .toEpochMilliseconds(), - )) - setChapter(content) - toggleLoading(false) - toggleRemoteLoading(false) - toggleLocalLoaded(true) - toggleRemoteLoaded(true) - clearError() - getChapter(chapter.id, source = source) - - } else { - showSnackBar(UiText.StringResource(R.string.something_is_wrong_with_this_chapter)) - } - }, - onError = { message -> - toggleRemoteLoading(false) - toggleLoading(false) - toggleLocalLoaded(false) - toggleRemoteLoading(false) - if (message != null) { - showSnackBar(message) - } - } - ) - } - - - } - - - private suspend fun getLocalBookById(bookId: Long, chapterId: Long, source: Source) { - viewModelScope.launch { - val book = getBookUseCases.findBookById(id = bookId) - if (book != null) { - setStateChapter(book) - toggleBookLoaded(true) - getLocalChaptersByPaging(bookId) - val last = historyUseCase.findHistoryByBookId(bookId) - if (chapterId != Constants.LAST_CHAPTER && chapterId != Constants.NO_VALUE) { - getChapter(chapterId, source = source) - } else if (last != null) { - getChapter(chapterId = last.chapterId, source = source) - } else { - val chapters = getChapterUseCase.findChaptersByBookId(bookId) - if (chapters.isNotEmpty()) { - getChapter(chapters.first().id, source = source) - } - } - if (stateChapters.isNotEmpty()) { - state.currentChapterIndex = - stateChapters.indexOfFirst { state.stateChapter?.id == it.id } - -// if (stateChapter == null && state.stateChapters.isNotEmpty()) { -// getChapter(state.stateChapters.first().id, source = source) -// } - } - - - } - } - - } - - private fun updateLastReadTime(chapter: Chapter) { - viewModelScope.launch(Dispatchers.IO) { - insertUseCases.insertChapter( - chapter = chapter.copy(read = true, - readAt = Clock.System.now().toEpochMilliseconds()) - ) - historyUseCase.insertHistory(History( - bookId = chapter.bookId, - chapterId = chapter.id, - readAt = currentTimeToLong())) - } - - } - fun reverseChapters() { - toggleIsAsc(!prefState.isAsc) - } var getChapterJob: Job? = null - fun getLocalChaptersByPaging(bookId: Long) { - getChapterJob?.cancel() - getChapterJob = viewModelScope.launch { - getChapterUseCase.subscribeChaptersByBookId(bookId = bookId, - isAsc = prefState.isAsc, "") - .collect { - stateChapters = it - } - } - - } - - - fun readBrightness(context: Context) { - val brightness = readerUseCases.brightnessStateUseCase.readBrightness() - val activity = context.findComponentActivity() - if (activity != null) { - val window = activity.window - if (!autoBrightnessMode) { - val layoutParams: WindowManager.LayoutParams = window.attributes - layoutParams.screenBrightness = brightness - window.attributes = layoutParams - this.brightness = brightness - } else { - val layoutParams: WindowManager.LayoutParams = window.attributes - showSystemBars(context = context) - layoutParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - window.attributes = layoutParams - } - } - } - - - fun readBackgroundColor(): Color { - val color = Color(readerUseCases.backgroundColorUseCase.read()) - this.backgroundColor = color - return color - } - - fun readTextColor(): Color { - val textColor = Color(readerUseCases.textColorUseCase.read()) - this.textColor = textColor - return textColor - } - - - @SuppressLint("SourceLockedOrientationActivity") - fun readOrientation(context: Context) { - val activity = context.findComponentActivity() - if (activity != null) { - when (readerUseCases.orientationUseCase.read()) { - OrientationMode.Portrait -> { - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - orientation = Orientation.Portrait - - } - OrientationMode.Landscape -> { - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - orientation = Orientation.Landscape - } - } - } - } - - fun changeBackgroundColor(colorIndex: Int) { - val bgColor = readerScreenBackgroundColors[colorIndex].color - val textColor = readerScreenBackgroundColors[colorIndex].onTextColor - backgroundColor = bgColor - this.textColor = textColor - setReaderBackgroundColor(bgColor) - setReaderTextColor(textColor) - Timber.e(backgroundColor.toArgb().toString()) - Timber.e(this.textColor.toArgb().toString()) - } - - fun setReaderBackgroundColor(color: Color) { - readerUseCases.backgroundColorUseCase.save(color.toArgb()) - } - - fun setReaderTextColor(color: Color) { - readerUseCases.textColorUseCase.save(color.toArgb()) - } - - fun setAutoScrollIntervalReader(increase: Boolean) { - if (increase) { - autoScrollInterval += 500 - readerUseCases.autoScrollMode.saveInterval(autoScrollInterval + 500) - } else { - autoScrollInterval -= 500 - readerUseCases.autoScrollMode.saveInterval(autoScrollInterval - 500) - } - - } - - fun setAutoScrollOffsetReader(increase: Boolean) { - if (increase) { - autoScrollOffset += 50 - readerUseCases.autoScrollMode.saveOffset(autoScrollOffset + 50) - } else { - autoScrollOffset -= 50 - readerUseCases.autoScrollMode.saveOffset(autoScrollOffset - 50) - } - - - } - - fun toggleAutoBrightness() { - if (autoBrightnessMode) { - autoBrightnessMode = false - readerUseCases.brightnessStateUseCase.saveAutoBrightness(false) - } else { - autoBrightnessMode = true - readerUseCases.brightnessStateUseCase.saveAutoBrightness(true) - } - - } - - @SuppressLint("SourceLockedOrientationActivity") - fun saveOrientation(context: Context) { - val activity = context.findComponentActivity() - if (activity != null) { - when (prefState.orientation) { - is Orientation.Landscape -> { - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - orientation = Orientation.Portrait - readerUseCases.orientationUseCase.save(OrientationMode.Portrait) - - } - is Orientation.Portrait -> { - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - orientation = Orientation.Landscape - readerUseCases.orientationUseCase.save(OrientationMode.Landscape) - } - } - } - } - - fun saveFontHeight(isIncreased: Boolean) { - val currentFontHeight = prefState.lineHeight - if (isIncreased) { - readerUseCases.fontHeightUseCase.save(currentFontHeight + 1) - lineHeight = currentFontHeight + 1 - - } else if (currentFontHeight > 20 && !isIncreased) { - readerUseCases.fontHeightUseCase.save(currentFontHeight - 1) - lineHeight = currentFontHeight - 1 - - } - } - - fun saveScrollIndicatorPadding(increase: Boolean) { - if (increase) { - scrollIndicatorPadding += 1 - - readerUseCases.scrollIndicatorUseCase.savePadding(scrollIndicatorPadding + 1) - } else { - scrollIndicatorPadding -= 1 - readerUseCases.scrollIndicatorUseCase.savePadding(scrollIndicatorPadding - 1) - } - - } - - fun saveScrollIndicatorWidth(increase: Boolean) { - if (increase) { - scrollIndicatorWith += 1 - readerUseCases.scrollIndicatorUseCase.saveWidth(scrollIndicatorWith + 1) - } else { - scrollIndicatorWith -= 1 - readerUseCases.scrollIndicatorUseCase.saveWidth(scrollIndicatorWith - 1) - } - } - - fun readScrollIndicatorWidth(): Int { - return readerUseCases.scrollIndicatorUseCase.readWidth() - } - - - fun readScrollIndicatorPadding(): Int { - return readerUseCases.scrollIndicatorUseCase.readPadding() - } - - - fun saveParagraphDistance(isIncreased: Boolean) { - val currentDistance = prefState.distanceBetweenParagraphs - if (isIncreased) { - readerUseCases.paragraphDistanceUseCase.save(currentDistance + 1) - distanceBetweenParagraphs = currentDistance + 1 - - } else if (currentDistance > 1 && !isIncreased) { - readerUseCases.paragraphDistanceUseCase.save(currentDistance - 1) - distanceBetweenParagraphs = currentDistance - 1 - - } - } - - fun toggleScrollMode() { - verticalScrolling = !verticalScrolling - readerUseCases.scrollModeUseCase.save(verticalScrolling) - - } - - fun toggleAutoScrollMode() { - autoScrollMode = !autoScrollMode - } - fun toggleImmersiveMode(context: Context) { - immersiveMode = !immersiveMode - readerUseCases.immersiveModeUseCase.save(immersiveMode) - if (immersiveMode) { - hideSystemBars(context = context) - } else { - showSystemBars(context) - } - } - - fun readImmersiveMode(context: Context) { - immersiveMode = readerUseCases.immersiveModeUseCase.read() - if (immersiveMode) { - hideSystemBars(context = context) - } else { - showSystemBars(context) - } - } - - fun saveParagraphIndent(isIncreased: Boolean) { - val paragraphsIndent = prefState.paragraphsIndent - if (isIncreased) { - readerUseCases.paragraphIndentUseCase.save(paragraphsIndent + 1) - this.paragraphsIndent = paragraphsIndent + 1 - - - } else if (paragraphsIndent > 1 && !isIncreased) { - readerUseCases.paragraphIndentUseCase.save(paragraphsIndent - 1) - this.paragraphsIndent = paragraphsIndent - 1 - } - } - - private fun saveFontSize(event: FontSizeEvent) { - if (event == FontSizeEvent.Increase) { - this.fontSize = this.fontSize + 1 - readerUseCases.fontSizeStateUseCase.save(prefState.fontSize) - } else { - if (prefState.fontSize > 0) { - this.fontSize = this.fontSize - 1 - readerUseCases.fontSizeStateUseCase.save(prefState.fontSize) - } - } - } - - private fun saveFont(index: Int) { - this.font = fonts[index] - readerUseCases.selectedFontStateUseCase.save(index) - - } - - private fun saveBrightness(brightness: Float, context: Context) { - val activity = context.findComponentActivity() - if (activity != null) { - val window = activity.window - this.brightness = brightness - val layoutParams: WindowManager.LayoutParams = window.attributes - layoutParams.screenBrightness = brightness - window.attributes = layoutParams - readerUseCases.brightnessStateUseCase.saveBrightness(brightness) - } - } - - - /** - * need a index, there is no need to confuse the index because the list reversed - */ - fun updateChapterSliderIndex(index: Int) { - currentChapterIndex = index - } - - /** - * get the index pf chapter based on the reversed state - */ - fun getCurrentIndexOfChapter(chapter: Chapter): Int { - - val selectedChapter = - state.stateChapters.indexOfFirst { it.id == chapter.id && it.title == chapter.title } - return if (selectedChapter != -1) selectedChapter else 0 - } - - private fun getCurrentIndex(): Int { - return if (state.currentChapterIndex < 0) { - 0 - } else if (state.currentChapterIndex > (state.stateChapters.lastIndex)) { - state.stateChapters.lastIndex - } else if (state.currentChapterIndex == -1) { - 0 - } else { - state.currentChapterIndex - } - } - - fun getCurrentChapterByIndex(): Chapter { - return try { - state.stateChapters[getCurrentIndex()] - } catch (e: Exception) { - state.stateChapters[0] - } - } - - fun hideSystemBars(context: Context) { - val activity = context.findComponentActivity() - if (activity != null && immersiveMode) { - val window = activity.window - val windowInsetsController = - ViewCompat.getWindowInsetsController(window.decorView) ?: return - // Configure the behavior of the hidden system bars - windowInsetsController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - // Hide both the status bar and the navigation bar - windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) - - // windowInsetsController.hide(WindowInsetsCompat.Type.navigationBars()) - } - } - - fun showSystemBars(context: Context) { - val activity = context.findComponentActivity() - if (activity != null) { - val window = activity.window - val windowInsetsController = - ViewCompat.getWindowInsetsController(window.decorView) ?: return - // Configure the behavior of the hidden system bars - windowInsetsController.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - // Hide both the status bar and the navigation bar - //windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) - windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) - } - } - - fun restoreSetting(context: Context, scrollState: LazyListState) { - val activity = context.findComponentActivity() - if (activity != null) { - val window = activity.window - val layoutParams: WindowManager.LayoutParams = window.attributes - showSystemBars(context = context) - layoutParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE - activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - window.attributes = layoutParams - stateChapter?.let { chapter -> - activity.lifecycleScope.launch { - insertChapter(chapter.copy(progress = scrollState.firstVisibleItemScrollOffset)) - } - } - } - - - } - override fun onDestroy() { enable = false @@ -642,9 +115,9 @@ class ReaderScreenViewModel @Inject constructor( getContentJob?.cancel() speaker?.shutdown() super.onDestroy() + } - var speaker: TextToSpeech? = null fun onNextVoice() { speaker?.stop() @@ -652,218 +125,5 @@ class ReaderScreenViewModel @Inject constructor( 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.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], - TextToSpeech.QUEUE_FLUSH, - null, - currentReadingParagraph.toString()) - - ttl.setOnUtteranceProgressListener(object : - UtteranceProgressListener() { - override fun onStop(utteranceId: String?, interrupted: Boolean) { - super.onStop(utteranceId, interrupted) - isPlaying = false - } - - override fun onStart(p0: String?) { - // showTextReaderNotification(context) - isPlaying = true - } - - override fun onDone(p0: String?) { - 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?) { - Timber.e("onError....") - isPlaying = false - } - - - override fun onBeginSynthesis( - utteranceId: String?, - sampleRateInHz: Int, - audioFormat: Int, - channelCount: Int, - ) { - super.onBeginSynthesis(utteranceId, - sampleRateInHz, - audioFormat, - channelCount) - Timber.e(utteranceId) - } - }) - - } catch (e: Exception) { - Timber.e(e.localizedMessage) - } - - - } - } - - - } else { - context.toast("Text-to-Speech Not Available") - } - } - } - - - private fun toggleReaderMode(enable: Boolean? = null) { - isReaderModeEnable = enable ?: !state.isReaderModeEnable - isMainBottomModeEnable = true - isSettingModeEnable = false - } - - fun toggleSettingMode(enable: Boolean, returnToMain: Boolean? = null) { - if (returnToMain == null) { - isSettingModeEnable = enable - isMainBottomModeEnable = false - - } else { - isSettingModeEnable = false - isMainBottomModeEnable = true - } - } - - suspend fun insertBook(book: Book) { - insertUseCases.insertBook(book) - } - - private suspend fun insertChapter(chapter: Chapter) { - withIOContext { - insertUseCases.insertChapter(chapter) - } - } - - - private fun toggleIsAsc(isAsc: Boolean) { - this.isAsc = isAsc - } - - private fun toggleIsChaptersReversed(value: Boolean) { - this.isChaptersReversed = value - } - - - /********************************************************/ - private fun setChapters(chapters: List) { - state.stateChapters = chapters - } - - private fun setChapter(chapter: Chapter) { - state.stateChapter = chapter - } - - /********************************************************/ - private fun toggleBookLoaded(loaded: Boolean) { - state.isBookLoaded = loaded - } - private fun toggleLocalLoaded(loaded: Boolean) { - state.isLocalLoaded = loaded - } - - private fun toggleRemoteLoaded(loaded: Boolean) { - state.isRemoteLoaded = loaded - } - - private fun toggleLoading(isLoading: Boolean) { - state.isLoading = isLoading - } - - private fun toggleRemoteLoading(isLoading: Boolean) { - state.isRemoteLoading = isLoading - } - -// private fun toggleLoading(isLoading: Boolean) { -// state = state.copy(isLoading = isLoading) -// } - - private fun setStateChapter(book: Book) { - state.book = book - } - - private fun clearError() { - state.error = UiText.StringResource(R.string.no_error) - } - - fun saveScrollState(currentProgress: Int? = 0) { - val ch = state.stateChapter - viewModelScope.launch(Dispatchers.IO) { - if (ch != null) { - insertUseCases.insertChapter(ch.copy(progress = currentProgress - ?: ch.progress)) - } - } - } } 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 new file mode 100644 index 000000000..61dd75b59 --- /dev/null +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/ReaderUiFunc.kt @@ -0,0 +1,133 @@ +package org.ireader.presentation.feature_reader.presentation.reader.viewmodel + +import android.content.Context +import android.content.pm.ActivityInfo +import android.view.WindowManager +import androidx.compose.foundation.lazy.LazyListState +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import org.ireader.core.utils.UiText +import org.ireader.core.utils.findComponentActivity +import org.ireader.domain.R +import org.ireader.domain.models.entities.Book +import org.ireader.domain.models.entities.Chapter +import javax.inject.Inject + +interface ReaderUiFunctions { + fun ReaderScreenViewModel.toggleLocalLoaded(loaded: Boolean) + fun ReaderScreenViewModel.toggleBookLoaded(loaded: Boolean) + fun ReaderScreenViewModel.setChapter(chapter: Chapter) + fun ReaderScreenViewModel.toggleIsAsc(isAsc: Boolean) + fun ReaderScreenViewModel.clearError() + fun ReaderScreenViewModel.setStateChapter(book: Book) + fun ReaderScreenViewModel.toggleRemoteLoading(isLoading: Boolean) + fun ReaderScreenViewModel.toggleLoading(isLoading: Boolean) + fun ReaderScreenViewModel.restoreSetting(context: Context, scrollState: LazyListState) + fun ReaderScreenViewModel.toggleSettingMode(enable: Boolean, returnToMain: Boolean? = null) + fun ReaderScreenViewModel.getCurrentIndexOfChapter(chapter: Chapter): Int + fun ReaderScreenViewModel.updateChapterSliderIndex(index: Int) + fun ReaderScreenViewModel.getCurrentIndex(): Int + fun ReaderScreenViewModel.getCurrentChapterByIndex(): Chapter + fun ReaderScreenViewModel.reverseChapters() +} + + +class ReaderUiFunctionsImpl @Inject constructor() : ReaderUiFunctions { + override fun ReaderScreenViewModel.toggleLocalLoaded(loaded: Boolean) { + state.isLocalLoaded = loaded + } + + override fun ReaderScreenViewModel.toggleBookLoaded(loaded: Boolean) { + state.isBookLoaded = loaded + } + + override fun ReaderScreenViewModel.setChapter(chapter: Chapter) { + state.stateChapter = chapter + } + + override fun ReaderScreenViewModel.toggleIsAsc(isAsc: Boolean) { + this.isAsc = isAsc + } + + override fun ReaderScreenViewModel.clearError() { + state.error = UiText.StringResource(R.string.no_error) + } + + override fun ReaderScreenViewModel.setStateChapter(book: Book) { + state.book = book + } + + override fun ReaderScreenViewModel.toggleRemoteLoading(isLoading: Boolean) { + state.isRemoteLoading = isLoading + } + + override fun ReaderScreenViewModel.toggleLoading(isLoading: Boolean) { + state.isLoading = isLoading + } + + override fun ReaderScreenViewModel.restoreSetting( + context: Context, + scrollState: LazyListState, + ) { + val activity = context.findComponentActivity() + if (activity != null) { + val window = activity.window + val layoutParams: WindowManager.LayoutParams = window.attributes + showSystemBars(context = context) + layoutParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + window.attributes = layoutParams + stateChapter?.let { chapter -> + activity.lifecycleScope.launch { + insertChapter(chapter.copy(progress = scrollState.firstVisibleItemScrollOffset)) + } + } + } + } + + override fun ReaderScreenViewModel.toggleSettingMode(enable: Boolean, returnToMain: Boolean?) { + if (returnToMain == null) { + isSettingModeEnable = enable + isMainBottomModeEnable = false + + } else { + isSettingModeEnable = false + isMainBottomModeEnable = true + } + } + + override fun ReaderScreenViewModel.getCurrentIndexOfChapter(chapter: Chapter): Int { + + val selectedChapter = + state.stateChapters.indexOfFirst { it.id == chapter.id && it.title == chapter.title } + return if (selectedChapter != -1) selectedChapter else 0 + } + + override fun ReaderScreenViewModel.updateChapterSliderIndex(index: Int) { + currentChapterIndex = index + } + + override fun ReaderScreenViewModel.getCurrentIndex(): Int { + return if (state.currentChapterIndex < 0) { + 0 + } else if (state.currentChapterIndex > (state.stateChapters.lastIndex)) { + state.stateChapters.lastIndex + } else if (state.currentChapterIndex == -1) { + 0 + } else { + state.currentChapterIndex + } + } + + override fun ReaderScreenViewModel.getCurrentChapterByIndex(): Chapter { + return try { + state.stateChapters[getCurrentIndex()] + } catch (e: Exception) { + state.stateChapters[0] + } + } + + override fun ReaderScreenViewModel.reverseChapters() { + toggleIsAsc(!prefState.isAsc) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/SystemBroadcastReceiver.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/SystemBroadcastReceiver.kt new file mode 100644 index 000000000..51e2b60ea --- /dev/null +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/SystemBroadcastReceiver.kt @@ -0,0 +1,40 @@ +package org.ireader.presentation.feature_reader.presentation.reader.viewmodel + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.platform.LocalContext + +@Composable +fun SystemBroadcastReceiver( + systemAction: String, + onSystemEvent: (intent: Intent?) -> Unit, +) { + // Grab the current context in this part of the UI tree + val context = LocalContext.current + + // Safely use the latest onSystemEvent lambda passed to the function + val currentOnSystemEvent by rememberUpdatedState(onSystemEvent) + + // If either context or systemAction changes, unregister and register again + DisposableEffect(context, systemAction) { + val intentFilter = IntentFilter(systemAction) + val broadcast = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + currentOnSystemEvent(intent) + } + } + + context.registerReceiver(broadcast, intentFilter) + + // When the effect leaves the Composition, remove the callback + onDispose { + context.unregisterReceiver(broadcast) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderBroadcastReceiver.kt b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderBroadcastReceiver.kt new file mode 100644 index 000000000..1863c69ad --- /dev/null +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderBroadcastReceiver.kt @@ -0,0 +1,24 @@ +package org.ireader.presentation.feature_reader.presentation.reader.viewmodel + +// +//@AndroidEntryPoint() +//class TextReaderBroadcastReceiver : BroadcastReceiver() { +// +// @Inject +// lateinit var state: TextReaderScreenStateImpl +// +// val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) +// +// override fun onReceive(p0: Context?, intent: Intent?) { +// Timber.e(intent?.extras?.toString()) +// intent?.getIntExtra("PLAYER",-1)?.let { +// scope.launch { +// state.mediaPlayerNotification.emit(it) +// } +// } +// +// +// +// } +// +//} \ 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 new file mode 100644 index 000000000..c56b4e271 --- /dev/null +++ b/presentation/src/main/java/org/ireader/presentation/feature_reader/presentation/reader/viewmodel/TextReaderManager.kt @@ -0,0 +1,163 @@ +package org.ireader.presentation.feature_reader.presentation.reader.viewmodel + +import android.app.PendingIntent +import android.content.Context +import android.os.Build +import android.speech.tts.TextToSpeech +import android.speech.tts.UtteranceProgressListener +import android.support.v4.media.session.MediaSessionCompat +import androidx.core.app.NotificationManagerCompat +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import org.ireader.core.utils.toast +import org.ireader.domain.feature_services.notification.DefaultNotificationHelper +import org.ireader.domain.feature_services.notification.Notifications +import timber.log.Timber +import javax.inject.Inject + +class TextReaderManager @Inject constructor( + private val defaultNotificationHelper: DefaultNotificationHelper, +) { + + + val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) + + + fun ReaderScreenViewModel.readText(context: Context, mediaSessionCompat: MediaSessionCompat) { + + if (speaker == null) { + speaker = TextToSpeech(context) { status -> + isLoading = true + if (status == TextToSpeech.ERROR) { + context.toast("Text-to-Speech Not Available") + return@TextToSpeech + } + isLoading = false + } + } + speaker?.let { ttl -> + 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) + val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + stateChapter?.let { chapter -> + book?.let { book -> + try { + NotificationManagerCompat.from(context).apply { + viewModelScope.launch { + val builder = + defaultNotificationHelper.basicPlayingTextReaderNotification( + chapter, + book, + true, + currentReadingParagraph, + mediaSessionCompat) + + notify(Notifications.ID_TEXT_READER_PROGRESS, builder.build()) + + } + + ttl.speak(chapter.content[currentReadingParagraph], + TextToSpeech.QUEUE_FLUSH, + null, + currentReadingParagraph.toString()) + + ttl.setOnUtteranceProgressListener(object : + UtteranceProgressListener() { + override fun onStop( + utteranceId: String?, + interrupted: Boolean, + ) { + super.onStop(utteranceId, interrupted) + + isPlaying = false + } + + override fun onStart(p0: String?) { + // showTextReaderNotification(context) + isPlaying = true + } + + override fun onDone(p0: String?) { + if (currentReadingParagraph < chapter.content.size) { + currentReadingParagraph += 1 + // builder.setProgress(chapter.content.size, currentReadingParagraph,false) + // notify(Notifications.ID_TEXT_READER_PROGRESS, builder.build()) + readText(context, mediaSessionCompat) + } + if (currentReadingParagraph == chapter.content.size) { + isPlaying = false + if (autoNextChapter) { + source?.let { + updateChapterSliderIndex(currentChapterIndex + 1) + viewModelScope.launch { + getChapter(getCurrentChapterByIndex().id, + source = it) + readText(context = context, mediaSessionCompat) + isPlaying = true + } + + } + + + } + } + } + + override fun onError(p0: String?) { + isPlaying = false + } + + + override fun onBeginSynthesis( + utteranceId: String?, + sampleRateInHz: Int, + audioFormat: Int, + channelCount: Int, + ) { + super.onBeginSynthesis(utteranceId, + sampleRateInHz, + audioFormat, + channelCount) + Timber.e(utteranceId) + } + }) + } + } catch (e: Exception) { + Timber.e(e.localizedMessage) + } + + } + } + } + } + + fun ReaderScreenViewModel.releaseMediaSession(context: Context) { + speaker?.shutdown() + mediaSessionCompat(context).release() + NotificationManagerCompat.from(context) + .cancel(Notifications.ID_TEXT_READER_PROGRESS) + } + + +} \ No newline at end of file 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 f7ac1d121..16208709e 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,6 +1,6 @@ package org.ireader.presentation.feature_ttl -import androidx.activity.OnBackPressedDispatcher +import androidx.activity.compose.BackHandler import androidx.compose.animation.core.TweenSpec import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -22,10 +22,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.app.NotificationManagerCompat import androidx.navigation.NavController +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.ireader.core.R import org.ireader.domain.feature_services.io.BookCover +import org.ireader.domain.feature_services.notification.Notifications import org.ireader.domain.models.entities.Chapter import org.ireader.presentation.feature_reader.presentation.reader.ReaderScreenDrawer import org.ireader.presentation.feature_reader.presentation.reader.components.SettingItemComposable @@ -39,6 +42,7 @@ import org.ireader.presentation.presentation.reusable_composable.BigSizeTextComp import org.ireader.presentation.presentation.reusable_composable.MidSizeTextComposable import org.ireader.presentation.presentation.reusable_composable.SuperSmallTextComposable import tachiyomi.source.Source +import timber.log.Timber import java.math.RoundingMode @@ -56,9 +60,11 @@ fun TTSScreen( source: Source, onChapter: (Chapter) -> Unit, ) { - navController.setOnBackPressedDispatcher(OnBackPressedDispatcher { - }) + + BackHandler { + vm.voiceMode = false + } val context = LocalContext.current val drawerScrollState = rememberLazyListState() @@ -72,9 +78,67 @@ fun TTSScreen( LaunchedEffect(key1 = scaffoldState.drawerState.targetValue) { if (chapter != null && scaffoldState.drawerState.targetValue == DrawerValue.Open && vm.stateChapters.isNotEmpty()) { - drawerScrollState.scrollToItem(vm.getCurrentIndexOfChapter(chapter)) + vm.uiFunc.apply { + drawerScrollState.scrollToItem(vm.getCurrentIndexOfChapter(chapter)) + } } } + LaunchedEffect(key1 = true) { + vm.notificationStates.mediaPlayerNotification.collectLatest { + Timber.e(it.toString()) + when (it) { + 1 -> { + onPrev() + } + 2 -> { + onPrevPar() + } + 3 -> { + 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()) + } + } + + } + + } + 4 -> { + onNextPar() + } + 5 -> { + onNext() + } + 6 -> { + NotificationManagerCompat.from(context).apply { + cancel(Notifications.ID_TEXT_READER_PROGRESS) + } + } + } + } + } + ModalBottomSheetLayout( modifier = Modifier.systemBarsPadding(), sheetContent = { @@ -151,9 +215,13 @@ fun TTSScreen( modifier = Modifier.statusBarsPadding(), onReverseIcon = { if (chapter != null) { - vm.reverseChapters() - scope.launch { - vm.getLocalChaptersByPaging(chapter.bookId) + vm.uiFunc.apply { + vm.reverseChapters() + } + vm.mainFunc.apply { + scope.launch { + vm.getLocalChaptersByPaging(chapter.bookId) + } } } }, @@ -192,7 +260,7 @@ fun TTSScreen( BigSizeTextComposable(text = chapter.title, align = TextAlign.Center) MidSizeTextComposable(text = book.title, align = TextAlign.Center) vm.stateChapter?.let { chapter -> - SuperSmallTextComposable(text = "${vm.currentReadingParagraph}/${chapter.content.size - 1}") + SuperSmallTextComposable(text = "${vm.currentReadingParagraph}/${chapter.content.size}") } } diff --git a/presentation/src/main/java/org/ireader/presentation/feature_ttl/VoiceChip.kt b/presentation/src/main/java/org/ireader/presentation/feature_ttl/VoiceChip.kt index 4cdac541f..45c952293 100644 --- a/presentation/src/main/java/org/ireader/presentation/feature_ttl/VoiceChip.kt +++ b/presentation/src/main/java/org/ireader/presentation/feature_ttl/VoiceChip.kt @@ -51,16 +51,16 @@ fun VoiceChip( .clip(RectangleShape) .background(MaterialTheme.colors.background) .border(2.dp, - if (voices[index].name == viewModel.currentVoice) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground.copy( + if (voices[index].locale.displayName == viewModel.currentVoice) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground.copy( .4f), CircleShape) .clickable { - viewModel.currentVoice = voices[index].name - viewModel.speechPrefUseCases.saveVoice(voices[index].name) + viewModel.currentVoice = voices[index].locale.displayName + viewModel.speechPrefUseCases.saveVoice(voices[index].locale.displayName) }, contentAlignment = Alignment.Center ) { - CaptionTextComposable(text = voices[index].name, + CaptionTextComposable(text = voices[index].locale.displayName, maxLine = 1, align = TextAlign.Center, modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp)) diff --git a/presentation/src/main/java/org/ireader/presentation/ui/GlobalSearchScreenSpec.kt b/presentation/src/main/java/org/ireader/presentation/ui/GlobalSearchScreenSpec.kt index e99d8812b..0cf9ffedc 100644 --- a/presentation/src/main/java/org/ireader/presentation/ui/GlobalSearchScreenSpec.kt +++ b/presentation/src/main/java/org/ireader/presentation/ui/GlobalSearchScreenSpec.kt @@ -29,4 +29,5 @@ object GlobalSearchScreenSpec : ScreenSpec { GlobalSearchScreen(navController = navController) } -} \ No newline at end of file +} + 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 883397517..cdc0b9bbd 100644 --- a/presentation/src/main/java/org/ireader/presentation/ui/ReaderScreenSpec.kt +++ b/presentation/src/main/java/org/ireader/presentation/ui/ReaderScreenSpec.kt @@ -3,8 +3,10 @@ package org.ireader.presentation.ui import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.ScaffoldState import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext +import androidx.core.app.NotificationManagerCompat import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NamedNavArgument import androidx.navigation.NavBackStackEntry @@ -14,6 +16,7 @@ import kotlinx.coroutines.launch import org.ireader.core.R import org.ireader.core.utils.UiText import org.ireader.core_ui.theme.TransparentStatusBar +import org.ireader.domain.feature_services.notification.Notifications import org.ireader.domain.ui.NavigationArgs import org.ireader.presentation.feature_reader.presentation.reader.ReadingScreen import org.ireader.presentation.feature_reader.presentation.reader.reverse_swip_refresh.rememberSwipeRefreshState @@ -58,6 +61,14 @@ object ReaderScreenSpec : ScreenSpec { val swipeState = rememberSwipeRefreshState(isRefreshing = vm.isLoading) val context = LocalContext.current + DisposableEffect(key1 = true) { + onDispose { + vm.speaker?.shutdown() + vm.mediaSessionCompat(context).release() + NotificationManagerCompat.from(context) + .cancel(Notifications.ID_TEXT_READER_PROGRESS) + } + } if (source != null) { when { vm.voiceMode -> { @@ -66,13 +77,21 @@ object ReaderScreenSpec : ScreenSpec { vm = vm, onPrev = { if (currentIndex > 0) { - vm.updateChapterSliderIndex(currentIndex - 1) - scope.launch { - vm.getChapter(vm.getCurrentChapterByIndex().id, - source = source) - scrollState.animateScrollToItem(0, 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) + } + } } + } else { scope.launch { vm.showSnackBar(UiText.StringResource(org.ireader.core.R.string.this_is_first_chapter)) @@ -85,18 +104,27 @@ object ReaderScreenSpec : ScreenSpec { vm.speaker?.stop() } else -> { - vm.readText(context = context) + vm.textReaderManager.apply { + vm.readText(context, vm.mediaSessionCompat(context)) + } } } }, onNext = { if (currentIndex < chapters.lastIndex) { - vm.updateChapterSliderIndex(currentIndex + 1) - scope.launch { - vm.getChapter(vm.getCurrentChapterByIndex().id, - source = source) - scrollState.animateScrollToItem(0, 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) + } + } } } else { @@ -108,14 +136,19 @@ object ReaderScreenSpec : ScreenSpec { }, onChapter = { ch -> scope.launch { - vm.getChapter(ch.id, - source = source) + vm.mainFunc.apply { + vm.getChapter(ch.id, + source = source) + } + } scope.launch { scrollState.scrollToItem(0, 0) } - vm.updateChapterSliderIndex(vm.getCurrentIndexOfChapter( - ch)) + vm.uiFunc.apply { + vm.updateChapterSliderIndex(vm.getCurrentIndexOfChapter( + ch)) + } }, source = source, navController = navController, @@ -124,7 +157,9 @@ object ReaderScreenSpec : ScreenSpec { vm.speaker?.stop() vm.currentReadingParagraph -= 1 if (vm.isPlaying) { - vm.readText(context) + vm.textReaderManager.apply { + vm.readText(context, vm.mediaSessionCompat(context)) + } } } @@ -135,7 +170,9 @@ object ReaderScreenSpec : ScreenSpec { vm.speaker?.stop() vm.currentReadingParagraph += 1 if (vm.isPlaying) { - vm.readText(context) + vm.textReaderManager.apply { + vm.readText(context, vm.mediaSessionCompat(context)) + } } } @@ -155,11 +192,15 @@ object ReaderScreenSpec : ScreenSpec { source = source, onNext = { if (currentIndex < chapters.lastIndex) { - vm.updateChapterSliderIndex(currentIndex + 1) - scope.launch { - vm.getChapter(vm.getCurrentChapterByIndex().id, - source = source) - scrollState.animateScrollToItem(0, 0) + vm.uiFunc.apply { + vm.mainFunc.apply { + vm.updateChapterSliderIndex(currentIndex + 1) + scope.launch { + vm.getChapter(vm.getCurrentChapterByIndex().id, + source = source) + scrollState.animateScrollToItem(0, 0) + } + } } } else { @@ -171,11 +212,15 @@ object ReaderScreenSpec : ScreenSpec { }, onPrev = { if (currentIndex > 0) { - vm.updateChapterSliderIndex(currentIndex - 1) - scope.launch { - vm.getChapter(vm.getCurrentChapterByIndex().id, - source = source) - scrollState.animateScrollToItem(0, 0) + vm.uiFunc.apply { + vm.mainFunc.apply { + vm.updateChapterSliderIndex(currentIndex - 1) + scope.launch { + vm.getChapter(vm.getCurrentChapterByIndex().id, + source = source) + scrollState.animateScrollToItem(0, 0) + } + } } } else { @@ -188,30 +233,41 @@ object ReaderScreenSpec : ScreenSpec { scope.launch { vm.showSnackBar(UiText.DynamicString(chapters[vm.currentChapterIndex].title)) } - vm.updateChapterSliderIndex(currentIndex) - scope.launch { - vm.getChapter(chapters[vm.currentChapterIndex].id, - source = source) + vm.uiFunc.apply { + vm.mainFunc.apply { + vm.updateChapterSliderIndex(currentIndex) + scope.launch { + vm.getChapter(chapters[vm.currentChapterIndex].id, + source = source) + } + } } scope.launch { scrollState.animateScrollToItem(0, 0) } }, onSliderChange = { - vm.updateChapterSliderIndex(it.toInt()) + vm.uiFunc.apply { + vm.updateChapterSliderIndex(it.toInt()) + } }, swipeState = swipeState, drawerScrollState = drawerScrollState, onChapter = { ch -> - scope.launch { - vm.getChapter(ch.id, - source = source) - } - scope.launch { - scrollState.scrollToItem(0, 0) + vm.uiFunc.apply { + vm.mainFunc.apply { + scope.launch { + vm.getChapter(ch.id, + source = source) + } + scope.launch { + scrollState.scrollToItem(0, 0) + } + vm.updateChapterSliderIndex(vm.getCurrentIndexOfChapter( + ch)) + } } - vm.updateChapterSliderIndex(vm.getCurrentIndexOfChapter( - ch)) + } ) }