Skip to content

Commit b399030

Browse files
Settings redesign debug page (#10876)
Initial Work for Settings Page with Jetpack Compose - Implemented a new settings page using Jetpack Compose. - Added a new settings option to enable the redesigned settings page. - This option allows for gradual integration and testing of the new settings page, minimizing disruptions to current functionality. Plan for Settings Items: - Jetpack Compose does not have a direct equivalent to the Preference/settings library. - We could consider using third-party libraries that offer preference items as composables. - However, these libraries may be incomplete or lack active development. - Given our specific needs for only a subset of preference types, creating custom composables would be beneficial. - This approach allows for fine-tuning the components to our specific use case.
1 parent 49bcf2c commit b399030

15 files changed

Lines changed: 364 additions & 10 deletions

app/build.gradle

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ plugins {
1010
id "checkstyle"
1111
id "org.sonarqube" version "4.0.0.2929"
1212
id "org.jetbrains.kotlin.plugin.compose" version "${kotlin_version}"
13+
id 'com.google.dagger.hilt.android'
1314
}
1415

1516
android {
@@ -190,6 +191,10 @@ sonar {
190191
}
191192
}
192193

194+
kapt {
195+
correctErrorTypes true
196+
}
197+
193198
dependencies {
194199
/** Desugaring **/
195200
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4'
@@ -200,7 +205,7 @@ dependencies {
200205
// name and the commit hash with the commit hash of the (pushed) commit you want to test
201206
// This works thanks to JitPack: https://jitpack.io/
202207
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
203-
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.24.2'
208+
implementation 'com.github.teamnewpipe:newpipeextractor:v0.24.2'
204209
implementation 'com.github.TeamNewPipe:NoNonsense-FilePicker:5.0.0'
205210

206211
/** Checkstyle **/
@@ -285,20 +290,29 @@ dependencies {
285290
// Date and time formatting
286291
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"
287292

288-
// Jetpack Compose
289-
implementation(platform('androidx.compose:compose-bom:2024.06.00'))
290-
implementation 'androidx.compose.material3:material3:1.3.0-beta05'
291-
implementation 'androidx.compose.material3.adaptive:adaptive:1.0.0-beta04'
292-
implementation 'androidx.activity:activity-compose'
293+
// Jetpack Compose BOM group
294+
implementation(platform('androidx.compose:compose-bom:2024.09.03'))
295+
implementation 'androidx.compose.material3:material3'
293296
implementation 'androidx.compose.ui:ui-tooling-preview'
294-
implementation 'androidx.compose.ui:ui-text:1.7.0-beta07' // Needed for parsing HTML to AnnotatedString
295-
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose'
297+
implementation 'androidx.compose.ui:ui-text' // Needed for parsing HTML to AnnotatedString
298+
299+
// Jetpack Compose related dependencies
300+
implementation 'androidx.compose.material3.adaptive:adaptive:1.0.0'
301+
implementation 'androidx.activity:activity-compose:1.9.2'
302+
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6'
296303
implementation 'androidx.paging:paging-compose:3.3.2'
297-
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
304+
implementation "androidx.navigation:navigation-compose:2.8.2"
298305

299306
// Coroutines interop
300307
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.8.1'
301308

309+
// Hilt
310+
implementation("com.google.dagger:hilt-android:2.51.1")
311+
kapt("com.google.dagger:hilt-compiler:2.51.1")
312+
313+
// Scroll
314+
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'
315+
302316
/** Debugging **/
303317
// Memory leak detection
304318
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@
7777
android:exported="false"
7878
android:label="@string/settings" />
7979

80+
<activity
81+
android:name=".settings.SettingsV2Activity"
82+
android:exported="true"
83+
android:label="@string/settings" />
84+
8085
<activity
8186
android:name=".about.AboutActivity"
8287
android:exported="false"

app/src/main/java/org/schabi/newpipe/App.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import coil.ImageLoader;
3737
import coil.ImageLoaderFactory;
3838
import coil.util.DebugLogger;
39+
import dagger.hilt.android.HiltAndroidApp;
3940
import io.reactivex.rxjava3.exceptions.CompositeException;
4041
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
4142
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
@@ -61,6 +62,7 @@
6162
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
6263
*/
6364

65+
@HiltAndroidApp
6466
public class App extends Application implements ImageLoaderFactory {
6567
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
6668
private static final String TAG = App.class.toString();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.schabi.newpipe
2+
3+
import android.content.Context
4+
import android.content.SharedPreferences
5+
import androidx.preference.PreferenceManager
6+
import dagger.Module
7+
import dagger.Provides
8+
import dagger.hilt.InstallIn
9+
import dagger.hilt.android.qualifiers.ApplicationContext
10+
import dagger.hilt.components.SingletonComponent
11+
import javax.inject.Singleton
12+
13+
@Module
14+
@InstallIn(SingletonComponent::class)
15+
class AppModule {
16+
17+
@Provides
18+
@Singleton
19+
fun providesSharedPreference(@ApplicationContext context: Context): SharedPreferences {
20+
return PreferenceManager.getDefaultSharedPreferences(context)
21+
}
22+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.schabi.newpipe.settings
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.padding
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.runtime.collectAsState
7+
import androidx.compose.runtime.getValue
8+
import androidx.compose.ui.Modifier
9+
import org.schabi.newpipe.R
10+
import org.schabi.newpipe.settings.viewmodel.SettingsViewModel
11+
import org.schabi.newpipe.ui.SwitchPreference
12+
import org.schabi.newpipe.ui.theme.SizeTokens
13+
14+
@Composable
15+
fun DebugScreen(viewModel: SettingsViewModel, modifier: Modifier = Modifier) {
16+
17+
val settingsLayoutRedesign by viewModel.settingsLayoutRedesign.collectAsState()
18+
19+
Column(modifier = modifier) {
20+
SwitchPreference(
21+
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
22+
R.string.settings_layout_redesign,
23+
settingsLayoutRedesign,
24+
viewModel::toggleSettingsLayoutRedesign
25+
)
26+
}
27+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.schabi.newpipe.settings
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.material3.HorizontalDivider
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.Modifier
7+
import androidx.compose.ui.graphics.Color
8+
import org.schabi.newpipe.R
9+
import org.schabi.newpipe.ui.TextPreference
10+
11+
@Composable
12+
fun SettingsScreen(
13+
onSelectSettingOption: (SettingsScreenKey) -> Unit,
14+
modifier: Modifier = Modifier
15+
) {
16+
Column(modifier = modifier) {
17+
TextPreference(
18+
title = R.string.settings_category_debug_title,
19+
onClick = { onSelectSettingOption(SettingsScreenKey.DEBUG) }
20+
)
21+
HorizontalDivider(color = Color.Black)
22+
}
23+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.schabi.newpipe.settings
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.activity.viewModels
7+
import androidx.annotation.StringRes
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.material3.Scaffold
10+
import androidx.compose.runtime.getValue
11+
import androidx.compose.runtime.mutableIntStateOf
12+
import androidx.compose.runtime.remember
13+
import androidx.compose.runtime.setValue
14+
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.navigation.compose.NavHost
17+
import androidx.navigation.compose.composable
18+
import androidx.navigation.compose.rememberNavController
19+
import androidx.navigation.navArgument
20+
import dagger.hilt.android.AndroidEntryPoint
21+
import org.schabi.newpipe.R
22+
import org.schabi.newpipe.settings.viewmodel.SettingsViewModel
23+
import org.schabi.newpipe.ui.Toolbar
24+
import org.schabi.newpipe.ui.theme.AppTheme
25+
26+
const val SCREEN_TITLE_KEY = "SCREEN_TITLE_KEY"
27+
28+
@AndroidEntryPoint
29+
class SettingsV2Activity : ComponentActivity() {
30+
31+
private val settingsViewModel: SettingsViewModel by viewModels()
32+
33+
override fun onCreate(savedInstanceState: Bundle?) {
34+
super.onCreate(savedInstanceState)
35+
36+
setContent {
37+
val navController = rememberNavController()
38+
var screenTitle by remember { mutableIntStateOf(SettingsScreenKey.ROOT.screenTitle) }
39+
navController.addOnDestinationChangedListener { _, _, arguments ->
40+
screenTitle =
41+
arguments?.getInt(SCREEN_TITLE_KEY) ?: SettingsScreenKey.ROOT.screenTitle
42+
}
43+
44+
AppTheme {
45+
Scaffold(topBar = {
46+
Toolbar(
47+
title = stringResource(id = screenTitle),
48+
hasSearch = true,
49+
onSearchQueryChange = null // TODO: Add suggestions logic
50+
)
51+
}) { padding ->
52+
NavHost(
53+
navController = navController,
54+
startDestination = SettingsScreenKey.ROOT.name,
55+
modifier = Modifier.padding(padding)
56+
) {
57+
composable(
58+
SettingsScreenKey.ROOT.name,
59+
listOf(createScreenTitleArg(SettingsScreenKey.ROOT.screenTitle))
60+
) {
61+
SettingsScreen(onSelectSettingOption = { screen ->
62+
navController.navigate(screen.name)
63+
})
64+
}
65+
composable(
66+
SettingsScreenKey.DEBUG.name,
67+
listOf(createScreenTitleArg(SettingsScreenKey.DEBUG.screenTitle))
68+
) {
69+
DebugScreen(settingsViewModel)
70+
}
71+
}
72+
}
73+
}
74+
}
75+
}
76+
}
77+
78+
fun createScreenTitleArg(@StringRes screenTitle: Int) = navArgument(SCREEN_TITLE_KEY) {
79+
defaultValue = screenTitle
80+
}
81+
82+
enum class SettingsScreenKey(@StringRes val screenTitle: Int) {
83+
ROOT(R.string.settings),
84+
DEBUG(R.string.settings_category_debug_title)
85+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.schabi.newpipe.settings.viewmodel
2+
3+
import android.app.Application
4+
import android.content.Context
5+
import android.content.SharedPreferences
6+
import androidx.core.content.ContextCompat
7+
import androidx.lifecycle.AndroidViewModel
8+
import dagger.hilt.android.lifecycle.HiltViewModel
9+
import dagger.hilt.android.qualifiers.ApplicationContext
10+
import kotlinx.coroutines.flow.MutableStateFlow
11+
import kotlinx.coroutines.flow.asStateFlow
12+
import org.schabi.newpipe.R
13+
import javax.inject.Inject
14+
15+
@HiltViewModel
16+
class SettingsViewModel @Inject constructor(
17+
@ApplicationContext context: Context,
18+
private val preferenceManager: SharedPreferences
19+
) : AndroidViewModel(context.applicationContext as Application) {
20+
21+
private var _settingsLayoutRedesignPref: Boolean
22+
get() = preferenceManager.getBoolean(
23+
ContextCompat.getString(getApplication(), R.string.settings_layout_redesign_key), false
24+
)
25+
set(value) {
26+
preferenceManager.edit().putBoolean(
27+
ContextCompat.getString(getApplication(), R.string.settings_layout_redesign_key),
28+
value
29+
).apply()
30+
}
31+
private val _settingsLayoutRedesign: MutableStateFlow<Boolean> =
32+
MutableStateFlow(_settingsLayoutRedesignPref)
33+
val settingsLayoutRedesign = _settingsLayoutRedesign.asStateFlow()
34+
35+
fun toggleSettingsLayoutRedesign(newState: Boolean) {
36+
_settingsLayoutRedesign.value = newState
37+
_settingsLayoutRedesignPref = newState
38+
}
39+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.schabi.newpipe.ui
2+
3+
import androidx.annotation.StringRes
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Row
7+
import androidx.compose.foundation.layout.Spacer
8+
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.width
11+
import androidx.compose.material3.MaterialTheme
12+
import androidx.compose.material3.Switch
13+
import androidx.compose.material3.Text
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.ui.Alignment
16+
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.res.stringResource
18+
import androidx.compose.ui.text.style.TextAlign
19+
import org.schabi.newpipe.ui.theme.SizeTokens
20+
21+
@Composable
22+
fun SwitchPreference(
23+
modifier: Modifier = Modifier,
24+
@StringRes title: Int,
25+
isChecked: Boolean,
26+
onCheckedChange: (Boolean) -> Unit,
27+
@StringRes summary: Int? = null
28+
) {
29+
Row(
30+
verticalAlignment = Alignment.CenterVertically,
31+
horizontalArrangement = Arrangement.SpaceBetween,
32+
modifier = modifier.fillMaxWidth()
33+
) {
34+
Column {
35+
Text(
36+
text = stringResource(id = title),
37+
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
38+
style = MaterialTheme.typography.titleSmall,
39+
textAlign = TextAlign.Start,
40+
)
41+
summary?.let {
42+
Text(
43+
text = stringResource(id = summary),
44+
modifier = Modifier.padding(SizeTokens.SpacingExtraSmall),
45+
style = MaterialTheme.typography.bodySmall,
46+
textAlign = TextAlign.Start,
47+
)
48+
}
49+
}
50+
Spacer(modifier = Modifier.width(SizeTokens.SpacingSmall))
51+
Switch(checked = isChecked, onCheckedChange = onCheckedChange)
52+
}
53+
}

0 commit comments

Comments
 (0)