Skip to content

Commit

Permalink
Upgrade AGP, Compose, rework DAC rendering (+ fix toolbar)
Browse files Browse the repository at this point in the history
Signed-off-by: iTaysonLab <[email protected]>
  • Loading branch information
iTaysonLab committed Sep 26, 2022
1 parent 4d0197b commit 10ba01d
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 78 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ dependencies {
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.ui:ui-util:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.customview:customview:1.2.0-alpha01"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.customview:customview:1.2.0-alpha02"
debugImplementation "androidx.customview:customview-poolingcontainer:1.0.0"

// Compose - Additions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package bruhcollective.itaysonlab.jetispot.ui.dac

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import bruhcollective.itaysonlab.jetispot.proto.ErrorComponent
import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.*
import bruhcollective.itaysonlab.jetispot.ui.dac.components_plans.*
import com.google.protobuf.Message
Expand Down Expand Up @@ -37,11 +41,25 @@ fun DacRender (
is SectionHeaderComponent -> SectionHeaderComponentBinder(item.title)
is SectionComponent -> SectionComponentBinder(item)
is RecentlyPlayedSectionComponent -> RecentlyPlayedSectionComponentBinder()

// is SnappyGridSectionComponent -> SnappyGridSectionComponentBinder(item)
// Other

is ErrorComponent -> {
Column {
Text(if (item.type == ErrorComponent.ErrorType.UNSUPPORTED) {
"DAC unsupported component"
} else {
"DAC rendering error"
}, Modifier.padding(horizontal = 16.dp))
Text(item.message ?: "", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), modifier = Modifier
.padding(top = 4.dp)
.padding(horizontal = 16.dp))
}
}

else -> {
Text("DAC proto-known, but UI-unknown component: ${item::class.java.simpleName}\n\n${item}")
Spacer(modifier = Modifier.height(8.dp))
Text("DAC proto-known, but UI-unknown component: ${item::class.java.simpleName}\n\n${item}", modifier = Modifier.padding(16.dp))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package bruhcollective.itaysonlab.jetispot.ui.dac.components_home

import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.History
Expand All @@ -9,6 +10,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import bruhcollective.itaysonlab.jetispot.ui.ext.dynamicUnpack
import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController
import com.spotify.home.dac.component.v1.proto.ToolbarComponent
Expand All @@ -21,7 +23,7 @@ import com.spotify.home.dac.component.v1.proto.ToolbarItemSettingsComponent
fun ToolbarComponentBinder(
item: ToolbarComponent
) {
SmallTopAppBar(title = {
TopAppBar(title = {
Text(item.dayPartMessage)
}, actions = {
item.itemsList.forEach {
Expand All @@ -31,7 +33,7 @@ fun ToolbarComponentBinder(
is ToolbarItemSettingsComponent -> ToolbarItem(Icons.Rounded.Settings, protoItem.navigateUri, protoItem.title)
}
}
}, modifier = Modifier.statusBarsPadding())
}, modifier = Modifier.statusBarsPadding(), windowInsets = WindowInsets(top = 0.dp))
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bruhcollective.itaysonlab.jetispot.ui.ext
import com.google.protobuf.Any
import com.google.protobuf.Message

fun Any.dynamicUnpack() = unpack(Class.forName(typeUrl.split("/")[1].let {
@Suppress("UNCHECKED_CAST")
fun Any.dynamicUnpack(): Message = unpack(Class.forName(typeUrl.split("/")[1].let {
if (!it.startsWith("com.spotify")) "com.spotify.${it}" else it
}) as Class<out Message>)
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fun BaseConfigScreen(
}
}
}, scrollBehavior = scrollBehavior)
}, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { padding ->
}, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(top = 0.dp)) { padding ->
LazyColumn(
Modifier
.fillMaxHeight()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fun StorageScreen(
Icon(Icons.Rounded.ArrowBack, null)
}
}, scrollBehavior = scrollBehavior)
}, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { padding ->
}, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(top = 0.dp)) { padding ->
LazyColumn(
modifier = Modifier
.fillMaxHeight()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package bruhcollective.itaysonlab.jetispot.ui.screens.dac

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
Expand All @@ -8,14 +9,12 @@ import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import bruhcollective.itaysonlab.jetispot.core.SpPlayerServiceManager
import bruhcollective.itaysonlab.jetispot.core.api.SpInternalApi
import bruhcollective.itaysonlab.jetispot.proto.ErrorComponent
import bruhcollective.itaysonlab.jetispot.ui.dac.DacRender
import bruhcollective.itaysonlab.jetispot.ui.dac.components_home.FilterComponentBinder
import bruhcollective.itaysonlab.jetispot.ui.ext.dynamicUnpack
Expand All @@ -28,13 +27,16 @@ import com.spotify.dac.api.components.VerticalListComponent
import com.spotify.dac.api.v1.proto.DacResponse
import com.spotify.home.dac.component.experimental.v1.proto.FilterComponent
import com.spotify.home.dac.component.v1.proto.HomePageComponent
import com.spotify.home.dac.component.v1.proto.ToolbarComponent
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject

// generally just a HubScreen with simplifed code and DAC arch usage
// DAC is something like another ServerSideUI from Spotify
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun DacRendererScreen(
title: String,
Expand All @@ -55,10 +57,9 @@ fun DacRendererScreen(
is DacViewModel.State.Loaded -> {
Scaffold(topBar = {
if (fullscreen) {
TopAppBar(title = {}, colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
), scrollBehavior = scrollBehavior)
(viewModel.state as? DacViewModel.State.Loaded)?.sticky?.let { msg ->
DacRender(msg)
}
} else {
LargeTopAppBar(title = {
Text(title)
Expand All @@ -68,56 +69,26 @@ fun DacRendererScreen(
}
}, scrollBehavior = scrollBehavior)
}
}, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) { padding ->
}, modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(top = 0.dp)) { padding ->
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.let { if (!fullscreen) it.padding(padding) else it }
.padding(padding)
) {
(viewModel.state as? DacViewModel.State.Loaded)?.data?.apply {
val cmBind: (List<Any>) -> Unit = { componentsList ->
items(componentsList) { item ->
var exception: Exception? = null
var unpackedItem: Message? = null

try {
unpackedItem = item.dynamicUnpack()
} catch (e: Exception) {
exception = e
}

if (unpackedItem == null && exception != null) {
when (exception) {
is ClassNotFoundException -> {
Text("DAC unsupported component", Modifier.padding(horizontal = 16.dp))
Text(exception.message ?: "", color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f), modifier = Modifier.padding(top = 4.dp).padding(horizontal = 16.dp))
}
else -> {
Text("DAC rendering error: ${exception.message}\n\n${exception.stackTraceToString()}")
}
}

Spacer(modifier = Modifier.height(8.dp))
} else if (unpackedItem != null) {
if (unpackedItem is FilterComponent) {
FilterComponentBinder(unpackedItem, viewModel.facet) { nf ->
scope.launch {
viewModel.facet = nf
viewModel.reload(loader)
}
}
} else {
DacRender(unpackedItem)
(viewModel.state as? DacViewModel.State.Loaded)?.apply {
items(data) { item ->
if (item is FilterComponent) {
FilterComponentBinder(item, viewModel.facet) { nf ->
scope.launch {
viewModel.facet = nf
viewModel.reload(loader)
}
}
} else {
DacRender(item)
}
}

when (this) {
is VerticalListComponent -> cmBind(this.componentsList)
is HomePageComponent -> cmBind(this.componentsList)
}

item {
Spacer(modifier = Modifier.height(8.dp))
}
Expand All @@ -138,8 +109,7 @@ fun DacRendererScreen(

@HiltViewModel
class DacViewModel @Inject constructor(
private val spInternalApi: SpInternalApi,
private val spPlayerServiceManager: SpPlayerServiceManager
private val spInternalApi: SpInternalApi
) : ViewModel() {
var facet = "default"

Expand All @@ -148,22 +118,44 @@ class DacViewModel @Inject constructor(

suspend fun load(loader: suspend SpInternalApi.(String) -> DacResponse) {
_state.value = try {
val unpackedRaw = spInternalApi.loader(facet)
val unpackedMessage = unpackedRaw.component.dynamicUnpack()
State.Loaded(unpackedMessage)
val (sticky, list) = withContext(Dispatchers.Default) {
val messages = parseMessages(when (val protoList = spInternalApi.loader(facet).component.dynamicUnpack()) {
is VerticalListComponent -> protoList.componentsList
is HomePageComponent -> protoList.componentsList
else -> error("Invalid root for DAC renderer! Found: ${protoList.javaClass.simpleName}")
})

if (messages.first() is ToolbarComponent) {
messages.first() to messages.drop(1)
} else {
null to messages
}
}

State.Loaded(sticky, list)
} catch (e: Exception) {
e.printStackTrace()
State.Error(e)
}
}

private fun parseMessages(list: List<Any>): List<Message> = list.map { item ->
try {
item.dynamicUnpack()
} catch (e: ClassNotFoundException) {
ErrorComponent.newBuilder().setType(ErrorComponent.ErrorType.UNSUPPORTED).setMessage(e.message).build()
} catch (e: java.lang.Exception) {
ErrorComponent.newBuilder().setType(ErrorComponent.ErrorType.GENERIC_EXCEPTION).setMessage(e.message + "\n\n" + e.stackTraceToString()).build()
}
}

suspend fun reload(loader: suspend SpInternalApi.(String) -> DacResponse) {
_state.value = State.Loading
load(loader)
}

sealed class State {
class Loaded(val data: Message) : State()
class Loaded(val sticky: Message?, val data: List<Message>) : State()
class Error(val error: Exception) : State()
object Loading : State()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bruhcollective.itaysonlab.jetispot.ui.screens.hub

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
Expand All @@ -10,6 +11,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import bruhcollective.itaysonlab.jetispot.ui.ext.rememberEUCScrollBehavior
import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController

Expand All @@ -28,7 +30,7 @@ fun BrowseRadioScreen() {
Icon(Icons.Rounded.ArrowBack, null)
}
}, colors = TopAppBarDefaults.largeTopAppBarColors(), scrollBehavior = scrollBehavior)
}, modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection)) { padding ->
}, modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(top = 0.dp)) { padding ->
Box(Modifier.padding(padding)) {
HubScreen(
needContentPadding = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import bruhcollective.itaysonlab.jetispot.ui.ext.rememberEUCScrollBehavior
import bruhcollective.itaysonlab.jetispot.ui.navigation.LocalNavigationController

Expand All @@ -29,7 +30,7 @@ fun BrowseScreen(
Icon(Icons.Rounded.ArrowBack, null)
}
}, colors = TopAppBarDefaults.largeTopAppBarColors(), scrollBehavior = scrollBehavior)
}, modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection)) { padding ->
}, modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(top = 0.dp)) { padding ->
Box(Modifier.padding(padding)) {
HubScreen(
needContentPadding = false,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package bruhcollective.itaysonlab.jetispot.ui.screens.hub

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
Expand Down Expand Up @@ -54,7 +51,7 @@ fun HubScaffold(
}
}, colors = TopAppBarDefaults.largeTopAppBarColors(), scrollBehavior = scrollBehavior)
} else {
SmallTopAppBar(title = {
TopAppBar(title = {
Text(appBarTitle, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.alpha(scrollBehavior.state.overlappedFraction))
}, navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Expand All @@ -67,7 +64,7 @@ fun HubScaffold(
}
}, modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)) { padding ->
.nestedScroll(scrollBehavior.nestedScrollConnection), contentWindowInsets = WindowInsets(top = 0.dp)) { padding ->
CompositionLocalProvider(LocalHubScreenDelegate provides viewModel) {
LazyColumn(
modifier = Modifier
Expand Down
1 change: 1 addition & 0 deletions app/src/main/proto/sp/context_menu.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// com.spotify.home.dac.contextMenu.v1.proto
Loading

0 comments on commit 10ba01d

Please sign in to comment.