1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-05-22 13:22:13 +00:00

Initial import of about screen logic from refactor

Rework for multiplatform support while cleaning and making logic more modular
for ease with testing individual components

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2026-05-04 13:32:13 +08:00
parent f4ae6b75c5
commit ff9b38eed9
25 changed files with 4448 additions and 23 deletions
+1
View File
@@ -22,4 +22,5 @@ plugins {
alias(libs.plugins.jetbrains.kotlinx.serialization) apply false
alias(libs.plugins.sonarqube) apply false
alias(libs.plugins.koin) apply false
alias(libs.plugins.about.libraries) apply false
}
+14
View File
@@ -12,6 +12,7 @@ plugins {
alias(libs.plugins.jetbrains.compose.multiplatform)
alias(libs.plugins.koin)
alias(libs.plugins.jetbrains.kotlinx.serialization)
alias(libs.plugins.about.libraries)
}
kotlin {
@@ -72,6 +73,8 @@ kotlin {
implementation(libs.koin.annotations)
implementation(libs.russhwolf.settings)
implementation(libs.about.libraries.compose.m3)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
@@ -107,3 +110,14 @@ compose.desktop {
koinCompiler {
userLogs = true // See what the compiler plugin detects
}
// Run ./gradlew exportLibraryDefinitions to generate/update the libraries and license definitions
aboutLibraries {
collect {
fetchRemoteLicense = true
}
export {
outputFile = file("src/commonMain/composeResources/files/aboutlibraries.json")
prettyPrint = true
}
}
@@ -9,7 +9,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.core.content.IntentCompat
import kotlinx.serialization.json.Json
import net.newpipe.Constants
import net.newpipe.app.navigation.Screen
@@ -21,14 +21,13 @@ class ComposeActivity : ComponentActivity() {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
val startDestination = IntentCompat.getParcelableExtra(
intent,
Constants.INTENT_SCREEN_KEY,
Screen::class.java
)!!
setContent {
App(startDestination)
App(
// TODO: Change when everything is in compose and this is the primary activity
startDestination = Json.decodeFromString<Screen>(
intent.getStringExtra(Constants.INTENT_SCREEN_KEY)!!
)
)
}
}
}
@@ -7,6 +7,7 @@ package net.newpipe.app.extensions
import android.content.Context
import android.content.Intent
import kotlinx.serialization.json.Json
import net.newpipe.Constants
import net.newpipe.app.ComposeActivity
import net.newpipe.app.navigation.Screen
@@ -16,6 +17,6 @@ import kotlin.jvm.java
* Navigates to a given compose destination
*/
fun Context.navigateTo(screen: Screen) = Intent(this, ComposeActivity::class.java).also { intent ->
intent.putExtra(Constants.INTENT_SCREEN_KEY, screen.name)
intent.putExtra(Constants.INTENT_SCREEN_KEY, Json.encodeToString(screen))
startActivity(intent)
}
@@ -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>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="470dp"
android:height="464dp"
android:viewportWidth="470"
android:viewportHeight="464">
<path
android:pathData="M144.1,388.93L191.69,358.59L192.69,157.51L315.15,230.22L226.54,281.32V339.8L410.63,230.22L143.85,73.44L144.1,388.93Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/aboutlibraries" />
@@ -5,4 +5,33 @@
-->
<resources>
<string name="app_name">NewPipe</string>
<!-- TopAppBar -->
<string name="navigate_back">Back</string>
<!-- AboutScreen -->
<string name="title_activity_about">About NewPipe</string>
<string name="title_licenses">Third-party Licenses</string>
<string name="copyright">© %1$s by %2$s under %3$s</string>
<string name="tab_about">About \u0026 FAQ</string>
<string name="tab_licenses">Licenses</string>
<string name="app_description">Libre lightweight streaming on Android.</string>
<string name="contribution_title">Contribute</string>
<string name="contribution_encouragement">Whether you have ideas of; translation, design changes, code cleaning, or real heavy code changes—help is always welcome. The more is done the better it gets!</string>
<string name="view_on_github">View on GitHub</string>
<string name="donation_title">Donate</string>
<string name="donation_encouragement">NewPipe is developed by volunteers spending their free time bringing you the best user experience. Give back to help developers make NewPipe even better while they enjoy a cup of coffee.</string>
<string name="give_back">Give back</string>
<string name="website_title">Website</string>
<string name="website_encouragement">Visit the NewPipe Website for more info and news.</string>
<string name="privacy_policy_title">NewPipe\'s Privacy Policy</string>
<string name="privacy_policy_encouragement">The NewPipe project takes your privacy very seriously. Therefore, the app does not collect any data without your consent.\nNewPipe\'s privacy policy explains in detail what data is sent and stored when you send a crash report.</string>
<string name="read_privacy_policy">Read privacy policy</string>
<string name="app_license_title">NewPipe\'s License</string>
<string name="app_license">NewPipe is copyleft libre software: You can use, study, share, and improve it at will. Specifically you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</string>
<string name="read_full_license">Read license</string>
<string name="faq_title">Frequently asked questions</string>
<string name="faq_description">If you are having trouble using the app, be sure to check out these answers to common questions!</string>
<string name="faq">View on website</string>
<string name="open_in_browser">Open in browser</string>
</resources>
@@ -7,6 +7,7 @@ package net.newpipe.app
import androidx.compose.runtime.Composable
import net.newpipe.app.di.KoinApp
import net.newpipe.app.navigation.NavDisplay
import net.newpipe.app.navigation.Screen
import net.newpipe.app.theme.AppTheme
import org.koin.compose.KoinApplication
@@ -14,11 +15,13 @@ import org.koin.plugin.module.dsl.koinConfiguration
/**
* Entry point for the multiplatform compose application
* @param startDestination Starting destination for the activity/app, defaults to about
*/
@Composable
fun App(startDestination: Screen? = null) {
fun App(startDestination: Screen = Screen.About) {
KoinApplication(configuration = koinConfiguration<KoinApp>()) {
AppTheme {
NavDisplay(startDestination)
}
}
}
@@ -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
object Constants {
const val URL_GITHUB = "https://github.com/TeamNewPipe/NewPipe"
const val URL_DONATION = "https://newpipe.net/donate/"
const val URL_WEBSITE = "https://newpipe.net/"
const val URL_PRIVACY = "https://newpipe.net/legal/privacy/"
const val URL_FAQ = "https://newpipe.net/FAQ/"
const val URL_LICENSE = "https://github.com/TeamNewPipe/NewPipe/blob/master/LICENSE"
// TODO: CHANGE IT TO RESPECT THE MAIN APP MODULE
const val CODE_VERSION = "1.0.0"
}
@@ -0,0 +1,76 @@
/*
* 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.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import net.newpipe.app.preview.ThemePreviewProvider
import newpipe.composeapp.generated.resources.Res
import newpipe.composeapp.generated.resources.ic_arrow_back
import newpipe.composeapp.generated.resources.navigate_back
import newpipe.composeapp.generated.resources.title_activity_about
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,
actions: @Composable (RowScope.() -> Unit) = {}
) {
TopAppBar(
modifier = modifier,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
scrolledContainerColor = MaterialTheme.colorScheme.primaryContainer,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
actionIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer
),
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)
@Preview(showBackground = true)
@Composable
private fun TopAppBarPreview() {
TopAppBar(
title = stringResource(Res.string.title_activity_about),
onNavigateUp = {}
)
}
@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.composable.about
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import net.newpipe.app.model.Link
import net.newpipe.app.preview.ThemePreviewProvider
import net.newpipe.app.theme.spaceXSmall
import newpipe.composeapp.generated.resources.Res
import newpipe.composeapp.generated.resources.contribution_encouragement
import newpipe.composeapp.generated.resources.contribution_title
import newpipe.composeapp.generated.resources.view_on_github
import org.jetbrains.compose.resources.stringResource
/**
* Composable to display item providing information about NewPipe
* @param link A link item with information
* @param onAction Callback when the action button is clicked
*/
@Composable
fun LinkListItem(modifier: Modifier = Modifier, link: Link, onAction: () -> Unit = {}) {
Column(
modifier = modifier
.fillMaxWidth()
.padding(spaceXSmall)
) {
Text(
text = stringResource(link.title),
style = MaterialTheme.typography.titleMedium
)
Text(
text = stringResource(link.description),
style = MaterialTheme.typography.bodyMedium
)
TextButton(
modifier = Modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.End),
onClick = onAction
) {
Text(text = stringResource(link.action))
}
}
}
@PreviewWrapper(ThemePreviewProvider::class)
@Preview(showBackground = true)
@Composable
private fun LinkListItemPreview() {
LinkListItem(
link = Link(
Res.string.contribution_title,
Res.string.contribution_encouragement,
Res.string.view_on_github,
""
)
)
}
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.model
import org.jetbrains.compose.resources.StringResource
/**
* Class to hold data for links shown to users
*/
data class Link(
val title: StringResource,
val description: StringResource,
val action: StringResource,
val url: String
)
@@ -9,10 +9,11 @@ import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import net.newpipe.app.screen.about.AboutScreen
/**
* Navigation display for compose screens
* @param startDestination Starting destination for the activity/app, defaults to about
* @param startDestination Starting destination for the app
*/
@Composable
fun NavDisplay(startDestination: Screen) {
@@ -21,6 +22,11 @@ fun NavDisplay(startDestination: Screen) {
NavDisplay(
backStack = backstack,
entryProvider = entryProvider {
entry<Screen.About> {
AboutScreen(
onNavigateUp = {}
)
}
}
)
}
@@ -18,8 +18,8 @@ import kotlinx.serialization.modules.polymorphic
@Serializable
sealed interface Screen : NavKey {
val name: String
get() = this::class.simpleName.toString()
@Serializable
data object About : Screen
}
/**
@@ -0,0 +1,21 @@
/*
* 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.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(content = content)
}
}
@@ -0,0 +1,152 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.screen.about
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import net.newpipe.app.Constants
import net.newpipe.app.composable.about.LinkListItem
import net.newpipe.app.model.Link
import net.newpipe.app.preview.ThemePreviewProvider
import net.newpipe.app.theme.iconTVDPI
import net.newpipe.app.theme.logoBackground
import net.newpipe.app.theme.spaceLarge
import net.newpipe.app.theme.spaceNormal
import net.newpipe.app.theme.spaceXSmall
import net.newpipe.app.theme.spaceXXSmall
import newpipe.composeapp.generated.resources.Res
import newpipe.composeapp.generated.resources.app_description
import newpipe.composeapp.generated.resources.app_name
import newpipe.composeapp.generated.resources.contribution_encouragement
import newpipe.composeapp.generated.resources.contribution_title
import newpipe.composeapp.generated.resources.donation_encouragement
import newpipe.composeapp.generated.resources.donation_title
import newpipe.composeapp.generated.resources.faq
import newpipe.composeapp.generated.resources.faq_description
import newpipe.composeapp.generated.resources.faq_title
import newpipe.composeapp.generated.resources.give_back
import newpipe.composeapp.generated.resources.ic_foreground
import newpipe.composeapp.generated.resources.open_in_browser
import newpipe.composeapp.generated.resources.privacy_policy_encouragement
import newpipe.composeapp.generated.resources.privacy_policy_title
import newpipe.composeapp.generated.resources.read_privacy_policy
import newpipe.composeapp.generated.resources.view_on_github
import newpipe.composeapp.generated.resources.website_encouragement
import newpipe.composeapp.generated.resources.website_title
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@Composable
fun AboutPage() {
val links = listOf(
Link(
title = Res.string.faq_title,
description = Res.string.faq_description,
action = Res.string.faq,
url = Constants.URL_FAQ
),
Link(
title = Res.string.contribution_title,
description = Res.string.contribution_encouragement,
action = Res.string.view_on_github,
url = Constants.URL_GITHUB
),
Link(
title = Res.string.donation_title,
description = Res.string.donation_encouragement,
action = Res.string.give_back,
url = Constants.URL_DONATION
),
Link(
title = Res.string.website_title,
description = Res.string.website_encouragement,
action = Res.string.open_in_browser,
url = Constants.URL_WEBSITE
),
Link(
title = Res.string.privacy_policy_title,
description = Res.string.privacy_policy_encouragement,
action = Res.string.read_privacy_policy,
url = Constants.URL_PRIVACY
)
)
LazyColumn(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(spaceNormal),
verticalArrangement = Arrangement.spacedBy(spaceXXSmall)
) {
item { PageHeader() }
items(items = links, key = { link -> link.url }) { link ->
LinkListItem(
link = link
)
}
}
}
@Composable
private fun PageHeader() {
Column(
modifier = Modifier
.padding(spaceLarge)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
modifier = Modifier
.requiredSize(iconTVDPI)
.clip(CircleShape)
.background(color = logoBackground),
painter = painterResource(Res.drawable.ic_foreground),
contentDescription = stringResource(Res.string.app_name),
)
Spacer(modifier = Modifier.height(spaceXSmall))
Text(
text = stringResource(Res.string.app_name),
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center
)
Text(
text = Constants.CODE_VERSION,
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)
Text(
modifier = Modifier.fillMaxWidth(),
text = stringResource(Res.string.app_description),
textAlign = TextAlign.Center
)
}
}
@PreviewWrapper(ThemePreviewProvider::class)
@Preview(showBackground = true)
@Composable
private fun AboutPagePreview() {
AboutPage()
}
@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.screen.about
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.MaterialTheme
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.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import androidx.compose.ui.util.fastForEachIndexed
import kotlinx.coroutines.launch
import net.newpipe.app.composable.TopAppBar
import net.newpipe.app.preview.ThemePreviewProvider
import org.jetbrains.compose.resources.stringResource
import net.newpipe.app.screen.about.navigation.Page
import newpipe.composeapp.generated.resources.Res
import newpipe.composeapp.generated.resources.title_activity_about
@Composable
fun AboutScreen(onNavigateUp: () -> Unit) {
ScreenContent(
onNavigateUp = onNavigateUp
)
}
@Composable
private fun ScreenContent(
pages: List<Page> = listOf(Page.ABOUT, Page.LICENSE),
onNavigateUp: () -> Unit = {}
) {
Scaffold(
topBar = {
TopAppBar(
title = stringResource(Res.string.title_activity_about),
onNavigateUp = onNavigateUp
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
val pagerState = rememberPagerState { pages.size }
val coroutineScope = rememberCoroutineScope()
SecondaryTabRow(
modifier = Modifier.fillMaxWidth(),
containerColor = MaterialTheme.colorScheme.primaryContainer,
selectedTabIndex = pagerState.currentPage
) {
pages.fastForEachIndexed { index, _ ->
Tab(
selected = pagerState.currentPage == index,
text = { Text(text = stringResource(pages[index].localizedTitle)) },
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(index)
}
}
)
}
}
HorizontalPager(state = pagerState, modifier = Modifier.fillMaxSize()) { page ->
when (pages[page]) {
Page.ABOUT -> AboutPage()
Page.LICENSE -> LicensePage()
}
}
}
}
}
@PreviewWrapper(ThemePreviewProvider::class)
@Preview
@Composable
private fun AboutScreenPreview() {
ScreenContent()
}
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package net.newpipe.app.screen.about
import androidx.compose.foundation.layout.Arrangement
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.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.produceLibraries
import net.newpipe.app.Constants
import net.newpipe.app.composable.about.LinkListItem
import net.newpipe.app.model.Link
import net.newpipe.app.preview.ThemePreviewProvider
import net.newpipe.app.theme.spaceXSmall
import newpipe.composeapp.generated.resources.Res
import newpipe.composeapp.generated.resources.app_license
import newpipe.composeapp.generated.resources.app_license_title
import newpipe.composeapp.generated.resources.read_full_license
import newpipe.composeapp.generated.resources.title_licenses
import org.jetbrains.compose.resources.stringResource
@Composable
fun LicensePage() {
val libraries by produceLibraries {
Res.readBytes("files/aboutlibraries.json").decodeToString()
}
LibrariesContainer(
modifier = Modifier.fillMaxSize(),
showDescription = true,
libraries = libraries,
header = {
item { PageHeader() }
}
)
}
@Composable
private fun PageHeader() {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(LibraryDefaults.libraryPadding().contentPadding),
verticalArrangement = Arrangement.spacedBy(spaceXSmall)
) {
LinkListItem(
link = Link(
title = Res.string.app_license_title,
description = Res.string.app_license,
action = Res.string.read_full_license,
url = Constants.URL_LICENSE
)
)
Text(
text = stringResource(Res.string.title_licenses),
style = MaterialTheme.typography.titleMedium
)
}
}
@PreviewWrapper(ThemePreviewProvider::class)
@Preview(showBackground = true)
@Composable
private fun LicensePagePreview() {
LicensePage()
}
@@ -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.screen.about.navigation
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
/**
* Possible pages to show in about screen
*/
enum class Page(val localizedTitle: StringResource) {
ABOUT(Res.string.tab_about),
LICENSE(Res.string.tab_licenses)
}
@@ -8,6 +8,8 @@ package net.newpipe.app.theme
import androidx.compose.ui.graphics.Color
val logoBackground = Color(0xFFCD201F)
val primaryLight = Color(0xFF904A45)
val onPrimaryLight = Color(0xFFFFFFFF)
val primaryContainerLight = Color(0xFFFFDAD6)
@@ -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.theme
import androidx.compose.ui.unit.dp
val spaceXXSmall = 2.dp
val spaceXSmall = 4.dp
val spaceSmall = 8.dp
val spaceMedium = 10.dp
val spaceNormal = 12.dp
val spaceLarge = 16.dp
val spaceXLarge = 32.dp
val iconLDPI = 36.dp
val iconMDPI = 48.dp
val iconTVDPI = 64.dp
val iconHDPI = 72.dp
val iconXHDPI = 96.dp
val iconXXHDPI = 144.dp
val iconXXXHDPI = 192.dp
@@ -7,12 +7,13 @@
package net.newpipe.app.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialExpressiveTheme
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 androidx.compose.ui.platform.LocalInspectionMode
import com.russhwolf.settings.Settings
import org.koin.compose.koinInject
@@ -94,24 +95,41 @@ private val darkScheme = darkColorScheme(
private val blackScheme = darkScheme.copy(surface = Color.Black)
/**
* Gets current color scheme chosen by the user
*/
@Composable
fun AppTheme(
fun currentColorScheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
settings: Settings = koinInject(),
content: @Composable () -> Unit
) {
): ColorScheme {
val nightScheme = when(settings.getString("night_theme", "dark_theme")) {
"black_theme" -> blackScheme
else -> darkScheme
}
return when(settings.getString("theme", "auto_device_theme")) {
"light_theme" -> lightScheme
"dark_theme" -> darkScheme
"black_theme" -> blackScheme
else -> if (!useDarkTheme) lightScheme else nightScheme
}
}
/**
* Default app theme for the NewPipe composables
* @param isPreview Whether the theme is being used in preview mode
* @param colorScheme Color scheme to use for theme, defaults to light in preview mode
* @param content Composable content to show to the user
*/
@Composable
fun AppTheme(
isPreview: Boolean = LocalInspectionMode.current,
colorScheme: ColorScheme = if (isPreview) lightScheme else currentColorScheme(),
content: @Composable () -> Unit
) {
MaterialExpressiveTheme(
colorScheme = when(settings.getString("theme", "auto_device_theme")) {
"light_theme" -> lightScheme
"dark_theme" -> darkScheme
"black_theme" -> blackScheme
else -> if (!useDarkTheme) lightScheme else nightScheme
},
colorScheme = colorScheme,
content = content
)
}
+3
View File
@@ -4,6 +4,7 @@
#
[versions]
about-libraries = "14.1.0"
acra = "5.13.1"
activity = "1.13.0"
agp = "9.2.0"
@@ -74,6 +75,7 @@ webkit = "1.15.0"
work = "2.11.2"
[libraries]
about-libraries-compose-m3 = { group = "com.mikepenz", name = "aboutlibraries-compose-m3", version.ref = "about-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" }
@@ -159,6 +161,7 @@ squareup-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhtt
zacsweers-autoservice-compiler = { module = "dev.zacsweers.autoservice:auto-service-ksp", version.ref = "autoservice-zacsweers" }
[plugins]
about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-legacy-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" } # Needed for statesaver
android-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }