Compare commits

..

No commits in common. "master" and "2024-03-01" have entirely different histories.

45 changed files with 54 additions and 703 deletions

46
.gitignore vendored
View File

@ -1,33 +1,15 @@
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
# IntelliJ
*.iml *.iml
.idea/ .gradle
misc.xml /local.properties
deploymentTargetDropDown.xml /.idea/caches
render.experimental.xml /.idea/libraries
/.idea/modules.xml
# Keystore files /.idea/workspace.xml
*.jks /.idea/navEditor.xml
*.keystore /.idea/assetWizardSettings.xml
.DS_Store
# Google Services (e.g. APIs or Firebase) /build
google-services.json /captures
.externalNativeBuild
# Android Profiling .cxx
*.hprof local.properties

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>

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,7 +1,6 @@
plugins { plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp") version "1.9.0-1.0.13"
} }
android { android {
@ -23,10 +22,8 @@ android {
buildTypes { buildTypes {
release { release {
isMinifyEnabled = true isMinifyEnabled = false
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
isCrunchPngs = true
} }
} }
compileOptions { compileOptions {
@ -51,10 +48,10 @@ android {
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.8.2") implementation("androidx.activity:activity-compose:1.7.0")
implementation(platform("androidx.compose:compose-bom:2024.04.00")) implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.ui:ui-tooling-preview")
@ -62,25 +59,8 @@ dependencies {
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2024.04.00")) androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest") debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("androidx.navigation:navigation-compose:2.7.7")
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.5")
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
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

@ -2,12 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@ -21,6 +15,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.FlowSchool"> android:theme="@style/Theme.FlowSchool">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -4,10 +4,12 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.ramcosta.composedestinations.DestinationsNavHost import androidx.compose.ui.tooling.preview.Preview
import com.ramcosta.composedestinations.NavGraphs
import it.edu.cassandroferminervi.flowschool.ui.theme.FlowSchoolTheme import it.edu.cassandroferminervi.flowschool.ui.theme.FlowSchoolTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -15,12 +17,27 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
FlowSchoolTheme { FlowSchoolTheme {
Surface( // A surface container using the 'background' color from the theme
modifier = Modifier.fillMaxSize() Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
) { Greeting("Android")
DestinationsNavHost(navGraph = NavGraphs.root)
} }
} }
} }
} }
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
FlowSchoolTheme {
Greeting("Android")
}
} }

View File

@ -1,36 +0,0 @@
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

@ -1,23 +0,0 @@
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

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

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

@ -1,57 +0,0 @@
package it.edu.cassandroferminervi.flowschool.screens
import android.util.Log
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
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(navigator: DestinationsNavigator, token: String) {
val localContext = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val cameraProviderFuture = remember {
ProcessCameraProvider.getInstance(localContext)
}
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
val previewView = PreviewView(context)
val preview = Preview.Builder().build()
val selector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)
val imageAnalysis = ImageAnalysis.Builder().build()
imageAnalysis.setAnalyzer(
ContextCompat.getMainExecutor(context),
QrAnalyzer(context, navigator, token)
)
runCatching {
cameraProviderFuture.get().bindToLifecycle(
lifecycleOwner,
selector,
preview,
imageAnalysis
)
}.onFailure {
Log.e("CAMERA", "Camera bind error ${it.localizedMessage}", it)
}
previewView
}
)
}

View File

@ -1,108 +0,0 @@
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
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.ramcosta.composedestinations.PermissionScreenDestination
import com.ramcosta.composedestinations.SearchingScreenDestination
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@Destination
@Composable
fun HomeScreen(navigator: DestinationsNavigator, token: String) {
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
) {
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))
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,116 +0,0 @@
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
import androidx.compose.foundation.layout.Spacer
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.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
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
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
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
fun LoginScreen(navigator: DestinationsNavigator) {
var code by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 50.dp)
) {
Image(
painterResource(id = R.drawable.logo),
contentDescription = "Logo",
Modifier
.size(192.dp)
.align(Alignment.CenterHorizontally)
.clip(CircleShape)
)
Spacer(modifier = Modifier.height(64.dp))
TextField(
value = code,
onValueChange = { code = it },
label = { Text("Username") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
singleLine = true,
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
val image = if (passwordVisible)
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
val description = if (passwordVisible) "Nascondi password" else "Mostra password"
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description)
}
},
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = {
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

@ -1,40 +0,0 @@
package it.edu.cassandroferminervi.flowschool.screens
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.google.accompanist.permissions.ExperimentalPermissionsApi
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(navigator: DestinationsNavigator, token: String) {
val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
if (cameraPermissionState.status.isGranted) {
CameraScreen(navigator, token)
} else {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
if (cameraPermissionState.status.shouldShowRationale) {
Text("Attiva il permesso per la fotocamera dalle impostazioni")
} else {
SideEffect {
cameraPermissionState.run { launchPermissionRequest() }
}
Text("Permessi disattivati per la fotocamera")
}
}
}
}

View File

@ -1,104 +0,0 @@
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.OutlinedTextField
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.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 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,
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)
}
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 {
OutlinedTextField(
value = searchText,
onValueChange = { searchText = it },
label = { Text("Cerca professore") },
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
)
LazyColumn {
groupedByLetter.forEach { (letter, professors) ->
stickyHeader {
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)
}
}
}
}
}
@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,59 +0,0 @@
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(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("Nome: ${resBody.nome}")
Text("Cognome: ${resBody.cognome}")
Text("Email: ${resBody.email}")
}
}

View File

@ -1,57 +0,0 @@
package it.edu.cassandroferminervi.flowschool.util
import android.annotation.SuppressLint
import android.content.Context
import android.widget.Toast
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
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, private val navigator: DestinationsNavigator, val token: String) : ImageAnalysis.Analyzer {
private val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
private val scanner = BarcodeScanning.getClient(options)
@SuppressLint("UnsafeOptInUsageError")
override fun analyze(imageProxy: ImageProxy) {
imageProxy.image
?.let { image ->
scanner.process(
InputImage.fromMediaImage(
image, imageProxy.imageInfo.rotationDegrees
)
).addOnSuccessListener { barcode ->
barcode?.takeIf { it.isNotEmpty() }
?.mapNotNull { it.rawValue }
?.joinToString(",")
?.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()
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

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.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.0" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false
} }

View File

@ -1,6 +1,6 @@
#Wed Feb 21 15:30:24 CET 2024 #Wed Feb 21 15:30:24 CET 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists