feat: tidy up code

This commit is contained in:
Cutiful 2025-05-20 18:02:05 +07:00
parent ae6e0a31db
commit ce5ddf39e0
12 changed files with 380 additions and 161 deletions

View File

@ -49,6 +49,10 @@
<option name="composableFile" value="true" /> <option name="composableFile" value="true" />
<option name="previewFile" value="true" /> <option name="previewFile" value="true" />
</inspection_tool> </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"> <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" /> <option name="composableFile" value="true" />
<option name="previewFile" value="true" /> <option name="previewFile" value="true" />

View File

@ -6,134 +6,53 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels 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.DownloadModuleConfirmationDialog
import com.syaroful.agrilinkvocpro.core.components.DownloadProgressDialog import com.syaroful.agrilinkvocpro.core.components.DownloadProgressDialog
import com.syaroful.agrilinkvocpro.core.utils.DownloadState import com.syaroful.agrilinkvocpro.core.route.SetupNavigation
import com.syaroful.agrilinkvocpro.presentation.dynamicModule.DynamicModuleViewModel import com.syaroful.agrilinkvocpro.ui.screen.home.HomeViewModel
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeScreen
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@Suppress("DEPRECATION")
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val viewModel: DynamicModuleViewModel by viewModels() private val homeViewModel: HomeViewModel 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)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
AgrilinkVocproTheme { AgrilinkVocproTheme {
HomeScreen( SetupNavigation(
onControlFeatureClick = { onFeatureClicked("control_feature") }, homeViewModel = homeViewModel,
onRecipeFeatureClick = { onFeatureClicked("recipe_feature") }, onLaunchFeature = { className ->
onPricePredictionFeatureClick = { onFeatureClicked("price_prediction_feature") }, val intent = Intent().apply {
onPlantDiseaseDetectionFeatureClick = { onFeatureClicked("diseasedetection_feature") }, setClassName(packageName, className)
dialogState = currentModuleToDownload }
startActivity(intent)
}
) )
DownloadProgressDialog( DownloadProgressDialog(
showDialog = showProgressDialog.value, showDialog = homeViewModel.showProgressDialog.value,
message = progressMessage.value, message = homeViewModel.progressMessage.value,
progress = progressPercent.floatValue, progress = homeViewModel.progressPercent.floatValue,
onDismiss = { showProgressDialog.value = false } onDismiss = {homeViewModel.showProgressDialog.value = false}
) )
DownloadModuleConfirmationDialog( DownloadModuleConfirmationDialog(
moduleName = currentModuleToDownload.value, moduleName = homeViewModel.currentModuleToDownload.value,
onDismiss = { currentModuleToDownload.value = null }, onDismiss = { homeViewModel.currentModuleToDownload.value = null },
onClickConfirm = { onClickConfirm = { homeViewModel.confirmDownload { startFeature(it) } }
currentModuleToDownload.value?.let { moduleName ->
downloadDynamicModule(moduleName)
}
currentModuleToDownload.value = null
}
) )
} }
} }
observeDownloadStatus()
} }
private fun startFeature(className: String) {
private fun observeDownloadStatus() { val intent = Intent().apply {
lifecycleScope.launchWhenStarted { setClassName(packageName, className)
viewModel.downloadState.collect { status ->
when (status) {
is DownloadState.Idle -> {
showProgressDialog.value = false
progressMessage.value = ""
progressPercent.floatValue = 0f
} }
startActivity(intent)
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 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)
} }
private fun startFeatureActivity(moduleName: String) { private fun startFeatureActivity(moduleName: String) {

View File

@ -6,4 +6,3 @@ import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp @HiltAndroidApp
class MyApplication : Application() { class MyApplication : Application() {
} }

View File

@ -30,9 +30,11 @@ fun MenuItemButton(
icon: Painter, icon: Painter,
onClick: () -> Unit = {} onClick: () -> Unit = {}
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
OutlinedButton( OutlinedButton(
onClick = onClick, onClick = { onClick() },
modifier = Modifier.size(48.dp), modifier = Modifier.size(48.dp),
shape = CircleShape, shape = CircleShape,
border = BorderStroke(0.dp, Color.Transparent), border = BorderStroke(0.dp, Color.Transparent),
@ -60,5 +62,8 @@ fun MenuItemButton(
@Preview @Preview
@Composable @Composable
fun MenuItemPreview() { fun MenuItemPreview() {
MenuItemButton(label = "Kontrol\nAktuator",icon = painterResource(id = R.drawable.control_actuator_icon)) MenuItemButton(
label = "Kontrol\nAktuator",
icon = painterResource(id = R.drawable.control_actuator_icon)
)
} }

View File

@ -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()
}
}
}

View File

@ -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()
}

View File

@ -1,8 +1,12 @@
package com.syaroful.agrilinkvocpro.ui.screen.home package com.syaroful.agrilinkvocpro.ui.screen.home
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.viewModels
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border 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.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -21,104 +25,160 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp 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.R
import com.syaroful.agrilinkvocpro.core.components.MenuItemButton import com.syaroful.agrilinkvocpro.core.components.MenuItemButton
import com.syaroful.agrilinkvocpro.core.components.textTheme 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.AgrilinkVocproTheme
import com.syaroful.agrilinkvocpro.ui.theme.LightGrey import com.syaroful.agrilinkvocpro.ui.theme.LightGrey
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
@Composable @Composable
fun HomeScreen( fun HomeScreen(
onControlFeatureClick: () -> Unit, navController: NavController,
onRecipeFeatureClick: () -> Unit, onFeatureClick: (String) -> Unit,
onPricePredictionFeatureClick: () -> Unit, dialogState: MutableState<String?>,
onPlantDiseaseDetectionFeatureClick: () -> Unit, homeViewModel: HomeViewModel = viewModel()
dialogState: MutableState<String?>
) { ) {
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 { AgrilinkVocproTheme {
Scaffold { padding -> Scaffold { padding ->
Column( Column(
modifier = Modifier modifier = Modifier
.padding(padding) .padding(padding)
.fillMaxWidth() .fillMaxWidth()
) { ) {
GreenHouseInformationSection() GreenHouseInformationSection(navController = navController)
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
DynamicFeatureSection( DynamicFeatureSection(
onControlFeatureClick = onControlFeatureClick, onFeatureClick = onFeatureClick
onRecipeFeatureClick = onRecipeFeatureClick,
onPricePredictionFeatureClick = onPricePredictionFeatureClick,
onPlantDiseaseDetectionFeatureClick = onPlantDiseaseDetectionFeatureClick
) )
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(32.dp))
Text( Text(
modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth(), modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth(),
text = "Data Sensor Green House", text = "Data Sensor Green House",
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = textTheme.bodyMedium style = textTheme.bodyMedium
) )
BetDataComponent() BetDataComponent(
BetDataComponent() onClick = { },
BetDataComponent() 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 @Composable
private fun BetDataComponent() { private fun BetDataComponent(
Column(Modifier.padding(horizontal = 20.dp, vertical = 8.dp)) { 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( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Absolute.SpaceBetween horizontalArrangement = Arrangement.Absolute.SpaceBetween
) { ) {
Text("Bet 1") Text("Bet $betNUmber")
Text("Labu Kabocha", color = MainGreen) Text(commodity, color = MainGreen)
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Row( Row(
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.clip(shape = RoundedCornerShape(16.dp))
.fillMaxWidth() .fillMaxWidth()
.background(
color = if (isSystemInDarkTheme()) Color.Black else Color.White,
)
.border( .border(
width = 1.dp, width = 1.dp,
color = MainGreen.copy(alpha = 0.4f), color = MainGreen.copy(alpha = 0.4f),
shape = RoundedCornerShape(16.dp) shape = RoundedCornerShape(16.dp)
) )
.clickable { onClick() }
.padding(horizontal = 16.dp, vertical = 8.dp) .padding(horizontal = 16.dp, vertical = 8.dp)
) )
{ {
SensorDataItem( SensorDataItem(
icon = "N", icon = "N",
label = "Nitrogen", label = "Nitrogen",
value = 230.toString() value = nitrogenValue
) )
SensorDataItem( SensorDataItem(
icon = "P", icon = "P",
label = "Phosphor", label = "Phosphor",
value = 50.toString() value = phosphorValue
) )
SensorDataItem( SensorDataItem(
icon = "K", icon = "K",
label = "Potassium", label = "Potassium",
value = 40.toString() value = potassiumValue
) )
Image( Image(
modifier = Modifier.height(60.dp), modifier = Modifier
painter = painterResource(id = R.drawable.kabocha), .height(60.dp)
.width(60.dp),
painter = commodityImage,
contentDescription = "Commodity" contentDescription = "Commodity"
) )
} }
@ -134,16 +194,14 @@ private fun SensorDataItem(
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = icon, color = MainGreen, style = textTheme.headlineMedium) Text(text = icon, color = MainGreen, style = textTheme.headlineMedium)
Text(text = label, color = LightGrey, style = textTheme.bodySmall) Text(text = label, color = LightGrey, style = textTheme.bodySmall)
Spacer(modifier = Modifier.height(8.dp))
Text(text = value, style = textTheme.headlineSmall) Text(text = value, style = textTheme.headlineSmall)
} }
} }
@Composable @Composable
fun DynamicFeatureSection( fun DynamicFeatureSection(
onControlFeatureClick: () -> Unit, onFeatureClick: (String) -> Unit
onRecipeFeatureClick: () -> Unit,
onPricePredictionFeatureClick: () -> Unit,
onPlantDiseaseDetectionFeatureClick: () -> Unit
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -154,31 +212,37 @@ fun DynamicFeatureSection(
MenuItemButton( MenuItemButton(
label = "Kontrol\nAktuator", label = "Kontrol\nAktuator",
icon = painterResource(id = R.drawable.control_actuator_icon), icon = painterResource(id = R.drawable.control_actuator_icon),
onClick = onControlFeatureClick onClick = { onFeatureClick("control_feature") },
) )
MenuItemButton( MenuItemButton(
label = "Resep\nPertumbuhan", label = "Resep\nPertumbuhan",
icon = painterResource(id = R.drawable.growth_recipe_icon), icon = painterResource(id = R.drawable.growth_recipe_icon),
onClick = onRecipeFeatureClick onClick = { onFeatureClick("recipe_feature") },
) )
MenuItemButton( MenuItemButton(
label = "Harga\nKomoditas", label = "Harga\nKomoditas",
icon = painterResource(id = R.drawable.commodity_price_prediction_icon), icon = painterResource(id = R.drawable.commodity_price_prediction_icon),
onClick = onPricePredictionFeatureClick onClick = { onFeatureClick("commodity_price_feature") },
) )
MenuItemButton( MenuItemButton(
label = "Deteksi\nPenyakit", label = "Deteksi\nPenyakit",
icon = painterResource(id = R.drawable.plant_disease_detection_icon), icon = painterResource(id = R.drawable.plant_disease_detection_icon),
onClick = onPlantDiseaseDetectionFeatureClick onClick = { onFeatureClick("diseasedetection_feature") },
) )
} }
} }
@Composable @Composable
fun GreenHouseInformationSection() { fun GreenHouseInformationSection(navController: NavController) {
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) { Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
IconButton( IconButton(
onClick = {}, onClick = {
navController.navigate(Screen.Profile.route) {
popUpTo(Screen.Profile.route) {
inclusive = true
}
}
},
modifier = Modifier.align(Alignment.CenterVertically) modifier = Modifier.align(Alignment.CenterVertically)
) { ) {
Icon( Icon(
@ -225,12 +289,11 @@ fun GreenHouseInformationSection() {
@Preview(showBackground = true, name = "Dark Mode", uiMode = UI_MODE_NIGHT_YES) @Preview(showBackground = true, name = "Dark Mode", uiMode = UI_MODE_NIGHT_YES)
@Composable @Composable
fun HomePreview() { fun HomePreview() {
val state = remember { mutableStateOf<String?>("control_feature") } val navController = rememberNavController()
val state = remember { mutableStateOf<String?>(null) }
HomeScreen( HomeScreen(
onControlFeatureClick = {}, navController = navController,
onRecipeFeatureClick = {}, onFeatureClick = {},
onPricePredictionFeatureClick = {},
onPlantDiseaseDetectionFeatureClick = {},
dialogState = state dialogState = state
) )
} }

View File

@ -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)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@ -2,6 +2,7 @@ package com.syaroful.agrilinkvocpro.control_feature.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.DarkGreen 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.DividerColor
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.Grey10
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.MainGreen import com.syaroful.agrilinkvocpro.control_feature.ui.theme.MainGreen
@ -38,14 +39,15 @@ fun ControlCard(
isOn: Boolean, isOn: Boolean,
onToggle: (Boolean) -> Unit 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 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( Column(
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(12.dp)) .clip(RoundedCornerShape(12.dp))
.border(1.dp, Grey10, RoundedCornerShape(12.dp)) .border(1.dp, MainGreen.copy(alpha = 0.4f), RoundedCornerShape(12.dp))
.background(backgroundColor) .background(backgroundColor)
.padding(16.dp) .padding(16.dp)
.fillMaxWidth() .fillMaxWidth()
@ -77,20 +79,20 @@ fun ControlCard(
colors = SwitchDefaults.colors( colors = SwitchDefaults.colors(
checkedThumbColor = DarkGreen, checkedThumbColor = DarkGreen,
uncheckedThumbColor = Color.White, uncheckedThumbColor = Color.White,
uncheckedBorderColor = DividerColor, uncheckedBorderColor = trackColor,
checkedTrackColor = MainGreen, checkedTrackColor = MainGreen,
uncheckedTrackColor = DividerColor uncheckedTrackColor = trackColor
) )
) )
} }
Spacer(modifier = Modifier.height(16.dp)) 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( Text(
if (isOn) "On" else "Off", if (isOn) "On" else "Off",
color = textColor, color = MainGreen,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
} }