Android reader support

This commit is contained in:
Syer10
2022-02-27 15:29:43 -05:00
parent 223281372f
commit 730ea4a90c
11 changed files with 159 additions and 40 deletions

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)
}
}
}

View File

@@ -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
} }

View File

@@ -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) }
}

View File

@@ -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) {
}

View File

@@ -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 (

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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)
} }
} }

View File

@@ -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,