mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-11-12 17:44:27 +00:00
Merge pull request #13652 from Idadelveloper/settings-home
Migrate settings homepage to compose multiplatform
This commit is contained in:
26 files changed
+618
-1
No files matched your search
@@ -688,6 +688,7 @@ public final class NavigationHelper {
|
||||
}
|
||||
|
||||
public static void openSettings(final Context context) {
|
||||
// TODO: Replace with "ContextKt.navigateTo(context, Destination.Settings.INSTANCE);" later
|
||||
final Intent intent = new Intent(context, SettingsActivity.class);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ kotlin {
|
||||
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xexpect-actual-classes"
|
||||
"-Xexpect-actual-classes",
|
||||
"-Xexplicit-backing-fields"
|
||||
)
|
||||
optIn.addAll(
|
||||
"androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.platform
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import co.touchlab.kermit.Logger
|
||||
import org.koin.core.annotation.Singleton
|
||||
|
||||
@Singleton(binds = [BuildInfo::class])
|
||||
class AndroidBuildInfo(private val context: Context) : BuildInfo {
|
||||
|
||||
override val isDebug: Boolean =
|
||||
context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0
|
||||
|
||||
@get:SuppressLint("NewApi")
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val isReleaseApk: Boolean by lazy {
|
||||
val certificates = mapOf(
|
||||
RELEASE_CERT_PUBLIC_KEY_SHA256.hexToByteArray() to PackageManager.CERT_INPUT_SHA256
|
||||
)
|
||||
try {
|
||||
PackageInfoCompat.hasSignatures(
|
||||
context.packageManager,
|
||||
context.packageName,
|
||||
certificates,
|
||||
false
|
||||
)
|
||||
} catch (exception: PackageManager.NameNotFoundException) {
|
||||
Logger.e(messageString = "Could not find package info", throwable = exception)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val RELEASE_CERT_PUBLIC_KEY_SHA256 =
|
||||
"cb84069bd68116bafae5ee4ee5b08a567aa6d898404e7cb12f9e756df5cf5cab"
|
||||
}
|
||||
}
|
||||
@@ -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,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z" />
|
||||
</vector>
|
||||
@@ -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="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" />
|
||||
</vector>
|
||||
@@ -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="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
|
||||
</vector>
|
||||
@@ -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="M12,1c-4.97,0 -9,4.03 -9,9v7c0,1.66 1.34,3 3,3h3v-8H5v-2c0,-3.87 3.13,-7 7,-7s7,3.13 7,7v2h-4v8h3c1.66,0 3,-1.34 3,-3v-7c0,-4.97 -4.03,-9 -9,-9z" />
|
||||
</vector>
|
||||
@@ -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="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
|
||||
</vector>
|
||||
@@ -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="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z" />
|
||||
</vector>
|
||||
@@ -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="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
|
||||
</vector>
|
||||
@@ -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="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10c1.38,0 2.5,-1.12 2.5,-2.5c0,-0.61 -0.23,-1.2 -0.64,-1.67c-0.08,-0.1 -0.13,-0.21 -0.13,-0.33c0,-0.28 0.22,-0.5 0.5,-0.5H16c3.31,0 6,-2.69 6,-6C22,6.04 17.51,2 12,2zM17.5,13c-0.83,0 -1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5C19,12.33 18.33,13 17.5,13zM14.5,9C13.67,9 13,8.33 13,7.5C13,6.67 13.67,6 14.5,6S16,6.67 16,7.5C16,8.33 15.33,9 14.5,9zM5,11.5C5,10.67 5.67,10 6.5,10S8,10.67 8,11.5C8,12.33 7.33,13 6.5,13S5,12.33 5,11.5zM11,7.5C11,8.33 10.33,9 9.5,9S8,8.33 8,7.5C8,6.67 8.67,6 9.5,6S11,6.67 11,7.5z" />
|
||||
</vector>
|
||||
@@ -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="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
||||
</vector>
|
||||
@@ -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="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z" />
|
||||
</vector>
|
||||
@@ -34,4 +34,17 @@
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="not_available">Not available</string>
|
||||
|
||||
<!-- SettingsHomeScreen -->
|
||||
<string name="settings">Settings</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="content">Content</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
<string name="settings_category_video_audio_title">Video and audio</string>
|
||||
<string name="settings_category_downloads_title">Download</string>
|
||||
<string name="settings_category_appearance_title">Appearance</string>
|
||||
<string name="settings_category_history_title">History and cache</string>
|
||||
<string name="settings_category_updates_title">Updates</string>
|
||||
<string name="settings_category_backup_restore_title">Backup and restore</string>
|
||||
<string name="settings_category_debug_title">Debug</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.clickable
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.Text
|
||||
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 newpipe.shared.generated.resources.Res
|
||||
import newpipe.shared.generated.resources.ic_headset
|
||||
import newpipe.shared.generated.resources.settings_category_video_audio_title
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
/**
|
||||
* A row that mirrors the standard AndroidX preference item
|
||||
*
|
||||
* @param title Title text of the preference
|
||||
* @param modifier Modifier applied to the row
|
||||
* @param icon Leading icon painter; null hides the leading slot
|
||||
* @param summary Optional secondary line under the title
|
||||
* @param onClick Action executed when the row is tapped
|
||||
*/
|
||||
@Composable
|
||||
fun PreferenceRow(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
icon: Painter? = null,
|
||||
summary: String? = null,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
ListItem(
|
||||
modifier = modifier.clickable(onClick = onClick),
|
||||
headlineContent = { Text(text = title) },
|
||||
supportingContent = summary?.let { text -> { Text(text = text) } },
|
||||
leadingContent = icon?.let { painter ->
|
||||
{ Icon(painter = painter, contentDescription = null) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun PreferenceRowPreview() {
|
||||
PreferenceRow(
|
||||
title = stringResource(Res.string.settings_category_video_audio_title),
|
||||
icon = painterResource(Res.drawable.ic_headset)
|
||||
)
|
||||
}
|
||||
@@ -15,6 +15,9 @@ import net.newpipe.app.model.License
|
||||
@Serializable
|
||||
sealed interface Destination : NavKey {
|
||||
|
||||
@Serializable
|
||||
data object Settings : Destination
|
||||
|
||||
@Serializable
|
||||
data object About : Destination
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package net.newpipe.app.navigation
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import co.touchlab.kermit.Logger
|
||||
import net.newpipe.app.screen.about.AboutScreen
|
||||
import net.newpipe.app.screen.settings.SettingsHomeScreen
|
||||
import org.koin.core.annotation.KoinExperimentalAPI
|
||||
import org.koin.core.annotation.Provided
|
||||
import org.koin.core.annotation.Singleton
|
||||
@@ -27,6 +28,10 @@ fun navModule() = module {
|
||||
navigation<Destination.About> {
|
||||
AboutScreen()
|
||||
}
|
||||
|
||||
navigation<Destination.Settings> {
|
||||
SettingsHomeScreen()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.platform
|
||||
|
||||
/**
|
||||
* Information about the currently running build.
|
||||
*/
|
||||
|
||||
interface BuildInfo {
|
||||
val isReleaseApk: Boolean
|
||||
val isDebug: Boolean
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.screen.settings
|
||||
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import net.newpipe.app.composable.PreferenceRow
|
||||
import net.newpipe.app.composable.TopAppBar
|
||||
import net.newpipe.app.navigation.Navigator
|
||||
import net.newpipe.app.preview.ThemePreviewProvider
|
||||
import net.newpipe.app.screen.settings.model.SettingsCategory
|
||||
import net.newpipe.app.screen.settings.model.SettingsCategoryType
|
||||
import net.newpipe.app.viewmodel.settings.SettingsViewModel
|
||||
import newpipe.shared.generated.resources.Res
|
||||
import newpipe.shared.generated.resources.ic_search
|
||||
import newpipe.shared.generated.resources.search
|
||||
import newpipe.shared.generated.resources.settings
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun SettingsHomeScreen(
|
||||
navigator: Navigator = koinInject(),
|
||||
viewModel: SettingsViewModel = koinViewModel()
|
||||
) {
|
||||
val categories by viewModel.categories.collectAsStateWithLifecycle()
|
||||
|
||||
SettingsHomeScreenContent(
|
||||
categories = categories.map { type ->
|
||||
SettingsCategory(
|
||||
title = type.title,
|
||||
icon = type.icon,
|
||||
// TODO: Replace with a Destination once sub-screens are migrated
|
||||
onClick = {}
|
||||
)
|
||||
},
|
||||
onNavigateUp = { navigator.navigateUp() }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsHomeScreenContent(
|
||||
categories: List<SettingsCategory> = SettingsCategoryType.entries.map { type ->
|
||||
SettingsCategory(title = type.title, icon = type.icon)
|
||||
},
|
||||
onNavigateUp: () -> Unit = {},
|
||||
onSearchClick: () -> Unit = {}
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = stringResource(Res.string.settings),
|
||||
onNavigateUp = onNavigateUp,
|
||||
actions = {
|
||||
IconButton(onClick = onSearchClick) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_search),
|
||||
contentDescription = stringResource(Res.string.search)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding()),
|
||||
contentPadding = WindowInsets.navigationBars.asPaddingValues()
|
||||
) {
|
||||
items(items = categories) { category ->
|
||||
PreferenceRow(
|
||||
title = stringResource(category.title),
|
||||
icon = painterResource(category.icon),
|
||||
onClick = category.onClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun SettingsHomeScreenPreview() {
|
||||
SettingsHomeScreenContent()
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.screen.settings.model
|
||||
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
data class SettingsCategory(
|
||||
val title: StringResource,
|
||||
val icon: DrawableResource,
|
||||
// TODO: Replace with a Destination once sub-screens are migrated
|
||||
val onClick: () -> Unit = {}
|
||||
)
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package net.newpipe.app.screen.settings.model
|
||||
|
||||
import newpipe.shared.generated.resources.Res
|
||||
import newpipe.shared.generated.resources.content
|
||||
import newpipe.shared.generated.resources.ic_bug_report
|
||||
import newpipe.shared.generated.resources.ic_cloud_download
|
||||
import newpipe.shared.generated.resources.ic_file_download
|
||||
import newpipe.shared.generated.resources.ic_headset
|
||||
import newpipe.shared.generated.resources.ic_history
|
||||
import newpipe.shared.generated.resources.ic_language
|
||||
import newpipe.shared.generated.resources.ic_notifications
|
||||
import newpipe.shared.generated.resources.ic_palette
|
||||
import newpipe.shared.generated.resources.ic_settings_backup_restore
|
||||
import newpipe.shared.generated.resources.notifications
|
||||
import newpipe.shared.generated.resources.settings_category_appearance_title
|
||||
import newpipe.shared.generated.resources.settings_category_backup_restore_title
|
||||
import newpipe.shared.generated.resources.settings_category_debug_title
|
||||
import newpipe.shared.generated.resources.settings_category_downloads_title
|
||||
import newpipe.shared.generated.resources.settings_category_history_title
|
||||
import newpipe.shared.generated.resources.settings_category_updates_title
|
||||
import newpipe.shared.generated.resources.settings_category_video_audio_title
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
/**
|
||||
* Categories on the settings home screen.
|
||||
*/
|
||||
enum class SettingsCategoryType(
|
||||
val title: StringResource,
|
||||
val icon: DrawableResource
|
||||
) {
|
||||
VIDEO_AUDIO(Res.string.settings_category_video_audio_title, Res.drawable.ic_headset),
|
||||
DOWNLOADS(Res.string.settings_category_downloads_title, Res.drawable.ic_file_download),
|
||||
APPEARANCE(Res.string.settings_category_appearance_title, Res.drawable.ic_palette),
|
||||
HISTORY(Res.string.settings_category_history_title, Res.drawable.ic_history),
|
||||
CONTENT(Res.string.content, Res.drawable.ic_language),
|
||||
NOTIFICATIONS(Res.string.notifications, Res.drawable.ic_notifications),
|
||||
UPDATES(Res.string.settings_category_updates_title, Res.drawable.ic_cloud_download),
|
||||
BACKUP_RESTORE(Res.string.settings_category_backup_restore_title, Res.drawable.ic_settings_backup_restore),
|
||||
DEBUG(Res.string.settings_category_debug_title, Res.drawable.ic_bug_report)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.viewmodel.settings
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import net.newpipe.app.platform.BuildInfo
|
||||
import net.newpipe.app.screen.settings.model.SettingsCategoryType
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
|
||||
@KoinViewModel
|
||||
class SettingsViewModel(buildInfo: BuildInfo) : ViewModel() {
|
||||
|
||||
val categories: StateFlow<List<SettingsCategoryType>>
|
||||
field = MutableStateFlow(computeVisible(buildInfo))
|
||||
|
||||
private fun computeVisible(buildInfo: BuildInfo): List<SettingsCategoryType> =
|
||||
SettingsCategoryType.entries.filter { type ->
|
||||
when (type) {
|
||||
SettingsCategoryType.UPDATES -> buildInfo.isReleaseApk
|
||||
SettingsCategoryType.DEBUG -> buildInfo.isDebug
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.v2.runComposeUiTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
class PreferenceRowTest {
|
||||
|
||||
@Test
|
||||
fun testPreferenceRowTitleAndClick() = runComposeUiTest {
|
||||
val title = "Test title"
|
||||
var clicked = false
|
||||
setContent {
|
||||
PreferenceRow(
|
||||
title = title,
|
||||
onClick = { clicked = true }
|
||||
)
|
||||
}
|
||||
|
||||
onNodeWithText(title).apply {
|
||||
assertIsDisplayed()
|
||||
performClick()
|
||||
}
|
||||
assertTrue(clicked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPreferenceRowShowsSummary() = runComposeUiTest {
|
||||
val title = "Test title"
|
||||
val summary = "Test summary"
|
||||
setContent {
|
||||
PreferenceRow(title = title, summary = summary)
|
||||
}
|
||||
|
||||
onNodeWithText(title).assertIsDisplayed()
|
||||
onNodeWithText(summary).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.screen.settings
|
||||
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
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 net.newpipe.app.extensions.withKoin
|
||||
import net.newpipe.app.screen.settings.model.SettingsCategory
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
import newpipe.shared.generated.resources.Res
|
||||
import newpipe.shared.generated.resources.ic_headset
|
||||
import newpipe.shared.generated.resources.settings
|
||||
import newpipe.shared.generated.resources.settings_category_video_audio_title
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.dsl.module
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
class SettingsHomeScreenTest {
|
||||
|
||||
private val emptySettings = module {
|
||||
single<Settings> { MapSettings() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTitleAndCategoriesRender() = runComposeUiTest {
|
||||
withKoin(
|
||||
modules = listOf(emptySettings),
|
||||
content = { SettingsHomeScreenContent() },
|
||||
onContent = {
|
||||
onNodeWithText(getString(Res.string.settings)).assertIsDisplayed()
|
||||
onNodeWithText(getString(Res.string.settings_category_video_audio_title))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCategoryClickFires() = runComposeUiTest {
|
||||
var clicked = false
|
||||
val categories = listOf(
|
||||
SettingsCategory(
|
||||
title = Res.string.settings_category_video_audio_title,
|
||||
icon = Res.drawable.ic_headset,
|
||||
onClick = { clicked = true }
|
||||
)
|
||||
)
|
||||
withKoin(
|
||||
modules = listOf(emptySettings),
|
||||
content = { SettingsHomeScreenContent(categories = categories) },
|
||||
onContent = {
|
||||
onNodeWithText(getString(Res.string.settings_category_video_audio_title))
|
||||
.performClick()
|
||||
assertTrue(clicked)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.newpipe.app.platform
|
||||
|
||||
import org.koin.core.annotation.Singleton
|
||||
|
||||
@Singleton(binds = [BuildInfo::class])
|
||||
class IOSBuildInfo : BuildInfo {
|
||||
override val isReleaseApk: Boolean = false
|
||||
override val isDebug: Boolean = false
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.newpipe.app.platform
|
||||
|
||||
import org.koin.core.annotation.Singleton
|
||||
|
||||
@Singleton(binds = [BuildInfo::class])
|
||||
class JVMBuildInfo : BuildInfo {
|
||||
override val isReleaseApk: Boolean = false
|
||||
override val isDebug: Boolean = false
|
||||
}
|
||||
Reference in new issue
Block a user