Compare commits

...

10 Commits
ui ... master

Author SHA1 Message Date
Mariano Riefolo
30ab8e2fe9 Improved menu interface 2024-05-03 22:28:31 +02:00
Mariano Riefolo
cf9e8b483c Removed toast showing the code retrieved from the qrcode 2024-04-14 12:22:09 +02:00
RIEFOLO MARIANO
4914bc3666 Connection to qrcode api 2024-04-13 10:58:44 +02:00
Mariano Riefolo
adaf82d7af Added connection to teachers information api, formatted code, updated gradle 2024-04-12 22:50:24 +02:00
Mariano Riefolo
5d09a7fc7b Fixed login when in release mode, activated isCrunchPngs and updated gradle 2024-04-11 15:49:11 +02:00
Mariano Riefolo
7704631661 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	app/build.gradle.kts
2024-04-05 22:49:58 +02:00
Mariano Riefolo
2d59ab8782 Added connection to the api (login only) 2024-04-05 22:44:42 +02:00
Mariano Riefolo
0d8c431448 Added connection to the api (login only) 2024-04-05 22:33:29 +02:00
Mariano Riefolo
7067cf5072 Merge 2024-03-02 14:54:18 +01:00
Mariano Riefolo
342f2f9fce Default code 2024-02-21 16:07:08 +01:00
20 changed files with 543 additions and 127 deletions

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="app">
<State />
</entry>
</value>
</component>
</project>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -26,7 +26,7 @@ android {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
isCrunchPngs = false
isCrunchPngs = true
}
}
compileOptions {
@ -54,7 +54,7 @@ dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2024.02.01"))
implementation(platform("androidx.compose:compose-bom:2024.04.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
@ -62,7 +62,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.02.01"))
androidTestImplementation(platform("androidx.compose:compose-bom:2024.04.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
@ -71,11 +71,16 @@ dependencies {
implementation("io.github.raamcosta.compose-destinations:core:1.0.2-beta")
ksp("io.github.raamcosta.compose-destinations:ksp:1.0.2-beta")
implementation("androidx.compose.material:material-icons-extended:1.6.2")
implementation("androidx.compose.material:material-icons-extended:1.6.5")
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
implementation("androidx.camera:camera-camera2:1.3.1")
implementation("androidx.camera:camera-lifecycle:1.3.1")
implementation("androidx.camera:camera-view:1.3.1")
implementation("androidx.camera:camera-camera2:1.3.2")
implementation("androidx.camera:camera-lifecycle:1.3.2")
implementation("androidx.camera:camera-view:1.3.2")
implementation("com.google.mlkit:barcode-scanning:17.2.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.12")
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12")
}

View File

@ -6,6 +6,8 @@
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -0,0 +1,36 @@
package it.edu.cassandroferminervi.flowschool.remote
import it.edu.cassandroferminervi.flowschool.remote.dto.LoginResult
import it.edu.cassandroferminervi.flowschool.remote.dto.PresenceResult
import it.edu.cassandroferminervi.flowschool.remote.dto.TeacherInfoResult
import retrofit2.Response
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface ApiService {
@FormUrlEncoded
@POST("login.php")
suspend fun postLogin(
@Field("username") code: String,
@Field("password") password: String
): Response<LoginResult>
@FormUrlEncoded
@POST("presenza.php")
suspend fun postPresence(
@Field("token") token: String,
@Field("codice") qrcode: String
): Response<PresenceResult>
@FormUrlEncoded
@POST("lista.php")
suspend fun postTeacherList(@Field("token") token: String): Response<List<TeacherInfoResult>>
@FormUrlEncoded
@POST("ottieni_prof.php")
suspend fun postTeacherInfo(
@Field("token") token: String,
@Field("profId") teacherId: Int
): Response<TeacherInfoResult>
}

View File

@ -0,0 +1,23 @@
package it.edu.cassandroferminervi.flowschool.remote
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitInstance {
private val client = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.build()
val api: ApiService by lazy {
Retrofit.Builder()
.baseUrl("https://flowschool.riefolo.me/api/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(ApiService::class.java)
}
}

View File

@ -0,0 +1,6 @@
package it.edu.cassandroferminervi.flowschool.remote.dto
import androidx.annotation.Keep
@Keep
data class LoginResult(val token: String?, val error: String?)

View File

@ -0,0 +1,6 @@
package it.edu.cassandroferminervi.flowschool.remote.dto
import androidx.annotation.Keep
@Keep
data class PresenceResult(val success: Boolean)

View File

@ -0,0 +1,11 @@
package it.edu.cassandroferminervi.flowschool.remote.dto
import androidx.annotation.Keep
@Keep
data class TeacherInfoResult(
val codice: Int,
val nome: String,
val cognome: String,
val email: String?
)

View File

@ -14,10 +14,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import it.edu.cassandroferminervi.flowschool.util.QrAnalyzer
@Composable
fun CameraScreen() {
fun CameraScreen(navigator: DestinationsNavigator, token: String) {
val localContext = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val cameraProviderFuture = remember {
@ -37,7 +38,7 @@ fun CameraScreen() {
val imageAnalysis = ImageAnalysis.Builder().build()
imageAnalysis.setAnalyzer(
ContextCompat.getMainExecutor(context),
QrAnalyzer(context)
QrAnalyzer(context, navigator, token)
)
runCatching {

View File

@ -1,12 +1,18 @@
package it.edu.cassandroferminervi.flowschool.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -19,23 +25,84 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@Destination
@Composable
fun HomeScreen(navigator: DestinationsNavigator) {
fun HomeScreen(navigator: DestinationsNavigator, token: String) {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 50.dp)
) {
Button(onClick = {
navigator.navigate(PermissionScreenDestination)
}, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Text("Scannerizza codice QR")
ElevatedCard(
elevation = CardDefaults.cardElevation(
defaultElevation = 6.dp
),
modifier = Modifier
.fillMaxHeight(0.5F)
.fillMaxWidth()
.padding(25.dp)
) {
Text(
"Scannerizza codice QR",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.padding(16.dp)
)
Text(
"Scannerizzando il codice QR generato dinamicamente dal dispositivo situato nella scuola ti permette di segnare la presenza in modo efficiente.",
modifier = Modifier
.padding(16.dp)
)
Box(
modifier = Modifier
.fillMaxSize()
) {
Button(
onClick = {
navigator.navigate(PermissionScreenDestination(token))
}, modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp)
) {
Text("Prosegui")
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = {
navigator.navigate(SearchingScreenDestination)
}, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Text("Cerca professore")
ElevatedCard(
elevation = CardDefaults.cardElevation(
defaultElevation = 6.dp
),
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(25.dp)
) {
Text(
"Cerca professore",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.padding(16.dp)
)
Text(
"Cerca il professore sul quale vuoi ottenere informazioni come l'orario di entrata, l'orario di uscita e la classe in cui presiede attualmente.",
modifier = Modifier
.padding(16.dp)
)
Box(
modifier = Modifier
.fillMaxSize()
) {
Button(
onClick = {
navigator.navigate(SearchingScreenDestination(token))
}, modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp)
) {
Text("Prosegui")
}
}
}
}
}

View File

@ -1,5 +1,6 @@
package it.edu.cassandroferminervi.flowschool.screens
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -23,10 +24,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
@ -36,6 +39,9 @@ import com.ramcosta.composedestinations.HomeScreenDestination
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import it.edu.cassandroferminervi.flowschool.R
import it.edu.cassandroferminervi.flowschool.remote.RetrofitInstance
import kotlinx.coroutines.launch
@Destination(start = true)
@Composable
@ -44,6 +50,9 @@ fun LoginScreen(navigator: DestinationsNavigator) {
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
@ -62,7 +71,7 @@ fun LoginScreen(navigator: DestinationsNavigator) {
TextField(
value = code,
onValueChange = { code = it },
label = { Text("Codice") },
label = { Text("Username") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
@ -89,7 +98,17 @@ fun LoginScreen(navigator: DestinationsNavigator) {
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = {
navigator.navigate(HomeScreenDestination)
coroutineScope.launch {
val response = RetrofitInstance.api.postLogin(code, password)
val loginResult = response.body()
if (!response.isSuccessful || loginResult == null)
Toast.makeText(context, "Errore del server", Toast.LENGTH_LONG).show()
else if (!loginResult.token.isNullOrEmpty() && loginResult.error.isNullOrEmpty())
navigator.navigate(HomeScreenDestination(loginResult.token))
else
Toast.makeText(context, loginResult.error, Toast.LENGTH_LONG).show()
}
}, modifier = Modifier.align(Alignment.End)) {
Text("Accedi")
}

View File

@ -12,15 +12,16 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@OptIn(ExperimentalPermissionsApi::class)
@Destination
@Composable
fun PermissionScreen() {
fun PermissionScreen(navigator: DestinationsNavigator, token: String) {
val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
if (cameraPermissionState.status.isGranted) {
CameraScreen()
CameraScreen(navigator, token)
} else {
Box(
contentAlignment = Alignment.Center,

View File

@ -1,129 +1,104 @@
package it.edu.cassandroferminervi.flowschool.screens
import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.ramcosta.composedestinations.TeacherScreenDestination
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import it.edu.cassandroferminervi.flowschool.remote.RetrofitInstance
import it.edu.cassandroferminervi.flowschool.remote.dto.TeacherInfoResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class)
@Destination
@Composable
fun SearchingScreen(navigator: DestinationsNavigator) {
val teachers = listOf(
"Mario Rossi",
"Michele Verdi",
"Fabio Bianchi"
).groupBy {
it.first()
}.toSortedMap()
.map {
Category(
it.key.toString(),
items = it.value
)
fun SearchingScreen(
navigator: DestinationsNavigator,
token: String,
) {
val context = LocalContext.current
var professorList by remember { mutableStateOf<List<TeacherInfoResult>>(emptyList()) }
var searchText by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
val response = withContext(Dispatchers.IO) {
RetrofitInstance.api.postTeacherList(token)
}
var filter by remember { mutableStateOf("") }
if (response.isSuccessful) {
professorList = response.body() ?: emptyList()
} else {
Toast.makeText(context, "Errore nel server", Toast.LENGTH_SHORT).show()
}
}
val groupedByLetter = professorList.sortedWith(compareBy({ it.cognome }, { it.nome }))
.groupBy { it.cognome.first().uppercaseChar() }
Column {
TextField(
value = filter,
onValueChange = { filter = it },
label = { Text("Cerca") },
singleLine = true,
OutlinedTextField(
value = searchText,
onValueChange = { searchText = it },
label = { Text("Cerca professore") },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
CategorizedLazyColumn(
categories = teachers,
navigator = navigator,
filter = filter
)
}
}
data class Category(
val name: String,
val items: List<String>
)
@Composable
private fun CategoryHeader(
text: String,
modifier: Modifier = Modifier
) {
Text(
text = text,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(16.dp)
)
}
@Composable
private fun CategoryItem(
text: String,
navigator: DestinationsNavigator,
modifier: Modifier = Modifier
) {
TextButton(onClick = {
navigator.navigate(TeacherScreenDestination("test"))
}) {
Text(
text = text,
fontSize = 14.sp,
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun CategorizedLazyColumn(
categories: List<Category>,
navigator: DestinationsNavigator,
filter: String,
modifier: Modifier = Modifier
) {
LazyColumn(modifier) {
categories.forEach { category ->
if (category.items.any {
it.contains(filter, ignoreCase = true)
}) {
LazyColumn {
groupedByLetter.forEach { (letter, professors) ->
stickyHeader {
CategoryHeader(category.name)
Text(
text = letter.toString(),
style = TextStyle(fontWeight = FontWeight.Bold),
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray)
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
items(professors.filter {
it.nome.contains(searchText, ignoreCase = true) ||
it.cognome.contains(searchText, ignoreCase = true)
}) { professor ->
ProfessorItem(navigator = navigator, token = token, professor = professor)
}
items(items = category.items.filter {
it.contains(filter, ignoreCase = true)
}) { text ->
CategoryItem(text, navigator)
}
}
}
}
@Composable
fun ProfessorItem(navigator: DestinationsNavigator, token: String, professor: TeacherInfoResult) {
Column(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = {
navigator.navigate(TeacherScreenDestination(token, professor.codice))
})
.padding(16.dp)
) {
Text(text = "${professor.nome} ${professor.cognome}")
}
}

View File

@ -1,23 +1,59 @@
package it.edu.cassandroferminervi.flowschool.screens
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.ramcosta.composedestinations.annotation.Destination
import it.edu.cassandroferminervi.flowschool.remote.RetrofitInstance
import it.edu.cassandroferminervi.flowschool.remote.dto.TeacherInfoResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Destination
@Composable
fun TeacherScreen(codice: String) {
fun TeacherScreen(token: String, code: Int) {
val context = LocalContext.current
var resBody by remember {
mutableStateOf(
TeacherInfoResult(
code,
"err",
"err",
null
)
)
}
LaunchedEffect(Unit) {
val response = withContext(Dispatchers.IO) {
RetrofitInstance.api.postTeacherInfo(token, code)
}
if (response.isSuccessful && response.body() != null) {
resBody = response.body()!!
} else {
Toast.makeText(context, "Errore nel server", Toast.LENGTH_SHORT).show()
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
Text("Codice prof: $codice")
Text("Presente: si")
Text("Nome: ${resBody.nome}")
Text("Cognome: ${resBody.cognome}")
Text("Email: ${resBody.email}")
}
}

View File

@ -9,8 +9,14 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import com.ramcosta.composedestinations.HomeScreenDestination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import it.edu.cassandroferminervi.flowschool.remote.RetrofitInstance
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class QrAnalyzer(private val context: Context) : ImageAnalysis.Analyzer {
class QrAnalyzer(private val context: Context, private val navigator: DestinationsNavigator, val token: String) : ImageAnalysis.Analyzer {
private val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
@ -29,7 +35,18 @@ class QrAnalyzer(private val context: Context) : ImageAnalysis.Analyzer {
barcode?.takeIf { it.isNotEmpty() }
?.mapNotNull { it.rawValue }
?.joinToString(",")
?.let { Toast.makeText(context, it, Toast.LENGTH_SHORT).show() }
?.let {
CoroutineScope(Dispatchers.Main).launch {
val response = RetrofitInstance.api.postPresence(token, it)
val resBody = response.body()
if (resBody != null && resBody.success) {
navigator.navigate(HomeScreenDestination(token))
} else {
Toast.makeText(context, "Errore", Toast.LENGTH_LONG).show()
}
}
}
}.addOnCompleteListener {
imageProxy.close()
}

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -1,5 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.3.0" apply false
id("com.android.application") version "8.3.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
}