Skip to content

Commit

Permalink
Merge pull request #49 from Divinelink/feature/share-url
Browse files Browse the repository at this point in the history
[Feature] Share Media item
  • Loading branch information
Divinelink authored Jun 1, 2024
2 parents d60d07d + b3c0c10 commit 09eb5dd
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.andreolas.movierama.details.ui

import android.content.Intent
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
Expand All @@ -15,11 +16,15 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
Expand All @@ -35,24 +40,29 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.andreolas.movierama.ExcludeFromKoverReport
import com.andreolas.movierama.R
import com.andreolas.movierama.home.ui.LoadingContent
import com.andreolas.movierama.ui.TestTags
import com.andreolas.movierama.ui.UIText
import com.andreolas.movierama.ui.components.FavoriteButton
import com.andreolas.movierama.ui.components.MovieImage
Expand Down Expand Up @@ -100,8 +110,19 @@ fun DetailsContent(
onConsumeSnackbar: () -> Unit,
onAddRateClicked: () -> Unit,
onAddToWatchlistClicked: () -> Unit,
showOrHideShareDialog: (Boolean) -> Unit,
) {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
var showOverflowMenu by remember { mutableStateOf(false) }

if (viewState.openShareDialog) {
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, viewState.shareUrl)
}
LocalContext.current.startActivity(Intent.createChooser(shareIntent, "Share via"))
showOrHideShareDialog(false)
}

SnackbarMessageHandler(
snackbarMessage = viewState.snackbarMessage,
Expand Down Expand Up @@ -138,12 +159,39 @@ fun DetailsContent(
},
actions = {
FavoriteButton(
modifier = Modifier
.padding(end = MaterialTheme.dimensions.keyline_8)
.clip(MaterialTheme.shape.roundedShape),
modifier = Modifier.clip(MaterialTheme.shape.roundedShape),
isFavorite = viewState.mediaDetails?.isFavorite ?: false,
onClick = onMarkAsFavoriteClicked,
inactiveColor = MaterialTheme.colorScheme.onSurface,
)

IconButton(
modifier = Modifier.testTag(TestTags.Menu.MENU_BUTTON_VERTICAL),
onClick = { showOverflowMenu = !showOverflowMenu }) {
Icon(Icons.Outlined.MoreVert, "More")
}

DropdownMenu(
modifier = Modifier
.widthIn(min = 180.dp)
.testTag(TestTags.Menu.DROPDOWN_MENU),
expanded = showOverflowMenu,
onDismissRequest = { showOverflowMenu = false }
) {

DropdownMenuItem(
modifier = Modifier.testTag(
TestTags.Menu.MENU_ITEM.format(stringResource(id = R.string.share))
),
text = {
Text(text = stringResource(id = R.string.share))
},
onClick = {
showOverflowMenu = false
showOrHideShareDialog(true)
},
)
}
}
)
},
Expand Down Expand Up @@ -516,6 +564,7 @@ private fun DetailsContentPreview(
onConsumeSnackbar = {},
onAddRateClicked = {},
onAddToWatchlistClicked = {},
showOrHideShareDialog = {},
)
}
}
Expand Down Expand Up @@ -616,13 +665,13 @@ class DetailsViewStateProvider : PreviewParameterProvider<DetailsViewState> {

return sequenceOf(
DetailsViewState(
movieId = popularMovie.id,
mediaId = popularMovie.id,
mediaType = MediaType.MOVIE,
isLoading = true,
),

DetailsViewState(
movieId = popularMovie.id,
mediaId = popularMovie.id,
userDetails = AccountMediaDetails(
id = 8679,
favorite = false,
Expand All @@ -634,22 +683,22 @@ class DetailsViewStateProvider : PreviewParameterProvider<DetailsViewState> {
),

DetailsViewState(
movieId = popularMovie.id,
mediaId = popularMovie.id,
mediaType = MediaType.TV,
mediaDetails = movieDetails,
similarMovies = similarMovies,
),

DetailsViewState(
movieId = popularMovie.id,
mediaId = popularMovie.id,
mediaType = MediaType.MOVIE,
mediaDetails = movieDetails,
similarMovies = similarMovies,
reviews = reviews,
),

DetailsViewState(
movieId = popularMovie.id,
mediaId = popularMovie.id,
mediaType = MediaType.MOVIE,
mediaDetails = movieDetails,
similarMovies = similarMovies,
Expand All @@ -663,7 +712,7 @@ class DetailsViewStateProvider : PreviewParameterProvider<DetailsViewState> {
),

DetailsViewState(
movieId = popularMovie.id,
mediaId = popularMovie.id,
mediaType = MediaType.MOVIE,
error = UIText.StringText("Something went wrong.")
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ fun DetailsScreen(
},
onConsumeSnackbar = viewModel::consumeSnackbarMessage,
onAddRateClicked = viewModel::onAddRateClicked,
onAddToWatchlistClicked = viewModel::onAddToWatchlist
onAddToWatchlistClicked = viewModel::onAddToWatchlist,
showOrHideShareDialog = viewModel::onShareClicked
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class DetailsViewModel @Inject constructor(

private val _viewState: MutableStateFlow<DetailsViewState> = MutableStateFlow(
value = DetailsViewState(
movieId = args.id,
mediaId = args.id,
mediaType = MediaType.from(args.mediaType),
isLoading = true,
)
Expand Down Expand Up @@ -142,7 +142,7 @@ class DetailsViewModel @Inject constructor(
viewModelScope.launch {
submitRatingUseCase.invoke(
SubmitRatingParameters(
id = viewState.value.movieId,
id = viewState.value.mediaId,
mediaType = viewState.value.mediaType,
rating = rating
)
Expand Down Expand Up @@ -188,7 +188,7 @@ class DetailsViewModel @Inject constructor(
viewModelScope.launch {
deleteRatingUseCase.invoke(
DeleteRatingParameters(
id = viewState.value.movieId,
id = viewState.value.mediaId,
mediaType = viewState.value.mediaType
)
).collectLatest { result ->
Expand Down Expand Up @@ -225,7 +225,7 @@ class DetailsViewModel @Inject constructor(
viewModelScope.launch {
addToWatchlistUseCase.invoke(
AddToWatchlistParameters(
id = viewState.value.movieId,
id = viewState.value.mediaId,
mediaType = viewState.value.mediaType,
addToWatchlist = viewState.value.userDetails?.watchlist == false,
)
Expand Down Expand Up @@ -279,6 +279,14 @@ class DetailsViewModel @Inject constructor(
}
}

fun onShareClicked(openShareDialog: Boolean) {
_viewState.update { viewState ->
viewState.copy(
openShareDialog = openShareDialog
)
}
}

internal fun navigateToLogin(snackbarResult: SnackbarResult) {
if (snackbarResult == SnackbarResult.ActionPerformed) {
_viewState.update { viewState ->
Expand All @@ -292,7 +300,7 @@ class DetailsViewModel @Inject constructor(

private suspend fun fetchAccountMediaDetails() {
val params = AccountMediaDetailsParams(
id = viewState.value.movieId,
id = viewState.value.mediaId,
mediaType = viewState.value.mediaType
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.divinelink.core.model.media.MediaType
data class DetailsViewState(
val isLoading: Boolean = false,
val mediaType: MediaType,
val movieId: Int,
val mediaId: Int,
val mediaDetails: MediaDetails? = null,
val userDetails: AccountMediaDetails? = null,
val reviews: List<Review>? = null,
Expand All @@ -24,6 +24,7 @@ data class DetailsViewState(
val snackbarMessage: SnackbarMessage? = null,
val showRateDialog: Boolean = false,
val navigateToLogin: Boolean? = null,
val openShareDialog: Boolean = false,
) {
val mediaItem = when (mediaDetails) {
is Movie -> MediaItem.Media.Movie(
Expand All @@ -46,4 +47,12 @@ data class DetailsViewState(
)
null -> null
}

private val urlTitle = mediaDetails
?.title
?.lowercase()
?.replace(":", "")
?.replace(regex = "[\\s|/]".toRegex(), replacement = "-")

val shareUrl = "https://themoviedb.org/${mediaType.value}/$mediaId-$urlTitle"
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/andreolas/movierama/ui/TestTags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ object TestTags {
const val LOGOUT_BUTTON = "Account Logout Button"
}
}

object Menu {
const val MENU_BUTTON_VERTICAL = "Menu Button Vertical"
const val DROPDOWN_MENU = "Dropdown Menu"
const val MENU_ITEM = "Menu Item %s"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ fun FavoriteButton(
modifier: Modifier = Modifier,
isFavorite: Boolean,
transparentBackground: Boolean = false,
inactiveColor: Color = colorResource(id = R.color.core_grey_55),
onClick: () -> Unit,
) {
val color by animateColorAsState(
targetValue = when (isFavorite) {
true -> colorResource(id = R.color.core_red_highlight)
false -> colorResource(id = R.color.core_grey_55)
false -> inactiveColor
},
label = "Like button color",
)
Expand Down Expand Up @@ -62,7 +63,7 @@ fun FavoriteButton(
false -> Icons.Default.FavoriteBorder
}
Icon(
modifier = Modifier.size(MaterialTheme.dimensions.keyline_32),
modifier = Modifier.size(MaterialTheme.dimensions.keyline_26),
imageVector = image,
tint = color,
contentDescription = stringResource(R.string.mark_as_favorite_button_content_description),
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<string name="save">Save</string>
<string name="update">Update</string>

<string name="share">Share</string>

<string name="load_more">Loading more…</string>

<string name="toolbar_search">Search MovieRama</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class GetMoviesDetailsUseCaseTest {
moviesRepository.mockCheckFavorite(555, MediaType.MOVIE, Result.success(true))
repository.mockFetchMovieDetails(request, Result.success(movieDetails))
// repository.mockFetchMovieReviews(ReviewsRequestApi.Movie(555), Result.failure<Exception>())
// repository.mockFetchSimilarMovies(SimilarRequestApi.Movie(movieId = 555), Result.Loading)
// repository.mockFetchSimilarMovies(SimilarRequestApi.Movie(mediaId = 555), Result.Loading)
val flow = GetMovieDetailsUseCase(
repository = repository.mock,
mediaRepository = moviesRepository.mock,
Expand All @@ -108,7 +108,7 @@ class GetMoviesDetailsUseCaseTest {
moviesRepository.mockCheckFavorite(555, MediaType.MOVIE, Result.success(false))
repository.mockFetchMovieDetails(request, Result.success(movieDetails))
// repository.mockFetchMovieReviews(ReviewsRequestApi.Movie(555), Result.Loading)
// repository.mockFetchSimilarMovies(SimilarRequestApi.Movie(movieId = 555), Result.Loading)
// repository.mockFetchSimilarMovies(SimilarRequestApi.Movie(mediaId = 555), Result.Loading)
val flow = GetMovieDetailsUseCase(
repository = repository.mock,
mediaRepository = moviesRepository.mock,
Expand All @@ -127,7 +127,7 @@ class GetMoviesDetailsUseCaseTest {
ReviewsRequestApi.Movie(movieId = 555),
Result.success(reviewsList)
)
// repository.mockFetchSimilarMovies(SimilarRequestApi.Movie(movieId = 555), Result.Loading)
// repository.mockFetchSimilarMovies(SimilarRequestApi.Movie(mediaId = 555), Result.Loading)
val flow = GetMovieDetailsUseCase(
repository = repository.mock,
mediaRepository = moviesRepository.mock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ class DetailsViewModelRobot {
viewModel.onAddToWatchlist()
}

fun onShareClicked(openShareDialog: Boolean) = apply {
viewModel.onShareClicked(openShareDialog)
}

fun onDismissRateDialog() = apply {
viewModel.onDismissRateDialog()
}
Expand Down
Loading

0 comments on commit 09eb5dd

Please sign in to comment.