Skip to content

Commit

Permalink
generate the spotlight tools in the ToolsPresenter
Browse files Browse the repository at this point in the history
  • Loading branch information
frett committed Dec 22, 2023
1 parent 23a2ccf commit 1024597
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import dagger.hilt.components.SingletonComponent
import org.ccci.gto.android.common.androidx.compose.foundation.layout.padding
import org.cru.godtools.R
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_ALL_TOOLS
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_SPOTLIGHT
import org.cru.godtools.ui.banner.Banners
import org.cru.godtools.ui.tools.SquareToolCard
import org.cru.godtools.ui.tools.ToolCard
Expand Down Expand Up @@ -63,8 +62,7 @@ internal fun ToolsLayout(state: ToolsScreen.State, modifier: Modifier = Modifier
if (spotlightTools.isNotEmpty()) {
item("tool-spotlight", "tool-spotlight") {
ToolSpotlight(
state,
toolViewModels,
spotlightTools,
modifier = Modifier
.animateItemPlacement()
.padding(top = 16.dp)
Expand Down Expand Up @@ -111,11 +109,7 @@ internal fun ToolsLayout(state: ToolsScreen.State, modifier: Modifier = Modifier
}

@Composable
private fun ToolSpotlight(state: ToolsScreen.State, toolViewModels: ToolViewModels, modifier: Modifier = Modifier) {
val spotlightTools by rememberUpdatedState(state.spotlightTools)
val selectedLanguage by rememberUpdatedState(state.filters.selectedLanguage)
val eventSink by rememberUpdatedState(state.eventSink)

private fun ToolSpotlight(tools: List<ToolCard.State>, modifier: Modifier = Modifier) {
Column(modifier = modifier.fillMaxWidth()) {
Text(
stringResource(R.string.dashboard_tools_section_spotlight_label),
Expand All @@ -136,20 +130,11 @@ private fun ToolSpotlight(state: ToolsScreen.State, toolViewModels: ToolViewMode
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(vertical = 8.dp)
) {
items(spotlightTools, key = { it.code.orEmpty() }) { tool ->
val toolViewModel = toolViewModels[tool.code.orEmpty()]
val toolState = toolViewModel.toState(secondLanguage = selectedLanguage) {
when (it) {
ToolCard.Event.Click, ToolCard.Event.OpenTool, ToolCard.Event.OpenToolDetails ->
tool.code?.let { eventSink(ToolsScreen.Event.OpenToolDetails(it, SOURCE_SPOTLIGHT)) }
ToolCard.Event.PinTool -> toolViewModel.pinTool()
ToolCard.Event.UnpinTool -> toolViewModel.unpinTool()
}
}

items(tools, key = { it.tool?.code.orEmpty() }) { tool ->
SquareToolCard(
state = toolState,
state = tool,
showCategory = false,
showSecondLanguage = true,
showActions = false,
floatParallelLanguageUp = false,
confirmRemovalFromFavorites = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.lifecycle.viewmodel.compose.viewModel
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.runtime.Navigator
Expand All @@ -17,22 +18,31 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.ACTION_OPEN_TOOL_DETAILS
import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_SPOTLIGHT
import org.cru.godtools.base.Settings
import org.cru.godtools.db.repository.LanguagesRepository
import org.cru.godtools.db.repository.ToolsRepository
import org.cru.godtools.model.Language
import org.cru.godtools.model.Tool
import org.cru.godtools.ui.banner.BannerType
import org.cru.godtools.ui.tooldetails.ToolDetailsScreen
import org.cru.godtools.ui.tools.ToolCard
import org.cru.godtools.ui.tools.ToolCardPresenter
import org.greenrobot.eventbus.EventBus

class ToolsPresenter @AssistedInject constructor(
private val eventBus: EventBus,
private val settings: Settings,
private val toolCardPresenter: ToolCardPresenter,
private val languagesRepository: LanguagesRepository,
private val toolsRepository: ToolsRepository,
@Assisted private val navigator: Navigator,
) : Presenter<ToolsScreen.State> {
@Composable
override fun present(): ToolsScreen.State {
val viewModel: ToolsViewModel = viewModel()

// selected language
val selectedLocale by viewModel.selectedLocale.collectAsState()
val selectedLanguage by remember(selectedLocale) {
selectedLocale?.let { languagesRepository.findLanguageFlow(it) } ?: flowOf(null)
Expand All @@ -56,7 +66,7 @@ class ToolsPresenter @AssistedInject constructor(

return ToolsScreen.State(
banner = rememberBanner(),
spotlightTools = viewModel.spotlightTools.collectAsState().value,
spotlightTools = rememberSpotlightTools(secondLanguage = selectedLanguage, eventSink = eventSink),
filters = ToolsScreen.State.Filters(
categories = viewModel.categories.collectAsState().value,
selectedCategory = viewModel.selectedCategory.collectAsState().value,
Expand All @@ -76,6 +86,40 @@ class ToolsPresenter @AssistedInject constructor(
.map { if (!it) BannerType.TOOL_LIST_FAVORITES else null }
}.collectAsState(null).value

@Composable
private fun rememberSpotlightTools(
secondLanguage: Language?,
eventSink: (ToolsScreen.Event) -> Unit,
): List<ToolCard.State> {
val tools by remember {
toolsRepository.getNormalToolsFlow()
.map { it.filter { !it.isHidden && it.isSpotlight }.sortedWith(Tool.COMPARATOR_DEFAULT_ORDER) }
}.collectAsState(emptyList())
val eventSink by rememberUpdatedState(eventSink)

return tools.map { tool ->
val toolCode by rememberUpdatedState(tool.code)
val toolEventSink: (ToolCard.Event) -> Unit = remember {
{
when (it) {
ToolCard.Event.Click,
ToolCard.Event.OpenTool,
ToolCard.Event.OpenToolDetails ->
toolCode?.let { eventSink(ToolsScreen.Event.OpenToolDetails(it, SOURCE_SPOTLIGHT)) }
ToolCard.Event.PinTool,
ToolCard.Event.UnpinTool -> error("$it should be handled by the ToolCardPresenter")
}
}
}

toolCardPresenter.present(
tool = tool,
secondLanguage = secondLanguage,
eventSink = toolEventSink,
)
}
}

@AssistedFactory
@CircuitInject(ToolsScreen::class, SingletonComponent::class)
interface Factory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import kotlinx.parcelize.Parcelize
import org.cru.godtools.model.Language
import org.cru.godtools.model.Tool
import org.cru.godtools.ui.banner.BannerType
import org.cru.godtools.ui.tools.ToolCard

@Parcelize
data object ToolsScreen : Screen {
data class State(
val banner: BannerType? = null,
val spotlightTools: List<Tool> = emptyList(),
val spotlightTools: List<ToolCard.State> = emptyList(),
val filters: Filters = Filters(),
val tools: List<Tool> = emptyList(),
val eventSink: (Event) -> Unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import org.cru.godtools.db.repository.LanguagesRepository
import org.cru.godtools.db.repository.ToolsRepository
import org.cru.godtools.model.Language
import org.cru.godtools.model.Language.Companion.filterByDisplayAndNativeName
import org.cru.godtools.model.Tool

private const val KEY_SELECTED_CATEGORY = "selectedCategory"
private const val KEY_SELECTED_LANGUAGE = "selectedLanguage"
Expand All @@ -37,10 +36,6 @@ class ToolsViewModel @Inject constructor(
languagesRepository: LanguagesRepository,
private val savedState: SavedStateHandle,
) : ViewModel() {
val spotlightTools = toolsRepository.getNormalToolsFlow()
.map { it.filter { !it.isHidden && it.isSpotlight }.sortedWith(Tool.COMPARATOR_DEFAULT_ORDER) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

// region Tools
val selectedCategory = savedState.getStateFlow<String?>(KEY_SELECTED_CATEGORY, null)
fun setSelectedCategory(category: String?) = savedState.set(KEY_SELECTED_CATEGORY, category)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,43 +59,6 @@ class ToolsViewModelTest {
Dispatchers.resetMain()
}

// region Property spotlightTools
@Test
fun `Property spotlightTools`() = testScope.runTest {
viewModel.spotlightTools.test {
assertThat(awaitItem(), empty())

val normal = randomTool("normal", isHidden = false, isSpotlight = false)
val spotlight = randomTool("spotlight", isHidden = false, isSpotlight = true)
toolsFlow.value = listOf(normal, spotlight)
assertEquals(listOf(spotlight), awaitItem())
}
}

@Test
fun `Property spotlightTools - Don't show hidden tools`() = testScope.runTest {
viewModel.spotlightTools.test {
assertThat(awaitItem(), empty())

val hidden = randomTool("normal", isHidden = true, isSpotlight = true)
val spotlight = randomTool("spotlight", isHidden = false, isSpotlight = true)
toolsFlow.value = listOf(hidden, spotlight)
assertEquals(listOf(spotlight), awaitItem())
}
}

@Test
fun `Property spotlightTools - Sorted by default order`() = testScope.runTest {
viewModel.spotlightTools.test {
assertThat(awaitItem(), empty())

val tools = List(10) { randomTool("tool$it", Tool.Type.TRACT, isHidden = false, isSpotlight = true) }
toolsFlow.value = tools
assertEquals(tools.sortedWith(Tool.COMPARATOR_DEFAULT_ORDER), awaitItem())
}
}
// endregion Property spotlightTools

// region Property filteredTools
@Test
fun `Property filteredTools - return only default variants`() = testScope.runTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class ToolsPresenterTest {
private val presenter = ToolsPresenter(
eventBus = mockk(),
settings = settings,
toolCardPresenter = mockk(),
languagesRepository = mockk(),
toolsRepository = mockk(),
navigator = navigator,
)

Expand Down

0 comments on commit 1024597

Please sign in to comment.