From 0123b51638a99e0b851ce132ae5f8b7b6b9f9811 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Wed, 7 Jan 2026 12:45:46 +0800 Subject: [PATCH 1/5] Initial support for compose multiplatform Signed-off-by: Aayush Gupta --- build.gradle.kts | 5 ++ composeApp/build.gradle.kts | 73 +++++++++++++++++++ .../src/androidMain/AndroidManifest.xml | 13 ++++ .../kotlin/net/newpipe/app/ComposeActivity.kt | 22 ++++++ .../composeResources/values/strings.xml | 8 ++ .../commonMain/kotlin/net/newpipe/app/App.kt | 18 +++++ .../net/newpipe/app/ComposeAppCommonTest.kt | 17 +++++ .../net/newpipe/app/MainViewController.kt | 10 +++ .../jvmMain/kotlin/net/newpipe/app/main.kt | 18 +++++ gradle/libs.versions.toml | 14 ++++ iosApp/Configuration/Config.xcconfig | 0 iosApp/iosApp.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app-icon-1024.png | 0 iosApp/iosApp/Assets.xcassets/Contents.json | 0 iosApp/iosApp/ContentView.swift | 0 iosApp/iosApp/Info.plist | 0 .../Preview Assets.xcassets/Contents.json | 0 iosApp/iosApp/iOSApp.swift | 0 settings.gradle.kts | 1 + 22 files changed, 199 insertions(+) create mode 100644 composeApp/build.gradle.kts create mode 100644 composeApp/src/androidMain/AndroidManifest.xml create mode 100644 composeApp/src/androidMain/kotlin/net/newpipe/app/ComposeActivity.kt create mode 100644 composeApp/src/commonMain/composeResources/values/strings.xml create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt create mode 100644 composeApp/src/commonTest/kotlin/net/newpipe/app/ComposeAppCommonTest.kt create mode 100644 composeApp/src/iosMain/kotlin/net/newpipe/app/MainViewController.kt create mode 100644 composeApp/src/jvmMain/kotlin/net/newpipe/app/main.kt create mode 100644 iosApp/Configuration/Config.xcconfig create mode 100644 iosApp/iosApp.xcodeproj/project.pbxproj create mode 100644 iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png create mode 100644 iosApp/iosApp/Assets.xcassets/Contents.json create mode 100644 iosApp/iosApp/ContentView.swift create mode 100644 iosApp/iosApp/Info.plist create mode 100644 iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 iosApp/iosApp/iOSApp.swift diff --git a/build.gradle.kts b/build.gradle.kts index 2c9173f575c..ca5003b2cfe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,13 @@ plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false alias(libs.plugins.jetbrains.kotlin.android) apply false alias(libs.plugins.jetbrains.kotlin.kapt) apply false + alias(libs.plugins.jetbrains.kotlin.compose) apply false + alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false + alias(libs.plugins.jetbrains.compose.multiplatform) apply false + alias(libs.plugins.jetbrains.compose.hotreload) apply false alias(libs.plugins.google.ksp) apply false alias(libs.plugins.jetbrains.kotlin.parcelize) apply false alias(libs.plugins.sonarqube) apply false diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts new file mode 100644 index 00000000000..952c7063c3f --- /dev/null +++ b/composeApp/build.gradle.kts @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import org.jetbrains.compose.desktop.application.dsl.TargetFormat + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.multiplatform) + alias(libs.plugins.jetbrains.kotlin.compose) + alias(libs.plugins.jetbrains.compose.multiplatform) + alias(libs.plugins.jetbrains.compose.hotreload) + alias(libs.plugins.google.ksp) + alias(libs.plugins.jetbrains.kotlin.parcelize) +} + +kotlin { + jvmToolchain(17) + + androidLibrary { + namespace = "net.newpipe.app" + compileSdk = 36 + minSdk = 21 + } + + listOf( + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + jvm() + + sourceSets { + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + implementation(libs.jetbrains.lifecycle.viewmodel) + } + commonTest.dependencies { + implementation(libs.kotlin.test) + } + androidMain.dependencies { + implementation(compose.preview) + implementation(libs.androidx.activity) + } + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + implementation(libs.jetbrains.kotlinx.coroutinesSwing) + } + } +} + +compose.desktop { + application { + mainClass = "net.newpipe.app.MainKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "net.newpipe.app" + packageVersion = "1.0.0" + } + } +} diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000000..2a78e9e3c4c --- /dev/null +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/composeApp/src/androidMain/kotlin/net/newpipe/app/ComposeActivity.kt b/composeApp/src/androidMain/kotlin/net/newpipe/app/ComposeActivity.kt new file mode 100644 index 00000000000..ab716d9edd2 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/net/newpipe/app/ComposeActivity.kt @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge + +class ComposeActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + + setContent { + App() + } + } +} diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 00000000000..db4b09e008f --- /dev/null +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,8 @@ + + + + NewPipe + diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt new file mode 100644 index 00000000000..b6cfe27b100 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +@Preview +fun App() { + MaterialTheme { + + } +} diff --git a/composeApp/src/commonTest/kotlin/net/newpipe/app/ComposeAppCommonTest.kt b/composeApp/src/commonTest/kotlin/net/newpipe/app/ComposeAppCommonTest.kt new file mode 100644 index 00000000000..1dc47619029 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/net/newpipe/app/ComposeAppCommonTest.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ComposeAppCommonTest { + + @Test + fun example() { + assertEquals(3, 1 + 2) + } +} diff --git a/composeApp/src/iosMain/kotlin/net/newpipe/app/MainViewController.kt b/composeApp/src/iosMain/kotlin/net/newpipe/app/MainViewController.kt new file mode 100644 index 00000000000..d368521e8e0 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/net/newpipe/app/MainViewController.kt @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app + +import androidx.compose.ui.window.ComposeUIViewController + +fun MainViewController() = ComposeUIViewController { App() } diff --git a/composeApp/src/jvmMain/kotlin/net/newpipe/app/main.kt b/composeApp/src/jvmMain/kotlin/net/newpipe/app/main.kt new file mode 100644 index 00000000000..b5faff0509e --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/net/newpipe/app/main.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app + +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import newpipe.composeapp.generated.resources.Res +import newpipe.composeapp.generated.resources.app_name +import org.jetbrains.compose.resources.stringResource + +fun main() = application { + Window(onCloseRequest = ::exitApplication, title = stringResource(Res.string.app_name)) { + App() + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a7a856a41e0..ad2591358b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ [versions] acra = "5.13.1" +activity = "1.12.2" agp = "8.13.2" appcompat = "1.7.1" assertj = "3.27.6" @@ -15,11 +16,13 @@ cardview = "1.0.0" checkstyle = "12.2.0" constraintlayout = "2.2.1" core = "1.17.0" +coroutines = "1.10.2" desugar = "2.1.5" documentfile = "1.1.0" exoplayer = "2.19.1" fragment = "1.8.9" groupie = "2.10.1" +hotreload = "1.0.0" jsoup = "1.21.2" junit = "4.13.2" junit-ext = "1.3.0" @@ -28,11 +31,13 @@ ksp = "2.3.2" ktlint = "1.8.0" leakcanary = "2.14" lifecycle = "2.9.4" # Newer versions require minSdk >= 23 +lifecycle-jetbrains = "2.9.6" localbroadcastmanager = "1.1.0" markwon = "4.6.2" material = "1.11.0" # TODO: update to newer version after bug is fixed. See https://github.com/TeamNewPipe/NewPipe/pull/13018 media = "1.7.1" mockitoCore = "5.21.0" +multiplatform = "1.9.3" okhttp = "5.3.2" phoenix = "3.0.0" #noinspection NewerVersionAvailable,GradleDependency --> 2.8 is the last version, not 2.71828! @@ -67,6 +72,7 @@ work = "2.10.5" # Newer versions require minSdk >= 23 [libraries] acra-core = { module = "ch.acra:acra-core", version.ref = "acra" } android-desugar = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar" } +androidx-activity = { module = "androidx.activity:activity-compose", version.ref = "activity" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } androidx-cardview = { module = "androidx.cardview:cardview", version.ref = "cardview" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } @@ -107,8 +113,11 @@ google-exoplayer-smoothstreaming = { module = "com.google.android.exoplayer:exop google-exoplayer-ui = { module = "com.google.android.exoplayer:exoplayer-ui", version.ref = "exoplayer" } jakewharton-phoenix = { module = "com.jakewharton:process-phoenix", version.ref = "phoenix" } jakewharton-rxbinding = { module = "com.jakewharton.rxbinding4:rxbinding", version.ref = "rxbinding" } +jetbrains-kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" } +jetbrains-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle-jetbrains" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } lisawray-groupie-core = { module = "com.github.lisawray.groupie:groupie", version.ref = "groupie" } lisawray-groupie-viewbinding = { module = "com.github.lisawray.groupie:groupie-viewbinding", version.ref = "groupie" } livefront-bridge = { module = "com.github.livefront:bridge", version.ref = "bridge" } @@ -132,8 +141,13 @@ zacsweers-autoservice-compiler = { module = "dev.zacsweers.autoservice:auto-serv [plugins] android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } google-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +jetbrains-compose-hotreload = { id = "org.jetbrains.compose.hot-reload", version.ref = "hotreload" } +jetbrains-compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "multiplatform" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +jetbrains-kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } jetbrains-kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } # Needed for statesaver +jetbrains-kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } jetbrains-kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift new file mode 100644 index 00000000000..e69de29bb2d diff --git a/settings.gradle.kts b/settings.gradle.kts index 60a40c985c9..1958e2ff8e3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,7 @@ dependencyResolutionManagement { } } include (":app") +include("composeApp") // Use a local copy of NewPipe Extractor by uncommenting the lines below. // We assume, that NewPipe and NewPipe Extractor have the same parent directory. From 84e4ce8b4630bb18b13c529b2010d77fdb7d61c0 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Wed, 7 Jan 2026 16:05:15 +0800 Subject: [PATCH 2/5] Import compose theme setup from refactor Strip out Android-specific implementation for handling black theme for now Signed-off-by: Aayush Gupta --- .../commonMain/kotlin/net/newpipe/app/App.kt | 4 +- .../kotlin/net/newpipe/app/theme/Color.kt | 81 ++++++++++++++ .../kotlin/net/newpipe/app/theme/Theme.kt | 100 ++++++++++++++++++ 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Color.kt create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt index b6cfe27b100..dd5d556c7a0 100644 --- a/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt @@ -5,14 +5,14 @@ package net.newpipe.app -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import net.newpipe.app.theme.AppTheme import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @Preview fun App() { - MaterialTheme { + AppTheme { } } diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Color.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Color.kt new file mode 100644 index 00000000000..5bb59ee2e03 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Color.kt @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2024 NewPipe contributors + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.theme + +import androidx.compose.ui.graphics.Color + +val primaryLight = Color(0xFF904A45) +val onPrimaryLight = Color(0xFFFFFFFF) +val primaryContainerLight = Color(0xFFFFDAD6) +val onPrimaryContainerLight = Color(0xFF3B0908) +val secondaryLight = Color(0xFF775653) +val onSecondaryLight = Color(0xFFFFFFFF) +val secondaryContainerLight = Color(0xFFFFDAD6) +val onSecondaryContainerLight = Color(0xFF2C1513) +val tertiaryLight = Color(0xFF725B2E) +val onTertiaryLight = Color(0xFFFFFFFF) +val tertiaryContainerLight = Color(0xFFFEDEA6) +val onTertiaryContainerLight = Color(0xFF261900) +val errorLight = Color(0xFFBA1A1A) +val onErrorLight = Color(0xFFFFFFFF) +val errorContainerLight = Color(0xFFFFDAD6) +val onErrorContainerLight = Color(0xFF410002) +val backgroundLight = Color(0xFFFFF8F7) +val onBackgroundLight = Color(0xFF231918) +val surfaceLight = Color(0xFFFFF8F7) +val onSurfaceLight = Color(0xFF231918) +val surfaceVariantLight = Color(0xFFF5DDDB) +val onSurfaceVariantLight = Color(0xFF534342) +val outlineLight = Color(0xFF857371) +val outlineVariantLight = Color(0xFFD8C2BF) +val scrimLight = Color(0xFF000000) +val inverseSurfaceLight = Color(0xFF392E2D) +val inverseOnSurfaceLight = Color(0xFFFFEDEB) +val inversePrimaryLight = Color(0xFFFFB3AC) +val surfaceDimLight = Color(0xFFE8D6D4) +val surfaceBrightLight = Color(0xFFFFF8F7) +val surfaceContainerLowestLight = Color(0xFFFFFFFF) +val surfaceContainerLowLight = Color(0xFFFFF0EF) +val surfaceContainerLight = Color(0xFFFCEAE8) +val surfaceContainerHighLight = Color(0xFFF6E4E2) +val surfaceContainerHighestLight = Color(0xFFF1DEDC) + +val primaryDark = Color(0xFFFFB3AC) +val onPrimaryDark = Color(0xFF571E1B) +val primaryContainerDark = Color(0xFF73332F) +val onPrimaryContainerDark = Color(0xFFFFDAD6) +val secondaryDark = Color(0xFFE7BDB8) +val onSecondaryDark = Color(0xFF442927) +val secondaryContainerDark = Color(0xFF5D3F3C) +val onSecondaryContainerDark = Color(0xFFFFDAD6) +val tertiaryDark = Color(0xFFE1C38C) +val onTertiaryDark = Color(0xFF402D04) +val tertiaryContainerDark = Color(0xFF584419) +val onTertiaryContainerDark = Color(0xFFFEDEA6) +val errorDark = Color(0xFFFFB4AB) +val onErrorDark = Color(0xFF690005) +val errorContainerDark = Color(0xFF93000A) +val onErrorContainerDark = Color(0xFFFFDAD6) +val backgroundDark = Color(0xFF1A1110) +val onBackgroundDark = Color(0xFFF1DEDC) +val surfaceDark = Color(0xFF1A1110) +val onSurfaceDark = Color(0xFFF1DEDC) +val surfaceVariantDark = Color(0xFF534342) +val onSurfaceVariantDark = Color(0xFFD8C2BF) +val outlineDark = Color(0xFFA08C8A) +val outlineVariantDark = Color(0xFF534342) +val scrimDark = Color(0xFF000000) +val inverseSurfaceDark = Color(0xFFF1DEDC) +val inverseOnSurfaceDark = Color(0xFF392E2D) +val inversePrimaryDark = Color(0xFF904A45) +val surfaceDimDark = Color(0xFF1A1110) +val surfaceBrightDark = Color(0xFF423735) +val surfaceContainerLowestDark = Color(0xFF140C0B) +val surfaceContainerLowDark = Color(0xFF231918) +val surfaceContainerDark = Color(0xFF271D1C) +val surfaceContainerHighDark = Color(0xFF322827) +val surfaceContainerHighestDark = Color(0xFF3D3231) diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt new file mode 100644 index 00000000000..2d4356bd6e1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2024 NewPipe contributors + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable + +private val lightScheme = lightColorScheme( + primary = primaryLight, + onPrimary = onPrimaryLight, + primaryContainer = primaryContainerLight, + onPrimaryContainer = onPrimaryContainerLight, + secondary = secondaryLight, + onSecondary = onSecondaryLight, + secondaryContainer = secondaryContainerLight, + onSecondaryContainer = onSecondaryContainerLight, + tertiary = tertiaryLight, + onTertiary = onTertiaryLight, + tertiaryContainer = tertiaryContainerLight, + onTertiaryContainer = onTertiaryContainerLight, + error = errorLight, + onError = onErrorLight, + errorContainer = errorContainerLight, + onErrorContainer = onErrorContainerLight, + background = backgroundLight, + onBackground = onBackgroundLight, + surface = surfaceLight, + onSurface = onSurfaceLight, + surfaceVariant = surfaceVariantLight, + onSurfaceVariant = onSurfaceVariantLight, + outline = outlineLight, + outlineVariant = outlineVariantLight, + scrim = scrimLight, + inverseSurface = inverseSurfaceLight, + inverseOnSurface = inverseOnSurfaceLight, + inversePrimary = inversePrimaryLight, + surfaceDim = surfaceDimLight, + surfaceBright = surfaceBrightLight, + surfaceContainerLowest = surfaceContainerLowestLight, + surfaceContainerLow = surfaceContainerLowLight, + surfaceContainer = surfaceContainerLight, + surfaceContainerHigh = surfaceContainerHighLight, + surfaceContainerHighest = surfaceContainerHighestLight, +) + +private val darkScheme = darkColorScheme( + primary = primaryDark, + onPrimary = onPrimaryDark, + primaryContainer = primaryContainerDark, + onPrimaryContainer = onPrimaryContainerDark, + secondary = secondaryDark, + onSecondary = onSecondaryDark, + secondaryContainer = secondaryContainerDark, + onSecondaryContainer = onSecondaryContainerDark, + tertiary = tertiaryDark, + onTertiary = onTertiaryDark, + tertiaryContainer = tertiaryContainerDark, + onTertiaryContainer = onTertiaryContainerDark, + error = errorDark, + onError = onErrorDark, + errorContainer = errorContainerDark, + onErrorContainer = onErrorContainerDark, + background = backgroundDark, + onBackground = onBackgroundDark, + surface = surfaceDark, + onSurface = onSurfaceDark, + surfaceVariant = surfaceVariantDark, + onSurfaceVariant = onSurfaceVariantDark, + outline = outlineDark, + outlineVariant = outlineVariantDark, + scrim = scrimDark, + inverseSurface = inverseSurfaceDark, + inverseOnSurface = inverseOnSurfaceDark, + inversePrimary = inversePrimaryDark, + surfaceDim = surfaceDimDark, + surfaceBright = surfaceBrightDark, + surfaceContainerLowest = surfaceContainerLowestDark, + surfaceContainerLow = surfaceContainerLowDark, + surfaceContainer = surfaceContainerDark, + surfaceContainerHigh = surfaceContainerHighDark, + surfaceContainerHighest = surfaceContainerHighestDark, +) + +@Composable +fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + MaterialTheme( + colorScheme = when { + !useDarkTheme -> lightScheme + else -> darkScheme + }, + content = content + ) +} From c34bb6768967ae139d2adccab0c3aeee5649a3fa Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Wed, 7 Jan 2026 17:47:44 +0800 Subject: [PATCH 3/5] Import and setup Koin for multiplatform dependency injection Signed-off-by: Aayush Gupta --- composeApp/build.gradle.kts | 29 +++++++++++++++++++ .../commonMain/kotlin/net/newpipe/app/App.kt | 15 ++++++++-- .../kotlin/net/newpipe/app/module/Koin.kt | 12 ++++++++ gradle/libs.versions.toml | 9 ++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 952c7063c3f..b097d4cebf8 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -44,10 +44,23 @@ kotlin { implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) + + // Lifecycle implementation(libs.jetbrains.lifecycle.viewmodel) + + // Koin + implementation(project.dependencies.platform(libs.koin.bom)) + api(libs.koin.annotations) + implementation(libs.koin.core) + implementation(libs.koin.compose) + implementation(libs.koin.viewmodel) } commonTest.dependencies { implementation(libs.kotlin.test) + + // Koin + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.test) } androidMain.dependencies { implementation(compose.preview) @@ -58,8 +71,24 @@ kotlin { implementation(libs.jetbrains.kotlinx.coroutinesSwing) } } + + // Koin + sourceSets.named("commonMain").configure { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") + } +} + +// Koin +dependencies { + add("kspCommonMainMetadata", libs.koin.annotations) + add("kspAndroid", libs.koin.compiler) + add("kspIosArm64", libs.koin.compiler) + add("kspIosSimulatorArm64", libs.koin.compiler) } +tasks.matching { it.name.startsWith("ksp") && it.name != "kspCommonMainKotlinMetadata" } + .configureEach { dependsOn("kspCommonMainKotlinMetadata") } + compose.desktop { application { mainClass = "net.newpipe.app.MainKt" diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt index dd5d556c7a0..5ac426ed877 100644 --- a/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt @@ -6,13 +6,22 @@ package net.newpipe.app import androidx.compose.runtime.Composable +import net.newpipe.app.module.appModules import net.newpipe.app.theme.AppTheme -import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.KoinMultiplatformApplication +import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.dsl.koinConfiguration +@OptIn(KoinExperimentalAPI::class) @Composable -@Preview fun App() { - AppTheme { + KoinMultiplatformApplication( + config = koinConfiguration { + modules(appModules) + } + ) { + AppTheme { + } } } diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt new file mode 100644 index 00000000000..12654bb25c4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.module + +import org.koin.dsl.module + +val appModules = module { + +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ad2591358b2..8c544195ec3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,8 @@ hotreload = "1.0.0" jsoup = "1.21.2" junit = "4.13.2" junit-ext = "1.3.0" +koin-annotations = "2.3.1" +koin-bom = "4.1.1" kotlin = "2.2.21" ksp = "2.3.2" ktlint = "1.8.0" @@ -117,6 +119,13 @@ jetbrains-kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-co jetbrains-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle-jetbrains" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } +koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-annotations" } +koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" } +koin-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koin-annotations" } +koin-compose = { module = "io.insert-koin:koin-compose" } +koin-core = { module = "io.insert-koin:koin-core" } +koin-test = { module = "io.insert-koin:koin-test" } +koin-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } lisawray-groupie-core = { module = "com.github.lisawray.groupie:groupie", version.ref = "groupie" } lisawray-groupie-viewbinding = { module = "com.github.lisawray.groupie:groupie-viewbinding", version.ref = "groupie" } From b01ce34b55d9fa2f82314f80d4f982abee3d4548 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Fri, 9 Jan 2026 15:45:28 +0800 Subject: [PATCH 4/5] Setup multiplatform settings with KMP and theme Signed-off-by: Aayush Gupta --- composeApp/build.gradle.kts | 8 +++---- .../net/newpipe/app/module/Koin.android.kt | 18 ++++++++++++++ .../commonMain/kotlin/net/newpipe/app/App.kt | 4 ++-- .../kotlin/net/newpipe/app/module/Koin.kt | 9 +++---- .../kotlin/net/newpipe/app/theme/Theme.kt | 24 +++++++++++++++---- .../kotlin/net/newpipe/app/module/Koin.ios.kt | 17 +++++++++++++ .../kotlin/net/newpipe/app/module/Koin.jvm.kt | 17 +++++++++++++ gradle/libs.versions.toml | 11 +++++---- 8 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/net/newpipe/app/module/Koin.android.kt create mode 100644 composeApp/src/iosMain/kotlin/net/newpipe/app/module/Koin.ios.kt create mode 100644 composeApp/src/jvmMain/kotlin/net/newpipe/app/module/Koin.jvm.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index b097d4cebf8..92e8dc7f13e 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -49,22 +49,22 @@ kotlin { implementation(libs.jetbrains.lifecycle.viewmodel) // Koin - implementation(project.dependencies.platform(libs.koin.bom)) api(libs.koin.annotations) implementation(libs.koin.core) implementation(libs.koin.compose) implementation(libs.koin.viewmodel) + + // Settings + implementation(libs.russhwolf.settings) } commonTest.dependencies { implementation(libs.kotlin.test) - - // Koin - implementation(project.dependencies.platform(libs.koin.bom)) implementation(libs.koin.test) } androidMain.dependencies { implementation(compose.preview) implementation(libs.androidx.activity) + implementation(libs.androidx.preference) } jvmMain.dependencies { implementation(compose.desktop.currentOs) diff --git a/composeApp/src/androidMain/kotlin/net/newpipe/app/module/Koin.android.kt b/composeApp/src/androidMain/kotlin/net/newpipe/app/module/Koin.android.kt new file mode 100644 index 00000000000..4f0c22d402b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/net/newpipe/app/module/Koin.android.kt @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.module + +import androidx.preference.PreferenceManager +import com.russhwolf.settings.Settings +import com.russhwolf.settings.SharedPreferencesSettings +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +actual val platformModule = module { + single { + SharedPreferencesSettings(PreferenceManager.getDefaultSharedPreferences(androidContext())) + } +} diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt index 5ac426ed877..466742c6848 100644 --- a/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/App.kt @@ -6,7 +6,7 @@ package net.newpipe.app import androidx.compose.runtime.Composable -import net.newpipe.app.module.appModules +import net.newpipe.app.module.platformModule import net.newpipe.app.theme.AppTheme import org.koin.compose.KoinMultiplatformApplication import org.koin.core.annotation.KoinExperimentalAPI @@ -17,7 +17,7 @@ import org.koin.dsl.koinConfiguration fun App() { KoinMultiplatformApplication( config = koinConfiguration { - modules(appModules) + modules(platformModule) } ) { AppTheme { diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt index 12654bb25c4..6bc961c9fde 100644 --- a/composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/module/Koin.kt @@ -5,8 +5,9 @@ package net.newpipe.app.module -import org.koin.dsl.module +import org.koin.core.module.Module -val appModules = module { - -} +/** + * Contains platform specific module; See actual implementation for more details + */ +expect val platformModule: Module diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt index 2d4356bd6e1..c3114203e84 100644 --- a/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/theme/Theme.kt @@ -11,6 +11,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.russhwolf.settings.Settings +import org.koin.compose.koinInject private val lightScheme = lightColorScheme( primary = primaryLight, @@ -88,12 +91,25 @@ private val darkScheme = darkColorScheme( surfaceContainerHighest = surfaceContainerHighestDark, ) +private val blackScheme = darkScheme.copy(surface = Color.Black) + @Composable -fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { +fun AppTheme( + useDarkTheme: Boolean = isSystemInDarkTheme(), + settings: Settings = koinInject(), + content: @Composable () -> Unit +) { + val nightScheme = when(settings.getString("night_theme", "dark_theme")) { + "black_theme" -> blackScheme + else -> darkScheme + } + MaterialTheme( - colorScheme = when { - !useDarkTheme -> lightScheme - else -> darkScheme + colorScheme = when(settings.getString("theme", "auto_device_theme")) { + "light_theme" -> lightScheme + "dark_theme" -> darkScheme + "black_theme" -> blackScheme + else -> if (!useDarkTheme) lightScheme else nightScheme }, content = content ) diff --git a/composeApp/src/iosMain/kotlin/net/newpipe/app/module/Koin.ios.kt b/composeApp/src/iosMain/kotlin/net/newpipe/app/module/Koin.ios.kt new file mode 100644 index 00000000000..1a8c2e77b33 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/net/newpipe/app/module/Koin.ios.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.module + +import com.russhwolf.settings.NSUserDefaultsSettings +import com.russhwolf.settings.Settings +import org.koin.dsl.module +import platform.Foundation.NSUserDefaults + +actual val platformModule = module { + single { + NSUserDefaultsSettings(NSUserDefaults()) + } +} diff --git a/composeApp/src/jvmMain/kotlin/net/newpipe/app/module/Koin.jvm.kt b/composeApp/src/jvmMain/kotlin/net/newpipe/app/module/Koin.jvm.kt new file mode 100644 index 00000000000..ee177af7807 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/net/newpipe/app/module/Koin.jvm.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.module + +import com.russhwolf.settings.PreferencesSettings +import com.russhwolf.settings.Settings +import org.koin.dsl.module +import java.util.prefs.Preferences + +actual val platformModule = module { + single { + PreferencesSettings(Preferences.userRoot()) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8c544195ec3..9e0efbd7758 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,6 +52,7 @@ runner = "1.7.0" rxandroid = "3.0.2" rxbinding = "4.0.0" rxjava = "3.1.12" +settings = "1.3.0" sonarqube = "7.2.1.6560" statesaver = "1.4.1" # TODO: Drop because it is deprecated and incompatible with KSP2 stetho = "1.6.0" @@ -120,12 +121,11 @@ jetbrains-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lif jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-annotations" } -koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" } koin-compiler = { module = "io.insert-koin:koin-ksp-compiler", version.ref = "koin-annotations" } -koin-compose = { module = "io.insert-koin:koin-compose" } -koin-core = { module = "io.insert-koin:koin-core" } -koin-test = { module = "io.insert-koin:koin-test" } -koin-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" } +koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-bom" } +koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin-bom" } +koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin-bom" } +koin-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin-bom" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } lisawray-groupie-core = { module = "com.github.lisawray.groupie:groupie", version.ref = "groupie" } lisawray-groupie-viewbinding = { module = "com.github.lisawray.groupie:groupie-viewbinding", version.ref = "groupie" } @@ -141,6 +141,7 @@ 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" } 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" } From ff78dd108e411f70be9a09ab9e44bcaa06edfccb Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Sat, 17 Jan 2026 14:38:32 +0800 Subject: [PATCH 5/5] [DO NOT MERGE] Import about screen implementation from refactor Resolve deprecation errors and adjust code to make testing easier Signed-off-by: Aayush Gupta --- build.gradle.kts | 1 + composeApp/build.gradle.kts | 8 +- .../composeResources/values/strings.xml | 4 + .../newpipe/app/navigation/MainNavDisplay.kt | 28 ++++++ .../net/newpipe/app/navigation/Screen.kt | 34 ++++++++ .../newpipe/app/preview/PreviewTemplate.kt | 17 ++++ .../net/newpipe/app/screens/AboutScreen.kt | 87 +++++++++++++++++++ gradle/libs.versions.toml | 7 +- 8 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/MainNavDisplay.kt create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/Screen.kt create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/preview/PreviewTemplate.kt create mode 100644 composeApp/src/commonMain/kotlin/net/newpipe/app/screens/AboutScreen.kt diff --git a/build.gradle.kts b/build.gradle.kts index ca5003b2cfe..1cfc429caf9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,5 +14,6 @@ plugins { alias(libs.plugins.jetbrains.compose.hotreload) apply false alias(libs.plugins.google.ksp) apply false alias(libs.plugins.jetbrains.kotlin.parcelize) apply false + alias(libs.plugins.jetbrains.kotlin.serialization) apply false alias(libs.plugins.sonarqube) apply false } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 92e8dc7f13e..308e8afed62 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -12,7 +12,7 @@ plugins { alias(libs.plugins.jetbrains.compose.multiplatform) alias(libs.plugins.jetbrains.compose.hotreload) alias(libs.plugins.google.ksp) - alias(libs.plugins.jetbrains.kotlin.parcelize) + alias(libs.plugins.jetbrains.kotlin.serialization) } kotlin { @@ -56,6 +56,10 @@ kotlin { // Settings implementation(libs.russhwolf.settings) + + // Navigation + implementation(libs.jetbrains.navigation3.ui) + implementation(libs.jetbrains.serialization.json) } commonTest.dependencies { implementation(libs.kotlin.test) @@ -68,7 +72,7 @@ kotlin { } jvmMain.dependencies { implementation(compose.desktop.currentOs) - implementation(libs.jetbrains.kotlinx.coroutinesSwing) + implementation(libs.jetbrains.coroutines.swing) } } diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index db4b09e008f..a64bf61a575 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -5,4 +5,8 @@ --> NewPipe + + + About \u0026 FAQ + Licenses diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/MainNavDisplay.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/MainNavDisplay.kt new file mode 100644 index 00000000000..f6c47e38e42 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/MainNavDisplay.kt @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.navigation + +import androidx.compose.runtime.Composable +import androidx.navigation3.runtime.NavKey +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay + +/** + * Navigation display for compose screens + * @param startDestination Starting destination for the activity/app + */ +@Composable +fun MainNavDisplay(startDestination: NavKey) { + val backstack = rememberNavBackStack(Screen.config, startDestination) + + NavDisplay( + backStack = backstack, + entryProvider = entryProvider { + + } + ) +} diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/Screen.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/Screen.kt new file mode 100644 index 00000000000..5a8499adbfb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/navigation/Screen.kt @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.navigation + +import androidx.navigation3.runtime.NavKey +import androidx.savedstate.serialization.SavedStateConfiguration +import kotlinx.serialization.Serializable +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic + +/** + * Destinations for navigation in compose + */ +@Serializable +sealed class Screen : NavKey { + + @Serializable + data object About: Screen() + + companion object { + val config = SavedStateConfiguration { + serializersModule = SerializersModule { + polymorphic(NavKey::class) { + // TODO: Add all subclasses using a for-each loop + subclass(About::class, About.serializer()) + } + } + } + } +} + diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/preview/PreviewTemplate.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/preview/PreviewTemplate.kt new file mode 100644 index 00000000000..2850c98144e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/preview/PreviewTemplate.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.preview + +import androidx.compose.runtime.Composable +import net.newpipe.app.theme.AppTheme + +/** + * Template for previewing composable with defaults + */ +@Composable +fun PreviewTemplate(content: @Composable () -> Unit) { + AppTheme(content = content) +} diff --git a/composeApp/src/commonMain/kotlin/net/newpipe/app/screens/AboutScreen.kt b/composeApp/src/commonMain/kotlin/net/newpipe/app/screens/AboutScreen.kt new file mode 100644 index 00000000000..f7bdd33fdc3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/newpipe/app/screens/AboutScreen.kt @@ -0,0 +1,87 @@ +/* + * SPDX-FileCopyrightText: 2024 NewPipe contributors + * SPDX-FileCopyrightText: 2026 NewPipe e.V. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package net.newpipe.app.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SecondaryTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.util.fastForEachIndexed +import kotlinx.coroutines.launch +import net.newpipe.app.preview.PreviewTemplate +import newpipe.composeapp.generated.resources.Res +import newpipe.composeapp.generated.resources.tab_about +import newpipe.composeapp.generated.resources.tab_licenses +import org.jetbrains.compose.resources.stringResource +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun AboutScreen() { + ScreenContent() +} + +@Composable +private fun ScreenContent(onNavigateUp: () -> Unit = {}) { + Scaffold { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + val pages = listOf(Res.string.tab_about, Res.string.tab_licenses) + val pagerState = rememberPagerState { pages.size } + val coroutineScope = rememberCoroutineScope() + + SecondaryTabRow( + modifier = Modifier.fillMaxWidth(), + selectedTabIndex = pagerState.currentPage + ) { + pages.fastForEachIndexed { index, pageId -> + Tab( + selected = pagerState.currentPage == index, + text = { + Text(text = stringResource(pageId)) + }, + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage(index) + } + } + ) + } + } + + HorizontalPager( + state = pagerState, + modifier = Modifier.fillMaxSize() + ) { page -> + if (page == 0) { + AboutTab() + } else { + LicenseTab() + } + } + } + } +} + +@Preview +@Composable +private fun AboutScreenPreview() { + PreviewTemplate { + ScreenContent() + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9e0efbd7758..d91578a7e7d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ material = "1.11.0" # TODO: update to newer version after bug is fixed. See http media = "1.7.1" mockitoCore = "5.21.0" multiplatform = "1.9.3" +navigation3 = "1.0.0-alpha06" okhttp = "5.3.2" phoenix = "3.0.0" #noinspection NewerVersionAvailable,GradleDependency --> 2.8 is the last version, not 2.71828! @@ -52,6 +53,7 @@ runner = "1.7.0" rxandroid = "3.0.2" rxbinding = "4.0.0" rxjava = "3.1.12" +serialization = "1.9.0" settings = "1.3.0" sonarqube = "7.2.1.6560" statesaver = "1.4.1" # TODO: Drop because it is deprecated and incompatible with KSP2 @@ -116,8 +118,10 @@ google-exoplayer-smoothstreaming = { module = "com.google.android.exoplayer:exop google-exoplayer-ui = { module = "com.google.android.exoplayer:exoplayer-ui", version.ref = "exoplayer" } jakewharton-phoenix = { module = "com.jakewharton:process-phoenix", version.ref = "phoenix" } jakewharton-rxbinding = { module = "com.jakewharton.rxbinding4:rxbinding", version.ref = "rxbinding" } -jetbrains-kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" } +jetbrains-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" } jetbrains-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle-jetbrains" } +jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "navigation3" } +jetbrains-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } koin-annotations = { module = "io.insert-koin:koin-annotations", version.ref = "koin-annotations" } @@ -160,4 +164,5 @@ jetbrains-kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version jetbrains-kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } # Needed for statesaver jetbrains-kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } jetbrains-kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" }