feat: add MatchCard initial structure
This commit is contained in:
parent
3fd7e78090
commit
29a2985294
9 changed files with 307 additions and 18 deletions
|
@ -70,6 +70,8 @@ dependencies {
|
||||||
implementation(libs.retrofit.kotlinx.serialization.converter)
|
implementation(libs.retrofit.kotlinx.serialization.converter)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.okhttp)
|
implementation(libs.okhttp)
|
||||||
|
implementation(libs.coil.compose)
|
||||||
|
implementation(libs.coil.network)
|
||||||
ksp(libs.hilt.android.compiler)
|
ksp(libs.hilt.android.compiler)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
|
|
@ -12,20 +12,7 @@ class MatchLocalDataSource : MatchDataSource {
|
||||||
override suspend fun upcomingMatches(): Resource<List<MatchDto>> {
|
override suspend fun upcomingMatches(): Resource<List<MatchDto>> {
|
||||||
return Resource.Success(
|
return Resource.Success(
|
||||||
data = listOf(
|
data = listOf(
|
||||||
MatchDto(
|
// Happy path
|
||||||
beginAt = "2025-07-27T10:30:00Z",
|
|
||||||
opponents = emptyList(),
|
|
||||||
league = LeagueDto(
|
|
||||||
id = 5078,
|
|
||||||
name = "United21",
|
|
||||||
imageUrl = "https://cdn.pandascore.co/images/league/image/5078/800px-united21_allmode-png"
|
|
||||||
),
|
|
||||||
serie = SerieDto(
|
|
||||||
id = 9519,
|
|
||||||
fullName = "Season 35 2025"
|
|
||||||
),
|
|
||||||
status = "not_started"
|
|
||||||
),
|
|
||||||
MatchDto(
|
MatchDto(
|
||||||
beginAt = "2025-07-21T10:30:00Z",
|
beginAt = "2025-07-21T10:30:00Z",
|
||||||
opponents = listOf(
|
opponents = listOf(
|
||||||
|
@ -57,8 +44,111 @@ class MatchLocalDataSource : MatchDataSource {
|
||||||
),
|
),
|
||||||
status = "not_started"
|
status = "not_started"
|
||||||
),
|
),
|
||||||
|
// Empty Opponents
|
||||||
|
MatchDto(
|
||||||
|
beginAt = "2025-07-27T10:30:00Z",
|
||||||
|
opponents = emptyList(),
|
||||||
|
league = LeagueDto(
|
||||||
|
id = 5078,
|
||||||
|
name = "United21",
|
||||||
|
imageUrl = "https://cdn.pandascore.co/images/league/image/5078/800px-united21_allmode-png"
|
||||||
|
),
|
||||||
|
serie = SerieDto(
|
||||||
|
id = 9519,
|
||||||
|
fullName = "Season 35 2025"
|
||||||
|
),
|
||||||
|
status = "not_started"
|
||||||
|
),
|
||||||
|
|
||||||
)
|
// Only 1 opponent
|
||||||
|
MatchDto(
|
||||||
|
beginAt = "2025-07-21T10:30:00Z",
|
||||||
|
opponents = listOf(
|
||||||
|
OpponentDto(
|
||||||
|
type = "team",
|
||||||
|
opponent = OpponentRecord(
|
||||||
|
id = 128519,
|
||||||
|
name = "Bushido Wildcats",
|
||||||
|
imageUrl = "https://cdn.pandascore.co/images/team/image/136934/bushido_wildcatslogo_square.png"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
league = LeagueDto(
|
||||||
|
id = 5078,
|
||||||
|
name = "Gamers Club Liga Série A",
|
||||||
|
imageUrl = "https://cdn.pandascore.co/images/league/image/4554/Liga_Gamers_Club_SSrie_A_logo.png"
|
||||||
|
),
|
||||||
|
serie = SerieDto(
|
||||||
|
id = 9519,
|
||||||
|
fullName = "July 2025"
|
||||||
|
),
|
||||||
|
status = "not_started"
|
||||||
|
),
|
||||||
|
// 1 opponent without logo
|
||||||
|
MatchDto(
|
||||||
|
beginAt = "2025-07-21T10:30:00Z",
|
||||||
|
opponents = listOf(
|
||||||
|
OpponentDto(
|
||||||
|
type = "team",
|
||||||
|
opponent = OpponentRecord(
|
||||||
|
id = 128519,
|
||||||
|
name = "NO ORG",
|
||||||
|
imageUrl = null
|
||||||
|
)
|
||||||
|
),
|
||||||
|
OpponentDto(
|
||||||
|
type = "team",
|
||||||
|
opponent = OpponentRecord(
|
||||||
|
id = 128519,
|
||||||
|
name = "Bushido Wildcats",
|
||||||
|
imageUrl = "https://cdn.pandascore.co/images/team/image/136934/bushido_wildcatslogo_square.png"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
league = LeagueDto(
|
||||||
|
id = 5078,
|
||||||
|
name = "Gamers Club Liga Série A",
|
||||||
|
imageUrl = "https://cdn.pandascore.co/images/league/image/4554/Liga_Gamers_Club_SSrie_A_logo.png"
|
||||||
|
),
|
||||||
|
serie = SerieDto(
|
||||||
|
id = 9519,
|
||||||
|
fullName = "July 2025"
|
||||||
|
),
|
||||||
|
status = "not_started"
|
||||||
|
),
|
||||||
|
// League without logo
|
||||||
|
MatchDto(
|
||||||
|
beginAt = "2025-07-21T10:30:00Z",
|
||||||
|
opponents = listOf(
|
||||||
|
OpponentDto(
|
||||||
|
type = "team",
|
||||||
|
opponent = OpponentRecord(
|
||||||
|
id = 128519,
|
||||||
|
name = "Abyss team",
|
||||||
|
imageUrl = "https://cdn.pandascore.co/images/team/image/136868/146px_abyss_team_lightmode.png"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
OpponentDto(
|
||||||
|
type = "team",
|
||||||
|
opponent = OpponentRecord(
|
||||||
|
id = 128519,
|
||||||
|
name = "Bushido Wildcats",
|
||||||
|
imageUrl = "https://cdn.pandascore.co/images/team/image/136934/bushido_wildcatslogo_square.png"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
league = LeagueDto(
|
||||||
|
id = 5078,
|
||||||
|
name = "ESEA",
|
||||||
|
imageUrl = null
|
||||||
|
),
|
||||||
|
serie = SerieDto(
|
||||||
|
id = 9519,
|
||||||
|
fullName = "July 2025"
|
||||||
|
),
|
||||||
|
status = "not_started"
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
package xyz.leomurca.csgomatches.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
|
import xyz.leomurca.csgomatches.R
|
||||||
|
import xyz.leomurca.csgomatches.domain.model.League
|
||||||
|
import xyz.leomurca.csgomatches.domain.model.Opponent
|
||||||
|
import xyz.leomurca.csgomatches.ui.theme.LiveRed
|
||||||
|
import xyz.leomurca.csgomatches.ui.theme.White_20
|
||||||
|
import xyz.leomurca.csgomatches.ui.theme.White_50
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MatchCard(
|
||||||
|
opponents: List<Opponent>,
|
||||||
|
league: League,
|
||||||
|
serieName: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
|
) {
|
||||||
|
MatchupRow(opponents)
|
||||||
|
LeagueInfoRow(league, serieName)
|
||||||
|
}
|
||||||
|
ScheduleBadge()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MatchupRow(opponents: List<Opponent>) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 43.5.dp, bottom = 18.5.dp),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
val (leftOpponent, rightOpponent) = getOrDefaultOpponents(opponents)
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
AsyncImage(
|
||||||
|
model = leftOpponent.imageUrl,
|
||||||
|
contentDescription = "${leftOpponent.name} logo",
|
||||||
|
error = painterResource(R.drawable.fallback_image_round),
|
||||||
|
placeholder = painterResource(R.drawable.fallback_image_round),
|
||||||
|
modifier = Modifier.size(60.dp),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
leftOpponent.name,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
modifier = Modifier.padding(top = 10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"vs",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = White_50,
|
||||||
|
modifier = Modifier.padding(horizontal = 20.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
AsyncImage(
|
||||||
|
model = rightOpponent.imageUrl,
|
||||||
|
contentDescription = "${rightOpponent.name} logo",
|
||||||
|
error = painterResource(R.drawable.fallback_image_round),
|
||||||
|
placeholder = painterResource(R.drawable.fallback_image_round),
|
||||||
|
modifier = Modifier.size(60.dp),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
rightOpponent.name,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
modifier = Modifier.padding(top = 10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LeagueInfoRow(league: League, serieName: String) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.topBorder(White_20, 1.dp)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = league.imageUrl,
|
||||||
|
contentDescription = "${league.name} logo",
|
||||||
|
error = painterResource(R.drawable.fallback_image_round),
|
||||||
|
placeholder = painterResource(R.drawable.fallback_image_round),
|
||||||
|
onLoading = {
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.height(16.dp)
|
||||||
|
.wrapContentWidth(),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"${league.name} + $serieName",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
modifier = Modifier.padding(start = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BoxScope.ScheduleBadge() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.clip(RoundedCornerShape(topEnd = 16.dp, bottomStart = 16.dp))
|
||||||
|
.background(LiveRed)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "AGORA", style = MaterialTheme.typography.displayMedium, color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.topBorder(color: Color, thickness: Dp): Modifier = this.then(
|
||||||
|
Modifier.drawBehind {
|
||||||
|
val strokeWidth = thickness.toPx()
|
||||||
|
drawLine(
|
||||||
|
color = color,
|
||||||
|
start = Offset(0f, 0f),
|
||||||
|
end = Offset(size.width, 0f),
|
||||||
|
strokeWidth = strokeWidth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getOrDefaultOpponents(opponents: List<Opponent>): Pair<Opponent, Opponent> {
|
||||||
|
val default = Opponent(id = 0, name = "A ser definido", imageUrl = "")
|
||||||
|
return when {
|
||||||
|
opponents.size >= 2 -> opponents[0] to opponents[1]
|
||||||
|
opponents.size == 1 -> opponents[0] to default
|
||||||
|
else -> default to default
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,9 @@ import androidx.compose.ui.unit.sp
|
||||||
@Composable
|
@Composable
|
||||||
fun MatchesScreen() {
|
fun MatchesScreen() {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(MaterialTheme.colorScheme.background),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text("Main Screen", fontSize = 24.sp)
|
Text("Main Screen", fontSize = 24.sp)
|
||||||
|
|
|
@ -5,3 +5,6 @@ import androidx.compose.ui.graphics.Color
|
||||||
val White = Color(0xFFFFFFFF)
|
val White = Color(0xFFFFFFFF)
|
||||||
val DeepSpaceBlue = Color(0xFF161621)
|
val DeepSpaceBlue = Color(0xFF161621)
|
||||||
val StormySlate = Color(0xFF272639)
|
val StormySlate = Color(0xFF272639)
|
||||||
|
val LiveRed = Color(0xFFF42A35)
|
||||||
|
val White_50 = Color(0x80FFFFFF)
|
||||||
|
val White_20 = Color(0x33FFFFFF)
|
||||||
|
|
|
@ -8,6 +8,7 @@ private val AppColorScheme = lightColorScheme(
|
||||||
background = DeepSpaceBlue,
|
background = DeepSpaceBlue,
|
||||||
primary = White,
|
primary = White,
|
||||||
secondary = StormySlate,
|
secondary = StormySlate,
|
||||||
|
surface = StormySlate
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -65,4 +65,11 @@ val Typography = Typography(
|
||||||
lineHeight = 14.sp,
|
lineHeight = 14.sp,
|
||||||
letterSpacing = 0.sp,
|
letterSpacing = 0.sp,
|
||||||
), // Nickname
|
), // Nickname
|
||||||
|
displayMedium = TextStyle(
|
||||||
|
fontFamily = robotoFontFamily,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 8.sp,
|
||||||
|
lineHeight = 8.sp,
|
||||||
|
letterSpacing = 0.sp,
|
||||||
|
), // AGORA
|
||||||
)
|
)
|
5
app/src/main/res/drawable/fallback_image_round.xml
Normal file
5
app/src/main/res/drawable/fallback_image_round.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="61.5dp" android:viewportHeight="15.875" android:viewportWidth="15.485" android:width="59.989136dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#c4c4c4" android:fillType="nonZero" android:pathData="M-0,7.938a7.743,7.938 0,1 0,15.485 0a7.743,7.938 0,1 0,-15.485 0z" android:strokeColor="#00000000" android:strokeWidth="0.0297729"/>
|
||||||
|
|
||||||
|
</vector>
|
|
@ -16,6 +16,7 @@ retrofit = "1.0.0"
|
||||||
retrofit2 = "2.11.0"
|
retrofit2 = "2.11.0"
|
||||||
kotlinxSerializationJson = "1.8.1"
|
kotlinxSerializationJson = "1.8.1"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
|
coil = "3.2.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
@ -39,7 +40,9 @@ androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscree
|
||||||
retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit2" }
|
retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit2" }
|
||||||
retrofit-kotlinx-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofit" }
|
retrofit-kotlinx-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofit" }
|
||||||
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
|
||||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||||
|
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
|
||||||
|
coil-network = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
Loading…
Add table
Reference in a new issue