mirror of
https://github.com/LucasVbr/Queezer.git
synced 2026-05-13 17:11:55 +00:00
fix: Custom Party + Success rate
This commit is contained in:
@@ -46,6 +46,7 @@ apply(
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.runtime.livedata)
|
implementation(libs.androidx.runtime.livedata)
|
||||||
|
implementation(libs.material)
|
||||||
val roomVersion = "2.5.2"
|
val roomVersion = "2.5.2"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
package fr.univpau.queezer.data
|
package fr.univpau.queezer.data
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
data class Filter(
|
data class Filter(
|
||||||
val date: DateFilter = DateFilter.DESCENDING,
|
var dateOrderIsAscending: Boolean = true,
|
||||||
val mode : List<GameMode> = listOf(GameMode.TITLE, GameMode.ARTIST, GameMode.ALL),
|
|
||||||
val nbTitle : Int? = null,
|
val mode : Map<GameMode, Boolean> = mapOf(
|
||||||
|
GameMode.TITLE to true,
|
||||||
|
GameMode.ARTIST to true,
|
||||||
|
GameMode.ALL to true
|
||||||
|
),
|
||||||
|
|
||||||
|
val nbTitleIsAscending : Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun filterGames(filter: Filter, games: List<Game>): List<Game> {
|
fun filterGames(filter: Filter, games: List<Game>): List<Game> {
|
||||||
return games.filter { game ->
|
Log.i("Filter", "Filtering games with $filter")
|
||||||
filter.mode.contains(game.settings.gameMode) && (filter.nbTitle == null || game.playlist.tracks.size == filter.nbTitle)
|
|
||||||
}.sortedBy { game ->
|
|
||||||
when (filter.date) {
|
|
||||||
DateFilter.ASCENDING -> game.date.time
|
|
||||||
DateFilter.DESCENDING -> -game.date.time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class DateFilter {
|
return games
|
||||||
ASCENDING,
|
.filter { game ->
|
||||||
DESCENDING
|
// Filtrer uniquement par les modes de jeu activés
|
||||||
|
filter.mode.filter { it.value }.keys.contains(game.settings.gameMode)
|
||||||
|
}
|
||||||
|
.sortedWith(compareBy<Game> { game ->
|
||||||
|
// Tri par date
|
||||||
|
if (filter.dateOrderIsAscending) -game.date.time else game.date.time
|
||||||
|
}.thenBy { game ->
|
||||||
|
// Tri par nombre de titres
|
||||||
|
if (filter.nbTitleIsAscending) game.settings.numberOfTitles ?: 0 else -(game.settings.numberOfTitles ?: 0)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -11,4 +11,4 @@ data class Game(
|
|||||||
val playlist: Playlist = Playlist(),
|
val playlist: Playlist = Playlist(),
|
||||||
val score: Int = 0,
|
val score: Int = 0,
|
||||||
val date: Date = Date()
|
val date: Date = Date()
|
||||||
)
|
)
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package fr.univpau.queezer.view.components
|
package fr.univpau.queezer.view.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Badge
|
||||||
|
import androidx.compose.material3.BadgedBox
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -15,9 +18,11 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import fr.univpau.queezer.R
|
||||||
import fr.univpau.queezer.data.Game
|
import fr.univpau.queezer.data.Game
|
||||||
import fr.univpau.queezer.data.GameMode
|
import fr.univpau.queezer.data.GameMode
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -26,7 +31,8 @@ import java.util.Locale
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GameCardItem(game: Game) {
|
fun GameCardItem(game: Game) {
|
||||||
val formatter = SimpleDateFormat("dd MMMM yyyy", Locale.getDefault())
|
val context = LocalContext.current
|
||||||
|
val formatter = SimpleDateFormat("dd MMMM yyyy - HH:mm", Locale.getDefault())
|
||||||
val showBottomSheet = remember { mutableStateOf(false) }
|
val showBottomSheet = remember { mutableStateOf(false) }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -36,6 +42,8 @@ fun GameCardItem(game: Game) {
|
|||||||
(game.settings.numberOfTitles ?: 1)
|
(game.settings.numberOfTitles ?: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val gameModesLabels = context.resources.getStringArray(R.array.game_modes)
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
onClick = { showBottomSheet.value = true },
|
onClick = { showBottomSheet.value = true },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
@@ -43,14 +51,29 @@ fun GameCardItem(game: Game) {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Row(
|
||||||
text = game.playlist.title,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 14.sp,
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
fontWeight = FontWeight.SemiBold
|
) {
|
||||||
)
|
Text(
|
||||||
Text(text = formatter.format(game.date))
|
text = game.playlist.title,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
|
||||||
Text(text = "${game.score}/$maxScore")
|
Badge(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
content = { Text("${game.score}/${maxScore}") }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(text = formatter.format(game.date))
|
||||||
|
Text(text = "Mode: ${gameModesLabels[game.settings.gameMode.ordinal]}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,14 +99,24 @@ fun GameCardItem(game: Game) {
|
|||||||
text = game.playlist.title,
|
text = game.playlist.title,
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
Text(
|
Badge(
|
||||||
text = "${game.score}/$maxScore",
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
style = MaterialTheme.typography.titleMedium
|
content = {
|
||||||
|
Text(
|
||||||
|
text = "${game.score}/${maxScore}pts",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
|
||||||
text = formatter.format(game.date),
|
Row(
|
||||||
)
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(formatter.format(game.date))
|
||||||
|
Text("Mode: ${gameModesLabels[game.settings.gameMode.ordinal]}")
|
||||||
|
}
|
||||||
|
|
||||||
TrackCardItemList(game.playlist.tracks)
|
TrackCardItemList(game.playlist.tracks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,21 @@ package fr.univpau.queezer.view.screens
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -18,9 +26,12 @@ import androidx.compose.material3.Scaffold
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -31,22 +42,23 @@ import androidx.navigation.NavHostController
|
|||||||
import fr.univpau.queezer.R
|
import fr.univpau.queezer.R
|
||||||
import fr.univpau.queezer.data.Filter
|
import fr.univpau.queezer.data.Filter
|
||||||
import fr.univpau.queezer.data.Game
|
import fr.univpau.queezer.data.Game
|
||||||
|
import fr.univpau.queezer.data.GameMode
|
||||||
import fr.univpau.queezer.data.filterGames
|
import fr.univpau.queezer.data.filterGames
|
||||||
import fr.univpau.queezer.view.components.GameCardItemList
|
import fr.univpau.queezer.view.components.GameCardItemList
|
||||||
import fr.univpau.queezer.viewmodel.GameViewModel
|
import fr.univpau.queezer.viewmodel.GameViewModel
|
||||||
import kotlin.math.max
|
import java.util.Locale
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel) {
|
fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val filter = remember { mutableStateOf(Filter()) }
|
var filter by remember { mutableStateOf(Filter()) }
|
||||||
val games: List<Game> = gameViewModel.games.observeAsState().value ?: emptyList()
|
val games: List<Game> = gameViewModel.games.observeAsState().value ?: emptyList()
|
||||||
|
|
||||||
val nbGames = games.size;
|
val filteredGames by remember(filter, games) { derivedStateOf { filterGames(filter, games) } }
|
||||||
val averageSuccessRate = games.sumOf { it.score }.div(max(games.size, 1))
|
val averageSuccessRate by remember(filteredGames) { derivedStateOf { calculateAverageSuccessRate(filteredGames) } }
|
||||||
|
val nbGames by remember(filteredGames) { derivedStateOf { filteredGames.size } }
|
||||||
val filteredGames = filterGames(filter.value, games)
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -89,7 +101,7 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
|
|||||||
Text(text = "Parties jouées", fontSize = 14.sp)
|
Text(text = "Parties jouées", fontSize = 14.sp)
|
||||||
}
|
}
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text(text = "$averageSuccessRate%", fontSize = 24.sp, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
|
Text(text = "${String.format(Locale.getDefault(), "%.02f", averageSuccessRate)}%", fontSize = 24.sp, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
|
||||||
Text(text = "De réussite", fontSize = 14.sp)
|
Text(text = "De réussite", fontSize = 14.sp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,6 +110,89 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
|
|||||||
|
|
||||||
// Todo add filters
|
// Todo add filters
|
||||||
|
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
selected = true,
|
||||||
|
onClick = {
|
||||||
|
// Inverser l'ordre de tri
|
||||||
|
filter = filter.copy(dateOrderIsAscending = !filter.dateOrderIsAscending)
|
||||||
|
},
|
||||||
|
label = { Text("Date") },
|
||||||
|
leadingIcon = {
|
||||||
|
if (filter.dateOrderIsAscending) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.KeyboardArrowUp,
|
||||||
|
contentDescription = "Tri Ascendant",
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.KeyboardArrowDown,
|
||||||
|
contentDescription = "Tri Descendant",
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val gameModesLabels = context.resources.getStringArray(R.array.game_modes)
|
||||||
|
filter.mode.entries.forEachIndexed { index, entry ->
|
||||||
|
FilterChip(
|
||||||
|
selected = entry.value,
|
||||||
|
onClick = {
|
||||||
|
filter = filter.copy(mode = filter.mode.toMutableMap().apply {
|
||||||
|
this[entry.key] = !entry.value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
leadingIcon = {
|
||||||
|
if (entry.value) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Check,
|
||||||
|
contentDescription = "Filtre activé",
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
label = { Text(
|
||||||
|
gameModesLabels[index],
|
||||||
|
maxLines = 1,
|
||||||
|
) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtrer par nombre de titres
|
||||||
|
FilterChip(
|
||||||
|
selected = true,
|
||||||
|
onClick = {
|
||||||
|
// Inverser l'ordre de tri
|
||||||
|
filter = filter.copy(nbTitleIsAscending = !filter.nbTitleIsAscending)
|
||||||
|
},
|
||||||
|
label = { Text("Nombre de titres") },
|
||||||
|
leadingIcon = {
|
||||||
|
if (filter.nbTitleIsAscending) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.KeyboardArrowUp,
|
||||||
|
contentDescription = "Tri Ascendant",
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.KeyboardArrowDown,
|
||||||
|
contentDescription = "Tri Descendant",
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (filteredGames.isEmpty()) {
|
if (filteredGames.isEmpty()) {
|
||||||
Column (
|
Column (
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -110,4 +205,18 @@ fun ScoreScreen(navController: NavHostController, gameViewModel: GameViewModel)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateAverageSuccessRate(games: List<Game>): Double {
|
||||||
|
if (games.isEmpty()) return 0.0
|
||||||
|
|
||||||
|
val averageGameScore = games.sumOf {
|
||||||
|
if (it.settings.gameMode == GameMode.ALL) {
|
||||||
|
it.score.toDouble() / (it.settings.numberOfTitles!! * 2)
|
||||||
|
} else {
|
||||||
|
it.score.toDouble() / (it.settings.numberOfTitles ?: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (averageGameScore / games.size) * 100
|
||||||
}
|
}
|
||||||
@@ -82,6 +82,9 @@ fun SettingsScreen(navController: NavHostController, saveLocation: String = "set
|
|||||||
settings.value.validate(context)
|
settings.value.validate(context)
|
||||||
saveSettings(context, settings.value, saveLocation)
|
saveSettings(context, settings.value, saveLocation)
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
|
if (saveLocation != "settings") {
|
||||||
|
navController.navigate("custom_game")
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ roomPaging = "2.6.1"
|
|||||||
roomRuntime = "2.6.1"
|
roomRuntime = "2.6.1"
|
||||||
roomRuntimeVersion = "2.5.0"
|
roomRuntimeVersion = "2.5.0"
|
||||||
runtimeLivedata = "1.7.6"
|
runtimeLivedata = "1.7.6"
|
||||||
|
material = "1.12.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -54,6 +55,7 @@ androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref =
|
|||||||
room-compiler = { module = "androidx.room:room-compiler" }
|
room-compiler = { module = "androidx.room:room-compiler" }
|
||||||
room-ktx = { module = "androidx.room:room-ktx" }
|
room-ktx = { module = "androidx.room:room-ktx" }
|
||||||
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
|
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
|
||||||
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user