Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Commit

Permalink
feat(app): add page and viewModel for check the backpack content (Bac…
Browse files Browse the repository at this point in the history
…kpackContent and BackpackViewModel)
  • Loading branch information
AndreaBrighi committed May 16, 2023
1 parent ae2b0cf commit f689b0a
Show file tree
Hide file tree
Showing 3 changed files with 388 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package com.intelligentbackpack.app.view

import android.Manifest
import android.content.Context
import android.widget.Toast
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
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.material.icons.Icons
import androidx.compose.material.icons.twotone.ArrowBack
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.common.Barcode
import com.intelligentbackpack.app.sensor.BarcodeAnalyser
import com.intelligentbackpack.app.ui.common.CameraViewWithPermission
import com.intelligentbackpack.app.ui.common.SchoolSupplyCard
import com.intelligentbackpack.app.viewmodel.BackpackViewModel

/**
* The content of the backpack screen.
*/
@OptIn(ExperimentalPermissionsApi::class, ExperimentalComposeUiApi::class)
@Composable
fun BackpackContent(
navController: NavHostController,
backpackViewModel: BackpackViewModel = viewModel(
factory = BackpackViewModel.Factory
),
context: Context = LocalContext.current,
) {
var openErrorDialog by remember { mutableStateOf(false) }
var errorDialogMessage by remember { mutableStateOf("") }
val isBackpackAssociated = backpackViewModel.isBackpackAssociated.observeAsState(false)
if (openErrorDialog) {
AlertDialog(
onDismissRequest = {},
title = {
Text(
text = "Error with the " +
if (isBackpackAssociated.value) {
"disassociation"
} else {
"association"
}
)
},
text = {
Text(text = errorDialogMessage)
},
confirmButton = {
Button(
onClick = {
openErrorDialog = false
}) {
Text("Confirm")
}
})
}
backpackViewModel.getBackpackAssociated { error ->
errorDialogMessage = error
openErrorDialog = true
}
if (isBackpackAssociated.value) {
val backpack = backpackViewModel.backpack.observeAsState(setOf())
backpackViewModel.subscribe { error ->
errorDialogMessage = error
openErrorDialog = true
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 16.dp, horizontal = 0.dp)
) {

LazyColumn(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.Top)
)
{
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Backpack",
style = MaterialTheme.typography.titleMedium
)
Button(
onClick = {
backpackViewModel.disassociateBackpack({
Toast.makeText(context, "Backpack disassociated", Toast.LENGTH_SHORT).show()
}, { error ->
errorDialogMessage = error
openErrorDialog = true
})
},
modifier = Modifier
.fillMaxWidth(0.8f)
.padding(top = 10.dp),
enabled = true,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary),
shape = MaterialTheme.shapes.medium,
)
{
Text("Associate backpack")
}
}
}
items(backpack.value.toList()) {
SchoolSupplyCard(
navHostController = navController,
schoolSupply = it,
)
}
}
}
} else {
var openCameraDialog by remember { mutableStateOf(false) }
val permissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
if (openCameraDialog) {
Dialog(
onDismissRequest = { openCameraDialog = false },
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = false,
usePlatformDefaultWidth = false
)
) {
CameraViewWithPermission(
topBar = {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = { openCameraDialog = false }) {
Icon(
imageVector = Icons.TwoTone.ArrowBack,
contentDescription = null,
tint = Color.White
)
}
Text(
"Scan QR Code",
color = Color.White,
fontSize = 20.sp
)
}
},
message = {
Text(
"Place the QR code inside the frame to scan it.",
color = Color.White,
fontSize = 12.sp
)
},
barcodeAnalyser = BarcodeAnalyser(
onBarcodeFound = {
openCameraDialog = false
backpackViewModel.associateBackpack(it, success = {
Toast.makeText(context, "Backpack associated", Toast.LENGTH_SHORT)
.show()
}, error = { error ->
errorDialogMessage = error
openErrorDialog = true
})
Toast.makeText(context, "QR code found", Toast.LENGTH_SHORT).show()
},
options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
),
permissionState = permissionState,
onBack = { openCameraDialog = false },
)
}

}
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically)
) {
Text(text = "No backpack associated")
Button(
onClick = {
openCameraDialog = true
},
modifier = Modifier
.fillMaxWidth(0.8f)
.padding(top = 10.dp),
enabled = true,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.primary),
shape = MaterialTheme.shapes.medium,
)
{
Text("Associate backpack")
}
}
}
}

@Preview
@Composable
fun BackpackContentPreview() {
BackpackContent(navController = rememberNavController())
}

Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ fun HomePage(
CompositionLocalProvider(
LocalViewModelStoreOwner provides viewModelStoreOwner
) {
Text(text = "Backpack")
BackpackContent(navController = navController)
}
}
composable(TabNavigation.forget) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.intelligentbackpack.app.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.intelligentbackpack.app.App
import com.intelligentbackpack.app.viewdata.SchoolSupplyView
import com.intelligentbackpack.app.viewdata.adapter.SchoolSupplyAdapter.fromDomainToView
import com.intelligentbackpack.desktopdomain.usecase.DesktopUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class BackpackViewModel(
private val desktopUseCase: DesktopUseCase
) : ViewModel() {

private val backpackImpl = MutableLiveData<Set<SchoolSupplyView>>()

val backpack: LiveData<Set<SchoolSupplyView>> = backpackImpl

private val isBackpackAssociatedImpl = MutableLiveData<Boolean>()

val isBackpackAssociated: LiveData<Boolean> = isBackpackAssociatedImpl

fun getBackpackAssociated(
error: (error: String) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
desktopUseCase.getDesktop({
viewModelScope.launch(Dispatchers.Main) {
isBackpackAssociatedImpl.postValue(it.isBackpackAssociated)
}
}, {
viewModelScope.launch(Dispatchers.Main) {
error(it.message ?: "Unknown error")
}
})
}
}

fun associateBackpack(
hash: String,
success: () -> Unit,
error: (error: String) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
desktopUseCase.associateBackpack(hash, {
viewModelScope.launch(Dispatchers.Main) {
isBackpackAssociatedImpl.postValue(true)
success()
}
}, {
viewModelScope.launch(Dispatchers.Main) {
error(it.message ?: "Unknown error")
}
})
}
}

fun disassociateBackpack(
success: () -> Unit,
error: (error: String) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
desktopUseCase.getDesktop({ desktop ->
desktop.backpack?.let { backpack ->
viewModelScope.launch(Dispatchers.IO) {
desktopUseCase.disassociateBackpack(backpack, {
viewModelScope.launch(Dispatchers.Main) {
isBackpackAssociatedImpl.postValue(false)
success()
}
}, {
viewModelScope.launch(Dispatchers.Main) {
error(it.message ?: "Unknown error")
}
})
}
}
}, {
viewModelScope.launch(Dispatchers.Main) {
error(it.message ?: "Unknown error")
}
})
}
}

fun subscribe(
error: (error: String) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
/*(1..10).asFlow().map { (0..it).toSet() }.flowOn(Dispatchers.IO).collect {
viewModelScope.launch(Dispatchers.Main) {
backpackImpl.postValue(it)
}
}*/
desktopUseCase.subscribeToBackpack({
viewModelScope.launch(Dispatchers.IO) {
it.map { backpack ->
backpack.map { schoolSupply -> schoolSupply.fromDomainToView() }.toSet()
}.flowOn(Dispatchers.IO).collect {
viewModelScope.launch(Dispatchers.Main) {
backpackImpl.postValue(it)
}
}
}
}, {
viewModelScope.launch(Dispatchers.Main) {
error(it.message ?: "Unknown error")
}
})
}
}

companion object {

val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
// Get the Application object from extras
val application = checkNotNull(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY])
BackpackViewModel(
(application as App).desktopUseCase
)
}
}
}
}

0 comments on commit f689b0a

Please sign in to comment.