Skip to content

Commit

Permalink
Merge pull request #75 from Divinelink/feature/details-fab-actions
Browse files Browse the repository at this point in the history
[Expandable FAB] Introduce Expandable FAB Component on Details Screen
  • Loading branch information
Divinelink authored Oct 13, 2024
2 parents 227e43c + f79a15b commit a3f9a52
Show file tree
Hide file tree
Showing 53 changed files with 1,048 additions and 462 deletions.
2 changes: 0 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@ dependencies {
implementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.tooling.preview)

implementation(libs.compose.coil)

debugImplementation(libs.compose.ui.tooling.preview)
debugImplementation(libs.compose.ui.test.manifest)

Expand Down

Large diffs are not rendered by default.

86 changes: 81 additions & 5 deletions app/src/test/kotlin/com/andreolas/ui/details/DetailsContentTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.andreolas.ui.details

import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithContentDescription
Expand All @@ -13,11 +14,14 @@ import com.andreolas.factories.ReviewFactory
import com.andreolas.factories.details.domain.model.account.AccountMediaDetailsFactory
import com.andreolas.factories.details.domain.model.account.AccountMediaDetailsFactory.toWizard
import com.andreolas.movierama.R
import com.divinelink.core.model.details.DetailActionItem
import com.divinelink.core.model.details.DetailsMenuOptions
import com.divinelink.core.model.details.video.Video
import com.divinelink.core.model.details.video.VideoSite
import com.divinelink.core.model.media.MediaType
import com.divinelink.core.testing.ComposeTest
import com.divinelink.core.testing.factories.model.details.MediaDetailsFactory
import com.divinelink.core.testing.getString
import com.divinelink.core.testing.setContentWithTheme
import com.divinelink.core.ui.TestTags
import com.divinelink.core.ui.TestTags.LOADING_CONTENT
Expand All @@ -26,7 +30,6 @@ import com.divinelink.core.ui.components.details.reviews.REVIEWS_LIST
import com.divinelink.core.ui.components.details.videos.VIDEO_PLAYER_TAG
import com.divinelink.feature.details.media.ui.DetailsContent
import com.divinelink.feature.details.media.ui.DetailsViewState
import com.divinelink.feature.details.media.ui.MOVIE_DETAILS_SCROLLABLE_LIST_TAG
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
Expand Down Expand Up @@ -145,7 +148,7 @@ class DetailsContentTest : ComposeTest() {
val reviewsTitle = composeTestRule.activity.getString(uiR.string.details__reviews)

composeTestRule
.onNodeWithTag(MOVIE_DETAILS_SCROLLABLE_LIST_TAG)
.onNodeWithTag(TestTags.Details.CONTENT_LIST)
.performScrollToNode(
hasText(reviewsTitle),
)
Expand Down Expand Up @@ -464,6 +467,7 @@ class DetailsContentTest : ComposeTest() {
mediaId = 0,
mediaType = MediaType.MOVIE,
mediaDetails = MediaDetailsFactory.FightClub(),
menuOptions = listOf(DetailsMenuOptions.SHARE),
),
onNavigateUp = {},
onMarkAsFavoriteClicked = {},
Expand All @@ -480,9 +484,7 @@ class DetailsContentTest : ComposeTest() {
with(composeTestRule) {
onNodeWithTag(TestTags.Menu.MENU_BUTTON_VERTICAL).performClick()
onNodeWithTag(
TestTags.Menu.MENU_ITEM.format(
composeTestRule.activity.getString(uiR.string.core_ui_share),
),
TestTags.Menu.MENU_ITEM.format(getString(uiR.string.core_ui_share)),
)
.assertIsDisplayed()
.performClick()
Expand Down Expand Up @@ -516,5 +518,79 @@ class DetailsContentTest : ComposeTest() {
}
}

@Test
fun `test open request dialog for tv show`() {
setContentWithTheme {
DetailsContent(
viewState = DetailsViewState(
mediaId = 0,
mediaType = MediaType.TV,
actionButtons = DetailActionItem.entries,
mediaDetails = MediaDetailsFactory.TheOffice(),
),
onNavigateUp = {},
onMarkAsFavoriteClicked = {},
onSimilarMovieClicked = {},
onConsumeSnackbar = {},
onAddRateClicked = {},
onAddToWatchlistClicked = {},
requestMedia = {},
viewAllCreditsClicked = {},
onPersonClick = {},
)
}
composeTestRule
.onNodeWithText(getString(detailsR.string.feature_details_request))
.assertIsNotDisplayed()

composeTestRule
.onNodeWithTag(TestTags.Components.ExpandableFab.BUTTON)
.performClick()

composeTestRule
.onNodeWithText(getString(detailsR.string.feature_details_request))
.assertIsDisplayed()
.performClick()

composeTestRule.onNodeWithTag(TestTags.Dialogs.SELECT_SEASONS_DIALOG).assertIsDisplayed()
}

@Test
fun `test open request dialog for movie`() {
setContentWithTheme {
DetailsContent(
viewState = DetailsViewState(
mediaId = 0,
mediaType = MediaType.MOVIE,
actionButtons = DetailActionItem.entries,
mediaDetails = MediaDetailsFactory.FightClub(),
),
onNavigateUp = {},
onMarkAsFavoriteClicked = {},
onSimilarMovieClicked = {},
onConsumeSnackbar = {},
onAddRateClicked = {},
onAddToWatchlistClicked = {},
requestMedia = {},
viewAllCreditsClicked = {},
onPersonClick = {},
)
}
composeTestRule
.onNodeWithText(getString(detailsR.string.feature_details_request))
.assertIsNotDisplayed()

composeTestRule
.onNodeWithTag(TestTags.Components.ExpandableFab.BUTTON)
.performClick()

composeTestRule
.onNodeWithText(getString(detailsR.string.feature_details_request))
.assertIsDisplayed()
.performClick()

composeTestRule.onNodeWithTag(TestTags.Dialogs.REQUEST_MOVIE_DIALOG).assertIsDisplayed()
}

private val reviews = ReviewFactory.ReviewList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performScrollToIndex
import androidx.compose.ui.test.performScrollToNode
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeRight
Expand Down Expand Up @@ -34,7 +34,6 @@ import com.divinelink.core.ui.TestTags
import com.divinelink.feature.credits.screens.destinations.CreditsScreenDestination
import com.divinelink.feature.details.media.ui.DetailsScreen
import com.divinelink.feature.details.media.ui.DetailsViewModel
import com.divinelink.feature.details.media.ui.MOVIE_DETAILS_SCROLLABLE_LIST_TAG
import com.divinelink.feature.details.media.ui.MediaDetailsResult
import com.divinelink.feature.details.screens.destinations.DetailsScreenDestination
import com.divinelink.feature.settings.screens.destinations.AccountSettingsScreenDestination
Expand Down Expand Up @@ -112,7 +111,7 @@ class DetailsScreenTest : ComposeTest() {
}

composeTestRule
.onNodeWithTag(MOVIE_DETAILS_SCROLLABLE_LIST_TAG)
.onNodeWithTag(TestTags.Details.CONTENT_LIST)
.performScrollToNode(
matcher = hasText(
MediaItemFactory.MoviesList()[0].name,
Expand Down Expand Up @@ -316,9 +315,6 @@ class DetailsScreenTest : ComposeTest() {
val requestMediaUseCase = FakeRequestMediaUseCase()
val destinationsNavigator = FakeDestinationsNavigator()

var verifyNavigatedToCredits: Pair<Boolean, CreditsNavArguments?> = false to null
var verifyNavigatedToDetails = false

// Initial navigation to Details screen
destinationsNavigator.navigate(
direction = DetailsScreenDestination(
Expand Down Expand Up @@ -379,8 +375,9 @@ class DetailsScreenTest : ComposeTest() {
}

with(composeTestRule) {
onNodeWithTag(TestTags.Details.CONTENT_LIST).performScrollToIndex(2)

onNodeWithText(getString(R.string.core_ui_view_all))
.performScrollTo()
.assertIsDisplayed()
.performClick()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ class ErrorHandler private constructor() {

fun create(
throwable: Throwable,
actions: ErrorHandler.() -> Unit,
) = ErrorHandler().apply(actions).handle(throwable)
skipGlobal: Boolean = false,
actions: ErrorHandler.() -> Unit = {},
) = ErrorHandler().apply(actions).handle(throwable = throwable, skipGlobal = skipGlobal)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.divinelink.core.datastore.DataStorePreferenceStorage.PreferencesKeys.
import com.divinelink.core.datastore.DataStorePreferenceStorage.PreferencesKeys.PREF_SELECTED_THEME
import com.divinelink.core.designsystem.theme.Theme
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import timber.log.Timber

Expand Down Expand Up @@ -150,7 +151,7 @@ class DataStorePreferenceStorage(private val dataStore: DataStore<Preferences>)

override val hasSession: Flow<Boolean> = dataStore.data.map {
it[PREF_HAS_SESSION] ?: false
}
}.distinctUntilChanged()

override suspend fun clearAccountId() {
dataStore.edit {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,4 @@
<color name="colorShadow">@color/dark_colorShadow</color>
<color name="colorSurfaceTint">@color/dark_colorSurfaceTint</color>
<color name="colorSurfaceTintColor">@color/dark_colorSurfaceTintColor</color>

<color name="colorSurface2">@color/dark_colorSurface2</color>

<color name="colorSurface_60">@color/dark_colorSurface_60</color>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,4 @@
<color name="dark_colorShadow">#000000</color>
<color name="dark_colorSurfaceTint">#5BD5FA</color>
<color name="dark_colorSurfaceTintColor">#5BD5FA</color>

<color name="dark_colorSurface2">#172224</color>

<color name="dark_colorSurface_60">#99FBFCFF</color>-->
</resources>
4 changes: 0 additions & 4 deletions core/designsystem/src/main/res/values/material3_colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,4 @@
<color name="colorShadow">@color/light_colorShadow</color>
<color name="colorSurfaceTint">@color/light_colorSurfaceTint</color>
<color name="colorSurfaceTintColor">@color/light_colorSurfaceTintColor</color>

<color name="colorSurface2">@color/light_colorSurface2</color>

<color name="colorSurface_60">@color/light_colorSurface_60</color>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,4 @@
<color name="light_colorShadow">#000000</color>
<color name="light_colorSurfaceTint">#00677E</color>
<color name="light_colorSurfaceTintColor">#00677E</color>

<color name="light_colorSurface2">#F0F9FE</color>
<color name="light_colorSurface_60">#99FBFCFF</color>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.divinelink.core.domain

import com.divinelink.core.commons.domain.DispatcherProvider
import com.divinelink.core.commons.domain.FlowUseCase
import com.divinelink.core.datastore.PreferenceStorage
import com.divinelink.core.model.details.DetailActionItem
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine

class GetDetailsActionItemsUseCase(
private val storage: PreferenceStorage,
val dispatcher: DispatcherProvider,
) : FlowUseCase<Unit, List<DetailActionItem>>(dispatcher.io) {

override fun execute(parameters: Unit): Flow<Result<List<DetailActionItem>>> = combine(
storage.jellyseerrAccount,
) { account ->

val menuItems = buildList {
add(DetailActionItem.RATE)
add(DetailActionItem.WATCHLIST)
account.firstOrNull()?.let {
add(DetailActionItem.REQUEST)
}
}
Result.success(menuItems)
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
package com.divinelink.core.domain

import com.divinelink.core.commons.domain.DispatcherProvider
import com.divinelink.core.commons.domain.FlowUseCase
import com.divinelink.core.datastore.PreferenceStorage
import com.divinelink.core.model.details.DetailsMenuOptions
import com.divinelink.core.commons.domain.DispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow

class GetDropdownMenuItemsUseCase(
private val storage: PreferenceStorage,
val dispatcher: DispatcherProvider,
) : FlowUseCase<Unit, List<DetailsMenuOptions>>(dispatcher.io) {
class GetDropdownMenuItemsUseCase(val dispatcher: DispatcherProvider) :
FlowUseCase<Unit, List<DetailsMenuOptions>>(dispatcher.io) {

override fun execute(parameters: Unit): Flow<Result<List<DetailsMenuOptions>>> = combine(
storage.jellyseerrAccount,
) { account ->
val menuItems = mutableListOf<DetailsMenuOptions>()
menuItems.add(DetailsMenuOptions.SHARE)

account.firstOrNull()?.let {
menuItems.add(DetailsMenuOptions.REQUEST)
override fun execute(parameters: Unit): Flow<Result<List<DetailsMenuOptions>>> = flow {
val menuItems = buildList {
add(DetailsMenuOptions.SHARE)
}

Result.success(menuItems)
emit(Result.success(menuItems))
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.divinelink.core.domain.di

import com.divinelink.core.domain.CreateRequestTokenUseCase
import com.divinelink.core.domain.change.FetchChangesUseCase
import com.divinelink.core.domain.FetchWatchlistUseCase
import com.divinelink.core.domain.GetAccountDetailsUseCase
import com.divinelink.core.domain.GetDropdownMenuItemsUseCase
import com.divinelink.core.domain.GetDetailsActionItemsUseCase
import com.divinelink.core.domain.HandleAuthenticationRequestUseCase
import com.divinelink.core.domain.MarkAsFavoriteUseCase
import com.divinelink.core.domain.change.FetchChangesUseCase
import com.divinelink.core.domain.credits.FetchCreditsUseCase
import com.divinelink.core.domain.details.person.FetchPersonDetailsUseCase
import com.divinelink.core.domain.jellyseerr.GetJellyseerrAccountDetailsUseCase
Expand Down Expand Up @@ -43,6 +44,7 @@ val useCaseModule = module {
factoryOf(::FetchWatchlistUseCase)
factoryOf(::GetAccountDetailsUseCase)
factoryOf(::GetDropdownMenuItemsUseCase)
factoryOf(::GetDetailsActionItemsUseCase)
factoryOf(::HandleAuthenticationRequestUseCase)
factoryOf(::MarkAsFavoriteUseCase)
}
Loading

0 comments on commit a3f9a52

Please sign in to comment.