Small Rewrite

- Allow App specific migrations
- move AppComponent to apps
- Update checker for android
- Rewrite Reader and Categories to use a launcher
This commit is contained in:
Syer10
2022-03-05 19:36:39 -05:00
parent 0d7e992286
commit 5cdf715dd7
30 changed files with 349 additions and 142 deletions

View File

@@ -29,6 +29,7 @@ dependencies {
implementation(libs.core)
implementation(libs.appCompat)
implementation(libs.activityCompose)
implementation(libs.work)
// Android Lifecycle
implementation(libs.lifecycleCommon)

View File

@@ -34,7 +34,7 @@
</activity>
<service
android:name=".data.AndroidDownloadService"
android:name=".data.download.AndroidDownloadService"
android:exported="false" />
</application>
</manifest>

View File

@@ -14,8 +14,7 @@ import androidx.lifecycle.lifecycleScope
import ca.gosyer.core.logging.CKLogger
import ca.gosyer.core.prefs.getAsFlow
import ca.gosyer.data.ui.model.ThemeMode
import ca.gosyer.jui.android.data.Notifications
import ca.gosyer.ui.AppComponent
import ca.gosyer.jui.android.data.notification.Notifications
import kotlinx.coroutines.flow.launchIn
class App : Application(), DefaultLifecycleObserver {

View File

@@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui
package ca.gosyer.jui.android
import android.annotation.SuppressLint
import android.content.Context
@@ -13,18 +13,25 @@ import ca.gosyer.data.DataComponent
import ca.gosyer.data.create
import ca.gosyer.ui.base.UiComponent
import ca.gosyer.ui.base.create
import ca.gosyer.uicore.vm.ContextWrapper
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
@AppScope
@Component
actual abstract class AppComponent constructor(
val context: Context,
actual val dataComponent: DataComponent = DataComponent.create(context),
abstract class AppComponent(
context: Context,
val dataComponent: DataComponent = DataComponent.create(context),
@Component
actual val uiComponent: UiComponent = UiComponent.create(dataComponent)
val uiComponent: UiComponent = UiComponent.create(dataComponent)
) {
actual abstract val contextWrapper: ContextWrapper
abstract val appMigrations: AppMigrations
@get:AppScope
@get:Provides
protected val appMigrationsFactory: AppMigrations
get() = AppMigrations(dataComponent.migrationPreferences, uiComponent.contextWrapper)
companion object {
@SuppressLint("StaticFieldLeak")
private var appComponentInstance: AppComponent? = null

View File

@@ -0,0 +1,34 @@
/*
* 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 ca.gosyer.data.migration.MigrationPreferences
import ca.gosyer.jui.android.data.update.UpdateCheckWorker
import ca.gosyer.uicore.vm.ContextWrapper
import me.tatarka.inject.annotations.Inject
class AppMigrations @Inject constructor(
private val migrationPreferences: MigrationPreferences,
private val contextWrapper: ContextWrapper
) {
fun runMigrations(): Boolean {
val oldVersion = migrationPreferences.appVersion().get()
if (oldVersion < BuildConfig.VERSION_CODE) {
migrationPreferences.appVersion().set(BuildConfig.VERSION_CODE)
UpdateCheckWorker.setupTask(contextWrapper.context)
// Fresh install
if (oldVersion == 0) {
return false
}
return true
}
return false
}
}

View File

@@ -4,8 +4,7 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.CompositionLocalProvider
import ca.gosyer.jui.android.data.AndroidDownloadService
import ca.gosyer.ui.AppComponent
import ca.gosyer.jui.android.data.download.AndroidDownloadService
import ca.gosyer.ui.base.theme.AppTheme
import ca.gosyer.ui.main.MainMenu
@@ -16,6 +15,7 @@ class MainActivity : AppCompatActivity() {
val appComponent = AppComponent.getInstance(applicationContext)
if (savedInstanceState == null) {
appComponent.dataComponent.migrations.runMigrations()
appComponent.appMigrations.runMigrations()
}
AndroidDownloadService.start(this, AndroidDownloadService.Actions.START)

View File

@@ -16,7 +16,6 @@ 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

View File

@@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.android.data
package ca.gosyer.jui.android.data.download
import android.app.Notification
import android.app.Service
@@ -23,12 +23,13 @@ import ca.gosyer.data.download.model.DownloadState
import ca.gosyer.data.download.model.DownloadStatus
import ca.gosyer.data.server.requests.downloadsQuery
import ca.gosyer.i18n.MR
import ca.gosyer.jui.android.AppComponent
import ca.gosyer.jui.android.R
import ca.gosyer.jui.android.data.notification.Notifications
import ca.gosyer.jui.android.util.acquireWakeLock
import ca.gosyer.jui.android.util.notification
import ca.gosyer.jui.android.util.notificationBuilder
import ca.gosyer.jui.android.util.notificationManager
import ca.gosyer.ui.AppComponent
import dev.icerock.moko.resources.desc.desc
import dev.icerock.moko.resources.format
import io.ktor.client.features.websocket.ws

View File

@@ -4,7 +4,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.android.data
package ca.gosyer.jui.android.data.notification
import android.content.Context
import androidx.core.app.NotificationManagerCompat

View File

@@ -0,0 +1,80 @@
/*
* 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.data.update
import android.content.Context
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import ca.gosyer.data.update.UpdateChecker.Update
import ca.gosyer.i18n.MR
import ca.gosyer.jui.android.AppComponent
import ca.gosyer.jui.android.data.notification.Notifications
import ca.gosyer.jui.android.util.notificationBuilder
import ca.gosyer.jui.android.util.notificationManager
import dev.icerock.moko.resources.desc.desc
import kotlinx.coroutines.flow.singleOrNull
import java.util.concurrent.TimeUnit
class UpdateCheckWorker(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
return try {
val update = AppComponent.getInstance(context.applicationContext)
.dataComponent
.let {
if (it.updatePreferences.enabled().get()) {
it.updateChecker
.checkForUpdates()
.singleOrNull()
} else null
}
if (update is Update.UpdateFound) {
context.notificationBuilder(Notifications.CHANNEL_APP_UPDATE) {
setContentTitle(MR.strings.new_update_title.desc().toString(context))
setContentText(update.release.version)
setSmallIcon(android.R.drawable.stat_sys_download_done)
}.let {
context.notificationManager.notify(Notifications.ID_UPDATES_TO_APP, it.build())
}
}
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
companion object {
private const val TAG = "UpdateChecker"
fun setupTask(context: Context) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = PeriodicWorkRequestBuilder<UpdateCheckWorker>(
7,
TimeUnit.DAYS,
3,
TimeUnit.HOURS
)
.addTag(TAG)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
}
fun cancelTask(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
}
}
}

View File

@@ -13,4 +13,7 @@ class MigrationPreferences(private val preferenceStore: PreferenceStore) {
fun version(): Preference<Int> {
return preferenceStore.getInt("version", 0)
}
fun appVersion(): Preference<Int> {
return preferenceStore.getInt("app_version", 0)
}
}

View File

@@ -6,35 +6,35 @@
package ca.gosyer.data.update
import ca.gosyer.core.lang.launch
import ca.gosyer.core.lang.withIOContext
import ca.gosyer.data.build.BuildKonfig
import ca.gosyer.data.server.Http
import ca.gosyer.data.update.model.GithubRelease
import io.ktor.client.request.get
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import me.tatarka.inject.annotations.Inject
class UpdateChecker @Inject constructor(
private val updatePreferences: UpdatePreferences,
private val client: Http
) {
private val _updateFound = MutableSharedFlow<GithubRelease>()
val updateFound = _updateFound.asSharedFlow()
fun checkForUpdates() = flow {
// if (!updatePreferences.enabled().get()) return
val latestRelease = client.get<GithubRelease>(
"https://api.github.com/repos/$GITHUB_REPO/releases/latest"
)
@OptIn(DelicateCoroutinesApi::class)
fun checkForUpdates() {
if (!updatePreferences.enabled().get()) return
launch {
val latestRelease = withIOContext {
client.get<GithubRelease>("https://api.github.com/repos/$GITHUB_REPO/releases/latest")
}
if (isNewVersion(latestRelease.version)) {
_updateFound.emit(latestRelease)
}
if (isNewVersion(latestRelease.version)) {
emit(Update.UpdateFound(latestRelease))
} else {
emit(Update.NoUpdatesFound)
}
}.flowOn(Dispatchers.IO)
sealed class Update {
data class UpdateFound(val release: GithubRelease) : Update()
object NoUpdatesFound : Update()
}
// Thanks to Tachiyomi for inspiration

View File

@@ -4,24 +4,31 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.ui
package ca.gosyer
import ca.gosyer.core.di.AppScope
import ca.gosyer.data.DataComponent
import ca.gosyer.data.create
import ca.gosyer.ui.base.UiComponent
import ca.gosyer.ui.base.create
import ca.gosyer.uicore.vm.ContextWrapper
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
@AppScope
@Component
actual abstract class AppComponent constructor(
actual val dataComponent: DataComponent = DataComponent.create(),
abstract class AppComponent constructor(
val dataComponent: DataComponent = DataComponent.create(),
@Component
actual val uiComponent: UiComponent = UiComponent.create(dataComponent)
val uiComponent: UiComponent = UiComponent.create(dataComponent)
) {
actual abstract val contextWrapper: ContextWrapper
abstract val appMigrations: AppMigrations
@get:AppScope
@get:Provides
protected val appMigrationsFactory: AppMigrations
get() = AppMigrations(dataComponent.migrationPreferences, uiComponent.contextWrapper)
companion object {
private var appComponentInstance: AppComponent? = null

View File

@@ -0,0 +1,32 @@
/*
* 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
import ca.gosyer.data.migration.MigrationPreferences
import ca.gosyer.desktop.build.BuildConfig
import ca.gosyer.uicore.vm.ContextWrapper
import me.tatarka.inject.annotations.Inject
class AppMigrations @Inject constructor(
private val migrationPreferences: MigrationPreferences,
private val contextWrapper: ContextWrapper
) {
fun runMigrations(): Boolean {
val oldVersion = migrationPreferences.appVersion().get()
if (oldVersion < BuildConfig.MIGRATION_CODE) {
migrationPreferences.appVersion().set(BuildConfig.MIGRATION_CODE)
// Fresh install
if (oldVersion == 0) {
return false
}
return true
}
return false
}
}

View File

@@ -32,7 +32,6 @@ import ca.gosyer.data.server.ServerService.ServerResult
import ca.gosyer.data.ui.model.ThemeMode
import ca.gosyer.desktop.build.BuildConfig
import ca.gosyer.i18n.MR
import ca.gosyer.ui.AppComponent
import ca.gosyer.ui.base.dialog.getMaterialDialogProperties
import ca.gosyer.ui.base.theme.AppTheme
import ca.gosyer.ui.main.MainMenu
@@ -70,6 +69,7 @@ suspend fun main() {
val dataComponent = appComponent.dataComponent
val uiComponent = appComponent.uiComponent
dataComponent.migrations.runMigrations()
appComponent.appMigrations.runMigrations()
dataComponent.downloadService.init()
// dataComponent.libraryUpdateService.init()
val serverService = dataComponent.serverService

View File

@@ -17,6 +17,7 @@ materialDialogs = "0.6.6"
core = "1.7.0"
appCompat = "1.4.1"
activityCompose = "1.4.0"
work = "2.6.0"
# Android Lifecycle
lifecycle = "2.4.1"
@@ -75,6 +76,7 @@ materialDialogsCore = { module = "ca.gosyer:compose-material-dialogs-core", vers
core = { module = "androidx.core:core-ktx", version.ref = "core" }
appCompat = { module = "androidx.appcompat:appcompat", version.ref = "appCompat" }
activityCompose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
work = { module = "androidx.work:work-runtime-ktx", version.ref = "work" }
# Android Lifecycle
lifecycleCommon = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle" }

View File

@@ -10,6 +10,7 @@ import ca.gosyer.core.di.AppScope
import ca.gosyer.data.DataComponent
import ca.gosyer.ui.base.image.KamelConfigProvider
import ca.gosyer.ui.base.vm.ViewModelFactoryImpl
import ca.gosyer.uicore.vm.ContextWrapper
import ca.gosyer.uicore.vm.LocalViewModelFactory
import io.kamel.core.config.KamelConfig
import io.kamel.image.config.LocalKamelConfig
@@ -28,6 +29,8 @@ actual abstract class UiComponent(
actual abstract val kamelConfig: KamelConfig
actual abstract val contextWrapper: ContextWrapper
@get:AppScope
@get:Provides
protected actual val kamelConfigFactory: KamelConfig

View File

@@ -6,8 +6,24 @@
package ca.gosyer.ui.categories
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
actual fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator) {
navigator push CategoriesScreen(notifyFinished)
actual class CategoriesLauncher(private val navigator: Navigator?) {
actual fun open() {
navigator?.push(CategoriesScreen())
}
@Composable
actual fun CategoriesWindow() {
}
}
@Composable
actual fun rememberCategoriesLauncher(notifyFinished: () -> Unit): CategoriesLauncher {
val navigator = LocalNavigator.current
return remember(navigator) { CategoriesLauncher(navigator) }
}

View File

@@ -23,6 +23,10 @@ actual class ReaderLauncher(private val context: Context) {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
}.let(context::startActivity)
}
@Composable
actual fun Reader() {
}
}
@Composable

View File

@@ -10,6 +10,7 @@ import ca.gosyer.core.di.AppScope
import ca.gosyer.data.DataComponent
import ca.gosyer.ui.base.image.KamelConfigProvider
import ca.gosyer.ui.base.vm.ViewModelFactoryImpl
import ca.gosyer.uicore.vm.ContextWrapper
import ca.gosyer.uicore.vm.LocalViewModelFactory
import io.kamel.core.config.KamelConfig
import io.kamel.image.config.LocalKamelConfig
@@ -28,6 +29,8 @@ actual abstract class UiComponent(
actual abstract val kamelConfig: KamelConfig
actual abstract val contextWrapper: ContextWrapper
@get:AppScope
@get:Provides
protected actual val kamelConfigFactory: KamelConfig

View File

@@ -6,22 +6,39 @@
package ca.gosyer.ui.categories
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.window.Window
import ca.gosyer.presentation.build.BuildKonfig
import ca.gosyer.ui.AppComponent
import ca.gosyer.ui.util.compose.ThemedWindow
import ca.gosyer.ui.util.lang.launchApplication
import cafe.adriel.voyager.navigator.Navigator
import kotlinx.coroutines.DelicateCoroutinesApi
@OptIn(DelicateCoroutinesApi::class)
actual fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator) {
launchApplication {
CompositionLocalProvider(*remember { AppComponent.getInstance().uiComponent.getHooks() }) {
ThemedWindow(::exitApplication, title = "${BuildKonfig.NAME} - Categories") {
actual class CategoriesLauncher(private val notifyFinished: () -> Unit) {
private var isOpen by mutableStateOf(false)
actual fun open() {
isOpen = true
}
@Composable
actual fun CategoriesWindow() {
if (isOpen) {
Window(
onCloseRequest = { isOpen = false },
title = "${BuildKonfig.NAME} - Categories",
icon = painterResource("icon.png")
) {
Navigator(remember { CategoriesScreen(notifyFinished) })
}
}
}
}
@Composable
actual fun rememberCategoriesLauncher(notifyFinished: () -> Unit): CategoriesLauncher {
return remember(notifyFinished) { CategoriesLauncher(notifyFinished) }
}

View File

@@ -7,23 +7,39 @@
package ca.gosyer.ui.main.components
import ca.gosyer.data.update.UpdateChecker
import ca.gosyer.data.update.UpdatePreferences
import ca.gosyer.data.update.model.GithubRelease
import ca.gosyer.uicore.vm.ContextWrapper
import ca.gosyer.uicore.vm.ViewModel
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.tatarka.inject.annotations.Inject
class TrayViewModel @Inject constructor(
private val updateChecker: UpdateChecker,
updateChecker: UpdateChecker,
updatePreferences: UpdatePreferences,
contextWrapper: ContextWrapper
) : ViewModel(contextWrapper) {
override val scope = MainScope()
private val _updateFound = MutableSharedFlow<GithubRelease>()
val updateFound = _updateFound.asSharedFlow()
init {
updateChecker.checkForUpdates()
if (updatePreferences.enabled().get()) {
updateChecker.checkForUpdates()
.onEach {
if (it is UpdateChecker.Update.UpdateFound) {
_updateFound.emit(it.release)
}
}
.launchIn(scope)
}
}
val updateFound
get() = updateChecker.updateFound
override fun onDispose() {
super.onDispose()

View File

@@ -9,32 +9,73 @@ package ca.gosyer.ui.reader
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.currentCompositionLocalContext
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberWindowState
import ca.gosyer.data.ui.model.WindowSettings
import ca.gosyer.presentation.build.BuildKonfig
import ca.gosyer.ui.AppComponent
import ca.gosyer.ui.base.theme.AppTheme
import ca.gosyer.ui.util.compose.WindowGet
import ca.gosyer.ui.util.lang.launchApplication
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
actual class ReaderLauncher {
private var isOpen by mutableStateOf<Pair<Int, Long>?>(null)
actual fun launch(
chapterIndex: Int,
mangaId: Long
) {
openReaderMenu(chapterIndex, mangaId)
isOpen = chapterIndex to mangaId
}
@OptIn(DelicateCoroutinesApi::class)
@Composable
actual fun Reader() {
val localParams = currentCompositionLocalContext
DisposableEffect(isOpen) {
isOpen?.let { (chapterIndex, mangaId) ->
launchApplication {
val scope = rememberCoroutineScope()
val hotkeyFlow = remember { MutableSharedFlow<KeyEvent>() }
val windowState = rememberWindowState(
position = WindowPosition.Aligned(Alignment.Center)
)
val icon = painterResource("icon.png")
CompositionLocalProvider(localParams) {
Window(
onCloseRequest = ::exitApplication,
title = "${BuildKonfig.NAME} - Reader",
icon = icon,
state = windowState,
onKeyEvent = {
if (it.type != KeyEventType.KeyDown) return@Window false
scope.launch {
hotkeyFlow.emit(it)
}
it.key in supportedKeyList
}
) {
ReaderMenu(chapterIndex, mangaId, hotkeyFlow)
}
}
}
}
isOpen = null
onDispose {}
}
}
}
@@ -42,58 +83,3 @@ actual class ReaderLauncher {
actual fun rememberReaderLauncher(): ReaderLauncher {
return remember { ReaderLauncher() }
}
@OptIn(DelicateCoroutinesApi::class)
fun openReaderMenu(chapterIndex: Int, mangaId: Long) {
val windowSettings = AppComponent.getInstance().dataComponent.uiPreferences
.readerWindow()
val (
position,
size,
placement
) = WindowGet.from(windowSettings.get())
val hooks = AppComponent.getInstance().uiComponent.getHooks()
launchApplication {
val scope = rememberCoroutineScope()
val hotkeyFlow = remember { MutableSharedFlow<KeyEvent>() }
val icon = painterResource("icon.png")
val windowState = rememberWindowState(size = size, position = position, placement = placement)
DisposableEffect(Unit) {
onDispose {
windowSettings.set(
WindowSettings(
windowState.position.x.value.toInt(),
windowState.position.y.value.toInt(),
windowState.size.width.value.toInt(),
windowState.size.height.value.toInt(),
windowState.placement == WindowPlacement.Maximized,
windowState.placement == WindowPlacement.Fullscreen
)
)
}
}
Window(
onCloseRequest = ::exitApplication,
title = "${BuildKonfig.NAME} - Reader",
icon = icon,
state = windowState,
onKeyEvent = {
if (it.type != KeyEventType.KeyDown) return@Window false
scope.launch {
hotkeyFlow.emit(it)
}
it.key in supportedKeyList
}
) {
CompositionLocalProvider(
*hooks
) {
AppTheme {
ReaderMenu(chapterIndex, mangaId, hotkeyFlow)
}
}
}
}
}

View File

@@ -1,18 +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
import ca.gosyer.data.DataComponent
import ca.gosyer.ui.base.UiComponent
import ca.gosyer.uicore.vm.ContextWrapper
expect abstract class AppComponent {
val dataComponent: DataComponent
val uiComponent: UiComponent
abstract val contextWrapper: ContextWrapper
}

View File

@@ -10,6 +10,7 @@ import androidx.compose.runtime.ProvidedValue
import ca.gosyer.data.DataComponent
import ca.gosyer.ui.base.image.KamelConfigProvider
import ca.gosyer.ui.base.vm.ViewModelFactoryImpl
import ca.gosyer.uicore.vm.ContextWrapper
import io.kamel.core.config.KamelConfig
expect abstract class UiComponent {
@@ -20,6 +21,8 @@ expect abstract class UiComponent {
abstract val kamelConfig: KamelConfig
abstract val contextWrapper: ContextWrapper
protected val kamelConfigFactory: KamelConfig
fun getHooks(): Array<ProvidedValue<out Any>>

View File

@@ -13,9 +13,17 @@ import ca.gosyer.uicore.vm.viewModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.ScreenKey
import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.Navigator
expect fun openCategoriesMenu(notifyFinished: () -> Unit, navigator: Navigator)
expect class CategoriesLauncher {
fun open()
@Composable
fun CategoriesWindow()
}
@Composable
expect fun rememberCategoriesLauncher(notifyFinished: () -> Unit): CategoriesLauncher
class CategoriesScreen(
@Transient

View File

@@ -72,6 +72,7 @@ fun MangaScreenContent(
}
}
val readerLauncher = rememberReaderLauncher()
readerLauncher.Reader()
Scaffold(
topBar = {

View File

@@ -83,6 +83,9 @@ expect class ReaderLauncher {
chapterIndex: Int,
mangaId: Long
)
@Composable
fun Reader()
}
@Composable

View File

@@ -27,7 +27,7 @@ import ca.gosyer.ui.base.components.rememberScrollbarAdapter
import ca.gosyer.ui.base.navigation.Toolbar
import ca.gosyer.ui.base.prefs.PreferenceRow
import ca.gosyer.ui.base.prefs.SwitchPreference
import ca.gosyer.ui.categories.openCategoriesMenu
import ca.gosyer.ui.categories.rememberCategoriesLauncher
import ca.gosyer.uicore.prefs.PreferenceMutableStateFlow
import ca.gosyer.uicore.resources.stringResource
import ca.gosyer.uicore.vm.ContextWrapper
@@ -36,8 +36,6 @@ import ca.gosyer.uicore.vm.viewModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.screen.ScreenKey
import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
@@ -51,12 +49,13 @@ class SettingsLibraryScreen : Screen {
@Composable
override fun Content() {
val vm = viewModel<SettingsLibraryViewModel>()
val navigator = LocalNavigator.currentOrThrow
val categoriesLauncher = rememberCategoriesLauncher(vm::refreshCategoryCount)
SettingsLibraryScreenContent(
showAllCategory = vm.showAllCategory,
categoriesSize = vm.categories.collectAsState().value,
openCategoriesScreen = { openCategoriesMenu(vm::refreshCategoryCount, navigator) }
openCategoriesScreen = categoriesLauncher::open
)
categoriesLauncher.CategoriesWindow()
}
}

View File

@@ -37,5 +37,6 @@ class UpdatesScreen : Screen {
deleteDownloadedChapter = vm::deleteDownloadedChapter,
stopDownloadingChapter = vm::stopDownloadingChapter
)
readerLauncher.Reader()
}
}