Merge pull request #13525 from theimpulson/cmp
shared: Add top app bar composable to reflect different active services
This commit is contained in:
commit
d4698f9d8e
15 changed files with 526 additions and 2 deletions
|
|
@ -157,7 +157,8 @@ pinterest-ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "
|
|||
puppycrawl-checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
|
||||
reactivex-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroid" }
|
||||
reactivex-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava" }
|
||||
russhwolf-settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "settings" }
|
||||
russhwolf-settings-core = { module = "com.russhwolf:multiplatform-settings", version.ref = "settings" }
|
||||
russhwolf-settings-test = { module = "com.russhwolf:multiplatform-settings-test", version.ref = "settings" }
|
||||
squareup-leakcanary-core = { module = "com.squareup.leakcanary:leakcanary-android-core", version.ref = "leakcanary" }
|
||||
squareup-leakcanary-plumber = { module = "com.squareup.leakcanary:plumber-android", version.ref = "leakcanary" }
|
||||
squareup-leakcanary-watcher = { module = "com.squareup.leakcanary:leakcanary-object-watcher-android", version.ref = "leakcanary" }
|
||||
|
|
|
|||
|
|
@ -107,12 +107,13 @@ kotlin {
|
|||
implementation(libs.koin.compose.viewmodel)
|
||||
implementation(libs.koin.annotations)
|
||||
|
||||
implementation(libs.russhwolf.settings)
|
||||
implementation(libs.russhwolf.settings.core)
|
||||
}
|
||||
}
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test.core)
|
||||
implementation(libs.jetbrains.compose.test.ui)
|
||||
implementation(libs.russhwolf.settings.test)
|
||||
}
|
||||
androidMain.dependencies {
|
||||
implementation(libs.jetbrains.compose.preview)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.di.settings
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.russhwolf.settings.Settings
|
||||
import com.russhwolf.settings.SharedPreferencesSettings
|
||||
import org.koin.core.annotation.Singleton
|
||||
|
||||
/**
|
||||
* Settings for Android based on SharedPreferences
|
||||
*/
|
||||
@Singleton
|
||||
fun provideSettings(context: Context): Settings = SharedPreferencesSettings(
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||
~ SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
|
||||
</vector>
|
||||
|
|
@ -5,4 +5,7 @@
|
|||
-->
|
||||
<resources>
|
||||
<string name="app_name">NewPipe</string>
|
||||
|
||||
<!-- TopAppBar -->
|
||||
<string name="navigate_back">Back</string>
|
||||
</resources>
|
||||
|
|
|
|||
10
shared/src/commonMain/kotlin/net/newpipe/app/Constants.kt
Normal file
10
shared/src/commonMain/kotlin/net/newpipe/app/Constants.kt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app
|
||||
|
||||
object Constants {
|
||||
const val KEY_STREAMING_SERVICE = "service"
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.composable
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||
import net.newpipe.app.preview.ThemePreviewProvider
|
||||
import net.newpipe.app.theme.currentServiceTopAppBarColors
|
||||
import newpipe.shared.generated.resources.Res
|
||||
import newpipe.shared.generated.resources.app_name
|
||||
import newpipe.shared.generated.resources.ic_arrow_back
|
||||
import newpipe.shared.generated.resources.navigate_back
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
/**
|
||||
* A top app bar composable to be used with Scaffold
|
||||
* @param modifier The modifier to be applied to the composable
|
||||
* @param title Title of the screen
|
||||
* @param navigationIcon Icon for the navigation button
|
||||
* @param onNavigateUp Action when user clicks the navigation icon
|
||||
* @param actions Actions to display on the top app bar (for e.g. menu)
|
||||
*/
|
||||
@Composable
|
||||
fun TopAppBar(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null,
|
||||
navigationIcon: Painter = painterResource(Res.drawable.ic_arrow_back),
|
||||
onNavigateUp: (() -> Unit)? = null,
|
||||
colors: TopAppBarColors = currentServiceTopAppBarColors(),
|
||||
actions: @Composable (RowScope.() -> Unit) = {}
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
colors = colors,
|
||||
title = { if (title != null) Text(text = title) },
|
||||
navigationIcon = {
|
||||
if (onNavigateUp != null) {
|
||||
IconButton(onClick = onNavigateUp) {
|
||||
Icon(
|
||||
painter = navigationIcon,
|
||||
contentDescription = stringResource(Res.string.navigate_back)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun TopAppBarPreview() {
|
||||
TopAppBar(
|
||||
title = stringResource(Res.string.app_name),
|
||||
onNavigateUp = {}
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.di.settings
|
||||
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Configuration
|
||||
import org.koin.core.annotation.Module
|
||||
|
||||
/**
|
||||
* Settings module to access key-value pairs across different platforms.
|
||||
* See individual platform packages for the declarations included in this module.
|
||||
*/
|
||||
@Module
|
||||
@ComponentScan
|
||||
@Configuration
|
||||
class SettingsModule
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.preview
|
||||
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.PreviewWrapperProvider
|
||||
import net.newpipe.app.theme.AppTheme
|
||||
|
||||
/**
|
||||
* Default preview provider for composables
|
||||
*/
|
||||
class ThemePreviewProvider : PreviewWrapperProvider {
|
||||
|
||||
@Composable
|
||||
override fun Wrap(content: @Composable (() -> Unit)) {
|
||||
AppTheme {
|
||||
Surface(content = content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.TopAppBarColors
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import com.russhwolf.settings.Settings
|
||||
import net.newpipe.app.Constants.KEY_STREAMING_SERVICE
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
val youTubeLightScheme = lightColorScheme(
|
||||
primaryContainer = Color(0xFFE53935),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val youTubeDarkScheme = darkColorScheme(
|
||||
primaryContainer = Color(0xFF992722),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val soundCloudLightScheme = lightColorScheme(
|
||||
primaryContainer = Color(0xFFF57C00),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val soundCloudDarkScheme = darkColorScheme(
|
||||
primaryContainer = Color(0xFFA35300),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val mediaCCCLightScheme = lightColorScheme(
|
||||
primaryContainer = Color(0xFF9E9E9E),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val mediaCCCDarkScheme = darkColorScheme(
|
||||
primaryContainer = Color(0xFF878787),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val peerTubeLightScheme = lightColorScheme(
|
||||
primaryContainer = Color(0xFFFF6F00),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val peerTubeDarkScheme = darkColorScheme(
|
||||
primaryContainer = Color(0xFFA34700),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val bandCampLightScheme = lightColorScheme(
|
||||
primaryContainer = Color(0xFF17A0C4),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
val bandCampDarkScheme = darkColorScheme(
|
||||
primaryContainer = Color(0xFF1383A1),
|
||||
onPrimaryContainer = Color(0xFFFFFFFF)
|
||||
)
|
||||
|
||||
/**
|
||||
* Supported services in the NewPipe app and minor information about them for UI decisions.
|
||||
* @property serviceId ID of the service as defined in NewPipeExtractor
|
||||
* @property serviceName Name of the service as defined in NewPipeExtractor
|
||||
* @property lightScheme Light color scheme to reflect the brand
|
||||
* @property darkScheme Dark color scheme to reflect the brand
|
||||
* @property isSchemeColorDensityLight Whether this brand's color schemes are of lighter density.
|
||||
*/
|
||||
enum class Service(
|
||||
val serviceId: Int,
|
||||
val serviceName: String,
|
||||
val lightScheme: ColorScheme,
|
||||
val darkScheme: ColorScheme,
|
||||
val isSchemeColorDensityLight: Boolean = false
|
||||
) {
|
||||
YOUTUBE(
|
||||
serviceId = 0,
|
||||
serviceName = "YouTube",
|
||||
lightScheme = youTubeLightScheme,
|
||||
darkScheme = youTubeDarkScheme
|
||||
),
|
||||
SOUNDCLOUD(
|
||||
serviceId = 1,
|
||||
serviceName = "SoundCloud",
|
||||
lightScheme = soundCloudLightScheme,
|
||||
darkScheme = soundCloudDarkScheme
|
||||
),
|
||||
MEDIA_CCC(
|
||||
serviceId = 2,
|
||||
serviceName = "media.ccc.de",
|
||||
lightScheme = mediaCCCLightScheme,
|
||||
darkScheme = mediaCCCDarkScheme
|
||||
),
|
||||
PEERTUBE(
|
||||
serviceId = 3,
|
||||
serviceName = "PeerTube",
|
||||
lightScheme = peerTubeLightScheme,
|
||||
darkScheme = peerTubeDarkScheme
|
||||
),
|
||||
BANDCAMP(
|
||||
serviceId = 4,
|
||||
serviceName = "Bandcamp",
|
||||
lightScheme = bandCampLightScheme,
|
||||
darkScheme = bandCampDarkScheme
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently active/selected service by user
|
||||
*/
|
||||
@Composable
|
||||
fun currentService(settings: Settings = koinInject()): Service {
|
||||
return Service.entries.find { service ->
|
||||
service.serviceName == settings.getString(
|
||||
KEY_STREAMING_SERVICE,
|
||||
Service.YOUTUBE.serviceName
|
||||
)
|
||||
}!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently active/selected service's color that can be used to represent it.
|
||||
* Fallbacks to YouTube on preview.
|
||||
*/
|
||||
@Composable
|
||||
fun currentServiceScheme(
|
||||
isPreview: Boolean = LocalInspectionMode.current,
|
||||
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||
service: Service = if (isPreview) Service.YOUTUBE else currentService()
|
||||
): ColorScheme {
|
||||
return when {
|
||||
useDarkTheme -> service.darkScheme
|
||||
else -> service.lightScheme
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Top app bar colors to represent the currently active service.
|
||||
* Fallbacks to YouTube on preview.
|
||||
*/
|
||||
@Composable
|
||||
fun currentServiceTopAppBarColors(
|
||||
serviceScheme: ColorScheme = currentServiceScheme()
|
||||
): TopAppBarColors {
|
||||
return TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = serviceScheme.primaryContainer,
|
||||
scrolledContainerColor = serviceScheme.primaryContainer,
|
||||
navigationIconContentColor = serviceScheme.onPrimaryContainer,
|
||||
titleContentColor = serviceScheme.onPrimaryContainer,
|
||||
subtitleContentColor = serviceScheme.onPrimaryContainer,
|
||||
actionIconContentColor = serviceScheme.onPrimaryContainer
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.composable
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.v2.runComposeUiTest
|
||||
import com.russhwolf.settings.MapSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
import net.newpipe.app.extensions.withKoin
|
||||
import newpipe.shared.generated.resources.Res
|
||||
import newpipe.shared.generated.resources.app_name
|
||||
import newpipe.shared.generated.resources.navigate_back
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.dsl.module
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
class TopAppBarTest {
|
||||
|
||||
private val emptySettings = module {
|
||||
single<Settings> { MapSettings() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopAppBarHasNoNavigationByDefault() = runComposeUiTest {
|
||||
withKoin(
|
||||
modules = listOf(emptySettings),
|
||||
content = {
|
||||
TopAppBar()
|
||||
},
|
||||
onContent = {
|
||||
onNodeWithContentDescription(getString(Res.string.navigate_back))
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTopAppBarCanHaveNavigation() = runComposeUiTest {
|
||||
var navigationBackClicked = false
|
||||
withKoin(
|
||||
modules = listOf(emptySettings),
|
||||
content = {
|
||||
TopAppBar(
|
||||
title = stringResource(Res.string.app_name),
|
||||
onNavigateUp = { navigationBackClicked = true }
|
||||
)
|
||||
},
|
||||
onContent = {
|
||||
onNodeWithText(getString(Res.string.app_name)).assertIsDisplayed()
|
||||
onNodeWithContentDescription(getString(Res.string.navigate_back)).apply {
|
||||
assertExists()
|
||||
performClick()
|
||||
assertTrue(navigationBackClicked)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.extensions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.ComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import org.koin.compose.KoinApplication
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.core.logger.Level
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.koinConfiguration
|
||||
|
||||
/**
|
||||
* Sets the content for the UI test wrapped inside Koin
|
||||
* @param modules Modules for Koin to init for the composables
|
||||
* @param content Composable content for testing
|
||||
* @param onContent Non-composable code for testing, maybe dependent upon composable code
|
||||
*/
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
inline fun ComposeUiTest.withKoin(
|
||||
modules: List<Module>,
|
||||
noinline content: @Composable () -> Unit = {},
|
||||
onContent: () -> Unit = {}
|
||||
) {
|
||||
try {
|
||||
setContent {
|
||||
KoinApplication(
|
||||
configuration = koinConfiguration {
|
||||
modules(modules)
|
||||
},
|
||||
logLevel = Level.DEBUG,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
onContent()
|
||||
} finally {
|
||||
stopKoin()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.theme
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.v2.runComposeUiTest
|
||||
import com.russhwolf.settings.MapSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import net.newpipe.app.Constants.KEY_STREAMING_SERVICE
|
||||
import net.newpipe.app.extensions.withKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
class ServiceThemeTest {
|
||||
|
||||
@Test
|
||||
fun testDefaultServiceIsYouTube() = runComposeUiTest {
|
||||
val emptySettings = module {
|
||||
single<Settings> { MapSettings() }
|
||||
}
|
||||
|
||||
withKoin(
|
||||
modules = listOf(emptySettings),
|
||||
content = {
|
||||
assertEquals(currentService(), Service.YOUTUBE)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testServiceSwitchingWorks() = runComposeUiTest {
|
||||
val settings = module {
|
||||
single<Settings> {
|
||||
MapSettings(KEY_STREAMING_SERVICE to "PeerTube")
|
||||
}
|
||||
}
|
||||
|
||||
withKoin(
|
||||
modules = listOf(settings),
|
||||
content = {
|
||||
assertNotEquals(currentService(), Service.YOUTUBE)
|
||||
assertEquals(currentService(), Service.PEERTUBE)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.di.settings
|
||||
|
||||
import com.russhwolf.settings.NSUserDefaultsSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import org.koin.core.annotation.Singleton
|
||||
import platform.Foundation.NSUserDefaults
|
||||
|
||||
/**
|
||||
* Settings for iOS based on UserDefaultsSettings
|
||||
*/
|
||||
@Singleton
|
||||
fun provideSettings(): Settings = NSUserDefaultsSettings(NSUserDefaults())
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.di.settings
|
||||
|
||||
import com.russhwolf.settings.PreferencesSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import java.util.prefs.Preferences
|
||||
import org.koin.core.annotation.Singleton
|
||||
|
||||
/**
|
||||
* Settings for JVM devices based on Java Preferences
|
||||
*/
|
||||
@Singleton
|
||||
fun provideSettings(): Settings = PreferencesSettings(Preferences.userRoot())
|
||||
Loading…
Add table
Add a link
Reference in a new issue