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).
This commit is contained in:
parent
e37336eef2
commit
1af798b04b
|
@ -92,6 +92,7 @@ android {
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
|
compose true
|
||||||
}
|
}
|
||||||
|
|
||||||
packagingOptions {
|
packagingOptions {
|
||||||
|
@ -103,6 +104,10 @@ android {
|
||||||
'META-INF/COPYRIGHT']
|
'META-INF/COPYRIGHT']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.5.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
@ -284,6 +289,12 @@ dependencies {
|
||||||
// Date and time formatting
|
// Date and time formatting
|
||||||
implementation "org.ocpsoft.prettytime:prettytime:5.0.7.Final"
|
implementation "org.ocpsoft.prettytime:prettytime:5.0.7.Final"
|
||||||
|
|
||||||
|
// Jetpack Compose
|
||||||
|
implementation(platform('androidx.compose:compose-bom:2024.02.01'))
|
||||||
|
implementation 'androidx.compose.material3:material3'
|
||||||
|
implementation 'androidx.activity:activity-compose'
|
||||||
|
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||||
|
|
||||||
/** Debugging **/
|
/** Debugging **/
|
||||||
// Memory leak detection
|
// Memory leak detection
|
||||||
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
debugImplementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
||||||
|
@ -293,6 +304,9 @@ dependencies {
|
||||||
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
||||||
|
|
||||||
|
// Jetpack Compose
|
||||||
|
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||||
|
|
||||||
/** Testing **/
|
/** Testing **/
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.mockito:mockito-core:5.6.0'
|
testImplementation 'org.mockito:mockito-core:5.6.0'
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
package org.schabi.newpipe.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SearchBar
|
||||||
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
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.tooling.preview.Preview
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.ui.theme.AppTheme
|
||||||
|
import org.schabi.newpipe.ui.theme.SizeTokens
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TextAction(text: String, modifier: Modifier = Modifier) {
|
||||||
|
Text(text = text, color = MaterialTheme.colorScheme.onSurface, modifier = modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NavigationIcon() {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back",
|
||||||
|
modifier = Modifier.padding(horizontal = SizeTokens.SpacingExtraSmall)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchSuggestionItem(text: String) {
|
||||||
|
// TODO: Add more components here to display all the required details of a search suggestion item.
|
||||||
|
Text(text = text)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun Toolbar(
|
||||||
|
title: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
hasNavigationIcon: Boolean = true,
|
||||||
|
hasSearch: Boolean = false,
|
||||||
|
onSearchQueryChange: ((String) -> List<String>)? = null,
|
||||||
|
actions: @Composable RowScope.() -> Unit = {}
|
||||||
|
) {
|
||||||
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
|
var query by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(text = title) },
|
||||||
|
modifier = modifier,
|
||||||
|
navigationIcon = { if (hasNavigationIcon) NavigationIcon() },
|
||||||
|
actions = {
|
||||||
|
actions()
|
||||||
|
if (hasSearch) {
|
||||||
|
IconButton(onClick = { isSearchActive = true }) {
|
||||||
|
Icon(
|
||||||
|
painterResource(id = R.drawable.ic_search),
|
||||||
|
contentDescription = stringResource(id = R.string.search),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (isSearchActive) {
|
||||||
|
SearchBar(
|
||||||
|
query = query,
|
||||||
|
onQueryChange = { query = it },
|
||||||
|
onSearch = {},
|
||||||
|
placeholder = {
|
||||||
|
Text(text = stringResource(id = R.string.search))
|
||||||
|
},
|
||||||
|
active = true,
|
||||||
|
onActiveChange = {
|
||||||
|
isSearchActive = it
|
||||||
|
},
|
||||||
|
colors = SearchBarDefaults.colors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
|
inputFieldColors = SearchBarDefaults.inputFieldColors(
|
||||||
|
focusedTextColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
unfocusedTextColor = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
onSearchQueryChange?.invoke(query)?.takeIf { it.isNotEmpty() }
|
||||||
|
?.map { suggestionText -> SearchSuggestionItem(text = suggestionText) }
|
||||||
|
?: run {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.fillMaxWidth(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text(text = "╰(°●°╰)")
|
||||||
|
Text(text = stringResource(id = R.string.search_no_results))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ToolbarPreview() {
|
||||||
|
AppTheme {
|
||||||
|
Toolbar(
|
||||||
|
title = "Title",
|
||||||
|
hasSearch = true,
|
||||||
|
onSearchQueryChange = { emptyList() },
|
||||||
|
actions = {
|
||||||
|
TextAction(text = "Action1")
|
||||||
|
TextAction(text = "Action2")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.schabi.newpipe.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val md_theme_light_primary = Color(0xFFBB171C)
|
||||||
|
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_primaryContainer = Color(0xFFFFDAD6)
|
||||||
|
val md_theme_light_onPrimaryContainer = Color(0xFF410002)
|
||||||
|
val md_theme_light_secondary = Color(0xFF984061)
|
||||||
|
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_secondaryContainer = Color(0xFFFFD9E2)
|
||||||
|
val md_theme_light_onSecondaryContainer = Color(0xFF3E001D)
|
||||||
|
val md_theme_light_tertiary = Color(0xFF006874)
|
||||||
|
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_tertiaryContainer = Color(0xFF97F0FF)
|
||||||
|
val md_theme_light_onTertiaryContainer = Color(0xFF001F24)
|
||||||
|
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||||
|
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||||
|
val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||||
|
val md_theme_light_background = Color(0xFFEEEEEE)
|
||||||
|
val md_theme_light_onBackground = Color(0xFF1B1B1B)
|
||||||
|
val md_theme_light_surface = Color(0xFFE53835)
|
||||||
|
val md_theme_light_onSurface = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_surfaceVariant = Color(0xFFF5DDDB)
|
||||||
|
val md_theme_light_onSurfaceVariant = Color(0xFF534341)
|
||||||
|
val md_theme_light_outline = Color(0xFF857371)
|
||||||
|
val md_theme_light_inverseOnSurface = Color(0xFFD6F6FF)
|
||||||
|
val md_theme_light_inverseSurface = Color(0xFF00363F)
|
||||||
|
val md_theme_light_inversePrimary = Color(0xFFFFB4AC)
|
||||||
|
val md_theme_light_surfaceTint = Color(0xFFBB171C)
|
||||||
|
val md_theme_light_outlineVariant = Color(0xFFD8C2BF)
|
||||||
|
val md_theme_light_scrim = Color(0xFF000000)
|
||||||
|
|
||||||
|
val md_theme_dark_primary = Color(0xFFFFB4AC)
|
||||||
|
val md_theme_dark_onPrimary = Color(0xFF690006)
|
||||||
|
val md_theme_dark_primaryContainer = Color(0xFF93000D)
|
||||||
|
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDAD6)
|
||||||
|
val md_theme_dark_secondary = Color(0xFFFFB1C8)
|
||||||
|
val md_theme_dark_onSecondary = Color(0xFF5E1133)
|
||||||
|
val md_theme_dark_secondaryContainer = Color(0xFF7B2949)
|
||||||
|
val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9E2)
|
||||||
|
val md_theme_dark_tertiary = Color(0xFF4FD8EB)
|
||||||
|
val md_theme_dark_onTertiary = Color(0xFF00363D)
|
||||||
|
val md_theme_dark_tertiaryContainer = Color(0xFF004F58)
|
||||||
|
val md_theme_dark_onTertiaryContainer = Color(0xFF97F0FF)
|
||||||
|
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||||
|
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||||
|
val md_theme_dark_onError = Color(0xFF690005)
|
||||||
|
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||||
|
val md_theme_dark_background = Color(0xFF212121)
|
||||||
|
val md_theme_dark_onBackground = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_dark_surface = Color(0xFF992521)
|
||||||
|
val md_theme_dark_onSurface = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_dark_surfaceVariant = Color(0xFF534341)
|
||||||
|
val md_theme_dark_onSurfaceVariant = Color(0xFFD8C2BF)
|
||||||
|
val md_theme_dark_outline = Color(0xFFA08C8A)
|
||||||
|
val md_theme_dark_inverseOnSurface = Color(0xFF001F25)
|
||||||
|
val md_theme_dark_inverseSurface = Color(0xFFA6EEFF)
|
||||||
|
val md_theme_dark_inversePrimary = Color(0xFFBB171C)
|
||||||
|
val md_theme_dark_surfaceTint = Color(0xFFFFB4AC)
|
||||||
|
val md_theme_dark_outlineVariant = Color(0xFF534341)
|
||||||
|
val md_theme_dark_scrim = Color(0xFF000000)
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.schabi.newpipe.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
internal object SizeTokens {
|
||||||
|
val SpacingExtraSmall = 4.dp
|
||||||
|
val SpacingSmall = 8.dp
|
||||||
|
val SpacingMedium = 16.dp
|
||||||
|
val SpacingLarge = 24.dp
|
||||||
|
val SpacingExtraLarge = 32.dp
|
||||||
|
|
||||||
|
val SpaceMinSize = 44.dp // Minimum tappable size required for accessibility
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package org.schabi.newpipe.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
private val LightColors = lightColorScheme(
|
||||||
|
primary = md_theme_light_primary,
|
||||||
|
onPrimary = md_theme_light_onPrimary,
|
||||||
|
primaryContainer = md_theme_light_primaryContainer,
|
||||||
|
onPrimaryContainer = md_theme_light_onPrimaryContainer,
|
||||||
|
secondary = md_theme_light_secondary,
|
||||||
|
onSecondary = md_theme_light_onSecondary,
|
||||||
|
secondaryContainer = md_theme_light_secondaryContainer,
|
||||||
|
onSecondaryContainer = md_theme_light_onSecondaryContainer,
|
||||||
|
tertiary = md_theme_light_tertiary,
|
||||||
|
onTertiary = md_theme_light_onTertiary,
|
||||||
|
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||||
|
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||||
|
error = md_theme_light_error,
|
||||||
|
errorContainer = md_theme_light_errorContainer,
|
||||||
|
onError = md_theme_light_onError,
|
||||||
|
onErrorContainer = md_theme_light_onErrorContainer,
|
||||||
|
background = md_theme_light_background,
|
||||||
|
onBackground = md_theme_light_onBackground,
|
||||||
|
surface = md_theme_light_surface,
|
||||||
|
onSurface = md_theme_light_onSurface,
|
||||||
|
surfaceVariant = md_theme_light_surfaceVariant,
|
||||||
|
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||||
|
outline = md_theme_light_outline,
|
||||||
|
inverseOnSurface = md_theme_light_inverseOnSurface,
|
||||||
|
inverseSurface = md_theme_light_inverseSurface,
|
||||||
|
inversePrimary = md_theme_light_inversePrimary,
|
||||||
|
surfaceTint = md_theme_light_surfaceTint,
|
||||||
|
outlineVariant = md_theme_light_outlineVariant,
|
||||||
|
scrim = md_theme_light_scrim,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val DarkColors = darkColorScheme(
|
||||||
|
primary = md_theme_dark_primary,
|
||||||
|
onPrimary = md_theme_dark_onPrimary,
|
||||||
|
primaryContainer = md_theme_dark_primaryContainer,
|
||||||
|
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||||
|
secondary = md_theme_dark_secondary,
|
||||||
|
onSecondary = md_theme_dark_onSecondary,
|
||||||
|
secondaryContainer = md_theme_dark_secondaryContainer,
|
||||||
|
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
|
||||||
|
tertiary = md_theme_dark_tertiary,
|
||||||
|
onTertiary = md_theme_dark_onTertiary,
|
||||||
|
tertiaryContainer = md_theme_dark_tertiaryContainer,
|
||||||
|
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
|
||||||
|
error = md_theme_dark_error,
|
||||||
|
errorContainer = md_theme_dark_errorContainer,
|
||||||
|
onError = md_theme_dark_onError,
|
||||||
|
onErrorContainer = md_theme_dark_onErrorContainer,
|
||||||
|
background = md_theme_dark_background,
|
||||||
|
onBackground = md_theme_dark_onBackground,
|
||||||
|
surface = md_theme_dark_surface,
|
||||||
|
onSurface = md_theme_dark_onSurface,
|
||||||
|
surfaceVariant = md_theme_dark_surfaceVariant,
|
||||||
|
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
|
||||||
|
outline = md_theme_dark_outline,
|
||||||
|
inverseOnSurface = md_theme_dark_inverseOnSurface,
|
||||||
|
inverseSurface = md_theme_dark_inverseSurface,
|
||||||
|
inversePrimary = md_theme_dark_inversePrimary,
|
||||||
|
surfaceTint = md_theme_dark_surfaceTint,
|
||||||
|
outlineVariant = md_theme_dark_outlineVariant,
|
||||||
|
scrim = md_theme_dark_scrim,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppTheme(useDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = if (useDarkTheme) DarkColors else LightColors,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue