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.kotlinx.serialization.json)
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.coil.network)
|
||||
ksp(libs.hilt.android.compiler)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
|
|
|
@ -12,20 +12,7 @@ class MatchLocalDataSource : MatchDataSource {
|
|||
override suspend fun upcomingMatches(): Resource<List<MatchDto>> {
|
||||
return Resource.Success(
|
||||
data = listOf(
|
||||
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"
|
||||
),
|
||||
// Happy path
|
||||
MatchDto(
|
||||
beginAt = "2025-07-21T10:30:00Z",
|
||||
opponents = listOf(
|
||||
|
@ -57,8 +44,111 @@ class MatchLocalDataSource : MatchDataSource {
|
|||
),
|
||||
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
|
||||
fun MatchesScreen() {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("Main Screen", fontSize = 24.sp)
|
||||
|
|
|
@ -5,3 +5,6 @@ import androidx.compose.ui.graphics.Color
|
|||
val White = Color(0xFFFFFFFF)
|
||||
val DeepSpaceBlue = Color(0xFF161621)
|
||||
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,
|
||||
primary = White,
|
||||
secondary = StormySlate,
|
||||
surface = StormySlate
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -65,4 +65,11 @@ val Typography = Typography(
|
|||
lineHeight = 14.sp,
|
||||
letterSpacing = 0.sp,
|
||||
), // 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"
|
||||
kotlinxSerializationJson = "1.8.1"
|
||||
okhttp = "4.12.0"
|
||||
coil = "3.2.0"
|
||||
|
||||
[libraries]
|
||||
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" }
|
||||
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" }
|
||||
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]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
|
Loading…
Add table
Reference in a new issue