diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml
index bf7d4f2..c390e55 100644
--- a/.idea/androidTestResultsUserPreferences.xml
+++ b/.idea/androidTestResultsUserPreferences.xml
@@ -3,6 +3,123 @@
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 1a1d4a0..480dfe4 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -2,10 +2,13 @@
+
+
+
-
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 0ad17cb..8978d23 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/app/schemas/br.com.alexf.boraprofut.data.database.BoraProFutDatabase/1.json b/app/schemas/br.com.alexf.boraprofut.data.database.BoraProFutDatabase/1.json
index 4140eec..2ce71d8 100644
--- a/app/schemas/br.com.alexf.boraprofut.data.database.BoraProFutDatabase/1.json
+++ b/app/schemas/br.com.alexf.boraprofut.data.database.BoraProFutDatabase/1.json
@@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
- "identityHash": "5a69727f6e08bb3c14f33b339c57fe18",
+ "identityHash": "7f558be24c60997e8269ee8e2a802965",
"entities": [
{
"tableName": "PlayerEntity",
- "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `level` INTEGER NOT NULL, PRIMARY KEY(`name`))",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `level` INTEGER NOT NULL, `is_goal_keeper` INTEGER, PRIMARY KEY(`name`))",
"fields": [
{
"fieldPath": "name",
@@ -19,6 +19,12 @@
"columnName": "level",
"affinity": "INTEGER",
"notNull": true
+ },
+ {
+ "fieldPath": "isGoalKeeper",
+ "columnName": "is_goal_keeper",
+ "affinity": "INTEGER",
+ "notNull": false
}
],
"primaryKey": {
@@ -34,7 +40,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
- "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a69727f6e08bb3c14f33b339c57fe18')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7f558be24c60997e8269ee8e2a802965')"
]
}
}
\ No newline at end of file
diff --git a/app/schemas/br.com.alexf.boraprofut.data.database.BoraProFutDatabase/2.json b/app/schemas/br.com.alexf.boraprofut.data.database.BoraProFutDatabase/2.json
new file mode 100644
index 0000000..93edd55
--- /dev/null
+++ b/app/schemas/br.com.alexf.boraprofut.data.database.BoraProFutDatabase/2.json
@@ -0,0 +1,46 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "be2de52b07ed48f8f8942eed5268e9d4",
+ "entities": [
+ {
+ "tableName": "PlayerEntity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `level` INTEGER NOT NULL, `is_goal_keeper` INTEGER NOT NULL, PRIMARY KEY(`name`))",
+ "fields": [
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "level",
+ "columnName": "level",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isGoalKeeper",
+ "columnName": "is_goal_keeper",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "name"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'be2de52b07ed48f8f8942eed5268e9d4')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/br/com/alexf/boraprofut/features/PlayersFormScreenTest.kt b/app/src/androidTest/java/br/com/alexf/boraprofut/features/PlayersFormScreenTest.kt
deleted file mode 100644
index fd88957..0000000
--- a/app/src/androidTest/java/br/com/alexf/boraprofut/features/PlayersFormScreenTest.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package br.com.alexf.boraprofut.features
-
-import androidx.compose.ui.test.assertHasClickAction
-import androidx.compose.ui.test.isDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import br.com.alexf.boraprofut.features.playersForm.PlayersFormScreen
-import br.com.alexf.boraprofut.features.playersForm.PlayersUiState
-import org.junit.Rule
-import org.junit.Test
-
-class PlayersFormScreenTest {
-
- @get:Rule
- val composeTestRule = createComposeRule()
-
- @Test
- fun shouldDisplayTitleAndPlayersTextFieldWhenStartsScreen() {
- composeTestRule.setContent {
- PlayersFormScreen(
- uiState = PlayersUiState(),
- onClearPlayers = {},
- onSavePlayers = {}
- )
- }
- composeTestRule.onNodeWithText("Cadastro de jogadores")
- .isDisplayed()
- composeTestRule.onNodeWithText("Lista de jogadores")
- .isDisplayed()
- }
-
- @Test
- fun shouldDisplayPlayersCounterWhenInsertAtLeastOnePlayer(){
- composeTestRule.setContent {
- PlayersFormScreen(
- uiState = PlayersUiState("alex", 1),
- onClearPlayers = {},
- onSavePlayers = {}
- )
- }
- composeTestRule.onNodeWithText("Cadastro de jogadores")
- .isDisplayed()
- composeTestRule.onNodeWithText("Lista de jogadores")
- .isDisplayed()
- composeTestRule.onNodeWithText("Jogadores: 1")
- .isDisplayed()
- }
-
- @Test
- fun shouldDisplayClearButtonWhenInsertSomePlayersInTextField(){
- composeTestRule.setContent {
- PlayersFormScreen(
- uiState = PlayersUiState(
- players = "alex"
- ),
- onClearPlayers = {},
- onSavePlayers = {}
- )
- }
- composeTestRule.onNodeWithText("Cadastro de jogadores")
- .isDisplayed()
- composeTestRule.onNodeWithText("Limpar")
- .assertHasClickAction()
- .isDisplayed()
- }
-
- @Test
- fun shouldDisplaySaveButtonWhenInsertAtLeastFourPlayersInTextField() {
- composeTestRule.setContent {
- PlayersFormScreen(
- uiState = PlayersUiState(
- players = "alex\nthaylan\ndaniel\nmaria\n",
- amountPlayers = 4,
- ),
- onClearPlayers = {},
- onSavePlayers = {}
- )
- }
- composeTestRule.onNodeWithText("Cadastro de jogadores")
- .isDisplayed()
- composeTestRule.onNodeWithText("Limpar")
- .assertHasClickAction()
- .isDisplayed()
- composeTestRule.onNodeWithText("Jogadores: 4")
- .isDisplayed()
- composeTestRule.onNodeWithText("Salvar")
- .assertHasClickAction()
- .isDisplayed()
- }
-
-}
\ No newline at end of file
diff --git a/app/src/androidTest/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsScreenTest.kt b/app/src/androidTest/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsScreenTest.kt
new file mode 100644
index 0000000..9c9befb
--- /dev/null
+++ b/app/src/androidTest/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsScreenTest.kt
@@ -0,0 +1,118 @@
+package br.com.alexf.boraprofut.features.drawTeams
+
+import androidx.activity.compose.setContent
+import androidx.compose.ui.test.assertCountEquals
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onAllNodesWithTag
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import br.com.alexf.boraprofut.MainActivity
+import br.com.alexf.boraprofut.R
+import br.com.alexf.boraprofut.models.Player
+import org.junit.Rule
+import org.junit.Test
+
+private const val PLAYERS_PER_TEAM = 4
+
+class DrawTeamsScreenTest {
+
+ @get:Rule
+ val rule = createAndroidComposeRule()
+
+ private val players = listOf(
+ Player(name = "goalkeeper", isGoalKeeper = true),
+ Player(name = "player 1"),
+ Player(name = "player 2"),
+ Player(name = "player 3"),
+ )
+
+ @Test
+ fun shouldDisplayAllComponents() {
+ rule.activity.setContent {
+ DrawTeamsScreen(
+ uiState = DrawTeamsUiState(
+ players = players.toSet(),
+ playersPerTeam = PLAYERS_PER_TEAM,
+ isShowPlayers = true
+ ),
+ onDrawRandomTeamsClick = {},
+ onDrawBalancedTeamsClick = {}
+ ) {}
+ }
+ rule.onNodeWithText(rule.activity.getString(R.string.teams_draw))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.players_amount_per_team))
+ .assertIsDisplayed()
+ rule.onNode(
+ hasContentDescription(rule.activity.getString(R.string.decreases_players_amount_per_team))
+ and
+ hasClickAction()
+ ).assertIsDisplayed()
+ rule.onNodeWithText(PLAYERS_PER_TEAM.toString())
+ .assertIsDisplayed()
+ rule.onNode(
+ hasContentDescription(rule.activity.getString(R.string.increases_players_amount_per_team))
+ and
+ hasClickAction()
+ ).assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.random))
+ .assertIsDisplayed()
+ rule.onNodeWithContentDescription(rule.activity.getString(R.string.random))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.balanced))
+ .assertIsDisplayed()
+ rule.onNodeWithContentDescription(rule.activity.getString(R.string.balanced))
+ .assertIsDisplayed()
+ rule.onNodeWithContentDescription(
+ rule.activity.getString(R.string.icon_of_display_players_button)
+ )
+ .assertIsDisplayed()
+ .assertHasClickAction()
+ rule.onNodeWithText("${rule.activity.getString(R.string.hide_players)} (${players.size})")
+ .assertIsDisplayed()
+ .assertHasClickAction()
+ rule.onNodeWithText(rule.activity.getString(R.string.players, players.size))
+ .assertIsDisplayed()
+ rule.onNodeWithTag(EDIT_PLAYERS_BUTTON)
+ .assertHasClickAction()
+ .assertIsDisplayed()
+ rule.onNodeWithTag(PLAYERS_LIST)
+ .assertIsDisplayed()
+ rule.onAllNodesWithTag(PLAYERS_LIST_ITEM)
+ .assertCountEquals(4)
+ }
+
+ @Test
+ fun shouldNotDisplayPlayersList_WhenIsShowPlayersIsFalse() {
+ rule.activity.setContent {
+ DrawTeamsScreen(
+ uiState = DrawTeamsUiState(
+ players = players.toSet(),
+ playersPerTeam = PLAYERS_PER_TEAM,
+ isShowPlayers = false
+ ),
+ onDrawRandomTeamsClick = {},
+ onDrawBalancedTeamsClick = {}
+ ) {}
+ }
+ rule.onNodeWithContentDescription(
+ rule.activity.getString(R.string.icon_of_hide_players_button)
+ ).assertIsDisplayed()
+ rule.onNodeWithText("${rule.activity.getString(R.string.show_players)} (${players.size})")
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.players, players.size))
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag(EDIT_PLAYERS_BUTTON)
+ .assertIsNotDisplayed()
+ rule.onNodeWithTag(PLAYERS_LIST)
+ .assertIsNotDisplayed()
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/br/com/alexf/boraprofut/features/drawTeams/PlayersListTest.kt b/app/src/androidTest/java/br/com/alexf/boraprofut/features/drawTeams/PlayersListTest.kt
new file mode 100644
index 0000000..e968fae
--- /dev/null
+++ b/app/src/androidTest/java/br/com/alexf/boraprofut/features/drawTeams/PlayersListTest.kt
@@ -0,0 +1,65 @@
+package br.com.alexf.boraprofut.features.drawTeams
+
+import androidx.compose.ui.test.assertCountEquals
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithTag
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import br.com.alexf.boraprofut.models.Player
+import org.junit.Rule
+import org.junit.Test
+import kotlin.random.Random
+
+class PlayersListTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun shouldDisplayPlayersList() {
+ val players = listOf(
+ Player("Goalkeeper", isGoalKeeper = true),
+ Player("Player", level = Random.nextInt(1, 10))
+ )
+ rule.setContent {
+ PlayersList(
+ uiState = DrawTeamsUiState(
+ players = players.toSet()
+ )
+ )
+ }
+ rule.onNodeWithText("${players[0].name} (G)").assertIsDisplayed()
+ rule.onNodeWithText(players[1].name).assertIsDisplayed()
+ rule.onAllNodesWithTag(PLAYERS_LIST_ITEM).assertCountEquals(2)
+ }
+
+ @Test
+ fun shouldDisplayPlayerLevelMenu_WhenPlayerIsNotGoalkeeper() {
+ rule.setContent {
+ PlayersList(
+ uiState = DrawTeamsUiState(
+ players = setOf(
+ Player("Player")
+ )
+ )
+ )
+ }
+ rule.onNodeWithTag(PLAYER_LEVEL_MENU).assertIsDisplayed()
+ }
+
+ @Test
+ fun shouldNotDisplayPlayerLevelMenu_WhenPlayerIsGoalkeeper() {
+ rule.setContent {
+ PlayersList(
+ uiState = DrawTeamsUiState(
+ players = setOf(
+ Player("Goalkeeper", isGoalKeeper = true)
+ )
+ )
+ )
+ }
+ rule.onNodeWithTag(PLAYER_LEVEL_MENU).assertIsNotDisplayed()
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormScreenTest.kt b/app/src/androidTest/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormScreenTest.kt
new file mode 100644
index 0000000..c1613f0
--- /dev/null
+++ b/app/src/androidTest/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormScreenTest.kt
@@ -0,0 +1,154 @@
+package br.com.alexf.boraprofut.features.playersForm
+
+import androidx.activity.compose.setContent
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import br.com.alexf.boraprofut.MainActivity
+import br.com.alexf.boraprofut.R
+import kotlinx.coroutines.flow.flowOf
+import org.junit.Rule
+import org.junit.Test
+
+class PlayersFormScreenTest {
+
+ @get:Rule
+ val rule = createAndroidComposeRule()
+
+ @Test
+ fun shouldDisplayTitleAndPlayersTextFieldWhenStartsScreen() {
+ rule.activity.setContent {
+ PlayersFormScreen(
+ uiState = PlayersUiState(),
+ onClearPlayers = {},
+ onSavePlayers = {}
+ )
+ }
+ rule
+ .onNodeWithText(rule.activity.getString(R.string.register_of_players))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.players_list))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun shouldDisplayGoalKeeperInfoIcon_WhenStartsScreen() {
+ rule.activity.setContent {
+ PlayersFormScreen(
+ uiState = PlayersUiState(),
+ onClearPlayers = {},
+ onSavePlayers = {}
+ )
+ }
+ rule.onNode(
+ hasContentDescription(rule.activity.getString(R.string.show_add_goal_keeper_tip))
+ and
+ hasClickAction()
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun shouldDisplayGoalKeeperAlertDialog() {
+ rule.activity.setContent {
+ PlayersFormScreen(
+ uiState = PlayersUiState(
+ isGoalKeeperToolTipVisible = flowOf(true)
+ ),
+ onClearPlayers = {},
+ onSavePlayers = {}
+ )
+ }
+ rule.onNodeWithTag(GOAL_KEEPER_DIALOG)
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.add_goal_keeper))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.mark_player_as_goal_keeper_message))
+ .assertIsDisplayed()
+ rule.onNode(
+ hasText(rule.activity.getString(R.string.got_it))
+ and
+ hasClickAction()
+ ).assertIsDisplayed()
+ }
+
+ @Test
+ fun shouldNotDisplayGoalKeeperAlertDialog() {
+ rule.activity.setContent {
+ PlayersFormScreen(
+ uiState = PlayersUiState(
+ isGoalKeeperToolTipVisible = flowOf(false)
+ ),
+ onClearPlayers = {},
+ onSavePlayers = {}
+ )
+ }
+ rule.onNodeWithTag(GOAL_KEEPER_DIALOG)
+ .assertIsNotDisplayed()
+ }
+
+ @Test
+ fun shouldDisplayPlayersCounterWhenInsertAtLeastOnePlayer() {
+ rule.activity.setContent {
+ PlayersFormScreen(
+ uiState = PlayersUiState(players = "player", 1),
+ onClearPlayers = {},
+ onSavePlayers = {}
+ )
+ }
+ rule.onNodeWithText(rule.activity.getString(R.string.register_of_players))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.players_list))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.players_registered, 1))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun shouldDisplayClearButtonWhenInsertSomePlayersInTextField() {
+ rule.activity.setContent {
+ PlayersFormScreen(
+ uiState = PlayersUiState(
+ players = "player"
+ ),
+ onClearPlayers = {},
+ onSavePlayers = {}
+ )
+ }
+ rule.onNodeWithText(rule.activity.getString(R.string.register_of_players))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.clear))
+ .assertHasClickAction()
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun shouldDisplaySaveButtonWhenInsertAtLeastFourPlayersInTextField() {
+ rule.activity.setContent {
+ PlayersFormScreen(
+ uiState = PlayersUiState(
+ players = "player 1\nplayer 2\nplayer 3\nplayer 4\n",
+ amountPlayers = 4,
+ ),
+ onClearPlayers = {},
+ onSavePlayers = {}
+ )
+ }
+ rule.onNodeWithText(rule.activity.getString(R.string.register_of_players))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.clear))
+ .assertHasClickAction()
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.players_registered, 4))
+ .assertIsDisplayed()
+ rule.onNodeWithText(rule.activity.getString(R.string.save))
+ .assertHasClickAction()
+ .assertIsDisplayed()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/alexf/boraprofut/data/database/BoraProFutDatabase.kt b/app/src/main/java/br/com/alexf/boraprofut/data/database/BoraProFutDatabase.kt
index 9e3a9c6..49b487a 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/data/database/BoraProFutDatabase.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/data/database/BoraProFutDatabase.kt
@@ -1,14 +1,16 @@
package br.com.alexf.boraprofut.data.database
+import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.RoomDatabase
import br.com.alexf.boraprofut.data.database.dao.PlayersDao
import br.com.alexf.boraprofut.data.database.entities.PlayerEntity
@Database(
- version = 1,
+ version = 2,
exportSchema = true,
- entities = [PlayerEntity::class]
+ entities = [PlayerEntity::class],
+ autoMigrations = [AutoMigration(from = 1, to = 2)]
)
abstract class BoraProFutDatabase : RoomDatabase(){
diff --git a/app/src/main/java/br/com/alexf/boraprofut/data/database/dao/PlayersDao.kt b/app/src/main/java/br/com/alexf/boraprofut/data/database/dao/PlayersDao.kt
index 8563669..bb893c2 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/data/database/dao/PlayersDao.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/data/database/dao/PlayersDao.kt
@@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface PlayersDao {
- @Query("SELECT * FROM PlayerEntity ORDER by name")
+ @Query("SELECT * FROM PlayerEntity ORDER BY is_goal_keeper DESC, name")
fun findAll(): Flow>
@Insert(onConflict = OnConflictStrategy.REPLACE)
diff --git a/app/src/main/java/br/com/alexf/boraprofut/data/database/entities/PlayerEntity.kt b/app/src/main/java/br/com/alexf/boraprofut/data/database/entities/PlayerEntity.kt
index 6c0df68..3e78a37 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/data/database/entities/PlayerEntity.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/data/database/entities/PlayerEntity.kt
@@ -1,5 +1,6 @@
package br.com.alexf.boraprofut.data.database.entities
+import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@@ -8,4 +9,6 @@ class PlayerEntity(
@PrimaryKey
val name: String,
val level: Int,
+ @ColumnInfo("is_goal_keeper")
+ val isGoalKeeper: Boolean = false
)
\ No newline at end of file
diff --git a/app/src/main/java/br/com/alexf/boraprofut/data/repositories/PlayersFormPreferencesRepository.kt b/app/src/main/java/br/com/alexf/boraprofut/data/repositories/PlayersFormPreferencesRepository.kt
new file mode 100644
index 0000000..2187d99
--- /dev/null
+++ b/app/src/main/java/br/com/alexf/boraprofut/data/repositories/PlayersFormPreferencesRepository.kt
@@ -0,0 +1,34 @@
+package br.com.alexf.boraprofut.data.repositories
+
+import androidx.datastore.core.DataStore
+import androidx.datastore.preferences.core.Preferences
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class PlayersFormPreferencesRepository(private val dataStore: DataStore) {
+
+ suspend fun showGoalKeeperToolTip() {
+ dataStore.edit { preferences ->
+ preferences[IS_GOAL_KEEPER_TOOL_TIP_VISIBLE] = true
+ }
+ }
+
+ suspend fun hideGoalKeeperToolTip() {
+ dataStore.edit { preferences ->
+ preferences[IS_GOAL_KEEPER_TOOL_TIP_VISIBLE] = false
+ }
+ }
+
+ fun isGoalKeeperToolTipVisible(): Flow {
+ return dataStore.data.map { preferences ->
+ preferences[IS_GOAL_KEEPER_TOOL_TIP_VISIBLE] ?: true
+ }
+ }
+
+ companion object {
+ private val IS_GOAL_KEEPER_TOOL_TIP_VISIBLE =
+ booleanPreferencesKey("isGoalkeeperToolTipVisible")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/br/com/alexf/boraprofut/data/repositories/PlayersRepository.kt b/app/src/main/java/br/com/alexf/boraprofut/data/repositories/PlayersRepository.kt
index aa92a8a..426bc5a 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/data/repositories/PlayersRepository.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/data/repositories/PlayersRepository.kt
@@ -13,11 +13,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
private val playersPerTeamPreference = intPreferencesKey("playersPerTeam")
-private const val defaultPlayersPerTeam = 5
+private const val DEFAULT_PLAYERS_PER_TEAM = 5
class PlayersRepository(
private val dao: PlayersDao,
@@ -28,7 +27,7 @@ class PlayersRepository(
val games = _game.asStateFlow()
val playersPerTeam
get() = dataStore.data.map {
- it[playersPerTeamPreference] ?: defaultPlayersPerTeam
+ it[playersPerTeamPreference] ?: DEFAULT_PLAYERS_PER_TEAM
}
suspend fun save(players: Set) {
@@ -75,15 +74,6 @@ class PlayersRepository(
}
}
- fun saveGame(players: Set) {
- _game.update {
- players
- .filter {
- it.name.trim().isNotBlank()
- }.toSet()
- }
- }
-
suspend fun deleteAllPlayers() {
dao.deleteAllPlayers()
}
@@ -95,8 +85,11 @@ class PlayersRepository(
}
private fun Player.toPlayerEntity(): PlayerEntity {
+ val name = this.name.replace(Regex("\\([Gg]\\)"), "")
+ val isGoalKeeper = this.name.contains(Regex("\\([Gg]\\)")) || this.isGoalKeeper
return PlayerEntity(
- name = this.name,
- level = this.level
+ name = name,
+ level = this.level,
+ isGoalKeeper = isGoalKeeper
)
}
diff --git a/app/src/main/java/br/com/alexf/boraprofut/di/AppModules.kt b/app/src/main/java/br/com/alexf/boraprofut/di/AppModules.kt
index 529bb1b..f56acca 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/di/AppModules.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/di/AppModules.kt
@@ -5,12 +5,13 @@ import androidx.datastore.preferences.preferencesDataStoreFile
import androidx.room.Room
import br.com.alexf.boraprofut.data.database.BoraProFutDatabase
import br.com.alexf.boraprofut.data.repositories.PlayersRepository
+import br.com.alexf.boraprofut.data.repositories.PlayersFormPreferencesRepository
import br.com.alexf.boraprofut.features.balancedTeams.BalancedTeamViewModel
import br.com.alexf.boraprofut.features.drawTeams.DrawTeamsViewModel
import br.com.alexf.boraprofut.features.drawTeams.useCases.TeamDrawerUseCase
+import br.com.alexf.boraprofut.features.game.GameViewModel
import br.com.alexf.boraprofut.features.game.usecase.GameUseCase
import br.com.alexf.boraprofut.features.playersForm.PlayersFormViewModel
-import br.com.alexf.boraprofut.features.game.GameViewModel
import br.com.alexf.boraprofut.features.randomteams.RandomTeamsViewModel
import br.com.alexf.boraprofut.features.timer.TimerCountDown
import br.com.alexf.boraprofut.features.timer.TimerViewModel
@@ -33,6 +34,7 @@ val appModule = module {
val dataModule = module {
singleOf(::PlayersRepository)
+ singleOf(::PlayersFormPreferencesRepository)
single {
Room.databaseBuilder(
androidContext(),
diff --git a/app/src/main/java/br/com/alexf/boraprofut/features/balancedTeams/BalancedTeamsScreen.kt b/app/src/main/java/br/com/alexf/boraprofut/features/balancedTeams/BalancedTeamsScreen.kt
index a22abd8..bfb9604 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/features/balancedTeams/BalancedTeamsScreen.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/features/balancedTeams/BalancedTeamsScreen.kt
@@ -27,7 +27,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import br.com.alexf.boraprofut.R
@@ -98,27 +97,29 @@ fun BalancedTeamsScreen(
)
}
Column {
- team.players.forEach { p ->
+ team.players.forEach { player ->
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
- text = p.name,
+ text = "${player.name} ${if (player.isGoalKeeper) "(G)" else ""}",
Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
style = LocalTextStyle.current.copy(
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
)
- Text(
- text = "${p.level}",
- Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
- style = LocalTextStyle.current.copy(
- fontSize = 20.sp,
- fontWeight = FontWeight.Bold
+ if (!player.isGoalKeeper) {
+ Text(
+ text = "${player.level}",
+ Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
+ style = LocalTextStyle.current.copy(
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold
+ )
)
- )
+ }
}
}
}
diff --git a/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsScreen.kt b/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsScreen.kt
index 7acbd47..3adf6e4 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsScreen.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsScreen.kt
@@ -5,6 +5,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -28,7 +29,10 @@ import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.People
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Remove
+import androidx.compose.material.icons.outlined.SportsHandball
+import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -42,11 +46,11 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import br.com.alexf.boraprofut.R
@@ -66,6 +70,11 @@ import br.com.alexf.boraprofut.ui.theme.PlayersContainerPrimaryColor
import br.com.alexf.boraprofut.ui.theme.PlayersContainerSecondaryColor
import kotlin.random.Random
+const val PLAYERS_LIST = "PlayersList"
+const val PLAYERS_LIST_ITEM = "PlayersListItem"
+const val PLAYER_LEVEL_MENU = "PlayerLevelMenu"
+const val EDIT_PLAYERS_BUTTON = "EditPlayersButton"
+
private class DrawOption(
val title: String,
val icon: ImageVector,
@@ -73,7 +82,6 @@ private class DrawOption(
val action: () -> Unit
)
-@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DrawTeamsScreen(
uiState: DrawTeamsUiState,
@@ -84,6 +92,7 @@ fun DrawTeamsScreen(
) {
val context = LocalContext.current
val totalPlayers = uiState.players.size
+
Column(
modifier
.fillMaxSize()
@@ -104,8 +113,6 @@ fun DrawTeamsScreen(
.align(Alignment.CenterHorizontally)
)
val options = remember {
-
-
listOf(
DrawOption(
title = context.getString(R.string.random),
@@ -161,7 +168,7 @@ fun DrawTeamsScreen(
)
Spacer(modifier = Modifier.size(16.dp))
Icon(
- option.icon, contentDescription = null,
+ option.icon, contentDescription = option.title,
Modifier.size(64.dp),
tint = Color.White
)
@@ -207,154 +214,202 @@ fun DrawTeamsScreen(
)
}
if (uiState.isShowPlayers) {
- Column {
- Row(
- Modifier
- .fillMaxWidth()
- .background(
- Brush.linearGradient(
- listOf(
- PlayersContainerPrimaryColor,
- PlayersContainerSecondaryColor,
- MaterialTheme.colorScheme.background
- ),
- )
- ),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
- ) {
- Text(
- text = stringResource(R.string.players),
- Modifier.padding(16.dp),
- style = MaterialTheme.typography.titleLarge.copy(Color.White)
- )
-
+ if (uiState.players.isNotEmpty()) {
+ Column {
Row(
Modifier
- .padding(16.dp)
- .clickable {
- onEditPlayersClick()
- }
- .clip(RoundedCornerShape(15))
+ .fillMaxWidth()
.background(
Brush.linearGradient(
listOf(
- EditPlayersButtonContainerPrimaryColor,
- EditPlayersButtonContainerSecondaryColor,
- )
+ PlayersContainerPrimaryColor,
+ PlayersContainerSecondaryColor,
+ MaterialTheme.colorScheme.background
+ ),
)
- )
- .padding(8.dp),
- horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
) {
- Icon(
- imageVector = Icons.Filled.Edit,
- contentDescription = stringResource(R.string.players_edit_icon),
- tint = Color.White
- )
Text(
- text = stringResource(R.string.edit),
- style = LocalTextStyle.current.copy(color = Color.White)
+ text = stringResource(R.string.players, uiState.players.size),
+ Modifier.padding(16.dp),
+ style = MaterialTheme.typography.titleLarge.copy(Color.White)
)
- }
- }
- Column(
- Modifier.padding(16.dp),
- verticalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- uiState.players.forEach { player ->
+
Row(
Modifier
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- ) {
- Text(
- text = player.name, Modifier.weight(1f),
- style = LocalTextStyle.current.copy(
- fontSize = 20.sp,
- fontWeight = FontWeight.Bold
- )
- )
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(8.dp)
- ) {
-
- Box(
- modifier = Modifier
- .clip(CircleShape)
- .combinedClickable(
- onClick = {
- uiState.onDecreasePlayerLevel(player)
- },
- onLongClick = {
- uiState.onDecreasePlayerLevel(player)
- }
- )
- .background(
- DecreasePlayerLevelContainerColor
- .copy(alpha = 0.8f)
- )
- .padding(4.dp)
- ) {
- Icon(
- Icons.Outlined.Remove,
- contentDescription = null,
- tint = Color.White
- )
+ .padding(16.dp)
+ .clickable {
+ onEditPlayersClick()
}
- Text(
- text = "${player.level}",
- Modifier.width(30.dp),
- style = LocalTextStyle.current.copy(
- fontSize = 20.sp,
- fontWeight = FontWeight.Bold,
- textAlign = TextAlign.Center
- )
- )
-
- Box(
- modifier = Modifier
- .clip(CircleShape)
- .combinedClickable(
- onClick = {
- uiState.onIncreasePlayerLevel(player)
- },
- onLongClick = {
-
- uiState.onIncreasePlayerLevel(player)
- }
- )
- .background(
- IncreasePlayerLevelContainerColor
- .copy(alpha = 0.8f)
+ .clip(RoundedCornerShape(15))
+ .background(
+ Brush.linearGradient(
+ listOf(
+ EditPlayersButtonContainerPrimaryColor,
+ EditPlayersButtonContainerSecondaryColor,
)
- .padding(4.dp)
- ) {
- Icon(
- Icons.Outlined.Add,
- contentDescription = null,
- tint = Color.White
)
- }
- }
+ )
+ .padding(8.dp)
+ .testTag(EDIT_PLAYERS_BUTTON),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Edit,
+ contentDescription = stringResource(R.string.players_edit_icon),
+ tint = Color.White
+ )
+ Text(
+ text = stringResource(R.string.edit),
+ style = LocalTextStyle.current.copy(color = Color.White)
+ )
}
}
-
+ PlayersList(uiState = uiState)
}
}
}
}
}
+@Composable
+fun PlayersList(uiState: DrawTeamsUiState) {
+ Column(Modifier.testTag(PLAYERS_LIST)) {
+ uiState.players.forEachIndexed { index, player ->
+ PlayersListItem(uiState = uiState, player = player)
+ if (index < uiState.players.size - 1) {
+ HorizontalDivider()
+ }
+ }
+ }
+}
+
+@Composable
+fun PlayersListItem(uiState: DrawTeamsUiState, player: Player) {
+ Row(
+ Modifier
+ .fillMaxWidth()
+ .testTag(PLAYERS_LIST_ITEM),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ IconButton(onClick = {
+ uiState.onGoalKeeperChange(player)
+ }, modifier = Modifier.padding(vertical = 16.dp)) {
+ if (player.isGoalKeeper == true) {
+ Icon(
+ imageVector = Icons.Outlined.SportsHandball,
+ contentDescription = stringResource(R.string.goal_keeper_icon)
+ )
+ } else {
+ Icon(
+ imageVector = Icons.Outlined.SportsHandball,
+ contentDescription = stringResource(R.string.not_goal_keeper_icon),
+ tint = if (isSystemInDarkTheme()) {
+ Color.White.copy(alpha = 0.2f)
+ } else {
+ Color.Black.copy(alpha = 0.2f)
+ },
+ )
+ }
+
+ }
+ Text(
+ text = if (player.isGoalKeeper == true) {
+ "${player.name.trim()} (G)"
+ } else {
+ player.name
+ },
+ Modifier.weight(1f),
+ style = LocalTextStyle.current.copy(
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold
+ )
+ )
+ if (player.isGoalKeeper == false) {
+ PlayerLevelMenu(uiState = uiState, player = player)
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun PlayerLevelMenu(uiState: DrawTeamsUiState, player: Player) {
+ Row(
+ Modifier
+ .padding(end = 16.dp)
+ .testTag(PLAYER_LEVEL_MENU),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .clip(CircleShape)
+ .combinedClickable(
+ onClick = {
+ uiState.onDecreasePlayerLevel(player)
+ },
+ onLongClick = {
+ uiState.onDecreasePlayerLevel(player)
+ }
+ )
+ .background(
+ DecreasePlayerLevelContainerColor
+ .copy(alpha = 0.8f)
+ )
+ .padding(4.dp)
+ ) {
+ Icon(
+ Icons.Outlined.Remove,
+ contentDescription = stringResource(id = R.string.decrease_level),
+ tint = Color.White
+ )
+ }
+ Text(
+ text = "${player.level}",
+ Modifier.width(28.dp),
+ style = LocalTextStyle.current.copy(
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center
+ )
+ )
+
+ Box(
+ modifier = Modifier
+ .clip(CircleShape)
+ .combinedClickable(
+ onClick = {
+ uiState.onIncreasePlayerLevel(player)
+ },
+ onLongClick = {
+
+ uiState.onIncreasePlayerLevel(player)
+ }
+ )
+ .background(
+ IncreasePlayerLevelContainerColor
+ .copy(alpha = 0.8f)
+ )
+ .padding(4.dp)
+ ) {
+ Icon(
+ Icons.Outlined.Add,
+ contentDescription = stringResource(id = R.string.increase_level),
+ tint = Color.White
+ )
+ }
+ }
+}
@UiModePreviews
@Composable
fun DrawTeamsScreenPreview() {
BoraProFutTheme {
DrawTeamsScreen(
- uiState = DrawTeamsUiState(),
+ uiState = DrawTeamsUiState(isShowPlayers = false),
onDrawRandomTeamsClick = {},
onDrawBalancedTeamsClick = {},
onEditPlayersClick = {}
@@ -362,6 +417,29 @@ fun DrawTeamsScreenPreview() {
}
}
+@UiModePreviews
+@Composable
+fun PlayersListPreview() {
+
+ val players = mutableSetOf(
+ Player(name = "Alex", level = Random.nextInt(1, 10), isGoalKeeper = true),
+ Player(name = "Thailan", level = Random.nextInt(1, 10)),
+ Player(name = "Daniel", level = Random.nextInt(1, 10)),
+ Player(name = "Joao", level = 10),
+ Player(name = "Janssen", level = Random.nextInt(1, 10))
+ )
+
+ BoraProFutTheme {
+ Surface {
+ PlayersList(
+ uiState = DrawTeamsUiState(
+ players = players
+ )
+ )
+ }
+ }
+}
+
@UiModePreviews
@Composable
fun DrawTeamsScreenDisplayingPlayersPreview() {
@@ -370,7 +448,11 @@ fun DrawTeamsScreenDisplayingPlayersPreview() {
DrawTeamsScreen(
uiState = DrawTeamsUiState(
players = setOf(
- Player(name = "Alex", level = Random.nextInt(1, 10)),
+ Player(
+ name = "Alex",
+ level = Random.nextInt(1, 10),
+ isGoalKeeper = true
+ ),
Player(name = "Thailan", level = Random.nextInt(1, 10)),
Player(name = "Daniel", level = Random.nextInt(1, 10)),
Player(name = "Joao", level = 10),
diff --git a/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsViewModel.kt b/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsViewModel.kt
index 29f492c..5c2fa3b 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsViewModel.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/DrawTeamsViewModel.kt
@@ -24,6 +24,7 @@ data class DrawTeamsUiState(
val onIncreasePlayersPerTeam: () -> Unit = {},
val onIncreasePlayerLevel: (Player) -> Unit = {},
val onDecreasePlayerLevel: (Player) -> Unit = {},
+ val onGoalKeeperChange: (Player) -> Unit = {},
val initState: InitState = InitState.LOADING
)
@@ -70,6 +71,12 @@ class DrawTeamsViewModel(
viewModelScope.launch {
repository.increasePlayerLevel(it)
}
+ },
+ onGoalKeeperChange = {
+ val player = it.copy(isGoalKeeper = !it.isGoalKeeper)
+ viewModelScope.launch {
+ repository.save(setOf(player))
+ }
}
)
}
diff --git a/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/useCases/TeamDrawerUseCase.kt b/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/useCases/TeamDrawerUseCase.kt
index 7821557..90ce754 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/useCases/TeamDrawerUseCase.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/features/drawTeams/useCases/TeamDrawerUseCase.kt
@@ -1,7 +1,6 @@
package br.com.alexf.boraprofut.features.drawTeams.useCases
import br.com.alexf.boraprofut.models.Player
-import kotlin.math.ceil
class TeamDrawerUseCase {
@@ -9,40 +8,72 @@ class TeamDrawerUseCase {
players: Set,
playersPerTeam: Int
): List> {
- return players
- .shuffled()
- .chunked(playersPerTeam) {
- it.toSet()
- }
+ return getTeams(players = players, playersPerTeam = playersPerTeam)
}
fun drawBalancedTeams(
players: Set,
playersPerTeam: Int
): List> {
- val amountTeams = ceil(players.size / playersPerTeam.toFloat()).toInt()
- val sortedPlayers = players.shuffled()
- .sortedBy {
- it.level
- }.toMutableList()
-
- val teamsWithBestPlayersIncluded = MutableList(amountTeams) {
- val bestPlayer = sortedPlayers.removeLast()
- mutableListOf(bestPlayer)
+ return getTeams(isBalancedTeam = true, players = players, playersPerTeam = playersPerTeam)
+ }
+
+ private fun getTeams(
+ isBalancedTeam: Boolean = false,
+ players: Set,
+ playersPerTeam: Int
+ ): List> {
+ val onlyGoalKeepers = players
+ .filter { it.isGoalKeeper }
+ .shuffled()
+ .toMutableList()
+ val onlyPlayers = if (isBalancedTeam) {
+ players.filter { !it.isGoalKeeper }.sortedByDescending { it.level }.toMutableList()
+ } else {
+ players.filter { !it.isGoalKeeper }.shuffled().toMutableList()
}
+ val totalOfGoalKeepers = onlyGoalKeepers.size
+ val totalOfTeams: Int = players.size / playersPerTeam
+ val teams: MutableList> =
+ MutableList(totalOfTeams) { mutableListOf() }
+ var firstTeamWithoutGoalKeeper = 0
- while (sortedPlayers.isNotEmpty()) {
- teamsWithBestPlayersIncluded.forEach {
- if (sortedPlayers.isEmpty()) {
- return@forEach
+ // add players to the teams
+ if (teams.isNotEmpty()) {
+ for (current in 0 until playersPerTeam - 1) {
+ teams.forEach { team ->
+ if (onlyPlayers.isNotEmpty() && team.size < playersPerTeam) {
+ team.add(onlyPlayers.removeFirst())
+ }
}
- it.add(sortedPlayers.removeFirst())
}
}
- return teamsWithBestPlayersIncluded.map {
- it.toSet()
+ // add goalkeepers to the teams
+ teams.forEachIndexed { index, team ->
+ if (onlyGoalKeepers.isNotEmpty()) {
+ team.add(0, onlyGoalKeepers.removeFirst())
+ firstTeamWithoutGoalKeeper = index + 1
+ }
+ }
+
+ // when the player list has only one goalkeeper, add the remaining players to the teams without goalkeepers
+ if (totalOfGoalKeepers <= 1) {
+ teams.subList(firstTeamWithoutGoalKeeper, teams.size).forEach {
+ if (onlyPlayers.isNotEmpty())
+ it.add(onlyPlayers.removeFirst())
+ }
}
+
+ // when players remain, add to a new team
+ if (onlyPlayers.isNotEmpty()) {
+ teams.add(onlyPlayers)
+ if (onlyGoalKeepers.isNotEmpty()) {
+ teams.last().add(0, onlyGoalKeepers.removeFirst())
+ }
+ }
+
+ return teams.map { it.toSet() }
}
}
diff --git a/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormScreen.kt b/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormScreen.kt
index 742c5a7..38a3e47 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormScreen.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormScreen.kt
@@ -18,17 +18,26 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.Done
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
@@ -41,6 +50,9 @@ import br.com.alexf.boraprofut.ui.theme.ClearPlayersTextFieldContainerColor
import br.com.alexf.boraprofut.ui.theme.DuplicatesNamesContainerColor
import br.com.alexf.boraprofut.ui.theme.SavePlayersButtonContainerColor
+const val GOAL_KEEPER_DIALOG = "GoalKeeperDialog"
+
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlayersFormScreen(
uiState: PlayersUiState,
@@ -48,145 +60,200 @@ fun PlayersFormScreen(
onClearPlayers: () -> Unit,
modifier: Modifier = Modifier,
) {
- Column(
- modifier
- .fillMaxSize()
- .verticalScroll(rememberScrollState())
- .background(MaterialTheme.colorScheme.background)
- ) {
+ val isGoalKeeperToolTipVisible = uiState.isGoalKeeperToolTipVisible.collectAsState(initial = true)
+ Scaffold(
+ topBar = {
+ TopAppBar(title = {
+ Text(text = stringResource(id = R.string.register_of_players))
+ },
+ actions = {
+ IconButton(onClick = uiState.onShowGoalKeeperToolTip) {
+ Icon(
+ imageVector = Icons.Filled.Info,
+ contentDescription = stringResource(R.string.show_add_goal_keeper_tip),
+ )
+ }
+ })
+ }
+ ) { paddingValues ->
Column(
- Modifier
- .weight(1f)
+ modifier
+ .padding(paddingValues)
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .background(MaterialTheme.colorScheme.background)
) {
- Text(
- text = stringResource(id = R.string.register_of_players),
- Modifier.padding(16.dp),
- style = MaterialTheme.typography.titleLarge
- )
- AnimatedVisibility(
- visible = uiState.players.isNotBlank(),
- enter = fadeIn(initialAlpha = 0.0f)
+ Column(
+ Modifier
+ .weight(1f)
) {
- Row(
- modifier.padding(
- top = 10.dp, start = 16.dp, end = 16.dp
- )
+ AnimatedVisibility(
+ visible = uiState.players.isNotBlank(),
+ enter = fadeIn(initialAlpha = 0.0f)
) {
- Text(
- text = stringResource(R.string.players_registered),
- fontWeight = FontWeight(700)
- )
- Text(
- text = "${uiState.amountPlayers}",
- Modifier.padding(start = 8.dp),
- )
+ Row(
+ modifier.padding(
+ top = 10.dp, start = 16.dp, end = 16.dp
+ )
+ ) {
+ Text(
+ text = stringResource(R.string.players_registered),
+ fontWeight = FontWeight(700)
+ )
+ Text(
+ text = "${uiState.amountPlayers}",
+ Modifier.padding(start = 8.dp),
+ )
+ }
}
- }
- AnimatedVisibility(
- visible = uiState.duplicateNames.isNotEmpty(),
- enter = fadeIn(initialAlpha = 0.0f)
- ) {
- Column(Modifier.padding(horizontal = 16.dp)) {
- Text(
- text = stringResource(id = R.string.names_duplicated),
- Modifier.padding(top = 10.dp),
- fontWeight = FontWeight(700)
- )
+ AnimatedVisibility(
+ visible = uiState.duplicateNames.isNotEmpty(),
+ enter = fadeIn(initialAlpha = 0.0f)
+ ) {
+ Column(Modifier.padding(horizontal = 16.dp)) {
+ Text(
+ text = stringResource(id = R.string.names_duplicated),
+ Modifier.padding(top = 10.dp),
+ fontWeight = FontWeight(700)
+ )
- Text(
- text = uiState.duplicateNames, Modifier
- .clip(RoundedCornerShape(8.dp))
- .background(DuplicatesNamesContainerColor)
- .padding(
- horizontal = 12.dp,
- vertical = 8.dp
- ), color = Color.White, fontSize = 12.sp
- )
+ Text(
+ text = uiState.duplicateNames, Modifier
+ .clip(RoundedCornerShape(8.dp))
+ .background(DuplicatesNamesContainerColor)
+ .padding(
+ horizontal = 12.dp,
+ vertical = 8.dp
+ ), color = Color.White, fontSize = 12.sp
+ )
+ }
}
- }
- Column(
- Modifier.padding(top = 16.dp)
- ) {
- Row(
- Modifier
- .padding(horizontal = 16.dp)
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(16.dp)
+ Column(
+ Modifier.padding(top = 16.dp)
) {
-
- AnimatedVisibility(
- uiState.players.isNotBlank(),
- modifier,
- fadeIn(initialAlpha = 0.0f)
+ Row(
+ Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
- Row(
- Modifier
- .clip(CircleShape)
- .background(ClearPlayersTextFieldContainerColor)
- .padding(horizontal = 8.dp, vertical = 8.dp)
- .clickable { onClearPlayers() },
- horizontalArrangement = Arrangement.Absolute.Center,
- verticalAlignment = Alignment.CenterVertically
+ AnimatedVisibility(
+ uiState.players.isNotBlank(),
+ modifier,
+ fadeIn(initialAlpha = 0.0f)
) {
- Text(
- stringResource(id = R.string.clear),
- color = Color.White,
- fontWeight = FontWeight(700)
- )
- Spacer(Modifier.size(4.dp))
- Icon(
- imageVector = Icons.Outlined.Clear,
- contentDescription = null,
- Modifier.clip(CircleShape),
- tint = Color.White,
- )
- }
- }
- AnimatedVisibility(
- uiState.isShowSaveButton(),
- modifier.weight(1f),
- fadeIn(initialAlpha = 0.0f)
- ) {
+ Row(
+ Modifier
+ .clip(CircleShape)
+ .background(ClearPlayersTextFieldContainerColor)
+ .padding(horizontal = 8.dp, vertical = 8.dp)
+ .clickable { onClearPlayers() },
+ horizontalArrangement = Arrangement.Absolute.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ stringResource(id = R.string.clear),
+ color = Color.White,
+ fontWeight = FontWeight(700)
+ )
+ Spacer(Modifier.size(4.dp))
+ Icon(
+ imageVector = Icons.Outlined.Clear,
+ contentDescription = null,
+ Modifier.clip(CircleShape),
+ tint = Color.White,
+ )
+ }
+ }
- Row(
- Modifier
- .clip(CircleShape)
- .background(SavePlayersButtonContainerColor)
- .padding(horizontal = 8.dp, vertical = 8.dp)
- .clickable { onSavePlayers() },
- horizontalArrangement = Arrangement.Absolute.Center,
- verticalAlignment = Alignment.CenterVertically
+ AnimatedVisibility(
+ uiState.isShowSaveButton(),
+ modifier.weight(1f),
+ fadeIn(initialAlpha = 0.0f)
) {
- Text(
- stringResource(id = R.string.save),
- color = Color.White,
- fontWeight = FontWeight(700)
- )
- Spacer(Modifier.size(4.dp))
- Icon(
- imageVector = Icons.Outlined.Done,
- contentDescription = null,
- Modifier.clip(CircleShape),
- tint = Color.White,
- )
+
+ Row(
+ Modifier
+ .clip(CircleShape)
+ .background(SavePlayersButtonContainerColor)
+ .padding(horizontal = 8.dp, vertical = 8.dp)
+ .clickable { onSavePlayers() },
+ horizontalArrangement = Arrangement.Absolute.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ stringResource(id = R.string.save),
+ color = Color.White,
+ fontWeight = FontWeight(700)
+ )
+ Spacer(Modifier.size(4.dp))
+ Icon(
+ imageVector = Icons.Outlined.Done,
+ contentDescription = null,
+ Modifier.clip(CircleShape),
+ tint = Color.White,
+ )
+ }
}
}
}
+ OutlinedTextField(
+ value = uiState.players,
+ onValueChange = uiState.onPlayersChange,
+ Modifier
+ .heightIn(200.dp)
+ .fillMaxWidth()
+ .padding(16.dp),
+ label = { Text(text = stringResource(R.string.players_list)) },
+ shape = RoundedCornerShape(4)
+ )
+ if (isGoalKeeperToolTipVisible.value) {
+ GoalKeeperToolTipAlert(
+ onDismissRequest = uiState.onHideGoalKeeperToolTip,
+ onConfirmation = uiState.onHideGoalKeeperToolTip,
+ dialogTitle = stringResource(R.string.add_goal_keeper),
+ dialogText = stringResource(id = R.string.mark_player_as_goal_keeper_message),
+ dialogButtonText = stringResource(R.string.got_it)
+ )
+ }
}
- OutlinedTextField(
- value = uiState.players,
- onValueChange = uiState.onPlayersChange,
- Modifier
- .heightIn(200.dp)
- .fillMaxWidth()
- .padding(16.dp),
- label = { Text(text = stringResource(R.string.players_list)) },
- shape = RoundedCornerShape(4)
- )
}
}
+
+}
+
+@Composable
+fun GoalKeeperToolTipAlert(
+ onDismissRequest: () -> Unit,
+ onConfirmation: () -> Unit,
+ dialogTitle: String,
+ dialogText: String,
+ dialogButtonText: String
+) {
+ AlertDialog(
+ title = { Text(text = dialogTitle) },
+ text = { Text(text = dialogText) },
+ modifier = Modifier.testTag(GOAL_KEEPER_DIALOG),
+ onDismissRequest = { onDismissRequest() },
+ confirmButton = {
+ TextButton(onClick = { onConfirmation() }) {
+ Text(text = dialogButtonText)
+ }
+ })
+}
+
+@Preview
+@Composable
+fun GoalKeeperToolTipAlertPreview() {
+ GoalKeeperToolTipAlert(
+ onDismissRequest = {},
+ onConfirmation = {},
+ dialogTitle = "Adicionar Goleiro",
+ dialogText = "Para adicionar um goleiro...",
+ dialogButtonText = "Entendi"
+ )
}
@UiModePreviews
diff --git a/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormViewModel.kt b/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormViewModel.kt
index b34d12c..adc49b9 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormViewModel.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/features/playersForm/PlayersFormViewModel.kt
@@ -3,12 +3,15 @@ package br.com.alexf.boraprofut.features.playersForm
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import br.com.alexf.boraprofut.data.repositories.PlayersRepository
+import br.com.alexf.boraprofut.data.repositories.PlayersFormPreferencesRepository
import br.com.alexf.boraprofut.models.Player
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -20,6 +23,9 @@ data class PlayersUiState(
val duplicateNames: String = "",
val onPlayersChange: (String) -> Unit = {},
val isSaving: Boolean = false,
+ val isGoalKeeperToolTipVisible: Flow = flowOf(false),
+ val onShowGoalKeeperToolTip: () -> Unit = {},
+ val onHideGoalKeeperToolTip: () -> Unit = {}
) {
fun isShowSaveButton() = players.isNotBlank()
&& amountPlayers != null && amountPlayers > 0
@@ -27,7 +33,8 @@ data class PlayersUiState(
}
class PlayersFormViewModel(
- private val repository: PlayersRepository
+ private val repository: PlayersRepository,
+ private val playersFormPreferencesRepository: PlayersFormPreferencesRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(PlayersUiState())
@@ -38,24 +45,44 @@ class PlayersFormViewModel(
init {
_uiState.update { currentState ->
currentState.copy(
+ isGoalKeeperToolTipVisible = playersFormPreferencesRepository.isGoalKeeperToolTipVisible(),
onPlayersChange = { players ->
_uiState.update {
it.copy(
players = players,
amountPlayers = isTherePlayer(players),
duplicateNames = players.duplicateNames()
- .joinToString()
+ .joinToString(),
)
}
},
+ onShowGoalKeeperToolTip = {
+ viewModelScope.launch {
+ playersFormPreferencesRepository.showGoalKeeperToolTip()
+ }
+ },
+ onHideGoalKeeperToolTip = {
+ viewModelScope.launch {
+ playersFormPreferencesRepository.hideGoalKeeperToolTip()
+ }
+ }
)
}
+
+
viewModelScope.launch {
repository.players.collectLatest { players ->
_uiState.update { currentState ->
currentState.copy(
- players = players.joinToString("") { "${it.name}\n" },
+ players = players
+ .joinToString("") {
+ if (it.isGoalKeeper) {
+ "${it.name.trim()} (G)\n"
+ } else {
+ "${it.name}\n"
+ }
+ },
amountPlayers = players.size
)
}
diff --git a/app/src/main/java/br/com/alexf/boraprofut/features/randomteams/RandomTeamsScreen.kt b/app/src/main/java/br/com/alexf/boraprofut/features/randomteams/RandomTeamsScreen.kt
index a4c321a..b1f8638 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/features/randomteams/RandomTeamsScreen.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/features/randomteams/RandomTeamsScreen.kt
@@ -53,7 +53,7 @@ fun RandomTeamsScreen(
verticalAlignment = Alignment.CenterVertically
) {
Text(
- text = "Times sorteados",
+ text = stringResource(R.string.teams_drawn),
Modifier
.weight(1f),
style = MaterialTheme.typography.titleLarge
@@ -85,40 +85,42 @@ fun RandomTeamsScreen(
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
- text = "Time ${index + 1}",
+ text = stringResource(id = R.string.team, index + 1),
Modifier
.padding(16.dp),
style = MaterialTheme.typography.titleMedium
)
Text(
- text = "Nível ${team.level}",
+ text = stringResource(id = R.string.level, team.level),
Modifier
.padding(16.dp),
style = MaterialTheme.typography.titleMedium
)
}
Column {
- team.players.forEach { p ->
+ team.players.forEach { player ->
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
- text = p.name,
+ text = "${player.name} ${if (player.isGoalKeeper) "(G)" else ""}",
Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
style = LocalTextStyle.current.copy(
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
)
- Text(
- text = "${p.level}",
- Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
- style = LocalTextStyle.current.copy(
- fontSize = 20.sp,
- fontWeight = FontWeight.Bold
+ if (!player.isGoalKeeper) {
+ Text(
+ text = "${player.level}",
+ Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
+ style = LocalTextStyle.current.copy(
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold
+ )
)
- )
+ }
}
}
}
diff --git a/app/src/main/java/br/com/alexf/boraprofut/models/Player.kt b/app/src/main/java/br/com/alexf/boraprofut/models/Player.kt
index 6061e59..17c5055 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/models/Player.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/models/Player.kt
@@ -1,6 +1,10 @@
package br.com.alexf.boraprofut.models
+import androidx.room.ColumnInfo
+
data class Player(
val name: String,
- val level: Int = 0
+ val level: Int = 0,
+ @ColumnInfo("is_goal_keeper")
+ val isGoalKeeper: Boolean = false
)
\ No newline at end of file
diff --git a/app/src/main/java/br/com/alexf/boraprofut/ui/theme/Color.kt b/app/src/main/java/br/com/alexf/boraprofut/ui/theme/Color.kt
index 9105e76..2c69a24 100644
--- a/app/src/main/java/br/com/alexf/boraprofut/ui/theme/Color.kt
+++ b/app/src/main/java/br/com/alexf/boraprofut/ui/theme/Color.kt
@@ -10,10 +10,8 @@ val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
-
val SelectPlayerContainerPrimaryColor = Color(0xFF673AB7)
val SelectPlayerContainerSecondaryColor = Color(0xFFF44336)
-
val DrawRandomTeamsContainerPrimaryColor = Color(0xFF673AB7)
val DrawRandomTeamsContainerSecondaryColor = Color(0xFF223311)
val DrawBalancedTeamsContainerPrimaryColor = Color(0xFF2196F3)
@@ -24,12 +22,10 @@ val EditPlayersButtonContainerPrimaryColor = Color(0xFFD500F9)
val EditPlayersButtonContainerSecondaryColor = Color(0xFF651FFF)
val DecreasePlayerLevelContainerColor = Color(0xFFFF1744)
val IncreasePlayerLevelContainerColor = Color(0xFF00E676)
-
-val DuplicatesNamesContainerColor = Color(0xFF8B0000)
-val ClearPlayersTextFieldContainerColor = Color(0xFF8B0000)
-val SavePlayersButtonContainerColor = Color(0xFF006400)
-
val PauseButtonColor = Color(0xFFE53935)
val ContinueButtonColor = Color(0xFF008866)
val RestartButtonColor = Color(0xFFFDC417)
-val RestartButtonContentColor = Color(0xFF424242)
\ No newline at end of file
+val RestartButtonContentColor = Color(0xFF424242)
+val DuplicatesNamesContainerColor = Color(0xFF8B0000)
+val ClearPlayersTextFieldContainerColor = Color(0xFF8B0000)
+val SavePlayersButtonContainerColor = Color(0xFF006400)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 956ecff..464591c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -24,7 +24,7 @@
Esconder jogadores
ícone do botão para esconder jogadores
Mostrar jogadores
- Jogadores
+ Jogadores (%d)
Editar
Pausar
Continuar
@@ -32,4 +32,14 @@
botão para iniciar o cronômetro
botão para reiniciar cronômetro
botão para pausar o cronômetro
+ Nível:
+ diminuir nível
+ aumentar nível
+ Times sorteados
+ Para marcar um jogador como goleiro basta inserir (g) após o seu nome
+ Entendi
+ Adicionar Goleiro
+ mostrar dica para adicionar goleiro
+ goal keeper icon
+ not goal keeper icon
\ No newline at end of file
diff --git a/app/src/test/java/br/com/alexf/boraprofut/TeamDrawerUseCaseTest.kt b/app/src/test/java/br/com/alexf/boraprofut/TeamDrawerUseCaseTest.kt
index b3f75d7..bd0c301 100644
--- a/app/src/test/java/br/com/alexf/boraprofut/TeamDrawerUseCaseTest.kt
+++ b/app/src/test/java/br/com/alexf/boraprofut/TeamDrawerUseCaseTest.kt
@@ -2,12 +2,17 @@ package br.com.alexf.boraprofut
import br.com.alexf.boraprofut.features.drawTeams.useCases.TeamDrawerUseCase
import br.com.alexf.boraprofut.models.Player
+import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldContain
+import org.amshove.kluent.shouldContainAny
+import org.amshove.kluent.shouldNotBeEqualTo
import org.amshove.kluent.shouldNotBeGreaterThan
import org.amshove.kluent.shouldNotContainAny
-import org.junit.Assert
import org.junit.Test
import kotlin.random.Random
+private const val GOAL_KEEPER = "Goalkeeper"
+
class TeamDrawerUseCaseTest {
private val teamDrawer = TeamDrawerUseCase()
@@ -16,7 +21,7 @@ class TeamDrawerUseCaseTest {
fun shouldDrawTeamPlayersRandomlyGivenAmount() {
val players = generatePlayers(12)
val teams = teamDrawer.drawRandomTeams(players, 4)
- Assert.assertEquals(teams.size, 3)
+ teams.size shouldBeEqualTo 3
teams.forEach { team ->
teams.filter {
it != team
@@ -34,14 +39,78 @@ class TeamDrawerUseCaseTest {
}
teamAverages.forEach { average ->
teamAverages.forEach {
- (it - average).shouldNotBeGreaterThan(1)
+ (it - average) shouldNotBeGreaterThan 2
+ }
+ }
+ }
+
+ @Test
+ fun shouldContainGoalKeeperInAllTeams() {
+ val players = generatePlayers(8).toMutableSet()
+ val playersPerTeam = 5
+ players.addAll(
+ setOf(
+ Player("$GOAL_KEEPER 1", isGoalKeeper = true),
+ Player("$GOAL_KEEPER 2", isGoalKeeper = true),
+ )
+ )
+ val drawnTeams = teamDrawer.drawRandomTeams(players = players, playersPerTeam = playersPerTeam)
+ drawnTeams.size shouldBeEqualTo 2
+ drawnTeams.forEach { team ->
+ team shouldContainAny {
+ it.isGoalKeeper
}
+ team.size shouldBeEqualTo playersPerTeam
}
}
+ @Test
+ fun shouldNotContainGoalKeeperInAllTeams() {
+ val players = generatePlayers(10)
+ val playersPerTeam = 5
+ val drawnTeams = teamDrawer.drawRandomTeams(players = players, playersPerTeam = playersPerTeam)
+ drawnTeams.size shouldBeEqualTo 2
+ drawnTeams.forEach { team ->
+ team shouldNotContainAny {
+ it.isGoalKeeper
+ }
+ team.size shouldBeEqualTo playersPerTeam
+ }
+ }
+
+ @Test
+ fun shouldContainGoalKeeperJustInTheFirstTeam() {
+ val players = generatePlayers(9).toMutableSet()
+ val playersPerTeam = 5
+ val goalKeeper = Player(GOAL_KEEPER, isGoalKeeper = true)
+ players.add(goalKeeper)
+ val drawnTeams = teamDrawer.drawRandomTeams(players = players, playersPerTeam = playersPerTeam)
+ drawnTeams.size shouldBeEqualTo 2
+ drawnTeams[0] shouldContain goalKeeper
+ drawnTeams[1] shouldNotContainAny {
+ it.isGoalKeeper
+ }
+ drawnTeams.forEach {team ->
+ team.size shouldBeEqualTo playersPerTeam
+ }
+ }
+
+ @Test
+ fun shouldContainATeamWithoutASufficientNumberOfPlayers() {
+ var players = generatePlayers(9)
+ val playersPerTeam = 4
+ var drawnTeams =
+ teamDrawer.drawRandomTeams(players = players, playersPerTeam = playersPerTeam)
+ drawnTeams.last().size shouldNotBeEqualTo playersPerTeam
+ players = generatePlayers(11)
+ drawnTeams =
+ teamDrawer.drawBalancedTeams(players = players, playersPerTeam = playersPerTeam)
+ drawnTeams.last().size shouldNotBeEqualTo playersPerTeam
+ }
+
}
private fun generatePlayers(amount: Int): Set =
List(amount) {
- Player(name = "jogador ${it + 1}", level = Random.nextInt(1, 10))
+ Player(name = "player ${it + 1}", level = Random.nextInt(1, 10))
}.toSet()