display list of seasons/episodes on tv series details page

This commit is contained in:
Owen LeJeune
2023-06-11 11:14:33 -04:00
parent f90a2a91bd
commit 7130930564
8 changed files with 332 additions and 24 deletions

View File

@@ -55,4 +55,7 @@ interface TvApi {
@Query("session_id") sessionId: String @Query("session_id") sessionId: String
): Response<StatusResponse> ): Response<StatusResponse>
@GET("tv/{id}/season/{season}")
suspend fun getSeason(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<Season>
} }

View File

@@ -8,6 +8,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Season
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
@@ -77,4 +78,8 @@ class TvService: KoinComponent, DetailService, HomePageService {
return service.getKeywords(id) return service.getKeywords(id)
} }
suspend fun getSeason(seriesId: Int, seasonId: Int): Response<Season> {
return service.getSeason(seriesId, seasonId)
}
} }

View File

@@ -0,0 +1,18 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
data class Episode(
@SerializedName("name")
val name: String,
@SerializedName("overview")
val overview: String,
@SerializedName("episode_number")
val episodeNumber: Int,
@SerializedName("season_number")
val seasonNumber: Int,
@SerializedName("still_path")
val stillPath: String?,
@SerializedName("air_date")
val airDate: String?
)

View File

@@ -0,0 +1,10 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
data class Season(
@SerializedName("name")
val name: String,
@SerializedName("episodes")
val episodes: List<Episode>
)

View File

@@ -1,40 +1,47 @@
package com.owenlejeune.tvtime.ui.screens.main package com.owenlejeune.tvtime.ui.screens.main
import android.content.Context import android.content.Context
import android.media.MediaActionSound
import android.widget.Toast import android.widget.Toast
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Movie import androidx.compose.material.icons.filled.Movie
import androidx.compose.material.icons.filled.Send import androidx.compose.material.icons.filled.Send
import androidx.compose.material.icons.outlined.ExpandLess
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow import coil.compose.AsyncImage
import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
@@ -49,6 +56,8 @@ import com.owenlejeune.tvtime.extensions.listItems
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.* import com.owenlejeune.tvtime.ui.components.*
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.navigation.TabNavItem
import com.owenlejeune.tvtime.ui.screens.main.tabs.top.Tabs
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
import com.owenlejeune.tvtime.ui.theme.RatingSelected import com.owenlejeune.tvtime.ui.theme.RatingSelected
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
@@ -57,7 +66,6 @@ import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.json.JSONObject import org.json.JSONObject
import org.koin.java.KoinJavaComponent
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
import java.text.DecimalFormat import java.text.DecimalFormat
@@ -194,7 +202,7 @@ private fun MediaViewContent(
) )
Column( Column(
modifier = Modifier.padding(horizontal = 16.dp), // modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
if (type == MediaViewType.MOVIE) { if (type == MediaViewType.MOVIE) {
@@ -205,18 +213,55 @@ private fun MediaViewContent(
ActionsView(itemId = itemId, type = type, service = service) ActionsView(itemId = itemId, type = type, service = service)
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service) if (type == MediaViewType.MOVIE) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(horizontal = 16.dp)
) {
MainContent(
itemId = itemId,
mediaItem = mediaItem,
type = type,
service = service,
appNavController = appNavController,
windowSize = windowSize
)
}
} else {
val tabState = rememberPagerState()
val tabs = listOf(DetailsTab, SeasonsTab)
TvSeriesTabs(pagerState = tabState, tabs = tabs)
HorizontalPager(
count = tabs.size,
state = tabState,
userScrollEnabled = false
) { page ->
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(horizontal = 16.dp)
) {
when (tabs[page]) {
is DetailsTab -> {
MainContent(
itemId = itemId,
mediaItem = mediaItem,
type = type,
service = service,
appNavController = appNavController,
windowSize = windowSize
)
}
CastCard(itemId = itemId, service = service, appNavController = appNavController) is SeasonsTab -> {
SeasonsTab(
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController) itemId = itemId,
mediaItem = mediaItem,
VideosCard(itemId = itemId, service = service) service = service as TvService
)
AdditionalDetailsCard(itemId = itemId, mediaItem = mediaItem, service = service, type = type) }
}
if (windowSize != WindowSizeClass.Expanded) { }
ReviewsCard(itemId = itemId, service = service) }
} }
} }
@@ -238,6 +283,173 @@ private fun MediaViewContent(
} }
} }
@OptIn(ExperimentalPagerApi::class)
@Composable
private fun TvSeriesTabs(
pagerState: PagerState,
tabs: List<TabNavItem>
) {
Tabs(
modifier = Modifier.offset(y = (-16).dp),
pagerState = pagerState,
tabs = tabs
)
}
@Composable
private fun SeasonsTab(
itemId: Int?,
mediaItem: MutableState<DetailedItem?>,
service: TvService
) {
val scope = rememberCoroutineScope()
mediaItem.value?.let { tv ->
val series = tv as DetailedTv
val seasons = remember { mutableStateMapOf<Int, Season>() }
LaunchedEffect(Unit) {
itemId?.let {
for (i in 0..series.numberOfSeasons) {
scope.launch {
val season = service.getSeason(it, i)
if (season.isSuccessful) {
seasons[i] = season.body()!!
}
}
}
}
}
seasons.toSortedMap().values.forEach { season ->
SeasonSection(season = season)
}
}
}
@Composable
private fun SeasonSection(season: Season) {
var isExpanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.background(color = MaterialTheme.colorScheme.surfaceVariant)
.clickable {
isExpanded = !isExpanded
}
) {
Row(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 24.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = season.name,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.weight(1f))
var currentRotation by remember { mutableStateOf(0f) }
val rotation = remember { androidx.compose.animation.core.Animatable(currentRotation) }
LaunchedEffect(isExpanded) {
rotation.animateTo(
targetValue = if (isExpanded) 180f else 0f,
animationSpec = tween(200, easing = LinearEasing)
) {
currentRotation = value
}
}
Icon(
imageVector = Icons.Outlined.ExpandMore,
contentDescription = null,
modifier = Modifier
.size(32.dp)
.rotate(currentRotation),
tint = MaterialTheme.colorScheme.onSurface,
)
}
}
AnimatedVisibility(
visible = isExpanded,
enter = expandVertically(),
exit = shrinkVertically()
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
season.episodes.forEachIndexed { index, episode ->
EpisodeItem(episode = episode)
if (index != season.episodes.size - 1) {
Divider()
}
}
}
}
}
@Composable
private fun EpisodeItem(episode: Episode) {
val height = 170.dp
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.height(height)
) {
AsyncImage(
model = TmdbUtils.getFullEpisodeStillPath(episode),
contentDescription = null,
modifier = Modifier
.size(width = 100.dp, height = height)
.clip(RoundedCornerShape(10.dp)),
contentScale = ContentScale.Crop
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "S${episode.seasonNumber}E${episode.episodeNumber}${episode.name}",
color = MaterialTheme.colorScheme.secondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
TmdbUtils.convertEpisodeDate(episode.airDate)?.let {
Text(
text = it,
fontStyle = FontStyle.Italic,
fontSize = 12.sp
)
}
Text(
text = episode.overview,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onBackground
)
}
}
}
@Composable
private fun MainContent(
itemId: Int?,
mediaItem: MutableState<DetailedItem?>,
type: MediaViewType,
service: DetailService,
appNavController: NavController,
windowSize: WindowSizeClass
) {
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
CastCard(itemId = itemId, service = service, appNavController = appNavController)
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController)
VideosCard(itemId = itemId, service = service, modifier = Modifier.fillMaxWidth())
AdditionalDetailsCard(itemId = itemId, mediaItem = mediaItem, service = service, type = type)
if (windowSize != WindowSizeClass.Expanded) {
ReviewsCard(itemId = itemId, service = service)
}
}
@Composable @Composable
private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvService) { private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvService) {
mediaItem.value?.let { tv -> mediaItem.value?.let { tv ->
@@ -249,7 +461,8 @@ private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvSer
MiscDetails( MiscDetails(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight(), .wrapContentHeight()
.padding(horizontal = 16.dp),
year = TmdbUtils.getSeriesRun(series), year = TmdbUtils.getSeriesRun(series),
runtime = TmdbUtils.convertRuntimeToHoursMinutes(series), runtime = TmdbUtils.convertRuntimeToHoursMinutes(series),
genres = series.genres, genres = series.genres,
@@ -269,7 +482,8 @@ private fun MiscMovieDetails(mediaItem: MutableState<DetailedItem?>, service: Mo
MiscDetails( MiscDetails(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight(), .wrapContentHeight()
.padding(horizontal = 16.dp),
year = TmdbUtils.getMovieReleaseYear(movie), year = TmdbUtils.getMovieReleaseYear(movie),
runtime = TmdbUtils.convertRuntimeToHoursMinutes(movie), runtime = TmdbUtils.convertRuntimeToHoursMinutes(movie),
genres = movie.genres, genres = movie.genres,
@@ -328,7 +542,8 @@ private fun ActionsView(
val session = SessionManager.currentSession.value val session = SessionManager.currentSession.value
Row( Row(
modifier = modifier modifier = modifier
.wrapContentSize(), .wrapContentSize()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
RateButton( RateButton(
@@ -985,7 +1200,11 @@ fun SimilarContentCard(
} }
@Composable @Composable
fun VideosCard(itemId: Int?, service: DetailService, modifier: Modifier = Modifier) { fun VideosCard(
itemId: Int?,
service: DetailService,
modifier: Modifier = Modifier
) {
val videoResponse = remember { mutableStateOf<VideoResponse?>(null) } val videoResponse = remember { mutableStateOf<VideoResponse?>(null) }
itemId?.let { itemId?.let {
if (videoResponse.value == null) { if (videoResponse.value == null) {
@@ -1373,4 +1592,13 @@ private fun addToFavorite(
} }
} }
} }
}
object DetailsTab: TabNavItem("details_route") {
override val name: String
get() = "Details"
}
object SeasonsTab: TabNavItem("seasons_route") {
override val name: String
get() = "Seasons"
} }

View File

@@ -75,9 +75,9 @@ sealed class OnboardingPage(
.padding(all = 24.dp) .padding(all = 24.dp)
) { ) {
Icon( Icon(
painter = painterResource(id = R.drawable.tmdb_logo), painter = painterResource(id = R.drawable.tmdb_logo_long),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(32.dp), modifier = Modifier.height(height = 16.dp),
tint = Color.Unspecified tint = Color.Unspecified
) )
Text( Text(

View File

@@ -11,6 +11,7 @@ object TmdbUtils {
private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2" private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
private const val GRAVATAR_BASE = "https://www.gravatar.com/avatar/" private const val GRAVATAR_BASE = "https://www.gravatar.com/avatar/"
private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face" private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face"
private const val STILL_BASE = "https://www.themoviedb.org/t/p/w454_and_h254_bestv2/"
private const val DEF_REGION = "US" private const val DEF_REGION = "US"
@@ -63,6 +64,16 @@ object TmdbUtils {
return getFullAvatarPath(author.avatarPath) return getFullAvatarPath(author.avatarPath)
} }
fun getFullEpisodeStillPath(path: String?): String? {
return path?.let {
"${STILL_BASE}${path}"
}
}
fun getFullEpisodeStillPath(episode: Episode): String? {
return getFullEpisodeStillPath(episode.stillPath)
}
fun getMovieReleaseYear(movie: DetailedMovie): String { fun getMovieReleaseYear(movie: DetailedMovie): String {
return movie.releaseDate.split("-")[0] return movie.releaseDate.split("-")[0]
} }
@@ -208,4 +219,15 @@ object TmdbUtils {
return "$${thousands}" return "$${thousands}"
} }
fun convertEpisodeDate(inDate: String?): String? {
if (inDate == null) {
return null
}
val origFormat = SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault())
val outFormat = SimpleDateFormat("MMMM dd, yyyy", java.util.Locale.getDefault())
return origFormat.parse(inDate)?.let { outFormat.format(it) }
}
} }

View File

@@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="489.04dp"
android:height="35.4dp"
android:viewportWidth="489.04"
android:viewportHeight="35.4">
<path
android:pathData="M293.5,0h8.9l8.75,23.2h0.1L320.15,0h8.35L313.9,35.4h-6.25ZM340.1,0h7.8L347.9,35.4h-7.8ZM362.3,0h24.05L386.35,7.2L370.1,7.2v6.6h15.35L385.45,21L370.1,21v7.2h17.15v7.2L362.3,35.4ZM417.3,0L429,0a33.54,33.54 0,0 1,8.07 1A18.55,18.55 0,0 1,443.75 4a15.1,15.1 0,0 1,4.52 5.53A18.5,18.5 0,0 1,450 17.8a16.91,16.91 0,0 1,-1.63 7.58,16.37 16.37,0 0,1 -4.37,5.5 19.52,19.52 0,0 1,-6.35 3.37A24.59,24.59 0,0 1,430 35.4L417.29,35.4ZM425.11,28.2h4a21.57,21.57 0,0 0,5 -0.55,10.87 10.87,0 0,0 4,-1.83 8.69,8.69 0,0 0,2.67 -3.34,11.92 11.92,0 0,0 1,-5.08 9.87,9.87 0,0 0,-1 -4.52,9 9,0 0,0 -2.62,-3.18 11.68,11.68 0,0 0,-3.88 -1.88,17.43 17.43,0 0,0 -4.67,-0.62h-4.6ZM461.24,0h13.2a34.42,34.42 0,0 1,4.63 0.32,12.9 12.9,0 0,1 4.17,1.3 7.88,7.88 0,0 1,3 2.73A8.34,8.34 0,0 1,487.39 9a7.42,7.42 0,0 1,-1.67 5,9.28 9.28,0 0,1 -4.43,2.82v0.1a10,10 0,0 1,3.18 1,8.38 8.38,0 0,1 2.45,1.85 7.79,7.79 0,0 1,1.57 2.62,9.16 9.16,0 0,1 0.55,3.2 8.52,8.52 0,0 1,-1.2 4.68,9.42 9.42,0 0,1 -3.1,3 13.38,13.38 0,0 1,-4.27 1.65,23.11 23.11,0 0,1 -4.73,0.5h-14.5ZM469,14.15h5.65a8.16,8.16 0,0 0,1.78 -0.2A4.78,4.78 0,0 0,478 13.3a3.34,3.34 0,0 0,1.13 -1.2,3.63 3.63,0 0,0 0.42,-1.8 3.22,3.22 0,0 0,-0.47 -1.82,3.33 3.33,0 0,0 -1.23,-1.13 5.77,5.77 0,0 0,-1.7 -0.58,10.79 10.79,0 0,0 -1.85,-0.17L469,6.6ZM469,28.8h7a8.91,8.91 0,0 0,1.83 -0.2,4.78 4.78,0 0,0 1.67,-0.7 4,4 0,0 0,1.23 -1.3,3.71 3.71,0 0,0 0.47,-2 3.13,3.13 0,0 0,-0.62 -2A4,4 0,0 0,479 21.45,7.83 7.83,0 0,0 477,20.9a15.12,15.12 0,0 0,-2.05 -0.15L469,20.75ZM204,35.33L271,35.33a17.66,17.66 0,0 0,17.66 -17.66h0A17.67,17.67 0,0 0,271 0L204.06,0A17.67,17.67 0,0 0,186.4 17.67h0A17.66,17.66 0,0 0,204.06 35.33ZM10.1,6.9L0,6.9L0,0L28,0L28,6.9L17.9,6.9L17.9,35.4L10.1,35.4ZM39,0h7.8L46.8,13.2L61.9,13.2L61.9,0h7.8L69.7,35.4L61.9,35.4L61.9,20.1L46.75,20.1L46.75,35.4L39,35.4ZM80.2,0h24L104.2,7.2L88,7.2v6.6h15.35L103.35,21L88,21v7.2h17.15v7.2h-25ZM135.2,0L147,0l8.15,23.1h0.1L163.45,0L175.2,0L175.2,35.4h-7.8L167.4,8.25h-0.1L158,35.4h-5.95l-9,-27.15L143,8.25L143,35.4h-7.8Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="0"
android:startY="17.7"
android:endX="489.04"
android:endY="17.7"
android:type="linear">
<item android:offset="0" android:color="#FF90CEA1"/>
<item android:offset="0.56" android:color="#FF3CBEC9"/>
<item android:offset="1" android:color="#FF00B3E5"/>
</gradient>
</aapt:attr>
</path>
</vector>