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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user