Skip to content

Commit 1af798b

Browse files
Introducing Jetpack Compose in NewPipe
This pull request integrates Jetpack Compose into NewPipe by: - Adding the necessary dependencies and setup. - This is part of the NewPipe rewrite and fulfils the requirement for the planned settings page redesign. - Introducing a Toolbar composable with theming that aligns with NewPipe's design. Note: - Theme colors are generated using the Material Theme builder (https://m3.material.io/styles/color/overview).
1 parent e37336e commit 1af798b

5 files changed

Lines changed: 306 additions & 0 deletions

File tree

app/build.gradle

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ android {
9292

9393
buildFeatures {
9494
viewBinding true
95+
compose true
9596
}
9697

9798
packagingOptions {
@@ -103,6 +104,10 @@ android {
103104
'META-INF/COPYRIGHT']
104105
}
105106
}
107+
108+
composeOptions {
109+
kotlinCompilerExtensionVersion = "1.5.3"
110+
}
106111
}
107112

108113
ext {
@@ -284,6 +289,12 @@ dependencies {
284289
// Date and time formatting
285290
implementation "org.ocpsoft.prettytime:prettytime:5.0.7.Final"
286291

292+
// Jetpack Compose
293+
implementation(platform('androidx.compose:compose-bom:2024.02.01'))
294+
implementation 'androidx.compose.material3:material3'
295+
implementation 'androidx.activity:activity-compose'
296+
implementation 'androidx.compose.ui:ui-tooling-preview'
297+
287298
/** Debugging **/
288299
// Memory leak detection
289300
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
@@ -293,6 +304,9 @@ dependencies {
293304
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
294305
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
295306

307+
// Jetpack Compose
308+
debugImplementation 'androidx.compose.ui:ui-tooling'
309+
296310
/** Testing **/
297311
testImplementation 'junit:junit:4.13.2'
298312
testImplementation 'org.mockito:mockito-core:5.6.0'
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package org.schabi.newpipe.ui
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.RowScope
6+
import androidx.compose.foundation.layout.fillMaxHeight
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.material.icons.Icons
10+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
11+
import androidx.compose.material3.ExperimentalMaterial3Api
12+
import androidx.compose.material3.Icon
13+
import androidx.compose.material3.IconButton
14+
import androidx.compose.material3.MaterialTheme
15+
import androidx.compose.material3.SearchBar
16+
import androidx.compose.material3.SearchBarDefaults
17+
import androidx.compose.material3.Text
18+
import androidx.compose.material3.TopAppBar
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.getValue
21+
import androidx.compose.runtime.mutableStateOf
22+
import androidx.compose.runtime.remember
23+
import androidx.compose.runtime.setValue
24+
import androidx.compose.ui.Alignment
25+
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.res.painterResource
27+
import androidx.compose.ui.res.stringResource
28+
import androidx.compose.ui.tooling.preview.Preview
29+
import org.schabi.newpipe.R
30+
import org.schabi.newpipe.ui.theme.AppTheme
31+
import org.schabi.newpipe.ui.theme.SizeTokens
32+
33+
@Composable
34+
fun TextAction(text: String, modifier: Modifier = Modifier) {
35+
Text(text = text, color = MaterialTheme.colorScheme.onSurface, modifier = modifier)
36+
}
37+
38+
@Composable
39+
fun NavigationIcon() {
40+
Icon(
41+
imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back",
42+
modifier = Modifier.padding(horizontal = SizeTokens.SpacingExtraSmall)
43+
)
44+
}
45+
46+
@Composable
47+
fun SearchSuggestionItem(text: String) {
48+
// TODO: Add more components here to display all the required details of a search suggestion item.
49+
Text(text = text)
50+
}
51+
52+
@OptIn(ExperimentalMaterial3Api::class)
53+
@Composable
54+
fun Toolbar(
55+
title: String,
56+
modifier: Modifier = Modifier,
57+
hasNavigationIcon: Boolean = true,
58+
hasSearch: Boolean = false,
59+
onSearchQueryChange: ((String) -> List<String>)? = null,
60+
actions: @Composable RowScope.() -> Unit = {}
61+
) {
62+
var isSearchActive by remember { mutableStateOf(false) }
63+
var query by remember { mutableStateOf("") }
64+
65+
Column {
66+
TopAppBar(
67+
title = { Text(text = title) },
68+
modifier = modifier,
69+
navigationIcon = { if (hasNavigationIcon) NavigationIcon() },
70+
actions = {
71+
actions()
72+
if (hasSearch) {
73+
IconButton(onClick = { isSearchActive = true }) {
74+
Icon(
75+
painterResource(id = R.drawable.ic_search),
76+
contentDescription = stringResource(id = R.string.search),
77+
tint = MaterialTheme.colorScheme.onSurface
78+
)
79+
}
80+
}
81+
}
82+
)
83+
if (isSearchActive) {
84+
SearchBar(
85+
query = query,
86+
onQueryChange = { query = it },
87+
onSearch = {},
88+
placeholder = {
89+
Text(text = stringResource(id = R.string.search))
90+
},
91+
active = true,
92+
onActiveChange = {
93+
isSearchActive = it
94+
},
95+
colors = SearchBarDefaults.colors(
96+
containerColor = MaterialTheme.colorScheme.background,
97+
inputFieldColors = SearchBarDefaults.inputFieldColors(
98+
focusedTextColor = MaterialTheme.colorScheme.onBackground,
99+
unfocusedTextColor = MaterialTheme.colorScheme.onBackground
100+
)
101+
)
102+
) {
103+
onSearchQueryChange?.invoke(query)?.takeIf { it.isNotEmpty() }
104+
?.map { suggestionText -> SearchSuggestionItem(text = suggestionText) }
105+
?: run {
106+
Box(
107+
modifier = Modifier
108+
.fillMaxHeight()
109+
.fillMaxWidth(),
110+
contentAlignment = Alignment.Center
111+
) {
112+
Column {
113+
Text(text = "╰(°●°╰)")
114+
Text(text = stringResource(id = R.string.search_no_results))
115+
}
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
123+
@Preview
124+
@Composable
125+
fun ToolbarPreview() {
126+
AppTheme {
127+
Toolbar(
128+
title = "Title",
129+
hasSearch = true,
130+
onSearchQueryChange = { emptyList() },
131+
actions = {
132+
TextAction(text = "Action1")
133+
TextAction(text = "Action2")
134+
}
135+
)
136+
}
137+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.schabi.newpipe.ui.theme
2+
3+
import androidx.compose.ui.graphics.Color
4+
5+
val md_theme_light_primary = Color(0xFFBB171C)
6+
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
7+
val md_theme_light_primaryContainer = Color(0xFFFFDAD6)
8+
val md_theme_light_onPrimaryContainer = Color(0xFF410002)
9+
val md_theme_light_secondary = Color(0xFF984061)
10+
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
11+
val md_theme_light_secondaryContainer = Color(0xFFFFD9E2)
12+
val md_theme_light_onSecondaryContainer = Color(0xFF3E001D)
13+
val md_theme_light_tertiary = Color(0xFF006874)
14+
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
15+
val md_theme_light_tertiaryContainer = Color(0xFF97F0FF)
16+
val md_theme_light_onTertiaryContainer = Color(0xFF001F24)
17+
val md_theme_light_error = Color(0xFFBA1A1A)
18+
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
19+
val md_theme_light_onError = Color(0xFFFFFFFF)
20+
val md_theme_light_onErrorContainer = Color(0xFF410002)
21+
val md_theme_light_background = Color(0xFFEEEEEE)
22+
val md_theme_light_onBackground = Color(0xFF1B1B1B)
23+
val md_theme_light_surface = Color(0xFFE53835)
24+
val md_theme_light_onSurface = Color(0xFFFFFFFF)
25+
val md_theme_light_surfaceVariant = Color(0xFFF5DDDB)
26+
val md_theme_light_onSurfaceVariant = Color(0xFF534341)
27+
val md_theme_light_outline = Color(0xFF857371)
28+
val md_theme_light_inverseOnSurface = Color(0xFFD6F6FF)
29+
val md_theme_light_inverseSurface = Color(0xFF00363F)
30+
val md_theme_light_inversePrimary = Color(0xFFFFB4AC)
31+
val md_theme_light_surfaceTint = Color(0xFFBB171C)
32+
val md_theme_light_outlineVariant = Color(0xFFD8C2BF)
33+
val md_theme_light_scrim = Color(0xFF000000)
34+
35+
val md_theme_dark_primary = Color(0xFFFFB4AC)
36+
val md_theme_dark_onPrimary = Color(0xFF690006)
37+
val md_theme_dark_primaryContainer = Color(0xFF93000D)
38+
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDAD6)
39+
val md_theme_dark_secondary = Color(0xFFFFB1C8)
40+
val md_theme_dark_onSecondary = Color(0xFF5E1133)
41+
val md_theme_dark_secondaryContainer = Color(0xFF7B2949)
42+
val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9E2)
43+
val md_theme_dark_tertiary = Color(0xFF4FD8EB)
44+
val md_theme_dark_onTertiary = Color(0xFF00363D)
45+
val md_theme_dark_tertiaryContainer = Color(0xFF004F58)
46+
val md_theme_dark_onTertiaryContainer = Color(0xFF97F0FF)
47+
val md_theme_dark_error = Color(0xFFFFB4AB)
48+
val md_theme_dark_errorContainer = Color(0xFF93000A)
49+
val md_theme_dark_onError = Color(0xFF690005)
50+
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
51+
val md_theme_dark_background = Color(0xFF212121)
52+
val md_theme_dark_onBackground = Color(0xFFFFFFFF)
53+
val md_theme_dark_surface = Color(0xFF992521)
54+
val md_theme_dark_onSurface = Color(0xFFFFFFFF)
55+
val md_theme_dark_surfaceVariant = Color(0xFF534341)
56+
val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BF)
57+
val md_theme_dark_outline = Color(0xFFA08C8A)
58+
val md_theme_dark_inverseOnSurface = Color(0xFF001F25)
59+
val md_theme_dark_inverseSurface = Color(0xFFA6EEFF)
60+
val md_theme_dark_inversePrimary = Color(0xFFBB171C)
61+
val md_theme_dark_surfaceTint = Color(0xFFFFB4AC)
62+
val md_theme_dark_outlineVariant = Color(0xFF534341)
63+
val md_theme_dark_scrim = Color(0xFF000000)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.schabi.newpipe.ui.theme
2+
3+
import androidx.compose.ui.unit.dp
4+
5+
internal object SizeTokens {
6+
val SpacingExtraSmall = 4.dp
7+
val SpacingSmall = 8.dp
8+
val SpacingMedium = 16.dp
9+
val SpacingLarge = 24.dp
10+
val SpacingExtraLarge = 32.dp
11+
12+
val SpaceMinSize = 44.dp // Minimum tappable size required for accessibility
13+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package org.schabi.newpipe.ui.theme
2+
3+
import androidx.compose.foundation.isSystemInDarkTheme
4+
import androidx.compose.material3.MaterialTheme
5+
import androidx.compose.material3.darkColorScheme
6+
import androidx.compose.material3.lightColorScheme
7+
import androidx.compose.runtime.Composable
8+
9+
private val LightColors = lightColorScheme(
10+
primary = md_theme_light_primary,
11+
onPrimary = md_theme_light_onPrimary,
12+
primaryContainer = md_theme_light_primaryContainer,
13+
onPrimaryContainer = md_theme_light_onPrimaryContainer,
14+
secondary = md_theme_light_secondary,
15+
onSecondary = md_theme_light_onSecondary,
16+
secondaryContainer = md_theme_light_secondaryContainer,
17+
onSecondaryContainer = md_theme_light_onSecondaryContainer,
18+
tertiary = md_theme_light_tertiary,
19+
onTertiary = md_theme_light_onTertiary,
20+
tertiaryContainer = md_theme_light_tertiaryContainer,
21+
onTertiaryContainer = md_theme_light_onTertiaryContainer,
22+
error = md_theme_light_error,
23+
errorContainer = md_theme_light_errorContainer,
24+
onError = md_theme_light_onError,
25+
onErrorContainer = md_theme_light_onErrorContainer,
26+
background = md_theme_light_background,
27+
onBackground = md_theme_light_onBackground,
28+
surface = md_theme_light_surface,
29+
onSurface = md_theme_light_onSurface,
30+
surfaceVariant = md_theme_light_surfaceVariant,
31+
onSurfaceVariant = md_theme_light_onSurfaceVariant,
32+
outline = md_theme_light_outline,
33+
inverseOnSurface = md_theme_light_inverseOnSurface,
34+
inverseSurface = md_theme_light_inverseSurface,
35+
inversePrimary = md_theme_light_inversePrimary,
36+
surfaceTint = md_theme_light_surfaceTint,
37+
outlineVariant = md_theme_light_outlineVariant,
38+
scrim = md_theme_light_scrim,
39+
)
40+
41+
private val DarkColors = darkColorScheme(
42+
primary = md_theme_dark_primary,
43+
onPrimary = md_theme_dark_onPrimary,
44+
primaryContainer = md_theme_dark_primaryContainer,
45+
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
46+
secondary = md_theme_dark_secondary,
47+
onSecondary = md_theme_dark_onSecondary,
48+
secondaryContainer = md_theme_dark_secondaryContainer,
49+
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
50+
tertiary = md_theme_dark_tertiary,
51+
onTertiary = md_theme_dark_onTertiary,
52+
tertiaryContainer = md_theme_dark_tertiaryContainer,
53+
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
54+
error = md_theme_dark_error,
55+
errorContainer = md_theme_dark_errorContainer,
56+
onError = md_theme_dark_onError,
57+
onErrorContainer = md_theme_dark_onErrorContainer,
58+
background = md_theme_dark_background,
59+
onBackground = md_theme_dark_onBackground,
60+
surface = md_theme_dark_surface,
61+
onSurface = md_theme_dark_onSurface,
62+
surfaceVariant = md_theme_dark_surfaceVariant,
63+
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
64+
outline = md_theme_dark_outline,
65+
inverseOnSurface = md_theme_dark_inverseOnSurface,
66+
inverseSurface = md_theme_dark_inverseSurface,
67+
inversePrimary = md_theme_dark_inversePrimary,
68+
surfaceTint = md_theme_dark_surfaceTint,
69+
outlineVariant = md_theme_dark_outlineVariant,
70+
scrim = md_theme_dark_scrim,
71+
)
72+
73+
@Composable
74+
fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
75+
MaterialTheme(
76+
colorScheme = if (useDarkTheme) DarkColors else LightColors,
77+
content = content
78+
)
79+
}

0 commit comments

Comments
 (0)