mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Android reader support
This commit is contained in:
@@ -22,5 +22,10 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ReaderActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="false">
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -21,9 +21,9 @@ class App : Application(), DefaultLifecycleObserver {
|
||||
override fun onCreate() {
|
||||
super<Application>.onCreate()
|
||||
|
||||
/*if (BuildConfig.DEBUG) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
System.setProperty("kotlinx.coroutines.debug", "on")
|
||||
}*/
|
||||
}
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.jui.android
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.input.key.NativeKeyEvent
|
||||
import androidx.compose.ui.input.key.key
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import ca.gosyer.ui.AppComponent
|
||||
import ca.gosyer.ui.base.theme.AppTheme
|
||||
import ca.gosyer.ui.reader.ReaderMenu
|
||||
import ca.gosyer.ui.reader.supportedKeyList
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
fun newIntent(context: Context, mangaId: Long, chapterIndex: Int): Intent {
|
||||
return Intent(context, ReaderActivity::class.java).apply {
|
||||
putExtra("manga", mangaId)
|
||||
putExtra("chapter", chapterIndex)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val hotkeyFlow = MutableSharedFlow<KeyEvent>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val hooks = AppComponent.getInstance(applicationContext).uiComponent.getHooks()
|
||||
|
||||
val mangaId = intent.extras!!.getLong("manga", -1)
|
||||
val chapterIndex = intent.extras!!.getInt("chapter", -1)
|
||||
if (mangaId == -1L || chapterIndex == -1) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
setContent {
|
||||
CompositionLocalProvider(
|
||||
*hooks
|
||||
) {
|
||||
AppTheme {
|
||||
ReaderMenu(chapterIndex, mangaId, hotkeyFlow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyUp(keyCode: Int, event: android.view.KeyEvent?): Boolean {
|
||||
event ?: super.onKeyUp(keyCode, event)
|
||||
val composeKeyEvent = KeyEvent(event as NativeKeyEvent)
|
||||
lifecycleScope.launch {
|
||||
hotkeyFlow.emit(composeKeyEvent)
|
||||
}
|
||||
|
||||
return if (composeKeyEvent.key in supportedKeyList) {
|
||||
true
|
||||
} else {
|
||||
super.onKeyUp(keyCode, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,5 @@ object Config {
|
||||
val desktopJvmTarget = JavaVersion.VERSION_16
|
||||
val androidJvmTarget = JavaVersion.VERSION_11
|
||||
|
||||
const val androidDev = true
|
||||
const val androidDev = false
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.ui.reader
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
actual class ReaderLauncher(private val context: Context) {
|
||||
actual fun launch(
|
||||
chapterIndex: Int,
|
||||
mangaId: Long
|
||||
) {
|
||||
Intent(context, Class.forName("ca.gosyer.jui.android.ReaderActivity")).apply {
|
||||
putExtra("manga", mangaId)
|
||||
putExtra("chapter", chapterIndex)
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.let(context::startActivity)
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
actual fun rememberReaderLauncher(): ReaderLauncher {
|
||||
val context = LocalContext.current
|
||||
return remember(context) { ReaderLauncher(context) }
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.ui.reader
|
||||
|
||||
actual fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
package ca.gosyer.ui.reader
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -28,8 +29,21 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
actual class ReaderLauncher {
|
||||
actual fun launch(
|
||||
chapterIndex: Int,
|
||||
mangaId: Long
|
||||
) {
|
||||
openReaderMenu(chapterIndex, mangaId)
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
actual fun rememberReaderLauncher(): ReaderLauncher {
|
||||
return remember { ReaderLauncher() }
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
actual fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
|
||||
fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
|
||||
val windowSettings = AppComponent.getInstance().dataComponent.uiPreferences
|
||||
.readerWindow()
|
||||
val (
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
package ca.gosyer.ui.manga.components
|
||||
|
||||
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -15,7 +14,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Favorite
|
||||
@@ -32,9 +30,11 @@ import ca.gosyer.data.models.Category
|
||||
import ca.gosyer.data.models.Manga
|
||||
import ca.gosyer.i18n.MR
|
||||
import ca.gosyer.ui.base.chapter.ChapterDownloadItem
|
||||
import ca.gosyer.ui.base.components.VerticalScrollbar
|
||||
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
||||
import ca.gosyer.ui.base.navigation.ActionItem
|
||||
import ca.gosyer.ui.base.navigation.Toolbar
|
||||
import ca.gosyer.ui.reader.openReaderMenu
|
||||
import ca.gosyer.ui.reader.rememberReaderLauncher
|
||||
import ca.gosyer.uicore.components.ErrorScreen
|
||||
import ca.gosyer.uicore.components.LoadingScreen
|
||||
import ca.gosyer.uicore.resources.stringResource
|
||||
@@ -71,6 +71,7 @@ fun MangaScreenContent(
|
||||
categoryDialogState.show()
|
||||
}
|
||||
}
|
||||
val readerLauncher = rememberReaderLauncher()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -104,7 +105,7 @@ fun MangaScreenContent(
|
||||
ChapterItem(
|
||||
chapter,
|
||||
dateTimeFormatter::format,
|
||||
onClick = { openReaderMenu(it, manga.id) },
|
||||
onClick = { readerLauncher.launch(it, manga.id) },
|
||||
toggleRead = toggleRead,
|
||||
toggleBookmarked = toggleBookmarked,
|
||||
markPreviousAsRead = markPreviousRead,
|
||||
|
||||
@@ -78,7 +78,14 @@ val supportedKeyList = listOf(
|
||||
Key.DirectionRight
|
||||
)
|
||||
|
||||
expect fun openReaderMenu(chapterIndex: Int, mangaId: Long)
|
||||
expect class ReaderLauncher {
|
||||
fun launch(
|
||||
chapterIndex: Int,
|
||||
mangaId: Long
|
||||
)
|
||||
}
|
||||
@Composable
|
||||
expect fun rememberReaderLauncher(): ReaderLauncher
|
||||
|
||||
@Composable
|
||||
fun ReaderMenu(
|
||||
@@ -106,13 +113,6 @@ fun ReaderMenu(
|
||||
val currentPage by vm.currentPage.collectAsState()
|
||||
val currentPageOffset by vm.currentPageOffset.collectAsState()
|
||||
|
||||
fun hotkey(block: () -> Unit): (KeyEvent) -> Boolean {
|
||||
return {
|
||||
block()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(hotkeyFlow) {
|
||||
hotkeyFlow.collectLatest {
|
||||
when (it.key) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
package ca.gosyer.ui.reader
|
||||
|
||||
import ca.gosyer.core.lang.launchDefault
|
||||
import ca.gosyer.core.lang.throwIfCancellation
|
||||
import ca.gosyer.core.logging.CKLogger
|
||||
import ca.gosyer.core.prefs.getAsFlow
|
||||
@@ -27,7 +28,6 @@ import ca.gosyer.uicore.prefs.asStateIn
|
||||
import ca.gosyer.uicore.vm.ContextWrapper
|
||||
import ca.gosyer.uicore.vm.ViewModel
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -96,11 +96,13 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun init() {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
scope.launchDefault {
|
||||
runCatching {
|
||||
initManga(params.mangaId)
|
||||
initChapters(params.mangaId, params.chapterIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun navigate(navigationRegion: Navigation) {
|
||||
scope.launch {
|
||||
@@ -147,7 +149,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun setMangaReaderMode(mode: String) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
scope.launchDefault {
|
||||
_manga.value?.updateRemote(
|
||||
mangaHandler,
|
||||
mode
|
||||
@@ -157,8 +159,8 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun prevChapter() {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val prevChapter = previousChapter.value ?: return@launch
|
||||
scope.launchDefault {
|
||||
val prevChapter = previousChapter.value ?: return@launchDefault
|
||||
try {
|
||||
_state.value = ReaderChapter.State.Wait
|
||||
sendProgress()
|
||||
@@ -170,8 +172,8 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun nextChapter() {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
val nextChapter = nextChapter.value ?: return@launch
|
||||
scope.launchDefault {
|
||||
val nextChapter = nextChapter.value ?: return@launchDefault
|
||||
try {
|
||||
_state.value = ReaderChapter.State.Wait
|
||||
sendProgress()
|
||||
@@ -205,7 +207,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
)
|
||||
val pages = loader.loadChapter(chapter)
|
||||
viewerChapters.currChapter.value = chapter
|
||||
scope.launch(Dispatchers.Default) {
|
||||
scope.launchDefault {
|
||||
val chapters = try {
|
||||
chapterHandler.getChapters(mangaId)
|
||||
} catch (e: Exception) {
|
||||
@@ -265,7 +267,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
fun sendProgress(chapter: Chapter? = this.chapter.value?.chapter, lastPageRead: Int = currentPage.value) {
|
||||
chapter ?: return
|
||||
if (chapter.read) return
|
||||
GlobalScope.launch {
|
||||
GlobalScope.launchDefault {
|
||||
chapterHandler.updateChapter(chapter.mangaId, chapter.index, lastPageRead = lastPageRead)
|
||||
}
|
||||
}
|
||||
@@ -276,7 +278,7 @@ class ReaderMenuViewModel @Inject constructor(
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
private fun updateLastPageReadOffset(chapter: Chapter, offset: Int) {
|
||||
GlobalScope.launch {
|
||||
GlobalScope.launchDefault {
|
||||
chapter.updateRemote(chapterHandler, offset)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package ca.gosyer.ui.updates
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import ca.gosyer.ui.manga.MangaScreen
|
||||
import ca.gosyer.ui.reader.openReaderMenu
|
||||
import ca.gosyer.ui.reader.rememberReaderLauncher
|
||||
import ca.gosyer.ui.updates.components.UpdatesScreenContent
|
||||
import ca.gosyer.uicore.vm.viewModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
@@ -26,11 +26,12 @@ class UpdatesScreen : Screen {
|
||||
override fun Content() {
|
||||
val vm = viewModel<UpdatesScreenViewModel>()
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val readerLauncher = rememberReaderLauncher()
|
||||
UpdatesScreenContent(
|
||||
isLoading = vm.isLoading.collectAsState().value,
|
||||
updates = vm.updates.collectAsState().value,
|
||||
loadNextPage = vm::loadNextPage,
|
||||
openChapter = ::openReaderMenu,
|
||||
openChapter = readerLauncher::launch,
|
||||
openManga = { navigator push MangaScreen(it) },
|
||||
downloadChapter = vm::downloadChapter,
|
||||
deleteDownloadedChapter = vm::deleteDownloadedChapter,
|
||||
|
||||
Reference in New Issue
Block a user