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"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ReaderActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:exported="false">
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -21,9 +21,9 @@ class App : Application(), DefaultLifecycleObserver {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super<Application>.onCreate()
|
super<Application>.onCreate()
|
||||||
|
|
||||||
/*if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
System.setProperty("kotlinx.coroutines.debug", "on")
|
System.setProperty("kotlinx.coroutines.debug", "on")
|
||||||
}*/
|
}
|
||||||
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
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 desktopJvmTarget = JavaVersion.VERSION_16
|
||||||
val androidJvmTarget = JavaVersion.VERSION_11
|
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
|
package ca.gosyer.ui.reader
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -28,8 +29,21 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
|||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
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)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
actual fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
|
fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
|
||||||
val windowSettings = AppComponent.getInstance().dataComponent.uiPreferences
|
val windowSettings = AppComponent.getInstance().dataComponent.uiPreferences
|
||||||
.readerWindow()
|
.readerWindow()
|
||||||
val (
|
val (
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.manga.components
|
package ca.gosyer.ui.manga.components
|
||||||
|
|
||||||
import ca.gosyer.ui.base.components.VerticalScrollbar
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import ca.gosyer.ui.base.components.rememberScrollbarAdapter
|
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Favorite
|
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.data.models.Manga
|
||||||
import ca.gosyer.i18n.MR
|
import ca.gosyer.i18n.MR
|
||||||
import ca.gosyer.ui.base.chapter.ChapterDownloadItem
|
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.ActionItem
|
||||||
import ca.gosyer.ui.base.navigation.Toolbar
|
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.ErrorScreen
|
||||||
import ca.gosyer.uicore.components.LoadingScreen
|
import ca.gosyer.uicore.components.LoadingScreen
|
||||||
import ca.gosyer.uicore.resources.stringResource
|
import ca.gosyer.uicore.resources.stringResource
|
||||||
@@ -71,6 +71,7 @@ fun MangaScreenContent(
|
|||||||
categoryDialogState.show()
|
categoryDialogState.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val readerLauncher = rememberReaderLauncher()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -104,7 +105,7 @@ fun MangaScreenContent(
|
|||||||
ChapterItem(
|
ChapterItem(
|
||||||
chapter,
|
chapter,
|
||||||
dateTimeFormatter::format,
|
dateTimeFormatter::format,
|
||||||
onClick = { openReaderMenu(it, manga.id) },
|
onClick = { readerLauncher.launch(it, manga.id) },
|
||||||
toggleRead = toggleRead,
|
toggleRead = toggleRead,
|
||||||
toggleBookmarked = toggleBookmarked,
|
toggleBookmarked = toggleBookmarked,
|
||||||
markPreviousAsRead = markPreviousRead,
|
markPreviousAsRead = markPreviousRead,
|
||||||
|
|||||||
@@ -78,7 +78,14 @@ val supportedKeyList = listOf(
|
|||||||
Key.DirectionRight
|
Key.DirectionRight
|
||||||
)
|
)
|
||||||
|
|
||||||
expect fun openReaderMenu(chapterIndex: Int, mangaId: Long)
|
expect class ReaderLauncher {
|
||||||
|
fun launch(
|
||||||
|
chapterIndex: Int,
|
||||||
|
mangaId: Long
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
expect fun rememberReaderLauncher(): ReaderLauncher
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReaderMenu(
|
fun ReaderMenu(
|
||||||
@@ -106,13 +113,6 @@ fun ReaderMenu(
|
|||||||
val currentPage by vm.currentPage.collectAsState()
|
val currentPage by vm.currentPage.collectAsState()
|
||||||
val currentPageOffset by vm.currentPageOffset.collectAsState()
|
val currentPageOffset by vm.currentPageOffset.collectAsState()
|
||||||
|
|
||||||
fun hotkey(block: () -> Unit): (KeyEvent) -> Boolean {
|
|
||||||
return {
|
|
||||||
block()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(hotkeyFlow) {
|
LaunchedEffect(hotkeyFlow) {
|
||||||
hotkeyFlow.collectLatest {
|
hotkeyFlow.collectLatest {
|
||||||
when (it.key) {
|
when (it.key) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
package ca.gosyer.ui.reader
|
package ca.gosyer.ui.reader
|
||||||
|
|
||||||
|
import ca.gosyer.core.lang.launchDefault
|
||||||
import ca.gosyer.core.lang.throwIfCancellation
|
import ca.gosyer.core.lang.throwIfCancellation
|
||||||
import ca.gosyer.core.logging.CKLogger
|
import ca.gosyer.core.logging.CKLogger
|
||||||
import ca.gosyer.core.prefs.getAsFlow
|
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.ContextWrapper
|
||||||
import ca.gosyer.uicore.vm.ViewModel
|
import ca.gosyer.uicore.vm.ViewModel
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -96,9 +96,11 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launchDefault {
|
||||||
initManga(params.mangaId)
|
runCatching {
|
||||||
initChapters(params.mangaId, params.chapterIndex)
|
initManga(params.mangaId)
|
||||||
|
initChapters(params.mangaId, params.chapterIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +149,7 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setMangaReaderMode(mode: String) {
|
fun setMangaReaderMode(mode: String) {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launchDefault {
|
||||||
_manga.value?.updateRemote(
|
_manga.value?.updateRemote(
|
||||||
mangaHandler,
|
mangaHandler,
|
||||||
mode
|
mode
|
||||||
@@ -157,8 +159,8 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun prevChapter() {
|
fun prevChapter() {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launchDefault {
|
||||||
val prevChapter = previousChapter.value ?: return@launch
|
val prevChapter = previousChapter.value ?: return@launchDefault
|
||||||
try {
|
try {
|
||||||
_state.value = ReaderChapter.State.Wait
|
_state.value = ReaderChapter.State.Wait
|
||||||
sendProgress()
|
sendProgress()
|
||||||
@@ -170,8 +172,8 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun nextChapter() {
|
fun nextChapter() {
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launchDefault {
|
||||||
val nextChapter = nextChapter.value ?: return@launch
|
val nextChapter = nextChapter.value ?: return@launchDefault
|
||||||
try {
|
try {
|
||||||
_state.value = ReaderChapter.State.Wait
|
_state.value = ReaderChapter.State.Wait
|
||||||
sendProgress()
|
sendProgress()
|
||||||
@@ -205,7 +207,7 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
)
|
)
|
||||||
val pages = loader.loadChapter(chapter)
|
val pages = loader.loadChapter(chapter)
|
||||||
viewerChapters.currChapter.value = chapter
|
viewerChapters.currChapter.value = chapter
|
||||||
scope.launch(Dispatchers.Default) {
|
scope.launchDefault {
|
||||||
val chapters = try {
|
val chapters = try {
|
||||||
chapterHandler.getChapters(mangaId)
|
chapterHandler.getChapters(mangaId)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -265,7 +267,7 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
fun sendProgress(chapter: Chapter? = this.chapter.value?.chapter, lastPageRead: Int = currentPage.value) {
|
fun sendProgress(chapter: Chapter? = this.chapter.value?.chapter, lastPageRead: Int = currentPage.value) {
|
||||||
chapter ?: return
|
chapter ?: return
|
||||||
if (chapter.read) return
|
if (chapter.read) return
|
||||||
GlobalScope.launch {
|
GlobalScope.launchDefault {
|
||||||
chapterHandler.updateChapter(chapter.mangaId, chapter.index, lastPageRead = lastPageRead)
|
chapterHandler.updateChapter(chapter.mangaId, chapter.index, lastPageRead = lastPageRead)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +278,7 @@ class ReaderMenuViewModel @Inject constructor(
|
|||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
private fun updateLastPageReadOffset(chapter: Chapter, offset: Int) {
|
private fun updateLastPageReadOffset(chapter: Chapter, offset: Int) {
|
||||||
GlobalScope.launch {
|
GlobalScope.launchDefault {
|
||||||
chapter.updateRemote(chapterHandler, offset)
|
chapter.updateRemote(chapterHandler, offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ package ca.gosyer.ui.updates
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import ca.gosyer.ui.manga.MangaScreen
|
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.ui.updates.components.UpdatesScreenContent
|
||||||
import ca.gosyer.uicore.vm.viewModel
|
import ca.gosyer.uicore.vm.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
@@ -26,11 +26,12 @@ class UpdatesScreen : Screen {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val vm = viewModel<UpdatesScreenViewModel>()
|
val vm = viewModel<UpdatesScreenViewModel>()
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
val readerLauncher = rememberReaderLauncher()
|
||||||
UpdatesScreenContent(
|
UpdatesScreenContent(
|
||||||
isLoading = vm.isLoading.collectAsState().value,
|
isLoading = vm.isLoading.collectAsState().value,
|
||||||
updates = vm.updates.collectAsState().value,
|
updates = vm.updates.collectAsState().value,
|
||||||
loadNextPage = vm::loadNextPage,
|
loadNextPage = vm::loadNextPage,
|
||||||
openChapter = ::openReaderMenu,
|
openChapter = readerLauncher::launch,
|
||||||
openManga = { navigator push MangaScreen(it) },
|
openManga = { navigator push MangaScreen(it) },
|
||||||
downloadChapter = vm::downloadChapter,
|
downloadChapter = vm::downloadChapter,
|
||||||
deleteDownloadedChapter = vm::deleteDownloadedChapter,
|
deleteDownloadedChapter = vm::deleteDownloadedChapter,
|
||||||
|
|||||||
Reference in New Issue
Block a user