Compare commits

..

No commits in common. '85f3c8ce5e22b05976b110aaf2e1cabab6c36225' and 'b292ad92edd9db83a48e9d2937179ddb6a009a04' have entirely different histories.

@ -3,14 +3,16 @@ package but.androidstudio.tetris
import android.content.Context import android.content.Context
import android.hardware.Sensor import android.hardware.Sensor
import android.hardware.SensorManager import android.hardware.SensorManager
import android.media.MediaPlayer
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.TextView import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import modele.Game import modele.Game
import views.ViewsGame import views.ViewsGame
@ -39,22 +41,17 @@ class GameFragment : Fragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// La vue se refresh quand on fait quelque chose dessus (changer un parametre )
viewGame = view.findViewById<ViewsGame>(R.id.tabGame) viewGame = view.findViewById<ViewsGame>(R.id.tabGame)
viewGame.nbCaseHauteur = heightGame viewGame.nbCaseHauteur = heightGame
viewGame.nbCaseLargeur = withGame viewGame.nbCaseLargeur = withGame
val sensorManager = activity?.getSystemService(Context.SENSOR_SERVICE) as SensorManager modeleGame = Game(height = heightGame, width = withGame, viewGame = viewGame)
val sensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
val mediaPlayer = MediaPlayer.create(context,R.raw.tetris)
val buttonRight:Button = view.findViewById(R.id.Button_Right) val buttonRight:Button = view.findViewById(R.id.Button_Right)
val buttonLeft:Button = view.findViewById(R.id.Button_Left) val buttonLeft:Button = view.findViewById(R.id.Button_Left)
val buttonRotateRight:Button = view.findViewById(R.id.Button_Right_Rotation) val buttonRotateRight:Button = view.findViewById(R.id.Button_Right_Rotation)
val buttonRotateLeft:Button = view.findViewById(R.id.Button_Left_Rotation) val buttonRotateLeft:Button = view.findViewById(R.id.Button_Left_Rotation)
val points:TextView = view.findViewById(R.id.Id_Points)
modeleGame = Game(height = heightGame, width = withGame, viewGame = viewGame, points = points)
buttonRight.setOnClickListener { buttonRight.setOnClickListener {
modeleGame.dashBoard.moveRight(modeleGame.currentShape) modeleGame.dashBoard.moveRight(modeleGame.currentShape)
} }
@ -68,8 +65,7 @@ class GameFragment : Fragment(){
modeleGame.dashBoard.rotateShapeLeft(modeleGame.currentShape) modeleGame.dashBoard.rotateShapeLeft(modeleGame.currentShape)
} }
mediaPlayer.start()
modeleGame.startGame() modeleGame.startGame()
} }
} }

@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -45,14 +44,6 @@ class InfoUserFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val buttonBack: Button = view.findViewById(R.id.backButton)
buttonBack.setOnClickListener {
val fragmentManager = requireActivity().supportFragmentManager
fragmentManager.beginTransaction()
.replace(R.id.homeLayout, MainFragment())
.commit()
}
// Read the SSL certificate for the request // Read the SSL certificate for the request
val certificateStream : InputStream = resources.openRawResource(R.raw.sni_cloudflaressl_com) val certificateStream : InputStream = resources.openRawResource(R.raw.sni_cloudflaressl_com)

@ -7,7 +7,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.Spinner import android.widget.Spinner
class OptionFragment : Fragment() { class OptionFragment : Fragment() {
@ -42,13 +41,5 @@ class OptionFragment : Fragment() {
// Ne rien faire si aucun élément n'est sélectionné // Ne rien faire si aucun élément n'est sélectionné
} }
})*/ })*/
val buttonRetour: Button = view.findViewById(R.id.backButton)
buttonRetour.setOnClickListener {
val fragmentManager = requireActivity().supportFragmentManager
fragmentManager.beginTransaction()
.replace(R.id.homeLayout, MainFragment())
.commit()
}
} }
} }

@ -1,13 +1,16 @@
package modele package modele
import android.util.Log
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import views.ViewsGame import views.ViewsGame
import kotlin.io.path.fileVisitor import kotlin.random.Random
class DashBoard(private val width: Int,private val height: Int,private val view: ViewsGame) { class DashBoard(private val width: Int,private val height: Int,private val view: ViewsGame) {
val gridOfGame = Array(this.height) { IntArray(this.width) } val gridOfGame = Array(this.height) { IntArray(this.width) }
// To set something to occupied // To set something to occupied
fun toOccupied(col: Int, row: Int, value: Int) { fun toOccupied(col: Int, row: Int, value: Int) {
gridOfGame[row][col] = value gridOfGame[row][col] = value
} }
@ -15,7 +18,7 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
// To check if an position is occupied // To check if an position is occupied
fun isOccupied(col: Int, row: Int): Boolean = gridOfGame[row][col] != 0 fun isOccupied(col: Int, row: Int): Boolean = gridOfGame[row][col] != 0
private fun isLineFull(row: Int): Boolean { fun isLineFull(row: Int): Boolean {
for (col in 0 until this.width) { for (col in 0 until this.width) {
if (gridOfGame[row][col] == 0) { if (gridOfGame[row][col] == 0) {
return false return false
@ -24,48 +27,48 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
return true return true
} }
private fun clearLine(row: Int) { fun clearLine(row: Int) {
for (col in 0 until this.width) { for (col in 0 until this.width) {
gridOfGame[row][col] = 0 gridOfGame[row][col] = 0
} }
} }
private fun shiftDown(listeLine:MutableList<Int>) { fun shiftDown(rowToBegin: Int) {
for (row in (height - 1)..rowToBegin) {
println("Shift Down") for (col in 0 until this.width) {
for( index in listeLine){ gridOfGame[row][col] = gridOfGame[row - rowToBegin][col]
println(index)
for ( line in index downTo 1 ){
for ( column in 0 until width){
gridOfGame[line][column] = gridOfGame[line-1][column]
} }
} }
for (row in 0 until rowToBegin) {
for (col in 0 until this.width) {
gridOfGame[row][col] = 0
}
} }
updateViewGame()
} }
//To check each grid line and remove if a line is full. Uses clearLine(), isLineFull() and shiftDown() //To check each grid line and remove if a line is full. Uses clearLine(), isLineFull() and shiftDown()
fun clearLines():Int{ fun clearLines() {
val listeLine = mutableListOf<Int>()
var nbRowCleared: Int = 0 var nbRowCleared: Int = 0
for (row in 0 until this.height) { for (row in 0 until this.height) {
if (isLineFull(row)) { if (isLineFull(row)) {
clearLine(row) clearLine(row)
++nbRowCleared ++nbRowCleared
listeLine.add(row)
} }
} }
if (nbRowCleared != 0) { if (nbRowCleared != 0) {
shiftDown(listeLine) shiftDown(nbRowCleared)
} }
return nbRowCleared
} }
fun addShape(shape: Shape): Boolean { fun addShape(shape: Shape): Boolean {
for (line in 0..3) { // On vérifie que l'espace est disponible for (line in 0..3) { // On vérifie que l'espace est disponible
for (column in 0..3) { for (column in 0..3) {
if ( (shape.typeShape.showShape[line][column] == 1) and (gridOfGame[shape.position.y+line][shape.position.x+column] != 0)){ if ((isOccupied(
shape.position.x + column,
shape.position.y + line
)) and (shape.typeShape.showShape[line][column] == 1)
) {
return false return false
} }
} }
@ -109,6 +112,9 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
val pos:MutableList<Position> = shape.sharePositionLeft() val pos:MutableList<Position> = shape.sharePositionLeft()
for ( position in pos){ for ( position in pos){
println("X -> : "+position.x)
println("X -> : "+position.y)
if ( shape.position.x < 0 ){ if ( shape.position.x < 0 ){
return false return false
} }
@ -134,7 +140,7 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
} }
private fun moveDownPossible(shape: Shape):Boolean{ private fun moveDownPossible(shape: Shape):Boolean{
val pos:MutableList<Position> = shape.sharePositionDown(shape) val pos:MutableList<Position> = shape.sharePositionRight()
for ( position in pos){ for ( position in pos){
if ( position.y+shape.position.y >= height){ if ( position.y+shape.position.y >= height){
@ -170,11 +176,8 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
for ( line in 0..3){ for ( line in 0..3){
for ( column in 0..3){ for ( column in 0..3){
if ( matrix[line][column] == 1){ if ( (matrix[line][column] == 1) and (shape.typeShape.showShape[line][column] == 0 )){
if ( (shape.position.y+line >= height) or (shape.position.x+column >= width)){ if ( gridOfGame[shape.position.y+column][shape.position.x+line] != 0 ) {
return false
}
if (gridOfGame[shape.position.y+line][shape.position.x+column] != 0 ){
return false return false
} }
} }
@ -187,7 +190,6 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
fun rotateShapeRight(shape: Shape){ fun rotateShapeRight(shape: Shape){
println("Shape action -> Rotation right ! ") println("Shape action -> Rotation right ! ")
deleteShape(shape) deleteShape(shape)
rotationShapePossible(shape,0) rotationShapePossible(shape,0)
writeShape(shape) writeShape(shape)
@ -196,7 +198,6 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
fun rotateShapeLeft(shape: Shape){ fun rotateShapeLeft(shape: Shape){
println("Shape action -> Rotation left ! ") println("Shape action -> Rotation left ! ")
deleteShape(shape) deleteShape(shape)
rotationShapePossible(shape,1) rotationShapePossible(shape,1)
writeShape(shape) writeShape(shape)
@ -217,6 +218,8 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
// Write the shape in gridOfGame // Write the shape in gridOfGame
private fun writeShape(shape: Shape){ private fun writeShape(shape: Shape){
println("Final X : "+shape.position.x)
println("Final Y : "+shape.position.y)
for (line in 0..3) { for (line in 0..3) {
for (column in 0..3) { for (column in 0..3) {
@ -235,12 +238,8 @@ class DashBoard(private val width: Int,private val height: Int,private val view:
view.invalidate() view.invalidate()
} }
suspend fun fallingShape(shape: Shape, difficulty: Difficulty):Boolean{ suspend fun fallingShape(shape: Shape){
when(difficulty){ delay(800)
Difficulty.EASY -> delay(700) moveDown(shape)
Difficulty.MEDIUM -> delay(500)
Difficulty.HARD -> delay(300)
}
return moveDown(shape)
} }
} }

@ -1,70 +1,54 @@
package modele package modele
import android.widget.TextView import android.util.Log
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import views.ViewsGame import views.ViewsGame
import kotlin.random.Random
class Game(private val width: Int,private val height: Int,private val viewGame:ViewsGame,private val points:TextView) { class Game(private val width: Int,private val height: Int,private val viewGame:ViewsGame) {
val dashBoard: DashBoard = DashBoard(width,height,viewGame) val dashBoard: DashBoard = DashBoard(width,height,viewGame)
lateinit var currentShape: Shape lateinit var currentShape: Shape
private var difficulty: Difficulty = Difficulty.EASY var difficulty: Difficulty = Difficulty.EASY
//To get the next shape
private fun getNextShape(): TypeShape {
return when(Random.nextInt(1,7)){
1 -> TypeShape(EnumTypeShape.IShape)
2 -> TypeShape(EnumTypeShape.SquareShape)
3 -> TypeShape(EnumTypeShape.JShape)
4 -> TypeShape(EnumTypeShape.LShape)
5 -> TypeShape(EnumTypeShape.SShape)
6 -> TypeShape(EnumTypeShape.TShape)
7 -> TypeShape(EnumTypeShape.ZShape)
else -> throw Exception("Problème de random getNextShape()")
}
}
// The start game function // The start game function
fun startGame(){ fun startGame(){
//this.setCurrentShape(TypeShape.SquareShape)
currentShape = Shape(TypeShape(EnumTypeShape.SShape),Position(1,1))
dashBoard.addShape(currentShape)
currentShape = Shape(getNextShape(),Position(width/2,0)) currentShape = Shape(TypeShape(EnumTypeShape.IShape),Position(2,5))
dashBoard.addShape(currentShape) dashBoard.addShape(currentShape)
//currentShape = Shape(TypeShape(EnumTypeShape.SquareShape),Position(2,6))
//dashBoard.addShape(currentShape)
//currentShape = Shape(TypeShape(EnumTypeShape.TShape),Position(2,8))
//dashBoard.addShape(currentShape)
//currentShape = Shape(TypeShape(EnumTypeShape.ZShape),Position(2,10))
//dashBoard.addShape(currentShape)
//currentShape = Shape(TypeShape(EnumTypeShape.SShape),Position(1,1))
//dashBoard.addShape(currentShape)
//currentShape = Shape(TypeShape(EnumTypeShape.JShape),Position(1,1))
//dashBoard.addShape(currentShape)
//dashBoard.gridOfGame[2][0] = 5
println("RUN !!") println("RUN !!")
dashBoard.updateViewGame() dashBoard.updateViewGame()
// Ne pas utiliser de global scope !!!! SA ENLEVE DES POINTS !!!!!!
GlobalScope.launch { GlobalScope.launch {
while(true){ while (true){
if(dashBoard.fallingShape(currentShape,difficulty)){ dashBoard.fallingShape(currentShape)
// Clear line
val nbLine = dashBoard.clearLines()
// Score
if ( (nbLine > 0) and (nbLine<4)){
val tmpPoints:String = points.text as String
points.text = (tmpPoints.toInt()+(nbLine*100)).toString()
}
if ( nbLine == 4 ){
val tmpPoints:String = points.text as String
points.text = (tmpPoints.toInt()+1200).toString()
}
}
else {
// New shape
currentShape = Shape(getNextShape(),Position(width/2,0))
if ( !dashBoard.addShape(currentShape)){
break
}
}
} }
println("Game end !!")
} }
} }
} }

@ -1,6 +1,8 @@
package modele package modele
class Shape(val typeShape: TypeShape, var position: Position) { import java.lang.reflect.Type
class Shape(val typeShape: TypeShape,var position: Position) {
fun sharePositionRight():MutableList<Position>{ fun sharePositionRight():MutableList<Position>{
val sharePosition = mutableListOf<Position>() val sharePosition = mutableListOf<Position>()
for( line in 0..3){ for( line in 0..3){
@ -49,6 +51,7 @@ class Shape(val typeShape: TypeShape, var position: Position) {
} }
} }
} }
return sharePosition return sharePosition
} }
@ -66,10 +69,10 @@ class Shape(val typeShape: TypeShape, var position: Position) {
} }
} }
} }
return sharePosition return sharePosition
} }
fun sharePositionRotationRight(shape: Shape):Array<IntArray>{ fun sharePositionRotationRight(shape: Shape):Array<IntArray>{
var leftmostCol = 4 var leftmostCol = 4
val rotatedMatrix = Array(4) { row -> val rotatedMatrix = Array(4) { row ->
@ -100,9 +103,8 @@ class Shape(val typeShape: TypeShape, var position: Position) {
return rotatedMatrix return rotatedMatrix
} }
fun sharePositionRotationLeft(shape: Shape):Array<IntArray> { fun sharePositionRotationLeft(shape: Shape):Array<IntArray>{
var leftMostCol = 4 var leftmostCol = 4
var topMostLine = 4
// Pivoter la matrice // Pivoter la matrice
val rotatedMatrix = Array(4) { row -> val rotatedMatrix = Array(4) { row ->
IntArray(4) { col -> IntArray(4) { col ->
@ -112,28 +114,23 @@ class Shape(val typeShape: TypeShape, var position: Position) {
// Trouver l'index de la colonne la plus à gauche // Trouver l'index de la colonne la plus à gauche
for (row in 0 until 4) { for (row in 0 until 4) {
for (col in 0 until 4) { for (col in 0 until 4) {
if (rotatedMatrix[row][col] != 0 && col < leftMostCol) { if (rotatedMatrix[row][col] != 0 && col < leftmostCol) {
leftMostCol = col leftmostCol = col
}
if ( rotatedMatrix[row][col] != 0 && row < topMostLine){
topMostLine = row
} }
} }
} }
// Décaler chaque ligne de la matrice vers la gauche // Décaler chaque ligne de la matrice vers la gauche
for (col in 0 until 4) { for (row in 0 until 4) {
for (line in 0 until topMostLine) { for (col in 0 until leftmostCol) {
rotatedMatrix[line][col] = 0 rotatedMatrix[row][col] = 0
} }
for (line in topMostLine until 4) { for (col in leftmostCol until 4) {
rotatedMatrix[line - topMostLine][col] = rotatedMatrix[line][col] rotatedMatrix[row][col - leftmostCol] = rotatedMatrix[row][col]
if ( line - topMostLine != line){ if ( col - leftmostCol != col){
rotatedMatrix[line][col] = 0 rotatedMatrix[row][col] = 0
} }
} }
} }
return rotatedMatrix return rotatedMatrix
} }
} }

@ -3,8 +3,9 @@ package modele
import java.lang.reflect.Type import java.lang.reflect.Type
import kotlin.random.Random import kotlin.random.Random
class TypeShape(private val type: EnumTypeShape){ class TypeShape(val type: EnumTypeShape){
private var currentIndex = 0
var showShape:Array<IntArray> = Array(4){ IntArray(4 ) { 0 } } var showShape:Array<IntArray> = Array(4){ IntArray(4 ) { 0 } }
var couleur:Int = 0 var couleur:Int = 0
@ -63,4 +64,45 @@ class TypeShape(private val type: EnumTypeShape){
} }
} }
} }
/*
fun getPoints(): Array<Position>{
val positions = ArrayList<Position>()
val typeShape = getCurrentType()
for (i in typeShape.indices){
for (j in typeShape[i].indices){
if (typeShape[i][j] != 0) {
positions.add(Position(j, i))
}
}
}
return positions.toTypedArray()
}
//to augmente of 1 the current index
fun getNextType(): TypeShape {
currentIndex++
return this
}
//To get the next shape
fun getNextShape(){
val random: Int = Random.nextInt(1,7)
when(random){
1 -> TypeShape(EnumTypeShape.IShape, arrayOf(arrayOf(arrayOf())))
2 -> TypeShape(EnumTypeShape.SquareShape, arrayOf(arrayOf(arrayOf())))
3 -> TypeShape(EnumTypeShape.JShape, arrayOf(arrayOf(arrayOf())))
4 -> TypeShape(EnumTypeShape.LShape, arrayOf(arrayOf(arrayOf())))
5 -> TypeShape(EnumTypeShape.SShape, arrayOf(arrayOf(arrayOf())))
6 -> TypeShape(EnumTypeShape.TShape, arrayOf(arrayOf(arrayOf())))
7 -> TypeShape(EnumTypeShape.ZShape, arrayOf(arrayOf(arrayOf())))
else -> throw Exception("Problème de random getNextShape()")
}
}
// To get the table of position of the current shape type
private fun getCurrentType(): Array<Array<Int>> {
return showShape[currentIndex]
}*/
} }

@ -38,32 +38,12 @@
android:background="@drawable/flechegauche" android:background="@drawable/flechegauche"
android:layout_marginStart="15dp"/> android:layout_marginStart="15dp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="50dp">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/points" />
<TextView
android:id="@+id/Id_Points"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/_0" />
</LinearLayout>
<Button <Button
android:id="@+id/Button_Right" android:id="@+id/Button_Right"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="100dp" android:layout_height="100dp"
android:background="@drawable/flechedroite" android:background="@drawable/flechedroite"
android:layout_marginStart="50dp"/> android:layout_marginStart="150dp"/>
</LinearLayout> </LinearLayout>

@ -2,13 +2,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<Button
android:id="@+id/backButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Retour"
android:layout_margin="2mm"
/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

@ -7,8 +7,7 @@
tools:context=".OptionFragment"> tools:context=".OptionFragment">
<LinearLayout <LinearLayout
android:layout_gravity="center" android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -18,26 +17,24 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5"> app:layout_constraintVertical_bias="0.5">
<TextView <Button
android:id="@+id/backButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:text="Retour"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Difficulty"/> android:text="Difficulty"/>
<Spinner <Spinner
android:id="@+id/spinnerDifficulty" android:id="@+id/spinnerDifficulty"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="1mm" android:padding="1mm"
/> />
<Button
android:id="@+id/backButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Retour"
android:layout_margin="2mm"
/>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

@ -7,6 +7,4 @@
<string name="options">Options</string> <string name="options">Options</string>
<string name="best_score">Best Score :</string> <string name="best_score">Best Score :</string>
<string name="x">X</string> <string name="x">X</string>
<string name="points">Points :</string>
<string name="_0">0</string>
</resources> </resources>
Loading…
Cancel
Save