diff --git a/app/Home.kt b/app/Home.kt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/Home.kt @@ -0,0 +1 @@ + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 69fd629..11be49b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -56,7 +56,7 @@ android { } dependencies { - + implementation("io.coil-kt:coil-compose:2.6.0") implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bab787e..f090dc0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ + + () setContent { - IQBallTheme(darkTheme = false) { + IQBallTheme(darkTheme = false, dynamicColor = false) { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), @@ -108,8 +111,10 @@ fun App(service: IQBallService, sessionState: MutableState) { LoginPage( service = service, onLoginSuccess = { auth -> + Log.i("ZIZI", "auth : ${auth}") sessionState.value = DataSession(auth) navController.navigate("home") + Log.i("ZIZI", "auth : ${auth}") }, onNavigateToRegister = { navController.navigate("register") @@ -129,7 +134,7 @@ fun App(service: IQBallService, sessionState: MutableState) { ) } composable("home") { - HomePage(service = service, session = sessionState.value) + HomePage(service = service, sessionState.value.auth!!) } } } \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/model/Tactic.kt b/app/src/main/java/com/iqball/app/model/Tactic.kt new file mode 100644 index 0000000..1cf5067 --- /dev/null +++ b/app/src/main/java/com/iqball/app/model/Tactic.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/model/Team.kt b/app/src/main/java/com/iqball/app/model/Team.kt new file mode 100644 index 0000000..8a91a8e --- /dev/null +++ b/app/src/main/java/com/iqball/app/model/Team.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/net/service/UserService.kt b/app/src/main/java/com/iqball/app/net/service/UserService.kt index 2045697..dc475ca 100644 --- a/app/src/main/java/com/iqball/app/net/service/UserService.kt +++ b/app/src/main/java/com/iqball/app/net/service/UserService.kt @@ -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, val tactics: List) + data class UserDataResponse(val teams: List, val tactics: List) @GET("user-data") suspend fun getUserData(@Header("Authorization") auth: String): APIResult diff --git a/app/src/main/java/com/iqball/app/page/HomePage.kt b/app/src/main/java/com/iqball/app/page/HomePage.kt index 94496b5..b440db5 100644 --- a/app/src/main/java/com/iqball/app/page/HomePage.kt +++ b/app/src/main/java/com/iqball/app/page/HomePage.kt @@ -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 + val teams: List + var invalid = false + + val data = getDataFromApi(service, auth) + if (data == null) { + tactics = listOf() + teams = listOf() + 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, + teams: List, + invalid: Boolean +) { + Column( + modifier = Modifier + .padding(padding) + ) { + val selectedTab = remember { mutableIntStateOf(0) } + val tabs = listOf 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, selectedIndex: MutableState) { + 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 ListComponentCard(items: List, 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 +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) + + ) { + 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, teams: List, 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 HomePage(service: IQBallService, session: Session) { +fun ListComponentCard(componentCard: LazyStaggeredGridScope.() -> Unit) { + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Fixed(2), + modifier = ..., + content = { + componentCard() + } + ) +} - Text(text = "HELLO WELCOME") -} \ No newline at end of file + */ \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/page/LoginPage.kt b/app/src/main/java/com/iqball/app/page/LoginPage.kt index 725b448..90a286e 100644 --- a/app/src/main/java/com/iqball/app/page/LoginPage.kt +++ b/app/src/main/java/com/iqball/app/page/LoginPage.kt @@ -1,5 +1,6 @@ package com.iqball.app.page +import android.util.Log import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.foundation.layout.* @@ -80,6 +81,7 @@ fun LoginPage( Button(onClick = { runBlocking { + Log.i("ZIZI", "On click wesh") when (val response = service.login(AuthService.LoginRequest(email, password))) { is Either.Left -> { errors = response.value.toList() diff --git a/app/src/main/java/com/iqball/app/stub/Stub.kt b/app/src/main/java/com/iqball/app/stub/Stub.kt new file mode 100644 index 0000000..ba1a885 --- /dev/null +++ b/app/src/main/java/com/iqball/app/stub/Stub.kt @@ -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 { + val teams = ArrayList() + 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 { + val tactics = ArrayList() + 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 +} diff --git a/app/src/main/java/com/iqball/app/ui/theme/Color.kt b/app/src/main/java/com/iqball/app/ui/theme/Color.kt index 17833f0..ced48bb 100644 --- a/app/src/main/java/com/iqball/app/ui/theme/Color.kt +++ b/app/src/main/java/com/iqball/app/ui/theme/Color.kt @@ -10,9 +10,16 @@ val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) val Pink40 = Color(0xFF7D5260) +val black = Color(0xFF191A21) +val orange = Color(0xFFFFA239) +val blue = Color(0xFF0D6EFD) +val grey = Color(0xFF282A36) +val back = Color(0xFFf8f8f8) +val borderCard = Color(0xFFDADCE0) + val Allies = Color(0xFF64e4f5) val Opponents = Color(0xFFf59264) val BallColor = Color(0XFFc5520d) val StepNode = Color(0xFF2AC008) -val SelectedStepNode = Color(0xFF213519) \ No newline at end of file +val SelectedStepNode = Color(0xFF213519) diff --git a/app/src/main/java/com/iqball/app/ui/theme/Theme.kt b/app/src/main/java/com/iqball/app/ui/theme/Theme.kt index b521bd2..ae5f228 100644 --- a/app/src/main/java/com/iqball/app/ui/theme/Theme.kt +++ b/app/src/main/java/com/iqball/app/ui/theme/Theme.kt @@ -18,23 +18,29 @@ import androidx.core.view.WindowCompat private val DarkColorScheme = darkColorScheme( primary = Purple80, secondary = PurpleGrey80, - tertiary = Pink80 + tertiary = Pink80, + primaryContainer = black ) private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40 + primary = black, + secondary = orange, + tertiary = blue, + primaryContainer = black, + surface = grey, + background = back, + outline = borderCard - /* Other default colors to override - background = Color(0xFFFFFBFE), - surface = Color(0xFFFFFBFE), - onPrimary = Color.White, - onSecondary = Color.White, - onTertiary = Color.White, - onBackground = Color(0xFF1C1B1F), - onSurface = Color(0xFF1C1B1F), - */ + + /* Other default colors to override +background = Color(0xFFFFFBFE), +surface = Color(0xFFFFFBFE), +onPrimary = Color.White, +onSecondary = Color.White, +onTertiary = Color.White, +onBackground = Color(0xFF1C1B1F), +onSurface = Color(0xFF1C1B1F), +*/ ) @Composable diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f8c6127..9255bbb 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF018786 #FF000000 #FFFFFFFF + #FFDADCE0 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c64d05..46b279f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,4 @@ IQBall + Espace Personnel \ No newline at end of file