diff --git a/app/build.gradle b/app/build.gradle
index 92fd2a7e960..8f7036e03bc 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,6 +10,7 @@ plugins {
id "checkstyle"
id "org.sonarqube" version "4.0.0.2929"
id "org.jetbrains.kotlin.plugin.compose" version "${kotlin_version}"
+ id 'com.google.dagger.hilt.android'
}
android {
@@ -190,6 +191,10 @@ sonar {
}
}
+kapt {
+ correctErrorTypes true
+}
+
dependencies {
/** Desugaring **/
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4'
@@ -200,7 +205,7 @@ dependencies {
// name and the commit hash with the commit hash of the (pushed) commit you want to test
// This works thanks to JitPack: https://jitpack.io/
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
- implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.2'
+ implementation 'com.github.teamnewpipe:newpipeextractor:v0.24.2'
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
/** Checkstyle **/
@@ -285,20 +290,29 @@ dependencies {
// Date and time formatting
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
- // Jetpack Compose
- implementation(platform('androidx.compose:compose-bom:2024.06.00'))
- implementation 'androidx.compose.material3:material3:1.3.0-beta05'
- implementation 'androidx.compose.material3.adaptive:adaptive:1.0.0-beta04'
- implementation 'androidx.activity:activity-compose'
+ // Jetpack Compose BOM group
+ implementation(platform('androidx.compose:compose-bom:2024.09.03'))
+ implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.ui:ui-tooling-preview'
- implementation 'androidx.compose.ui:ui-text:1.7.0-beta07' // Needed for parsing HTML to AnnotatedString
- implementation 'androidx.lifecycle:lifecycle-viewmodel-compose'
+ implementation 'androidx.compose.ui:ui-text' // Needed for parsing HTML to AnnotatedString
+
+ // Jetpack Compose related dependencies
+ implementation 'androidx.compose.material3.adaptive:adaptive:1.0.0'
+ implementation 'androidx.activity:activity-compose:1.9.2'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6'
implementation 'androidx.paging:paging-compose:3.3.2'
- implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
+ implementation "androidx.navigation:navigation-compose:2.8.2"
// Coroutines interop
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.8.1'
+ // Hilt
+ implementation("com.google.dagger:hilt-android:2.51.1")
+ kapt("com.google.dagger:hilt-compiler:2.51.1")
+
+ // Scroll
+ implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
+
/** Debugging **/
// Memory leak detection
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d11de9f478d..c44f8bf2c4e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -77,6 +77,11 @@
android:exported="false"
android:label="@string/settings" />
+
+
.
*/
+@HiltAndroidApp
public class App extends Application implements ImageLoaderFactory {
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
private static final String TAG = App.class.toString();
diff --git a/app/src/main/java/org/schabi/newpipe/AppModule.kt b/app/src/main/java/org/schabi/newpipe/AppModule.kt
new file mode 100644
index 00000000000..0aaf2f72b14
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/AppModule.kt
@@ -0,0 +1,22 @@
+package org.schabi.newpipe
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+class AppModule {
+
+ @Provides
+ @Singleton
+ fun providesSharedPreference(@ApplicationContext context: Context): SharedPreferences {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt b/app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt
new file mode 100644
index 00000000000..ac08dd36bc8
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt
@@ -0,0 +1,27 @@
+package org.schabi.newpipe.settings
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import org.schabi.newpipe.R
+import org.schabi.newpipe.settings.viewmodel.SettingsViewModel
+import org.schabi.newpipe.ui.SwitchPreference
+import org.schabi.newpipe.ui.theme.SizeTokens
+
+@Composable
+fun DebugScreen(viewModel: SettingsViewModel, modifier: Modifier = Modifier) {
+
+ val settingsLayoutRedesign by viewModel.settingsLayoutRedesign.collectAsState()
+
+ Column(modifier = modifier) {
+ SwitchPreference(
+ modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
+ R.string.settings_layout_redesign,
+ settingsLayoutRedesign,
+ viewModel::toggleSettingsLayoutRedesign
+ )
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsScreen.kt b/app/src/main/java/org/schabi/newpipe/settings/SettingsScreen.kt
new file mode 100644
index 00000000000..5bd8f2b088b
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsScreen.kt
@@ -0,0 +1,23 @@
+package org.schabi.newpipe.settings
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import org.schabi.newpipe.R
+import org.schabi.newpipe.ui.TextPreference
+
+@Composable
+fun SettingsScreen(
+ onSelectSettingOption: (SettingsScreenKey) -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(modifier = modifier) {
+ TextPreference(
+ title = R.string.settings_category_debug_title,
+ onClick = { onSelectSettingOption(SettingsScreenKey.DEBUG) }
+ )
+ HorizontalDivider(color = Color.Black)
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SettingsV2Activity.kt b/app/src/main/java/org/schabi/newpipe/settings/SettingsV2Activity.kt
new file mode 100644
index 00000000000..821ff018746
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsV2Activity.kt
@@ -0,0 +1,85 @@
+package org.schabi.newpipe.settings
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import androidx.navigation.navArgument
+import dagger.hilt.android.AndroidEntryPoint
+import org.schabi.newpipe.R
+import org.schabi.newpipe.settings.viewmodel.SettingsViewModel
+import org.schabi.newpipe.ui.Toolbar
+import org.schabi.newpipe.ui.theme.AppTheme
+
+const val SCREEN_TITLE_KEY = "SCREEN_TITLE_KEY"
+
+@AndroidEntryPoint
+class SettingsV2Activity : ComponentActivity() {
+
+ private val settingsViewModel: SettingsViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ val navController = rememberNavController()
+ var screenTitle by remember { mutableIntStateOf(SettingsScreenKey.ROOT.screenTitle) }
+ navController.addOnDestinationChangedListener { _, _, arguments ->
+ screenTitle =
+ arguments?.getInt(SCREEN_TITLE_KEY) ?: SettingsScreenKey.ROOT.screenTitle
+ }
+
+ AppTheme {
+ Scaffold(topBar = {
+ Toolbar(
+ title = stringResource(id = screenTitle),
+ hasSearch = true,
+ onSearchQueryChange = null // TODO: Add suggestions logic
+ )
+ }) { padding ->
+ NavHost(
+ navController = navController,
+ startDestination = SettingsScreenKey.ROOT.name,
+ modifier = Modifier.padding(padding)
+ ) {
+ composable(
+ SettingsScreenKey.ROOT.name,
+ listOf(createScreenTitleArg(SettingsScreenKey.ROOT.screenTitle))
+ ) {
+ SettingsScreen(onSelectSettingOption = { screen ->
+ navController.navigate(screen.name)
+ })
+ }
+ composable(
+ SettingsScreenKey.DEBUG.name,
+ listOf(createScreenTitleArg(SettingsScreenKey.DEBUG.screenTitle))
+ ) {
+ DebugScreen(settingsViewModel)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fun createScreenTitleArg(@StringRes screenTitle: Int) = navArgument(SCREEN_TITLE_KEY) {
+ defaultValue = screenTitle
+}
+
+enum class SettingsScreenKey(@StringRes val screenTitle: Int) {
+ ROOT(R.string.settings),
+ DEBUG(R.string.settings_category_debug_title)
+}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/viewmodel/SettingsViewModel.kt b/app/src/main/java/org/schabi/newpipe/settings/viewmodel/SettingsViewModel.kt
new file mode 100644
index 00000000000..ae3520c9461
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/settings/viewmodel/SettingsViewModel.kt
@@ -0,0 +1,39 @@
+package org.schabi.newpipe.settings.viewmodel
+
+import android.app.Application
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.AndroidViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import org.schabi.newpipe.R
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingsViewModel @Inject constructor(
+ @ApplicationContext context: Context,
+ private val preferenceManager: SharedPreferences
+) : AndroidViewModel(context.applicationContext as Application) {
+
+ private var _settingsLayoutRedesignPref: Boolean
+ get() = preferenceManager.getBoolean(
+ ContextCompat.getString(getApplication(), R.string.settings_layout_redesign_key), false
+ )
+ set(value) {
+ preferenceManager.edit().putBoolean(
+ ContextCompat.getString(getApplication(), R.string.settings_layout_redesign_key),
+ value
+ ).apply()
+ }
+ private val _settingsLayoutRedesign: MutableStateFlow =
+ MutableStateFlow(_settingsLayoutRedesignPref)
+ val settingsLayoutRedesign = _settingsLayoutRedesign.asStateFlow()
+
+ fun toggleSettingsLayoutRedesign(newState: Boolean) {
+ _settingsLayoutRedesign.value = newState
+ _settingsLayoutRedesignPref = newState
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/ui/SwitchPreference.kt b/app/src/main/java/org/schabi/newpipe/ui/SwitchPreference.kt
new file mode 100644
index 00000000000..d479343f566
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/ui/SwitchPreference.kt
@@ -0,0 +1,53 @@
+package org.schabi.newpipe.ui
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import org.schabi.newpipe.ui.theme.SizeTokens
+
+@Composable
+fun SwitchPreference(
+ modifier: Modifier = Modifier,
+ @StringRes title: Int,
+ isChecked: Boolean,
+ onCheckedChange: (Boolean) -> Unit,
+ @StringRes summary: Int? = null
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = modifier.fillMaxWidth()
+ ) {
+ Column {
+ Text(
+ text = stringResource(id = title),
+ modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
+ style = MaterialTheme.typography.titleSmall,
+ textAlign = TextAlign.Start,
+ )
+ summary?.let {
+ Text(
+ text = stringResource(id = summary),
+ modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
+ style = MaterialTheme.typography.bodySmall,
+ textAlign = TextAlign.Start,
+ )
+ }
+ }
+ Spacer(modifier = Modifier.width(SizeTokens.SpacingSmall))
+ Switch(checked = isChecked, onCheckedChange = onCheckedChange)
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/ui/TextPreference.kt b/app/src/main/java/org/schabi/newpipe/ui/TextPreference.kt
new file mode 100644
index 00000000000..f58f2f305cc
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/ui/TextPreference.kt
@@ -0,0 +1,66 @@
+package org.schabi.newpipe.ui
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Icon
+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.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import org.schabi.newpipe.ui.theme.SizeTokens
+
+@Composable
+fun TextPreference(
+ modifier: Modifier = Modifier,
+ @StringRes title: Int,
+ @DrawableRes icon: Int? = null,
+ @StringRes summary: Int? = null,
+ onClick: () -> Unit,
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(SizeTokens.SpacingSmall)
+ .defaultMinSize(minHeight = SizeTokens.SpaceMinSize)
+ .clickable { onClick() }
+ ) {
+ icon?.let {
+ Icon(
+ painter = painterResource(id = icon),
+ contentDescription = "icon for $title preference"
+ )
+ Spacer(modifier = Modifier.width(SizeTokens.SpacingSmall))
+ }
+ Column {
+ Text(
+ text = stringResource(id = title),
+ modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
+ style = MaterialTheme.typography.titleSmall,
+ textAlign = TextAlign.Start,
+ )
+ summary?.let {
+ Text(
+ text = stringResource(id = summary),
+ modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
+ style = MaterialTheme.typography.bodySmall,
+ textAlign = TextAlign.Start,
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 534d7085bb7..21932887511 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -21,6 +21,7 @@
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
+import androidx.preference.PreferenceManager;
import com.jakewharton.processphoenix.ProcessPhoenix;
@@ -64,6 +65,7 @@
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.settings.SettingsActivity;
+import org.schabi.newpipe.settings.SettingsV2Activity;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.List;
@@ -648,7 +650,13 @@ public static void openAbout(final Context context) {
}
public static void openSettings(final Context context) {
- final Intent intent = new Intent(context, SettingsActivity.class);
+ final Class> settingsClass = PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(
+ ContextCompat.getString(context, R.string.settings_layout_redesign_key),
+ false
+ ) ? SettingsV2Activity.class : SettingsActivity.class;
+
+ final Intent intent = new Intent(context, settingsClass);
context.startActivity(intent);
}
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index e31cebb92ac..b0fceb89bf8 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -246,6 +246,7 @@
crash_the_app_key
show_error_snackbar_key
create_error_notification_key
+ settings_layout_redesign_key
theme
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 938a2497d00..eecce798816 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -492,6 +492,7 @@
Crash the app
Show an error snackbar
Create an error notification
+ Enable the Redesigned Settings page
Import
Import from
diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml
index d97c5aa1a2b..5b6909892e3 100644
--- a/app/src/main/res/xml/debug_settings.xml
+++ b/app/src/main/res/xml/debug_settings.xml
@@ -64,4 +64,11 @@
android:title="@string/create_error_notification"
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
+
+
diff --git a/build.gradle b/build.gradle
index 1acfb6f4a2f..7249be50384 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files