Compare commits

..

No commits in common. "master" and "2024-04-11" have entirely different histories.

13 changed files with 115 additions and 253 deletions

View File

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

View File

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

View File

@ -1,8 +1,6 @@
package it.edu.cassandroferminervi.flowschool.remote package it.edu.cassandroferminervi.flowschool.remote
import it.edu.cassandroferminervi.flowschool.remote.dto.LoginResult 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.Response
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded import retrofit2.http.FormUrlEncoded
@ -11,26 +9,5 @@ import retrofit2.http.POST
interface ApiService { interface ApiService {
@FormUrlEncoded @FormUrlEncoded
@POST("login.php") @POST("login.php")
suspend fun postLogin( suspend fun postLogin(@Field("username") code: String, @Field("password") password: String): Response<LoginResult>
@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

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

View File

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

View File

@ -1,18 +1,12 @@
package it.edu.cassandroferminervi.flowschool.screens package it.edu.cassandroferminervi.flowschool.screens
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button 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.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -30,79 +24,18 @@ fun HomeScreen(navigator: DestinationsNavigator, token: String) {
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 50.dp)
) { ) {
ElevatedCard( Button(onClick = {
elevation = CardDefaults.cardElevation( navigator.navigate(PermissionScreenDestination(token))
defaultElevation = 6.dp }, modifier = Modifier.align(Alignment.CenterHorizontally)) {
), Text("Scannerizza codice QR")
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)) Spacer(modifier = Modifier.height(8.dp))
ElevatedCard( Button(onClick = {
elevation = CardDefaults.cardElevation( navigator.navigate(SearchingScreenDestination(token))
defaultElevation = 6.dp }, modifier = Modifier.align(Alignment.CenterHorizontally)) {
), Text("Cerca professore")
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

@ -90,8 +90,8 @@ fun LoginScreen(navigator: DestinationsNavigator) {
val description = if (passwordVisible) "Nascondi password" else "Mostra password" val description = if (passwordVisible) "Nascondi password" else "Mostra password"
IconButton(onClick = { passwordVisible = !passwordVisible }) { IconButton(onClick = {passwordVisible = !passwordVisible}){
Icon(imageVector = image, description) Icon(imageVector = image, description)
} }
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()

View File

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

View File

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

View File

@ -1,59 +1,23 @@
package it.edu.cassandroferminervi.flowschool.screens package it.edu.cassandroferminervi.flowschool.screens
import android.widget.Toast
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.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.ramcosta.composedestinations.annotation.Destination 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 @Destination
@Composable @Composable
fun TeacherScreen(token: String, code: Int) { fun TeacherScreen(codice: String) {
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( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Text("Nome: ${resBody.nome}") Text("Codice prof: $codice")
Text("Cognome: ${resBody.cognome}") Text("Presente: si")
Text("Email: ${resBody.email}")
} }
} }

View File

@ -9,14 +9,8 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage 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, private val navigator: DestinationsNavigator, val token: String) : ImageAnalysis.Analyzer { class QrAnalyzer(private val context: Context) : ImageAnalysis.Analyzer {
private val options = BarcodeScannerOptions.Builder() private val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE) .setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build() .build()
@ -35,18 +29,7 @@ class QrAnalyzer(private val context: Context, private val navigator: Destinatio
barcode?.takeIf { it.isNotEmpty() } barcode?.takeIf { it.isNotEmpty() }
?.mapNotNull { it.rawValue } ?.mapNotNull { it.rawValue }
?.joinToString(",") ?.joinToString(",")
?.let { ?.let { Toast.makeText(context, it, Toast.LENGTH_SHORT).show() }
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 { }.addOnCompleteListener {
imageProxy.close() imageProxy.close()
} }

View File

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