Skip to content

Commit 017f991

Browse files
Complete the debug screen migration to Compose with Nav3
- Completed the UI and logic for pending items of Debug screen using Jetpack Compose. - Implemented a new navigation system for settings using the navigation3 lib as it is now stable. - Reuses the `ScaffoldWithToolbar` composable and removed the previous `Toolbar` composable to avoid redundancy in code. - Refactored the `SettingsViewModel` to use a `BooleanPreference` helper class to reuse and reducing boilerplate for state management. - Created a shared `TextBase` composable to remove duplicated UI logic between `SwitchPreference` and `TextPreference`. - Move the build-variant-dependent logic for LeakCanary and reused it in Compose and Fragment, this logic is used for ensuring the leak canary fields are only enabled in debug variants. - Fixed a layout bug in `SwitchPreference` where long summary text could misalign the switch component and also adjusted the paddings for consistency.
1 parent 61c25d4 commit 017f991

20 files changed

Lines changed: 783 additions & 365 deletions

app/build.gradle.kts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,14 @@ dependencies {
262262
implementation(libs.androidx.compose.ui.text) // Needed for parsing HTML to AnnotatedString
263263
implementation(libs.androidx.compose.material.icons.extended)
264264

265+
// Jetpack navigatio3
266+
implementation(libs.androidx.navigation3.ui)
267+
implementation(libs.androidx.navigation3.runtime)
268+
implementation(libs.androidx.navigation3.viewmodel)
269+
265270
// Jetpack Compose related dependencies
266271
implementation(libs.androidx.paging.compose)
267-
implementation(libs.androidx.navigation.compose)
272+
implementation(libs.androidx.hilt.navigation.compose)
268273

269274
// Coroutines interop
270275
implementation(libs.kotlinx.coroutines.rx3)

app/src/debug/java/org/schabi/newpipe/settings/DebugSettingsBVDLeakCanary.java

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. <https://newpipe-ev.de>
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
6+
package org.schabi.newpipe.settings
7+
8+
import android.content.Intent
9+
import leakcanary.LeakCanary.newLeakDisplayActivityIntent
10+
11+
/**
12+
* Build variant dependent (BVD) leak canary API implementation for the debug settings fragment.
13+
* This class is loaded via reflection by
14+
* [DebugSettingsBVDLeakCanaryAPI].
15+
*/
16+
@Suppress("unused") // Class is used but loaded via reflection
17+
class DebugSettingsBVDLeakCanary :
18+
19+
DebugSettingsBVDLeakCanaryAPI {
20+
override fun getNewLeakDisplayActivityIntent(): Intent {
21+
return newLeakDisplayActivityIntent()
22+
}
23+
}

app/src/main/java/org/schabi/newpipe/settings/DebugScreen.kt

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. <https://newpipe-ev.de>
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
6+
package org.schabi.newpipe.settings
7+
8+
import android.content.Intent
9+
10+
/**
11+
* Build variant dependent (BVD) leak canary API.
12+
* Why is LeakCanary not used directly? Because it can't be assured to be available.
13+
*/
14+
interface DebugSettingsBVDLeakCanaryAPI {
15+
fun getNewLeakDisplayActivityIntent(): Intent
16+
17+
companion object {
18+
const val IMPL_CLASS = "org.schabi.newpipe.settings.DebugSettingsBVDLeakCanary"
19+
}
20+
}

app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2017-2025 NewPipe contributors <https://newpipe.net>
3+
* SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. <https://newpipe-ev.de>
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
17
package org.schabi.newpipe.settings;
28

3-
import android.content.Intent;
49
import android.os.Bundle;
510

611
import androidx.preference.Preference;
@@ -88,15 +93,4 @@ private Optional<DebugSettingsBVDLeakCanaryAPI> getBVDLeakCanary() {
8893
return Optional.empty();
8994
}
9095
}
91-
92-
/**
93-
* Build variant dependent (BVD) leak canary API for this fragment.
94-
* Why is LeakCanary not used directly? Because it can't be assured
95-
*/
96-
public interface DebugSettingsBVDLeakCanaryAPI {
97-
String IMPL_CLASS =
98-
"org.schabi.newpipe.settings.DebugSettingsBVDLeakCanary";
99-
100-
Intent getNewLeakDisplayActivityIntent();
101-
}
10296
}

app/src/main/java/org/schabi/newpipe/settings/SettingsScreen.kt

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 10 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,30 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2017-2025 NewPipe contributors <https://newpipe.net>
3+
* SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. <https://newpipe-ev.de>
4+
* SPDX-License-Identifier: GPL-3.0-or-later
5+
*/
6+
17
package org.schabi.newpipe.settings
28

39
import android.os.Bundle
410
import androidx.activity.ComponentActivity
511
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
2012
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
13+
import org.schabi.newpipe.settings.navigation.SettingsNavigation
2414
import org.schabi.newpipe.ui.theme.AppTheme
2515

26-
const val SCREEN_TITLE_KEY = "SCREEN_TITLE_KEY"
27-
2816
@AndroidEntryPoint
2917
class SettingsV2Activity : ComponentActivity() {
3018

31-
private val settingsViewModel: SettingsViewModel by viewModels()
32-
3319
override fun onCreate(savedInstanceState: Bundle?) {
3420
super.onCreate(savedInstanceState)
3521

3622
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-
4423
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-
}
24+
SettingsNavigation(
25+
onExitSettings = { finish() },
26+
)
7327
}
7428
}
7529
}
7630
}
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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025-2026 NewPipe e.V. <https://newpipe-ev.de>
3+
* SPDX-License-Identifier: GPL-3.0-or-later
4+
*/
5+
6+
package org.schabi.newpipe.settings.navigation
7+
8+
import androidx.compose.material3.Text
9+
import androidx.compose.runtime.Composable
10+
import androidx.compose.ui.res.stringResource
11+
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
12+
import androidx.navigation3.runtime.entryProvider
13+
import androidx.navigation3.runtime.rememberNavBackStack
14+
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
15+
import androidx.navigation3.ui.NavDisplay
16+
import org.schabi.newpipe.R
17+
import org.schabi.newpipe.settings.screens.DebugScreen
18+
import org.schabi.newpipe.settings.screens.SettingsHomeScreen
19+
import org.schabi.newpipe.ui.screens.Screens
20+
21+
@Composable
22+
fun SettingsNavigation(onExitSettings: () -> Unit) {
23+
val backStack = rememberNavBackStack(Screens.Settings.Home)
24+
25+
val handleBack: () -> Unit = {
26+
if (backStack.size > 1) {
27+
backStack.removeLastOrNull()
28+
} else {
29+
onExitSettings()
30+
}
31+
}
32+
33+
NavDisplay(
34+
backStack = backStack,
35+
onBack = handleBack,
36+
entryProvider = entryProvider {
37+
entry<Screens.Settings.Home> { SettingsHomeScreen(backStack, handleBack) }
38+
entry<Screens.Settings.Player> { Text(stringResource(id = R.string.settings_category_player_title)) }
39+
entry<Screens.Settings.Behaviour> { Text(stringResource(id = R.string.settings_category_player_behavior_title)) }
40+
entry<Screens.Settings.Download> { Text(stringResource(id = R.string.settings_category_downloads_title)) }
41+
entry<Screens.Settings.LookFeel> { Text(stringResource(id = R.string.settings_category_look_and_feel_title)) }
42+
entry<Screens.Settings.HistoryCache> { Text(stringResource(id = R.string.settings_category_history_title)) }
43+
entry<Screens.Settings.Content> { Text(stringResource(id = R.string.settings_category_content_title)) }
44+
entry<Screens.Settings.Feed> { Text(stringResource(id = R.string.settings_category_feed_title)) }
45+
entry<Screens.Settings.Services> { Text(stringResource(id = R.string.settings_category_services_title)) }
46+
entry<Screens.Settings.Language> { Text(stringResource(id = R.string.settings_category_language_title)) }
47+
entry<Screens.Settings.BackupRestore> { Text(stringResource(id = R.string.settings_category_backup_restore_title)) }
48+
entry<Screens.Settings.Updates> { Text(stringResource(id = R.string.settings_category_updates_title)) }
49+
entry<Screens.Settings.Debug> { DebugScreen(backStack) }
50+
},
51+
entryDecorators = listOf(
52+
rememberSaveableStateHolderNavEntryDecorator(),
53+
rememberViewModelStoreNavEntryDecorator()
54+
)
55+
)
56+
}

0 commit comments

Comments
 (0)