feat: Initial source repo support

Mostly just getting Compose to work as intended, no functionality at the moment
This commit is contained in:
ziro 2024-01-12 13:53:41 +07:00
parent a1172f31e8
commit 16d5ff12e4
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
12 changed files with 315 additions and 8 deletions

View file

@ -1,14 +1,7 @@
package dev.yokai.domain.source package dev.yokai.domain.source
import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.core.preference.PreferenceStore
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import java.util.*
class SourcePreferences(private val preferenceStore: PreferenceStore) { class SourcePreferences(private val preferenceStore: PreferenceStore) {
fun lastUsedSources() = preferenceStore.getStringSet("last_used_sources", emptySet()) fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
fun enabledLanguages() = preferenceStore.getStringSet(
PreferenceKeys.enabledLanguages,
setOfNotNull("all", "en", Locale.getDefault().language.takeIf { !it.startsWith("en") }),
)
} }

View file

@ -0,0 +1,90 @@
package dev.yokai.presentation
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.core.view.WindowInsetsControllerCompat
import dev.yokai.presentation.component.ToolTipButton
import eu.kanade.tachiyomi.R
@Composable
fun YokaiScaffold(
onNavigationIconClicked: () -> Unit,
modifier: Modifier = Modifier,
title: String = "",
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(state = rememberTopAppBarState()),
fab: @Composable () -> Unit = {},
navigationIcon: ImageVector = Icons.Filled.ArrowBack,
navigationIconLabel: String = stringResource(id = R.string.back),
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
val view = LocalView.current
val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5
val color = getTopAppBarColor(title)
SideEffect {
val activity = view.context as Activity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity.window.statusBarColor = color.toArgb()
WindowInsetsControllerCompat(activity.window, view).isAppearanceLightStatusBars = useDarkIcons
}
}
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
floatingActionButton = fab,
topBar = {
LargeTopAppBar(
title = {
Text(text = title)
},
modifier = Modifier.statusBarsPadding(),
colors = topAppBarColors(
containerColor = color,
scrolledContainerColor = color,
),
navigationIcon = {
ToolTipButton(
toolTipLabel = navigationIconLabel,
icon = navigationIcon,
buttonClicked = onNavigationIconClicked,
)
},
scrollBehavior = scrollBehavior,
actions = actions,
)
},
content = content,
)
}
@Composable
fun getTopAppBarColor(title: String): Color {
return when (title.isEmpty()) {
true -> Color.Transparent
false -> MaterialTheme.colorScheme.surface.copy(alpha = .7f)
}
}

View file

@ -0,0 +1,129 @@
package dev.yokai.presentation.component
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltipBox
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
@Composable
fun ToolTipButton(
toolTipLabel: String,
modifier: Modifier = Modifier,
iconModifier: Modifier = Modifier,
icon: ImageVector? = null,
painter: Painter? = null,
isEnabled: Boolean = true,
enabledTint: Color = MaterialTheme.colorScheme.onSurface,
buttonClicked: () -> Unit = {},
) {
require(icon != null || painter != null)
val haptic = LocalHapticFeedback.current
PlainTooltipBox(
tooltip = { Text(modifier = Modifier.padding(4.dp), style = MaterialTheme.typography.bodyLarge, text = toolTipLabel) },
contentColor = MaterialTheme.colorScheme.onSurface,
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(8.dp),
) {
CombinedClickableIconButton(
enabled = isEnabled,
enabledTint = enabledTint,
modifier = modifier
.tooltipAnchor()
.iconButtonCombinedClickable(
toolTipLabel = toolTipLabel,
onClick = buttonClicked,
isEnabled = isEnabled,
onLongClick = {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
),
) {
if (icon != null) {
Icon(
imageVector = icon,
modifier = iconModifier,
contentDescription = toolTipLabel,
)
} else {
Icon(
painter = painter!!,
modifier = iconModifier,
contentDescription = toolTipLabel,
)
}
}
}
}
@Composable
fun CombinedClickableIconButton(
modifier: Modifier = Modifier,
enabled: Boolean = true,
enabledTint: Color,
content: @Composable () -> Unit,
) {
Box(
modifier =
modifier
.minimumInteractiveComponentSize()
.size(40.0.dp),
contentAlignment = Alignment.Center,
) {
val contentColor =
if (enabled) {
enabledTint
} else {
MaterialTheme.colorScheme.onSurface
.copy(alpha = .38f)
}
CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
}
}
fun Modifier.iconButtonCombinedClickable(
toolTipLabel: String,
isEnabled: Boolean,
onLongClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
onClick: () -> Unit,
) = composed {
if (isEnabled) {
combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(
bounded = false,
radius = 40.dp / 2,
),
onClickLabel = toolTipLabel,
role = Role.Button,
onClick = onClick,
onLongClick = onLongClick,
onDoubleClick = onDoubleClick,
)
} else {
this
}
}

View file

@ -0,0 +1,20 @@
package dev.yokai.presentation.source
import androidx.compose.runtime.Composable
import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController
class SourceRepoController :
BaseComposeController() {
override fun getTitle(): String {
return "Extension Repos"
}
@Composable
override fun ScreenContent() {
SourceRepoScreen(
title = getTitle().orEmpty(),
onBackPress = router::handleBack,
)
}
}

View file

@ -0,0 +1,40 @@
package dev.yokai.presentation.source
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import dev.yokai.presentation.YokaiScaffold
import eu.kanade.tachiyomi.util.system.toast
@Preview
@Composable
fun SourceRepoScreen(
title: String,
onBackPress: () -> Unit,
) {
val context = LocalContext.current
YokaiScaffold(
onNavigationIconClicked = onBackPress,
title = title,
fab = {
FloatingActionButton(
onClick = { context.toast("Test") },
) {
Icon(Icons.Filled.Add, "Add repo")
}
}
) { innerPadding ->
Text(
modifier = Modifier.padding(innerPadding),
text = "Hello World!"
)
}
}

View file

@ -292,6 +292,7 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
fun automaticExtUpdates() = preferenceStore.getBoolean(Keys.automaticExtUpdates, true) fun automaticExtUpdates() = preferenceStore.getBoolean(Keys.automaticExtUpdates, true)
// TODO: SourcePref
fun installedExtensionsOrder() = preferenceStore.getInt(Keys.installedExtensionsOrder, InstalledExtensionsOrder.Name.value) fun installedExtensionsOrder() = preferenceStore.getInt(Keys.installedExtensionsOrder, InstalledExtensionsOrder.Name.value)
// TODO: SourcePref // TODO: SourcePref
@ -342,6 +343,7 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE) fun migrateFlags() = preferenceStore.getInt("migrate_flags", Int.MAX_VALUE)
// TODO: SourcePref
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet()) fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
// using string instead of set so it is ordered // using string instead of set so it is ordered
@ -357,6 +359,7 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
fun refreshCoversToo() = preferenceStore.getBoolean(Keys.refreshCoversToo, true) fun refreshCoversToo() = preferenceStore.getBoolean(Keys.refreshCoversToo, true)
// TODO: SourcePref
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0) fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
fun recentsViewType() = preferenceStore.getInt("recents_view_type", 0) fun recentsViewType() = preferenceStore.getInt("recents_view_type", 0)
@ -461,6 +464,7 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto
fun appShouldAutoUpdate() = prefs.getInt(Keys.shouldAutoUpdate, AppDownloadInstallJob.ONLY_ON_UNMETERED) fun appShouldAutoUpdate() = prefs.getInt(Keys.shouldAutoUpdate, AppDownloadInstallJob.ONLY_ON_UNMETERED)
// TODO: SourcePref
fun autoUpdateExtensions() = prefs.getInt(Keys.autoUpdateExtensions, AppDownloadInstallJob.ONLY_ON_UNMETERED) fun autoUpdateExtensions() = prefs.getInt(Keys.autoUpdateExtensions, AppDownloadInstallJob.ONLY_ON_UNMETERED)
fun extensionInstaller() = preferenceStore.getInt("extension_installer", ExtensionInstaller.PACKAGE_INSTALLER) fun extensionInstaller() = preferenceStore.getInt("extension_installer", ExtensionInstaller.PACKAGE_INSTALLER)

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.di package eu.kanade.tachiyomi.di
import android.app.Application import android.app.Application
import dev.yokai.domain.source.SourcePreferences
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.core.preference.PreferenceStore
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -15,6 +16,10 @@ class PreferenceModule(val application: Application) : InjektModule {
AndroidPreferenceStore(application) AndroidPreferenceStore(application)
} }
addSingletonFactory {
SourcePreferences(get())
}
addSingletonFactory { addSingletonFactory {
PreferencesHelper( PreferencesHelper(
context = application, context = application,

View file

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import dev.yokai.presentation.theme.YokaiTheme import dev.yokai.presentation.theme.YokaiTheme
abstract class BaseComposeController(bundle: Bundle? = null) : abstract class BaseComposeController(bundle: Bundle? = null) :
@ -16,7 +17,13 @@ abstract class BaseComposeController(bundle: Bundle? = null) :
container: ViewGroup, container: ViewGroup,
savedViewState: Bundle? savedViewState: Bundle?
): View { ): View {
hideLegacyAppBar()
return ComposeView(container.context).apply { return ComposeView(container.context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent { setContent {
YokaiTheme { YokaiTheme {
ScreenContent() ScreenContent()

View file

@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.view.BackHandlerControllerInterface import eu.kanade.tachiyomi.util.view.BackHandlerControllerInterface
import eu.kanade.tachiyomi.util.view.activityBinding import eu.kanade.tachiyomi.util.view.activityBinding
@ -176,4 +177,12 @@ abstract class BaseController(bundle: Bundle? = null) :
true true
} }
} }
fun hideLegacyAppBar() {
(activity as? AppCompatActivity)?.findViewById<View>(R.id.app_bar)?.isVisible = false
}
fun showLegacyAppBar() {
(activity as? AppCompatActivity)?.findViewById<View>(R.id.app_bar)?.isVisible = true
}
} }

View file

@ -16,6 +16,7 @@ abstract class BaseLegacyController<VB : ViewBinding>(bundle: Bundle? = null) :
val isBindingInitialized get() = this::binding.isInitialized val isBindingInitialized get() = this::binding.isInitialized
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
showLegacyAppBar()
binding = createBinding(inflater) binding = createBinding(inflater)
binding.root.backgroundColor = binding.root.context.getResourceColor(R.attr.background) binding.root.backgroundColor = binding.root.context.getResourceColor(R.attr.background)
return binding.root return binding.root

View file

@ -6,6 +6,7 @@ import android.os.Build
import android.provider.Settings import android.provider.Settings
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import dev.yokai.presentation.source.SourceRepoController
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
@ -39,6 +40,13 @@ class SettingsBrowseController : SettingsController() {
preferenceCategory { preferenceCategory {
titleRes = R.string.extensions titleRes = R.string.extensions
preference {
titleRes = R.string.source_repos
onClick { router.pushController(SourceRepoController().withFadeTransaction()) }
// TODO: Enable once it's finished
summary = "Temporarily disabled, will be enabled once it's fully implemented"
isEnabled = BuildConfig.DEBUG
}
switchPreference { switchPreference {
key = PreferenceKeys.automaticExtUpdates key = PreferenceKeys.automaticExtUpdates
titleRes = R.string.check_for_extension_updates titleRes = R.string.check_for_extension_updates

View file

@ -905,6 +905,7 @@
<string name="nsfw_sources">NSFW (18+) sources</string> <string name="nsfw_sources">NSFW (18+) sources</string>
<string name="show_in_sources_and_extensions">Show in sources and extensions lists</string> <string name="show_in_sources_and_extensions">Show in sources and extensions lists</string>
<string name="does_not_prevent_unofficial_nsfw">This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app.</string> <string name="does_not_prevent_unofficial_nsfw">This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app.</string>
<string name="source_repos">Extension Repos</string>
<!-- About section --> <!-- About section -->
<string name="version">Version</string> <string name="version">Version</string>