Updates series details, adds movie genres, & adds critic rating (#358)

## Details

Updates the series details page to match style & layout of the new movie
details page (from #279 & #327)

Adds genre list to movie details, it was already on series details

Adds critic rating using fresh or rotten tomato icons, if available

## Screenshots
### Movie

![movie](https://github.com/user-attachments/assets/9bb9cb2e-921c-4f4b-8ea1-a1e73b1842f1)
### TV Show

![series](https://github.com/user-attachments/assets/542989da-bf39-46a4-aefb-7728613a97b1)

## Issues
Closes #357
Closes #347

## Attribution
The fresh & rotten tomato images were sourced from:
- https://commons.wikimedia.org/wiki/File:Rotten_Tomatoes.svg
- https://commons.wikimedia.org/wiki/File:Rotten_Tomatoes_rotten.svg
Credit: Emily Oberman from Pentagram, modified by Throast, Public
domain, via Wikimedia Commons
This commit is contained in:
damontecres 2025-12-01 18:04:40 -05:00 committed by GitHub
parent fc330a3400
commit 8b33dd6d1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 225 additions and 64 deletions

View file

@ -39,7 +39,8 @@ import com.github.damontecres.wholphin.ui.theme.WholphinTheme
@Composable @Composable
fun DotSeparatedRow( fun DotSeparatedRow(
texts: List<String>, texts: List<String>,
rating: Float? = null, communityRating: Float? = null,
criticRating: Float? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
textStyle: TextStyle = MaterialTheme.typography.titleSmall, textStyle: TextStyle = MaterialTheme.typography.titleSmall,
) { ) {
@ -54,27 +55,38 @@ fun DotSeparatedRow(
style = textStyle, style = textStyle,
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
) )
if (rating != null || index != texts.lastIndex) { if (communityRating != null || criticRating != null || index != texts.lastIndex) {
Box( Dot()
modifier =
Modifier
.padding(horizontal = 8.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 1f))
.size(4.dp),
)
} }
} }
rating?.let { communityRating?.let {
SimpleStarRating( SimpleStarRating(
communityRating = it, communityRating = it,
modifier = Modifier, modifier = Modifier,
) )
if (criticRating != null) {
Dot()
}
}
criticRating?.let {
TomatoRating(it)
} }
} }
} }
} }
@Composable
fun Dot(modifier: Modifier = Modifier) {
Box(
modifier =
modifier
.padding(horizontal = 8.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 1f))
.size(4.dp),
)
}
@PreviewTvSpec @PreviewTvSpec
@Composable @Composable
private fun DotSeparatedRowPreview() { private fun DotSeparatedRowPreview() {
@ -82,13 +94,22 @@ private fun DotSeparatedRowPreview() {
Column { Column {
DotSeparatedRow( DotSeparatedRow(
texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left"), texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left"),
rating = 7.5f, communityRating = null,
criticRating = .75f,
modifier = Modifier,
textStyle = MaterialTheme.typography.titleMedium,
)
DotSeparatedRow(
texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left"),
communityRating = 7.5f,
criticRating = .75f,
modifier = Modifier, modifier = Modifier,
textStyle = MaterialTheme.typography.titleMedium, textStyle = MaterialTheme.typography.titleMedium,
) )
DotSeparatedRow( DotSeparatedRow(
texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left 7.5"), texts = listOf("2025", "1h 48m", "PG-13", "1h 30m left 7.5"),
rating = 7.5f, communityRating = 7.5f,
criticRating = .45f,
modifier = Modifier, modifier = Modifier,
textStyle = MaterialTheme.typography.titleLarge, textStyle = MaterialTheme.typography.titleLarge,
) )

View file

@ -0,0 +1,38 @@
package com.github.damontecres.wholphin.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
private const val MAX_TO_SHOW = 4
@Composable
fun GenreText(
genres: List<String>,
modifier: Modifier = Modifier,
textStyle: TextStyle = MaterialTheme.typography.bodyMedium,
color: Color = MaterialTheme.colorScheme.onSurface,
) {
val text =
remember(genres) {
if (genres.size <= MAX_TO_SHOW) {
genres.joinToString(", ")
} else {
genres
.subList(0, MAX_TO_SHOW)
.joinToString(", ") + ", +${genres.size - MAX_TO_SHOW}"
}
}
Text(
text = text,
style = textStyle,
color = color,
overflow = TextOverflow.Ellipsis,
modifier = modifier,
)
}

View file

@ -30,7 +30,8 @@ fun MovieQuickDetails(
DotSeparatedRow( DotSeparatedRow(
texts = details, texts = details,
rating = dto?.communityRating, communityRating = dto?.communityRating,
criticRating = dto?.criticRating,
textStyle = MaterialTheme.typography.titleSmall, textStyle = MaterialTheme.typography.titleSmall,
modifier = modifier, modifier = modifier,
) )

View file

@ -1,6 +1,5 @@
package com.github.damontecres.wholphin.ui.components package com.github.damontecres.wholphin.ui.components
import android.R.attr.textStyle
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.focusGroup import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -37,6 +36,7 @@ import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.tv.material3.Icon import androidx.tv.material3.Icon
@ -44,6 +44,7 @@ import androidx.tv.material3.LocalContentColor
import androidx.tv.material3.LocalTextStyle import androidx.tv.material3.LocalTextStyle
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.AppColors import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.PreviewTvSpec import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
@ -114,6 +115,42 @@ fun SimpleStarRating(
} }
} }
@Composable
fun TomatoRating(
rating: Float?,
modifier: Modifier = Modifier,
threshold: Float = 60f,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = modifier,
) {
if (rating != null) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Text(
text = rating.toInt().toString() + "%",
modifier = Modifier,
)
}
val height = with(LocalDensity.current) { LocalTextStyle.current.fontSize.toDp() * .8f }
Icon(
painter =
painterResource(
if (rating >= threshold) {
R.drawable.ic_rotten_tomatoes_fresh
} else {
R.drawable.ic_rotten_tomatoes_rotten
},
),
contentDescription = null,
modifier = Modifier.height(height),
tint = Color.Unspecified,
)
}
}
}
/** /**
* Shows a rating out of 5 stars * Shows a rating out of 5 stars
*/ */
@ -269,8 +306,12 @@ private fun SimpleStarRatingPreview() {
WholphinTheme { WholphinTheme {
Column { Column {
SimpleStarRating(7.5f, Modifier.height(32.dp)) SimpleStarRating(7.5f, Modifier.height(32.dp))
TomatoRating(75f)
SimpleStarRating(7.5f, Modifier.height(20.dp)) SimpleStarRating(7.5f, Modifier.height(20.dp))
SimpleStarRating("", Modifier.height(32.dp)) SimpleStarRating("", Modifier.height(32.dp))
TomatoRating(75f)
TomatoRating(40f)
TomatoRating(3f)
} }
} }
} }

View file

@ -70,7 +70,8 @@ fun EpisodeQuickDetails(
} }
DotSeparatedRow( DotSeparatedRow(
texts = details, texts = details,
rating = dto?.communityRating, communityRating = dto?.communityRating,
criticRating = dto?.criticRating,
textStyle = MaterialTheme.typography.titleSmall, textStyle = MaterialTheme.typography.titleSmall,
modifier = modifier, modifier = modifier,
) )

View file

@ -28,6 +28,7 @@ import java.util.Locale
data class ItemDetailsDialogInfo( data class ItemDetailsDialogInfo(
val title: String, val title: String,
val overview: String?, val overview: String?,
val genres: List<String>,
val files: List<MediaSourceInfo>, val files: List<MediaSourceInfo>,
) )
@ -45,6 +46,14 @@ fun ItemDetailsDialog(
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
) )
} }
if (info.genres.isNotEmpty()) {
item {
Text(
text = info.genres.joinToString(", "),
style = MaterialTheme.typography.titleSmall,
)
}
}
if (info.overview.isNotNullOrBlank()) { if (info.overview.isNotNullOrBlank()) {
item { item {
Text( Text(

View file

@ -212,6 +212,7 @@ fun PersonPage(
ItemDetailsDialogInfo( ItemDetailsDialogInfo(
title = name, title = name,
overview = person.data.overview, overview = person.data.overview,
genres = listOf(),
files = listOf(), files = listOf(),
), ),
showFilePath = false, showFilePath = false,

View file

@ -136,6 +136,7 @@ fun EpisodeDetails(
ItemDetailsDialogInfo( ItemDetailsDialogInfo(
title = ep.name ?: context.getString(R.string.unknown), title = ep.name ?: context.getString(R.string.unknown),
overview = ep.data.overview, overview = ep.data.overview,
genres = ep.data.genres.orEmpty(),
files = ep.data.mediaSources.orEmpty(), files = ep.data.mediaSources.orEmpty(),
) )
}, },

View file

@ -182,6 +182,7 @@ fun MovieDetails(
ItemDetailsDialogInfo( ItemDetailsDialogInfo(
title = movie.name ?: context.getString(R.string.unknown), title = movie.name ?: context.getString(R.string.unknown),
overview = movie.data.overview, overview = movie.data.overview,
genres = movie.data.genres.orEmpty(),
files = movie.data.mediaSources.orEmpty(), files = movie.data.mediaSources.orEmpty(),
) )
}, },

View file

@ -1,17 +1,15 @@
package com.github.damontecres.wholphin.ui.detail.movie package com.github.damontecres.wholphin.ui.detail.movie
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
@ -24,10 +22,14 @@ import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.MovieQuickDetails import com.github.damontecres.wholphin.ui.components.MovieQuickDetails
import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.OverviewText
import com.github.damontecres.wholphin.ui.components.VideoStreamDetails import com.github.damontecres.wholphin.ui.components.VideoStreamDetails
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.util.ExceptionHandler
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.PersonKind import org.jellyfin.sdk.model.api.PersonKind
@Composable @Composable
@ -60,9 +62,13 @@ fun MovieDetailsHeader(
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.fillMaxWidth(.60f), modifier = Modifier.fillMaxWidth(.60f),
) { ) {
val padding = 8.dp val padding = 4.dp
MovieQuickDetails(dto, Modifier.padding(bottom = padding)) MovieQuickDetails(dto, Modifier.padding(bottom = padding))
dto.genres?.letNotEmpty {
GenreText(it, Modifier.padding(bottom = padding))
}
VideoStreamDetails( VideoStreamDetails(
preferences = preferences, preferences = preferences,
dto = dto, dto = dto,
@ -80,17 +86,19 @@ fun MovieDetailsHeader(
// Description // Description
dto.overview?.let { overview -> dto.overview?.let { overview ->
val interactionSource = remember { MutableInteractionSource() }
val focused = interactionSource.collectIsFocusedAsState().value
LaunchedEffect(focused) {
if (focused) bringIntoViewRequester.bringIntoView()
}
OverviewText( OverviewText(
overview = overview, overview = overview,
maxLines = 3, maxLines = 3,
onClick = overviewOnClick, onClick = overviewOnClick,
textBoxHeight = Dp.Unspecified, textBoxHeight = Dp.Unspecified,
interactionSource = interactionSource, modifier =
Modifier.onFocusChanged {
if (it.isFocused) {
scope.launch(ExceptionHandler()) {
bringIntoViewRequester.bringIntoView()
}
}
},
) )
} }

View file

@ -1,6 +1,7 @@
package com.github.damontecres.wholphin.ui.detail.series package com.github.damontecres.wholphin.ui.detail.series
import android.content.Context import android.content.Context
import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -25,6 +26,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -57,6 +59,7 @@ import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
import com.github.damontecres.wholphin.ui.components.GenreText
import com.github.damontecres.wholphin.ui.components.LoadingPage import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.Optional import com.github.damontecres.wholphin.ui.components.Optional
import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.OverviewText
@ -174,6 +177,7 @@ fun SeriesDetails(
ItemDetailsDialogInfo( ItemDetailsDialogInfo(
title = item.name ?: context.getString(R.string.unknown), title = item.name ?: context.getString(R.string.unknown),
overview = item.data.overview, overview = item.data.overview,
genres = item.data.genres.orEmpty(),
files = listOf(), files = listOf(),
) )
}, },
@ -303,6 +307,7 @@ fun SeriesDetailsContent(
var position by rememberInt() var position by rememberInt()
val focusRequesters = remember { List(SIMILAR_ROW + 1) { FocusRequester() } } val focusRequesters = remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
val playFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
focusRequesters.getOrNull(position)?.tryRequestFocus() focusRequesters.getOrNull(position)?.tryRequestFocus()
} }
@ -321,7 +326,7 @@ fun SeriesDetailsContent(
) { ) {
LazyColumn( LazyColumn(
contentPadding = PaddingValues(bottom = 80.dp), contentPadding = PaddingValues(bottom = 80.dp),
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(0.dp),
modifier = Modifier, modifier = Modifier,
) { ) {
item { item {
@ -330,9 +335,9 @@ fun SeriesDetailsContent(
overviewOnClick = overviewOnClick, overviewOnClick = overviewOnClick,
modifier = modifier =
Modifier Modifier
.fillMaxWidth(.7f) .fillMaxWidth()
.bringIntoViewRequester(bringIntoViewRequester) .bringIntoViewRequester(bringIntoViewRequester)
.padding(bottom = 8.dp), .padding(top = 32.dp, bottom = 16.dp),
) )
Row( Row(
horizontalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp),
@ -340,7 +345,9 @@ fun SeriesDetailsContent(
Modifier Modifier
.padding(start = 16.dp) .padding(start = 16.dp)
.focusRequester(focusRequesters[HEADER_ROW]) .focusRequester(focusRequesters[HEADER_ROW])
.padding(bottom = 80.dp), .focusRestorer(playFocusRequester)
.focusGroup()
.padding(bottom = 16.dp),
) { ) {
ExpandablePlayButton( ExpandablePlayButton(
title = R.string.play, title = R.string.play,
@ -351,13 +358,15 @@ fun SeriesDetailsContent(
playOnClick.invoke(false) playOnClick.invoke(false)
}, },
modifier = modifier =
Modifier.onFocusChanged { Modifier
if (it.isFocused) { .focusRequester(playFocusRequester)
scope.launch(ExceptionHandler()) { .onFocusChanged {
bringIntoViewRequester.bringIntoView() if (it.isFocused) {
scope.launch(ExceptionHandler()) {
bringIntoViewRequester.bringIntoView()
}
} }
} },
},
) )
ExpandableFaButton( ExpandableFaButton(
title = R.string.shuffle, title = R.string.shuffle,
@ -406,7 +415,7 @@ fun SeriesDetailsContent(
} }
item { item {
ItemRow( ItemRow(
title = stringResource(R.string.tv_seasons), title = stringResource(R.string.tv_seasons) + " (${seasons.size})",
items = seasons, items = seasons,
onClickItem = { index, item -> onClickItem = { index, item ->
position = SEASONS_ROW position = SEASONS_ROW
@ -564,13 +573,15 @@ fun SeriesDetailsHeader(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val dto = series.data val dto = series.data
val details = val details =
buildList { remember(series) {
dto.productionYear?.let { add(it.toString()) } buildList {
dto.runTimeTicks dto.productionYear?.let { add(it.toString()) }
?.ticks dto.runTimeTicks
?.roundMinutes ?.ticks
?.let { add(it.toString()) } ?.roundMinutes
dto.officialRating?.let(::add) ?.let { add(it.toString()) }
dto.officialRating?.let(::add)
}
} }
Column( Column(
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
@ -579,29 +590,32 @@ fun SeriesDetailsHeader(
Text( Text(
text = series.name ?: stringResource(R.string.unknown), text = series.name ?: stringResource(R.string.unknown),
style = MaterialTheme.typography.displaySmall, style = MaterialTheme.typography.displaySmall,
modifier = Modifier.fillMaxWidth(), maxLines = 2,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth(.75f),
) )
DotSeparatedRow( Column(
texts = details, verticalArrangement = Arrangement.spacedBy(4.dp),
rating = dto.communityRating, modifier = Modifier.fillMaxWidth(.60f),
textStyle = MaterialTheme.typography.titleMedium, ) {
) DotSeparatedRow(
dto.genres?.letNotEmpty { texts = details,
Text( communityRating = dto.communityRating,
text = it.joinToString(", "), criticRating = dto.criticRating,
style = MaterialTheme.typography.bodyLarge, textStyle = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurface,
overflow = TextOverflow.Ellipsis,
modifier = Modifier, modifier = Modifier,
) )
} dto.genres?.letNotEmpty {
dto.overview?.let { overview -> GenreText(it)
OverviewText( }
overview = overview, dto.overview?.let { overview ->
maxLines = 3, OverviewText(
onClick = overviewOnClick, overview = overview,
textBoxHeight = Dp.Unspecified, maxLines = 3,
) onClick = overviewOnClick,
textBoxHeight = Dp.Unspecified,
)
}
} }
} }
} }

View file

@ -332,6 +332,7 @@ fun SeriesOverview(
ItemDetailsDialogInfo( ItemDetailsDialogInfo(
title = it.name ?: context.getString(R.string.unknown), title = it.name ?: context.getString(R.string.unknown),
overview = it.data.overview, overview = it.data.overview,
genres = it.data.genres.orEmpty(),
files = it.data.mediaSources.orEmpty(), files = it.data.mediaSources.orEmpty(),
) )
} }

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="138.75dp"
android:height="141.25dp"
android:viewportWidth="138.75"
android:viewportHeight="141.25">
<path
android:pathData="m20.15,40.83c-28.15,27.62 -13.66,61.01 -5.73,71.93 35.25,41.95 92.79,25.34 111.89,-5.91 4.76,-8.2 22.55,-53.47 -23.98,-78.01z"
android:fillColor="#f93208"/>
<path
android:pathData="m39.61,39.26 l4.78,-8.86 28.41,-5.04 11.12,9.21z"
android:fillColor="#f93208"/>
<path
android:pathData="m39.44,8.57 l8.97,-5.28 6.76,15.48c3.79,-6.32 13.79,-16.32 24.94,-4.67 -4.73,1.26 -7.52,3.86 -7.74,8.48 15.15,-4.17 31.34,3.21 33.54,9.09 -10.95,-4.31 -27.69,10.38 -41.77,2.33 0.01,15.05 -12.62,16.64 -19.9,17.08 2.08,-5 5.59,-9.99 1.47,-14.99 -7.62,8.17 -13.87,10.67 -33.17,4.67 4.88,-1.68 14.84,-11.39 24.45,-11.43 -6.78,-2.47 -12.29,-2.09 -17.81,-1.48 2.92,-3.96 12.15,-15.2 28.63,-8.48z"
android:fillColor="#02902e"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="145dp"
android:height="140dp"
android:viewportWidth="145"
android:viewportHeight="140">
<path
android:pathData="M47.4,35.34c-13.61,-7.93 -12.32,-25.2 2.1,-31.88 26.12,-6.53 29.12,13.78 22.65,30.41 -6.54,24.11 18.09,23.66 19.92,10.07 3.61,-18.41 19.39,-26.69 31.67,-16.36 12.6,12.14 7.07,36.58 -17.83,34.19 -16.03,-1.54 -19.55,19.58 0.84,21.18 32.23,1.91 42.49,22.17 31.04,35.87 -15.99,15.15 -37.69,-4.44 -45.51,-19.5 -6.8,-9.31 -17.32,0.11 -13.42,6.5 12.98,19.47 2.92,31.23 -10.91,30.62 -13.37,-0.85 -20.96,-9.06 -13.21,-29.15 3.9,-12.48 -8.6,-15.39 -16.57,-5.45 -11.71,19.61 -28.86,13.68 -33.98,4.19 -3.24,-7.62 -2.92,-25.85 24.12,-23.7 16.69,4.14 11.78,-12.56 -0.63,-13.63 -9.24,-0.44 -30.5,-7.3 -22.86,-24.54 7.34,-11.06 24.96,-11.77 33.35,6.29 3.04,4.23 8.36,11.04 18.04,5.03 3.51,-5.2 1.21,-13.9 -8.81,-20.14z"
android:fillColor="#0fc755"/>
</vector>