feat: tidy up code
This commit is contained in:
parent
ae6e0a31db
commit
ce5ddf39e0
|
|
@ -49,6 +49,10 @@
|
|||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -6,134 +6,53 @@ import androidx.activity.ComponentActivity
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.syaroful.agrilinkvocpro.core.components.DownloadModuleConfirmationDialog
|
||||
import com.syaroful.agrilinkvocpro.core.components.DownloadProgressDialog
|
||||
import com.syaroful.agrilinkvocpro.core.utils.DownloadState
|
||||
import com.syaroful.agrilinkvocpro.presentation.dynamicModule.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeScreen
|
||||
import com.syaroful.agrilinkvocpro.core.route.SetupNavigation
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val viewModel: DynamicModuleViewModel by viewModels()
|
||||
|
||||
private val CONTROL_FEATURE_MODULE_NAME = "control_feature"
|
||||
|
||||
// State untuk menampilkan dialog log/progress
|
||||
private val showProgressDialog = mutableStateOf(false)
|
||||
private val progressMessage = mutableStateOf("")
|
||||
private val progressPercent = mutableFloatStateOf(0f)
|
||||
|
||||
private val currentModuleToDownload = mutableStateOf<String?>(null)
|
||||
private val homeViewModel: HomeViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
AgrilinkVocproTheme {
|
||||
HomeScreen(
|
||||
onControlFeatureClick = { onFeatureClicked("control_feature") },
|
||||
onRecipeFeatureClick = { onFeatureClicked("recipe_feature") },
|
||||
onPricePredictionFeatureClick = { onFeatureClicked("price_prediction_feature") },
|
||||
onPlantDiseaseDetectionFeatureClick = { onFeatureClicked("diseasedetection_feature") },
|
||||
dialogState = currentModuleToDownload
|
||||
)
|
||||
DownloadProgressDialog(
|
||||
showDialog = showProgressDialog.value,
|
||||
message = progressMessage.value,
|
||||
progress = progressPercent.floatValue,
|
||||
onDismiss = { showProgressDialog.value = false }
|
||||
)
|
||||
DownloadModuleConfirmationDialog(
|
||||
moduleName = currentModuleToDownload.value,
|
||||
onDismiss = { currentModuleToDownload.value = null },
|
||||
onClickConfirm = {
|
||||
currentModuleToDownload.value?.let { moduleName ->
|
||||
downloadDynamicModule(moduleName)
|
||||
SetupNavigation(
|
||||
homeViewModel = homeViewModel,
|
||||
onLaunchFeature = { className ->
|
||||
val intent = Intent().apply {
|
||||
setClassName(packageName, className)
|
||||
}
|
||||
currentModuleToDownload.value = null
|
||||
startActivity(intent)
|
||||
}
|
||||
)
|
||||
|
||||
DownloadProgressDialog(
|
||||
showDialog = homeViewModel.showProgressDialog.value,
|
||||
message = homeViewModel.progressMessage.value,
|
||||
progress = homeViewModel.progressPercent.floatValue,
|
||||
onDismiss = {homeViewModel.showProgressDialog.value = false}
|
||||
)
|
||||
|
||||
DownloadModuleConfirmationDialog(
|
||||
moduleName = homeViewModel.currentModuleToDownload.value,
|
||||
onDismiss = { homeViewModel.currentModuleToDownload.value = null },
|
||||
onClickConfirm = { homeViewModel.confirmDownload { startFeature(it) } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
observeDownloadStatus()
|
||||
}
|
||||
|
||||
private fun observeDownloadStatus() {
|
||||
lifecycleScope.launchWhenStarted {
|
||||
viewModel.downloadState.collect { status ->
|
||||
when (status) {
|
||||
is DownloadState.Idle -> {
|
||||
showProgressDialog.value = false
|
||||
progressMessage.value = ""
|
||||
progressPercent.floatValue = 0f
|
||||
}
|
||||
|
||||
is DownloadState.Starting -> {
|
||||
showProgressDialog.value = true
|
||||
progressMessage.value = "Starting download..."
|
||||
progressPercent.floatValue = 0f
|
||||
}
|
||||
|
||||
is DownloadState.Downloading -> {
|
||||
showProgressDialog.value = true
|
||||
progressMessage.value = "Downloading module..."
|
||||
// Progress update akan di-handle via listener tambahan (lihat catatan di bawah)
|
||||
}
|
||||
|
||||
is DownloadState.Downloaded -> {
|
||||
progressMessage.value = "Download completed"
|
||||
progressPercent.floatValue = 1f
|
||||
|
||||
}
|
||||
|
||||
is DownloadState.Installed -> {
|
||||
progressMessage.value = "Install completed"
|
||||
progressPercent.floatValue = 1f
|
||||
// Tutup dialog setelah beberapa saat
|
||||
delayAndDismissDialog()
|
||||
}
|
||||
|
||||
is DownloadState.Failed -> {
|
||||
progressMessage.value = "Failed: ${status.message}"
|
||||
showProgressDialog.value = true
|
||||
progressPercent.floatValue = 0f
|
||||
}
|
||||
|
||||
is DownloadState.DownloadingWithProgress -> {
|
||||
showProgressDialog.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun startFeature(className: String) {
|
||||
val intent = Intent().apply {
|
||||
setClassName(packageName, className)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFeatureClicked(moduleName: String) {
|
||||
if (viewModel.isModuleDownloaded(moduleName)) {
|
||||
startFeatureActivity(moduleName)
|
||||
} else {
|
||||
currentModuleToDownload.value = moduleName
|
||||
}
|
||||
}
|
||||
|
||||
private fun delayAndDismissDialog() {
|
||||
lifecycleScope.launch {
|
||||
kotlinx.coroutines.delay(1500)
|
||||
showProgressDialog.value = false
|
||||
currentModuleToDownload.value?.let { startFeatureActivity(it) }
|
||||
currentModuleToDownload.value = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadDynamicModule(moduleName: String) {
|
||||
viewModel.downloadModule(moduleName)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun startFeatureActivity(moduleName: String) {
|
||||
|
|
|
|||
|
|
@ -6,4 +6,3 @@ import dagger.hilt.android.HiltAndroidApp
|
|||
@HiltAndroidApp
|
||||
class MyApplication : Application() {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,9 +30,11 @@ fun MenuItemButton(
|
|||
icon: Painter,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
onClick = { onClick() },
|
||||
modifier = Modifier.size(48.dp),
|
||||
shape = CircleShape,
|
||||
border = BorderStroke(0.dp, Color.Transparent),
|
||||
|
|
@ -59,6 +61,9 @@ fun MenuItemButton(
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
fun MenuItemPreview(){
|
||||
MenuItemButton(label = "Kontrol\nAktuator",icon = painterResource(id = R.drawable.control_actuator_icon))
|
||||
fun MenuItemPreview() {
|
||||
MenuItemButton(
|
||||
label = "Kontrol\nAktuator",
|
||||
icon = painterResource(id = R.drawable.control_actuator_icon)
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.syaroful.agrilinkvocpro.core.route
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeScreen
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.screen.profile.ProfileScreen
|
||||
|
||||
sealed class Screen(val route: String) {
|
||||
object Home : Screen("home")
|
||||
object Profile : Screen("profile")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetupNavigation(
|
||||
homeViewModel: HomeViewModel,
|
||||
onLaunchFeature: (String) -> Unit
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
NavHost(navController, startDestination = "home") {
|
||||
composable("home") {
|
||||
HomeScreen(
|
||||
navController = navController,
|
||||
onFeatureClick = { moduleName ->
|
||||
homeViewModel.onFeatureClicked(moduleName, onLaunchFeature)
|
||||
},
|
||||
dialogState = homeViewModel.currentModuleToDownload,
|
||||
homeViewModel = homeViewModel
|
||||
)
|
||||
}
|
||||
composable("profile"){
|
||||
ProfileScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.syaroful.agrilinkvocpro.presentation.event
|
||||
|
||||
sealed class HomeUiEvent {
|
||||
data class RequestModule(val moduleName: String) : HomeUiEvent()
|
||||
data class ConfirmDownload(val moduleName: String) : HomeUiEvent()
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.screen.home
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -21,104 +25,160 @@ import androidx.compose.material3.Scaffold
|
|||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.syaroful.agrilinkvocpro.R
|
||||
import com.syaroful.agrilinkvocpro.core.components.MenuItemButton
|
||||
import com.syaroful.agrilinkvocpro.core.components.textTheme
|
||||
import com.syaroful.agrilinkvocpro.core.route.Screen
|
||||
import com.syaroful.agrilinkvocpro.presentation.dynamicModule.DynamicModuleViewModel
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey
|
||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
onControlFeatureClick: () -> Unit,
|
||||
onRecipeFeatureClick: () -> Unit,
|
||||
onPricePredictionFeatureClick: () -> Unit,
|
||||
onPlantDiseaseDetectionFeatureClick: () -> Unit,
|
||||
dialogState: MutableState<String?>
|
||||
navController: NavController,
|
||||
onFeatureClick: (String) -> Unit,
|
||||
dialogState: MutableState<String?>,
|
||||
homeViewModel: HomeViewModel = viewModel()
|
||||
) {
|
||||
val CONTROL_FEATURE_MODULE_NAME = "control_feature"
|
||||
|
||||
// State untuk menampilkan dialog log/progress
|
||||
val showProgressDialog: MutableState<Boolean> = mutableStateOf(false)
|
||||
val progressMessage = mutableStateOf("")
|
||||
val progressPercent = mutableFloatStateOf(0f)
|
||||
|
||||
val currentModuleToDownload = mutableStateOf<String?>(null)
|
||||
AgrilinkVocproTheme {
|
||||
Scaffold { padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
GreenHouseInformationSection()
|
||||
GreenHouseInformationSection(navController = navController)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
DynamicFeatureSection(
|
||||
onControlFeatureClick = onControlFeatureClick,
|
||||
onRecipeFeatureClick = onRecipeFeatureClick,
|
||||
onPricePredictionFeatureClick = onPricePredictionFeatureClick,
|
||||
onPlantDiseaseDetectionFeatureClick = onPlantDiseaseDetectionFeatureClick
|
||||
onFeatureClick = onFeatureClick
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 20.dp)
|
||||
.fillMaxWidth(),
|
||||
text = "Data Sensor Green House",
|
||||
textAlign = TextAlign.Center,
|
||||
style = textTheme.bodyMedium
|
||||
)
|
||||
BetDataComponent()
|
||||
BetDataComponent()
|
||||
BetDataComponent()
|
||||
BetDataComponent(
|
||||
onClick = { },
|
||||
betNUmber = 1,
|
||||
commodity = "Labu Kabocha",
|
||||
nitrogenValue = "120",
|
||||
phosphorValue = "34",
|
||||
potassiumValue = "65",
|
||||
commodityImage = painterResource(id = R.drawable.kabocha)
|
||||
)
|
||||
BetDataComponent(
|
||||
onClick = { },
|
||||
betNUmber = 2,
|
||||
commodity = "Melon",
|
||||
nitrogenValue = "154",
|
||||
phosphorValue = "45",
|
||||
potassiumValue = "32",
|
||||
commodityImage = painterResource(id = R.drawable.melon)
|
||||
)
|
||||
BetDataComponent(
|
||||
onClick = { },
|
||||
betNUmber = 3,
|
||||
commodity = "Kale",
|
||||
nitrogenValue = "230",
|
||||
phosphorValue = "50",
|
||||
potassiumValue = "40",
|
||||
commodityImage = painterResource(id = R.drawable.kale)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BetDataComponent() {
|
||||
Column(Modifier.padding(horizontal = 20.dp, vertical = 8.dp)) {
|
||||
private fun BetDataComponent(
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
betNUmber: Int,
|
||||
commodity: String,
|
||||
nitrogenValue: String,
|
||||
phosphorValue: String,
|
||||
potassiumValue: String,
|
||||
commodityImage: Painter
|
||||
) {
|
||||
Column(
|
||||
modifier
|
||||
.padding(horizontal = 20.dp, vertical = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Absolute.SpaceBetween
|
||||
) {
|
||||
Text("Bet 1")
|
||||
Text("Labu Kabocha", color = MainGreen)
|
||||
Text("Bet $betNUmber")
|
||||
Text(commodity, color = MainGreen)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(shape = RoundedCornerShape(16.dp))
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = if (isSystemInDarkTheme()) Color.Black else Color.White,
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = MainGreen.copy(alpha = 0.4f),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.clickable { onClick() }
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
{
|
||||
SensorDataItem(
|
||||
icon = "N",
|
||||
label = "Nitrogen",
|
||||
value = 230.toString()
|
||||
value = nitrogenValue
|
||||
)
|
||||
SensorDataItem(
|
||||
icon = "P",
|
||||
label = "Phosphor",
|
||||
value = 50.toString()
|
||||
value = phosphorValue
|
||||
)
|
||||
SensorDataItem(
|
||||
icon = "K",
|
||||
label = "Potassium",
|
||||
value = 40.toString()
|
||||
value = potassiumValue
|
||||
)
|
||||
Image(
|
||||
modifier = Modifier.height(60.dp),
|
||||
painter = painterResource(id = R.drawable.kabocha),
|
||||
modifier = Modifier
|
||||
.height(60.dp)
|
||||
.width(60.dp),
|
||||
painter = commodityImage,
|
||||
contentDescription = "Commodity"
|
||||
)
|
||||
}
|
||||
|
|
@ -134,16 +194,14 @@ private fun SensorDataItem(
|
|||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(text = icon, color = MainGreen, style = textTheme.headlineMedium)
|
||||
Text(text = label, color = LightGrey, style = textTheme.bodySmall)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(text = value, style = textTheme.headlineSmall)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DynamicFeatureSection(
|
||||
onControlFeatureClick: () -> Unit,
|
||||
onRecipeFeatureClick: () -> Unit,
|
||||
onPricePredictionFeatureClick: () -> Unit,
|
||||
onPlantDiseaseDetectionFeatureClick: () -> Unit
|
||||
onFeatureClick: (String) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
|
|
@ -154,31 +212,37 @@ fun DynamicFeatureSection(
|
|||
MenuItemButton(
|
||||
label = "Kontrol\nAktuator",
|
||||
icon = painterResource(id = R.drawable.control_actuator_icon),
|
||||
onClick = onControlFeatureClick
|
||||
onClick = { onFeatureClick("control_feature") },
|
||||
)
|
||||
MenuItemButton(
|
||||
label = "Resep\nPertumbuhan",
|
||||
icon = painterResource(id = R.drawable.growth_recipe_icon),
|
||||
onClick = onRecipeFeatureClick
|
||||
onClick = { onFeatureClick("recipe_feature") },
|
||||
)
|
||||
MenuItemButton(
|
||||
label = "Harga\nKomoditas",
|
||||
icon = painterResource(id = R.drawable.commodity_price_prediction_icon),
|
||||
onClick = onPricePredictionFeatureClick
|
||||
onClick = { onFeatureClick("commodity_price_feature") },
|
||||
)
|
||||
MenuItemButton(
|
||||
label = "Deteksi\nPenyakit",
|
||||
icon = painterResource(id = R.drawable.plant_disease_detection_icon),
|
||||
onClick = onPlantDiseaseDetectionFeatureClick
|
||||
onClick = { onFeatureClick("diseasedetection_feature") },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GreenHouseInformationSection() {
|
||||
fun GreenHouseInformationSection(navController: NavController) {
|
||||
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
|
||||
IconButton(
|
||||
onClick = {},
|
||||
onClick = {
|
||||
navController.navigate(Screen.Profile.route) {
|
||||
popUpTo(Screen.Profile.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Icon(
|
||||
|
|
@ -225,12 +289,11 @@ fun GreenHouseInformationSection() {
|
|||
@Preview(showBackground = true, name = "Dark Mode", uiMode = UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun HomePreview() {
|
||||
val state = remember { mutableStateOf<String?>("control_feature") }
|
||||
val navController = rememberNavController()
|
||||
val state = remember { mutableStateOf<String?>(null) }
|
||||
HomeScreen(
|
||||
onControlFeatureClick = {},
|
||||
onRecipeFeatureClick = {},
|
||||
onPricePredictionFeatureClick = {},
|
||||
onPlantDiseaseDetectionFeatureClick = {},
|
||||
navController = navController,
|
||||
onFeatureClick = {},
|
||||
dialogState = state
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package com.syaroful.agrilinkvocpro.ui.screen.home
|
||||
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.android.play.core.splitinstall.SplitInstallException
|
||||
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
|
||||
import com.google.android.play.core.splitinstall.model.SplitInstallErrorCode
|
||||
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
|
||||
import com.syaroful.agrilinkvocpro.core.utils.DownloadState
|
||||
import com.syaroful.agrilinkvocpro.data.repository.DynamicModuleRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class HomeViewModel @Inject constructor(
|
||||
private val dynamicModuleRepository: DynamicModuleRepository
|
||||
) : ViewModel() {
|
||||
|
||||
var currentModuleToDownload = mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
var showProgressDialog = mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var progressMessage = mutableStateOf("")
|
||||
private set
|
||||
|
||||
var progressPercent = mutableFloatStateOf(0f)
|
||||
private set
|
||||
|
||||
private val _downloadState = MutableStateFlow<DownloadState>(DownloadState.Idle)
|
||||
private val downloadState: StateFlow<DownloadState> = _downloadState.asStateFlow()
|
||||
|
||||
private val listener = SplitInstallStateUpdatedListener { state ->
|
||||
if (state.sessionId() == dynamicModuleRepository.getSessionId()) {
|
||||
when (state.status()) {
|
||||
SplitInstallSessionStatus.DOWNLOADING -> {
|
||||
val progress = if (state.totalBytesToDownload() > 0)
|
||||
state.bytesDownloaded().toFloat() / state.totalBytesToDownload()
|
||||
else 0f
|
||||
_downloadState.value = DownloadState.DownloadingWithProgress(progress)
|
||||
}
|
||||
|
||||
SplitInstallSessionStatus.DOWNLOADED -> {
|
||||
_downloadState.value = DownloadState.Downloaded
|
||||
}
|
||||
|
||||
SplitInstallSessionStatus.INSTALLED -> {
|
||||
_downloadState.value = DownloadState.Installed
|
||||
}
|
||||
|
||||
SplitInstallSessionStatus.FAILED -> {
|
||||
_downloadState.value = DownloadState.Failed("Installation Failed")
|
||||
}
|
||||
|
||||
SplitInstallSessionStatus.CANCELED -> {
|
||||
_downloadState.value = DownloadState.Failed("Installation Canceled")
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onFeatureClicked(moduleName: String, onLaunch: (String) -> Unit) {
|
||||
if (isModuleDownloaded(moduleName)) {
|
||||
startFeatureActivity(moduleName, onLaunch)
|
||||
} else {
|
||||
currentModuleToDownload.value = moduleName
|
||||
}
|
||||
}
|
||||
|
||||
fun confirmDownload(onLaunch: (String) -> Unit) {
|
||||
currentModuleToDownload.value?.let {
|
||||
downloadDynamicModule(it, onLaunch)
|
||||
}
|
||||
currentModuleToDownload.value = null
|
||||
}
|
||||
|
||||
private fun downloadDynamicModule(moduleName: String, onLaunch: (String) -> Unit) {
|
||||
_downloadState.value = DownloadState.Starting
|
||||
|
||||
dynamicModuleRepository.startDownload(moduleName, listener)
|
||||
.addOnFailureListener { exception ->
|
||||
val errorCode = (exception as? SplitInstallException)?.errorCode ?: -1
|
||||
val errorMessage = when (errorCode) {
|
||||
SplitInstallErrorCode.NETWORK_ERROR -> "No Internet Connection"
|
||||
SplitInstallErrorCode.MODULE_UNAVAILABLE -> "Module Unavailable"
|
||||
SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> "Active Session Limit Exceeded"
|
||||
SplitInstallErrorCode.INSUFFICIENT_STORAGE -> "Insufficient Storage"
|
||||
SplitInstallErrorCode.PLAY_STORE_NOT_FOUND -> "Play Store Not Found"
|
||||
else -> "Unknown Error: $errorCode"
|
||||
}
|
||||
_downloadState.value = DownloadState.Failed(errorMessage)
|
||||
}
|
||||
|
||||
observeDownloadStatus(onLaunch)
|
||||
}
|
||||
|
||||
private fun isModuleDownloaded(moduleName: String): Boolean {
|
||||
return dynamicModuleRepository.isModuleDownloaded(moduleName)
|
||||
}
|
||||
|
||||
private fun observeDownloadStatus(onLaunch: (String) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
downloadState.collect { status ->
|
||||
when (status) {
|
||||
is DownloadState.Idle -> {
|
||||
showProgressDialog.value = false
|
||||
progressMessage.value = ""
|
||||
progressPercent.floatValue = 0f
|
||||
}
|
||||
|
||||
is DownloadState.Starting -> {
|
||||
showProgressDialog.value = true
|
||||
progressMessage.value = "Starting download..."
|
||||
progressPercent.floatValue = 0f
|
||||
}
|
||||
|
||||
is DownloadState.Downloading -> {
|
||||
showProgressDialog.value = true
|
||||
progressMessage.value = "Downloading module..."
|
||||
}
|
||||
|
||||
is DownloadState.Downloaded -> {
|
||||
progressMessage.value = "Download completed"
|
||||
progressPercent.floatValue = 1f
|
||||
}
|
||||
|
||||
is DownloadState.Installed -> {
|
||||
progressMessage.value = "Install completed"
|
||||
progressPercent.floatValue = 1f
|
||||
delayAndDismissDialog(onLaunch)
|
||||
}
|
||||
|
||||
is DownloadState.Failed -> {
|
||||
progressMessage.value = "Failed: ${status.message}"
|
||||
showProgressDialog.value = true
|
||||
progressPercent.floatValue = 0f
|
||||
}
|
||||
|
||||
is DownloadState.DownloadingWithProgress -> {
|
||||
showProgressDialog.value = true
|
||||
progressPercent.floatValue = status.progress
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun delayAndDismissDialog(onLaunch: (String) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
delay(1500)
|
||||
showProgressDialog.value = false
|
||||
currentModuleToDownload.value?.let {
|
||||
startFeatureActivity(it, onLaunch)
|
||||
}
|
||||
currentModuleToDownload.value = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun startFeatureActivity(moduleName: String, onLaunch: (String) -> Unit) {
|
||||
val className = when (moduleName) {
|
||||
"control_feature" -> "com.syaroful.agrilinkvocpro.control_feature.ControlActuatorActivity"
|
||||
"recipe_feature" -> "com.syaroful.agrilinkvocpro.recipe_feature.RecipeActivity"
|
||||
"price_prediction_feature" -> "com.syaroful.agrilinkvocpro.price_prediction_feature.PricePredictionActivity"
|
||||
"plant_disease_detection_feature" -> "com.syaroful.agrilinkvocpro.diseasedetection_feature.PlantDiseaseDetectionActivity"
|
||||
else -> null
|
||||
}
|
||||
className?.let { onLaunch(it) }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
dynamicModuleRepository.unregisterListener(listener)
|
||||
}
|
||||
}
|
||||
BIN
agrilinkvocpro/app/src/main/res/drawable/kale.png
Normal file
BIN
agrilinkvocpro/app/src/main/res/drawable/kale.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
BIN
agrilinkvocpro/app/src/main/res/drawable/melon.png
Normal file
BIN
agrilinkvocpro/app/src/main/res/drawable/melon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 MiB |
|
|
@ -2,6 +2,7 @@ package com.syaroful.agrilinkvocpro.control_feature.components
|
|||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -26,8 +27,8 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.DarkGreen
|
||||
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.DarkGrey
|
||||
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.DividerColor
|
||||
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.Grey10
|
||||
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.MainGreen
|
||||
|
||||
|
||||
|
|
@ -38,14 +39,15 @@ fun ControlCard(
|
|||
isOn: Boolean,
|
||||
onToggle: (Boolean) -> Unit
|
||||
) {
|
||||
val backgroundColor = if (isOn) DarkGreen else Color.White
|
||||
val backgroundColor = if (isOn) DarkGreen else if (isSystemInDarkTheme()) Color.Black else Color.White
|
||||
val iconTint = if (isOn) Color(0xFFB2FF59) else Color(0xFF4CAF50)
|
||||
val textColor = if (isOn) MainGreen else MainGreen
|
||||
val textColor = if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||
val trackColor = if (isSystemInDarkTheme()) DarkGrey else DividerColor
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.border(1.dp, Grey10, RoundedCornerShape(12.dp))
|
||||
.border(1.dp, MainGreen.copy(alpha = 0.4f), RoundedCornerShape(12.dp))
|
||||
.background(backgroundColor)
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
|
|
@ -77,20 +79,20 @@ fun ControlCard(
|
|||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = DarkGreen,
|
||||
uncheckedThumbColor = Color.White,
|
||||
uncheckedBorderColor = DividerColor,
|
||||
uncheckedBorderColor = trackColor,
|
||||
checkedTrackColor = MainGreen,
|
||||
uncheckedTrackColor = DividerColor
|
||||
uncheckedTrackColor = trackColor
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(label, color = if (isOn) Color.White else Color.Black)
|
||||
Text(label, color = if (isOn) Color.White else textColor)
|
||||
|
||||
Text(
|
||||
if (isOn) "On" else "Off",
|
||||
color = textColor,
|
||||
color = MainGreen,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user