diff --git a/agrilinkvocpro/.idea/inspectionProfiles/Project_Default.xml b/agrilinkvocpro/.idea/inspectionProfiles/Project_Default.xml
index cde3e19..7061a0d 100644
--- a/agrilinkvocpro/.idea/inspectionProfiles/Project_Default.xml
+++ b/agrilinkvocpro/.idea/inspectionProfiles/Project_Default.xml
@@ -49,6 +49,10 @@
+
+
+
+
diff --git a/agrilinkvocpro/app/release/app-release.aab b/agrilinkvocpro/app/release/app-release.aab
index de5ee58..a47f002 100644
Binary files a/agrilinkvocpro/app/release/app-release.aab and b/agrilinkvocpro/app/release/app-release.aab differ
diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MainActivity.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MainActivity.kt
index 38a6376..878b312 100644
--- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MainActivity.kt
+++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MainActivity.kt
@@ -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(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) {
diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MyApplication.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MyApplication.kt
index 7ffeff6..4c4b020 100644
--- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MyApplication.kt
+++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/MyApplication.kt
@@ -5,5 +5,4 @@ import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application() {
-}
-
+}
\ No newline at end of file
diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/MenuItemButton.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/MenuItemButton.kt
index 470f539..98ee994 100644
--- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/MenuItemButton.kt
+++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/components/MenuItemButton.kt
@@ -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)
+ )
}
\ No newline at end of file
diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/route/NavGraph.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/route/NavGraph.kt
new file mode 100644
index 0000000..e2f3092
--- /dev/null
+++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/core/route/NavGraph.kt
@@ -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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/event/HomeUiEvent.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/event/HomeUiEvent.kt
new file mode 100644
index 0000000..fc1aace
--- /dev/null
+++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/presentation/event/HomeUiEvent.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/HomeScreen.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/HomeScreen.kt
index 7632675..a7f87ab 100644
--- a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/HomeScreen.kt
+++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/HomeScreen.kt
@@ -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
+ navController: NavController,
+ onFeatureClick: (String) -> Unit,
+ dialogState: MutableState,
+ homeViewModel: HomeViewModel = viewModel()
) {
+ val CONTROL_FEATURE_MODULE_NAME = "control_feature"
+
+ // State untuk menampilkan dialog log/progress
+ val showProgressDialog: MutableState = mutableStateOf(false)
+ val progressMessage = mutableStateOf("")
+ val progressPercent = mutableFloatStateOf(0f)
+
+ val currentModuleToDownload = mutableStateOf(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("control_feature") }
+ val navController = rememberNavController()
+ val state = remember { mutableStateOf(null) }
HomeScreen(
- onControlFeatureClick = {},
- onRecipeFeatureClick = {},
- onPricePredictionFeatureClick = {},
- onPlantDiseaseDetectionFeatureClick = {},
+ navController = navController,
+ onFeatureClick = {},
dialogState = state
)
}
\ No newline at end of file
diff --git a/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/HomeViewModel.kt b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/HomeViewModel.kt
new file mode 100644
index 0000000..b85c0ec
--- /dev/null
+++ b/agrilinkvocpro/app/src/main/java/com/syaroful/agrilinkvocpro/ui/screen/home/HomeViewModel.kt
@@ -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(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.Idle)
+ private val downloadState: StateFlow = _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)
+ }
+}
\ No newline at end of file
diff --git a/agrilinkvocpro/app/src/main/res/drawable/kale.png b/agrilinkvocpro/app/src/main/res/drawable/kale.png
new file mode 100644
index 0000000..564e2be
Binary files /dev/null and b/agrilinkvocpro/app/src/main/res/drawable/kale.png differ
diff --git a/agrilinkvocpro/app/src/main/res/drawable/melon.png b/agrilinkvocpro/app/src/main/res/drawable/melon.png
new file mode 100644
index 0000000..b3caecb
Binary files /dev/null and b/agrilinkvocpro/app/src/main/res/drawable/melon.png differ
diff --git a/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/components/ControlCard.kt b/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/components/ControlCard.kt
index 4f3f4e5..50b8907 100644
--- a/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/components/ControlCard.kt
+++ b/agrilinkvocpro/control_feature/src/main/java/com/syaroful/agrilinkvocpro/control_feature/components/ControlCard.kt
@@ -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
)
}