commit
f813d5053c
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1,9 @@
|
||||
package com.iqball.app.model
|
||||
|
||||
data class Tactic (
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val ownerId: Int,
|
||||
val courtType: String,
|
||||
val creationDate: Long
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.iqball.app.model;
|
||||
|
||||
data class Team (
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val picture: String,
|
||||
val mainColor: String,
|
||||
val secondColor: String
|
||||
)
|
@ -1,10 +1,12 @@
|
||||
package com.iqball.app.net.service
|
||||
|
||||
import com.iqball.app.model.Tactic
|
||||
import com.iqball.app.model.Team
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
|
||||
interface UserService {
|
||||
data class UserDataResponse(val teams: List<Any>, val tactics: List<Any>)
|
||||
data class UserDataResponse(val teams: List<Team>, val tactics: List<Tactic>)
|
||||
|
||||
@GET("user-data")
|
||||
suspend fun getUserData(@Header("Authorization") auth: String): APIResult<UserDataResponse>
|
||||
|
@ -1,12 +1,407 @@
|
||||
package com.iqball.app.page
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import com.iqball.app.model.Tactic
|
||||
import com.iqball.app.model.Team
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarDefaults.pinnedScrollBehavior
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import arrow.core.Either
|
||||
import coil.compose.AsyncImage
|
||||
import com.iqball.app.net.service.AuthService
|
||||
import com.iqball.app.net.service.IQBallService
|
||||
import com.iqball.app.session.Session
|
||||
import com.iqball.app.net.service.UserService
|
||||
import com.iqball.app.session.Authentication
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomePage(service: IQBallService, auth: Authentication) {
|
||||
val tactics: List<Tactic>
|
||||
val teams: List<Team>
|
||||
var invalid = false
|
||||
|
||||
val data = getDataFromApi(service, auth)
|
||||
if (data == null) {
|
||||
tactics = listOf<Tactic>()
|
||||
teams = listOf<Team>()
|
||||
invalid = true
|
||||
|
||||
} else {
|
||||
tactics = data.tactics
|
||||
teams = data.teams
|
||||
}
|
||||
val scrollBehavior = pinnedScrollBehavior(rememberTopAppBarState())
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
titleContentColor = MaterialTheme.colorScheme.secondary,
|
||||
),
|
||||
title = {
|
||||
Text(
|
||||
"IQBall",
|
||||
fontSize = 40.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
Body(innerPadding, tactics, teams, invalid)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Body(
|
||||
padding: PaddingValues,
|
||||
tactics: List<Tactic>,
|
||||
teams: List<Team>,
|
||||
invalid: Boolean
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
) {
|
||||
val selectedTab = remember { mutableIntStateOf(0) }
|
||||
val tabs = listOf<Pair<String, @Composable () -> Unit>>(
|
||||
Pair("Espace personnel") {
|
||||
ListComponentCard(tactics) { tactic ->
|
||||
TacticCard(tactic = tactic)
|
||||
}
|
||||
},
|
||||
Pair("Mes équipes") {
|
||||
ListComponentCard(teams) { team ->
|
||||
TeamCard(team = team)
|
||||
}
|
||||
}
|
||||
)
|
||||
TabsSelector(tabsTitles = tabs.map { it.first }, selectedIndex = selectedTab)
|
||||
if (!invalid) {
|
||||
tabs[selectedTab.intValue].second()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
TextCentered(
|
||||
text = "Erreur : Aucune connexion internet. Veillez activer votre connexion internet puis relancer l'application",
|
||||
fontSize = 20.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TabsSelector(tabsTitles: List<String>, selectedIndex: MutableState<Int>) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.padding(top = 20.dp, start = 2.dp, end = 2.dp, bottom = 10.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
|
||||
for ((idx, tab) in tabsTitles.withIndex()) {
|
||||
TabButton(
|
||||
tab,
|
||||
fill = idx == selectedIndex.value,
|
||||
onClick = { selectedIndex.value = idx }
|
||||
)
|
||||
if (idx != tabsTitles.size - 1) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.padding(5.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TabButton(title: String, fill: Boolean, onClick: () -> Unit) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
Button(
|
||||
border = BorderStroke(
|
||||
1.dp,
|
||||
color = scheme.tertiary
|
||||
),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (fill) scheme.tertiary else scheme.background,
|
||||
contentColor = if (fill) scheme.background else scheme.tertiary,
|
||||
),
|
||||
onClick = onClick
|
||||
) {
|
||||
Text(title)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun <C> ListComponentCard(items: List<C>, componentCard: @Composable (C) -> Unit) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Fixed(2),
|
||||
modifier = Modifier
|
||||
.padding(5.dp),
|
||||
content = {
|
||||
items(items) { tactic ->
|
||||
componentCard(tactic)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TacticCard(tactic: Tactic) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(5.dp)
|
||||
.border(
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.shadow(1.dp, shape = RoundedCornerShape(8.dp))
|
||||
.background(
|
||||
color = Color.White
|
||||
)
|
||||
.padding(15.dp)
|
||||
) {
|
||||
Row {
|
||||
TextCentered(text = tactic.name, fontSize = 16.sp)
|
||||
}
|
||||
Row {
|
||||
val date = LocalDateTime.ofInstant(
|
||||
Instant.ofEpochMilli(tactic.creationDate),
|
||||
ZoneId.systemDefault()
|
||||
)
|
||||
val dateFormatted = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy kk:mm"))
|
||||
TextCentered(
|
||||
text = dateFormatted,
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomePage(service: IQBallService, session: Session) {
|
||||
private fun TeamCard(team: Team) {
|
||||
var mainColor = Color.White
|
||||
var secondColor = Color.White
|
||||
var validMain = true
|
||||
var validSecond = true
|
||||
try {
|
||||
mainColor = Color(team.mainColor.toColorInt())
|
||||
} catch (e: Exception) {
|
||||
validMain = false
|
||||
}
|
||||
try {
|
||||
secondColor = Color(team.secondColor.toColorInt())
|
||||
} catch (e: Exception) {
|
||||
validSecond = false
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(5.dp)
|
||||
.border(
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.shadow(1.dp, shape = RoundedCornerShape(8.dp))
|
||||
.background(
|
||||
color = Color.White
|
||||
)
|
||||
.padding(15.dp)
|
||||
|
||||
Text(text = "HELLO WELCOME")
|
||||
) {
|
||||
AsyncImage(
|
||||
model = team.picture,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
|
||||
shape = RectangleShape
|
||||
)
|
||||
)
|
||||
TextCentered(text = team.name)
|
||||
Row {
|
||||
TeamColorCard("Couleur principale", mainColor, 0.5f)
|
||||
TeamColorCard("Couleur secondaire", secondColor)
|
||||
}
|
||||
if (!validMain || !validSecond) {
|
||||
TextCentered(text = "Erreur : Format des couleurs invalides", fontSize = 16.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TeamColorCard(text: String, color: Color, fraction: Float = 1f) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(fraction)
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(2.dp)
|
||||
|
||||
) {
|
||||
Canvas(
|
||||
modifier = Modifier.size(30.dp),
|
||||
onDraw = {
|
||||
drawCircle(color = color)
|
||||
}
|
||||
)
|
||||
}
|
||||
TextCentered(text = text, fontSize = 6.sp)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TextCentered(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
fontSize: TextUnit = 18.sp,
|
||||
fontWeight: FontWeight? = null
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier
|
||||
.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = fontSize,
|
||||
fontWeight = fontWeight
|
||||
)
|
||||
}
|
||||
|
||||
private fun getDataFromApi(
|
||||
service: IQBallService,
|
||||
auth: Authentication
|
||||
): UserService.UserDataResponse? {
|
||||
var res: UserService.UserDataResponse? = null
|
||||
try {
|
||||
runBlocking {
|
||||
val data = service.getUserData(auth.token)
|
||||
when (data) {
|
||||
is Either.Left -> null
|
||||
is Either.Right -> {
|
||||
res = data.value
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
} catch (error: Exception) {
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
=======================================================
|
||||
Comment
|
||||
=======================================================
|
||||
|
||||
Managing lists to display with pairs might not be the best thing to do in the context of composable.
|
||||
We chose to stick with this model due to a lack of time.
|
||||
|
||||
We could have also done something like this:
|
||||
|
||||
@Composable
|
||||
fun Body(padding: PaddingValues, tactics: List<Tactic>, teams: List<Team>, invalid: Boolean) {
|
||||
Column(...) {
|
||||
val selectedTab by remember { mutableIntStateOf(0) }
|
||||
val tabs = remember(selectedTab) {
|
||||
mutableStateOf(TabsGroup.entries.getOrNull(selectedTab))
|
||||
}
|
||||
TabsSelector(tabsTitles = TabsGroup.entries.map { it.title }, selectedIndex = selectedTab, )
|
||||
if (!invalid) {
|
||||
ListComponentCard {
|
||||
when(selectedTab) {
|
||||
TabsGroup.TEAM -> {
|
||||
items(tactics) {
|
||||
TacticCard(tactic = it)
|
||||
}
|
||||
}
|
||||
TabsGroup.TACTIC -> ...
|
||||
}
|
||||
}
|
||||
tabs[selectedTab.intValue].second()
|
||||
return
|
||||
}
|
||||
|
||||
TextCentered(...)
|
||||
}
|
||||
}
|
||||
|
||||
enum class TabsGroup {
|
||||
TEAM,
|
||||
TACTIC
|
||||
}
|
||||
|
||||
val TabsGroup.title: String
|
||||
@Composable
|
||||
get() = when(this) {
|
||||
TabsGroup.TACTIC -> "Espace personnel"
|
||||
TabsGroup.TEAM -> "Mes équipes"
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ListComponentCard(componentCard: LazyStaggeredGridScope.() -> Unit) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Fixed(2),
|
||||
modifier = ...,
|
||||
content = {
|
||||
componentCard()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
*/
|
@ -0,0 +1,72 @@
|
||||
package com.iqball.app.stub
|
||||
|
||||
import com.iqball.app.model.Tactic
|
||||
import com.iqball.app.model.Team
|
||||
|
||||
|
||||
private fun getStubTeam(): ArrayList<Team> {
|
||||
val teams = ArrayList<Team>()
|
||||
teams.addAll(
|
||||
listOf(
|
||||
Team(
|
||||
1,
|
||||
"equipe1",
|
||||
"https://www.shutterstock.com/image-vector/batman-logo-icon-vector-template-600nw-1998917738.jpg",
|
||||
"#4500FF",
|
||||
"#456789"
|
||||
),
|
||||
Team(
|
||||
2,
|
||||
"equipe2",
|
||||
"https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2f899b52-daf8-4098-83fe-5c5e27b69915/d4s4nzj-5f915488-7462-4908-b3c5-1605b0e4dc32.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzJmODk5YjUyLWRhZjgtNDA5OC04M2ZlLTVjNWUyN2I2OTkxNVwvZDRzNG56ai01ZjkxNTQ4OC03NDYyLTQ5MDgtYjNjNS0xNjA1YjBlNGRjMzIuanBnIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.KqdQgobH9kzyMIeYIneNdyWgKTpGbztwSKqK5pO3YYs",
|
||||
"121212",
|
||||
"#564738"
|
||||
),
|
||||
Team(
|
||||
3,
|
||||
"equipe3",
|
||||
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ1jiizrhhGsr48WrxxBbDpkFrRKeAYlGgcNQ&usqp=CAU",
|
||||
"#987654",
|
||||
"121212"
|
||||
),
|
||||
Team(
|
||||
4,
|
||||
"equipe4",
|
||||
"https://www.shutterstock.com/image-vector/batman-logo-icon-vector-template-600nw-1998917738.jpg",
|
||||
"121212",
|
||||
"121212"
|
||||
)
|
||||
)
|
||||
)
|
||||
return teams
|
||||
}
|
||||
|
||||
private fun getStubTactic(): ArrayList<Tactic> {
|
||||
val tactics = ArrayList<Tactic>()
|
||||
tactics.addAll(
|
||||
listOf(
|
||||
Tactic(1, "Test", 1, "testType", 1),
|
||||
Tactic(2, "Test2", 1, "testType", 1),
|
||||
Tactic(3, "Test3", 4, "test23Type", 1),
|
||||
Tactic(3, "Test6", 4, "test23Type", 1),
|
||||
Tactic(1, "Test", 1, "testType", 1),
|
||||
Tactic(2, "Test2", 1, "testType", 1),
|
||||
Tactic(3, "Test3", 4, "test23Type", 1),
|
||||
Tactic(3, "Test6", 4, "test23Type", 1),
|
||||
Tactic(1, "Test", 1, "testType", 1),
|
||||
Tactic(2, "Test2", 1, "testType", 1),
|
||||
Tactic(3, "Test3", 4, "test23Type", 1),
|
||||
Tactic(3, "Test6", 4, "test23Type", 1),
|
||||
Tactic(1, "Test", 1, "testType", 1),
|
||||
Tactic(2, "Test2", 1, "testType", 1),
|
||||
Tactic(3, "Test3", 4, "test23Type", 1),
|
||||
Tactic(3, "Test6", 4, "test23Type", 1),
|
||||
Tactic(1, "Test", 1, "testType", 1),
|
||||
Tactic(2, "Test2", 1, "testType", 1),
|
||||
Tactic(3, "Test3", 4, "test23Type", 1),
|
||||
Tactic(3, "Test6", 4, "test23Type", 1)
|
||||
)
|
||||
)
|
||||
|
||||
return tactics
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">IQBall</string>
|
||||
<string name="espace_personnel">Espace Personnel</string>
|
||||
</resources>
|
Loading…
Reference in new issue