feat: Add new drawables and project configuration files

This commit is contained in:
Cutiful 2025-05-19 21:22:41 +07:00
parent 7e4a678e56
commit a58fb72209
7 changed files with 317 additions and 187 deletions

View File

@ -6,27 +6,15 @@ 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.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope 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.components.MenuItemButton import com.syaroful.agrilinkvocpro.core.utils.DownloadState
import com.syaroful.agrilinkvocpro.ui.pages.GreenHouseInformationSection import com.syaroful.agrilinkvocpro.presentation.dynamicModule.DynamicModuleViewModel
import com.syaroful.agrilinkvocpro.ui.screen.home.HomeScreen
import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme import com.syaroful.agrilinkvocpro.ui.theme.AgrilinkVocproTheme
import com.syaroful.agrilinkvocpro.viewModel.DynamicModuleViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -42,20 +30,36 @@ class MainActivity : ComponentActivity() {
private val progressMessage = mutableStateOf("") private val progressMessage = mutableStateOf("")
private val progressPercent = mutableFloatStateOf(0f) private val progressPercent = mutableFloatStateOf(0f)
private var dialogState = mutableStateOf(false) 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() HomeScreen(
onControlFeatureClick = { onFeatureClicked("control_feature") },
onRecipeFeatureClick = { onFeatureClicked("recipe_feature") },
onPricePredictionFeatureClick = { onFeatureClicked("price_prediction_feature") },
onPlantDiseaseDetectionFeatureClick = { onFeatureClicked("diseasedetection_feature") },
dialogState = currentModuleToDownload
)
DownloadProgressDialog( DownloadProgressDialog(
showDialog = showProgressDialog.value, showDialog = showProgressDialog.value,
message = progressMessage.value, message = progressMessage.value,
progress = progressPercent.floatValue, progress = progressPercent.floatValue,
onDismiss = { showProgressDialog.value = false } onDismiss = { showProgressDialog.value = false }
) )
DownloadModuleConfirmationDialog(
moduleName = currentModuleToDownload.value,
onDismiss = { currentModuleToDownload.value = null },
onClickConfirm = {
currentModuleToDownload.value?.let { moduleName ->
downloadDynamicModule(moduleName)
}
currentModuleToDownload.value = null
}
)
} }
} }
@ -111,84 +115,42 @@ class MainActivity : ComponentActivity() {
} }
} }
private fun onFeatureClicked(moduleName: String) {
if (viewModel.isModuleDownloaded(moduleName)) {
startFeatureActivity(moduleName)
} else {
currentModuleToDownload.value = moduleName
}
}
private fun delayAndDismissDialog() { private fun delayAndDismissDialog() {
lifecycleScope.launch { lifecycleScope.launch {
kotlinx.coroutines.delay(1500) kotlinx.coroutines.delay(1500)
showProgressDialog.value = false showProgressDialog.value = false
openControlFeature() currentModuleToDownload.value?.let { startFeatureActivity(it) }
currentModuleToDownload.value = null
} }
} }
@Composable private fun downloadDynamicModule(moduleName: String) {
fun HomeScreen() { viewModel.downloadModule(moduleName)
AgrilinkVocproTheme { }
Scaffold { padding ->
Column( private fun startFeatureActivity(moduleName: String) {
modifier = Modifier val activityClassName = when (moduleName) {
.padding(padding) "control_feature" -> "com.syaroful.agrilinkvocpro.control_feature.ControlActuatorActivity"
.fillMaxWidth() "recipe_feature" -> "com.syaroful.agrilinkvocpro.recipe_feature.RecipeActivity"
) { "price_prediction_feature" -> "com.syaroful.agrilinkvocpro.price_prediction_feature.PricePredictionActivity"
GreenHouseInformationSection() "plant_disease_detection_feature" -> "com.syaroful.agrilinkvocpro.diseasedetection_feature.PlantDiseaseDetectionActivity"
Spacer(modifier = Modifier.height(20.dp)) else -> null
DynamicFeatureSection() }
Spacer(modifier = Modifier.height(32.dp)) activityClassName?.let {
DownloadModuleConfirmationDialog(dialogState, ::downloadDynamicModule) val intent = Intent().apply {
} setClassName("com.syaroful.agrilinkvocpro", it)
} }
startActivity(intent)
} }
} }
@Composable
fun DynamicFeatureSection() {
Row(
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
MenuItemButton(
label = "Kontrol\nAktuator",
icon = painterResource(id = R.drawable.control_actuator_icon),
onClick = { openControlFeature() })
MenuItemButton(
label = "Resep\nPertumbuhan",
icon = painterResource(id = R.drawable.growth_recipe_icon),
onClick = {}
)
MenuItemButton(
label = "Harga\nKomoditas",
icon = painterResource(id = R.drawable.commodity_price_prediction_icon),
onClick = {}
)
MenuItemButton(
label = "Deteksi\nPenyakit",
icon = painterResource(id = R.drawable.plant_disease_detection_icon),
onClick = {}
)
}
}
private fun openControlFeature() {
if (viewModel.isModuleDownloaded(CONTROL_FEATURE_MODULE_NAME)) {
startControlActuatorActivity()
} else {
dialogState.value = true
}
}
private fun downloadDynamicModule() {
viewModel.downloadModule(CONTROL_FEATURE_MODULE_NAME)
}
private fun startControlActuatorActivity() {
val intent = Intent().apply {
setClassName(
"com.syaroful.agrilinkvocpro",
"com.syaroful.agrilinkvocpro.control_feature.ControlActuatorActivity"
)
}
startActivity(intent)
}
} }

View File

@ -6,9 +6,6 @@ import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -17,8 +14,20 @@ import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R import com.syaroful.agrilinkvocpro.R
@Composable @Composable
fun DownloadModuleConfirmationDialog(state: MutableState<Boolean>, onClickConfirm: () -> Unit) { fun DownloadModuleConfirmationDialog(
if (!state.value) return moduleName: String?,
onDismiss: () -> Unit,
onClickConfirm: () -> Unit
) {
if (moduleName == null) return
val message = when (moduleName) {
"control_feature" -> "Apakah Anda ingin mendownload fitur Kontrol Aktuator?"
"recipe_feature" -> "Apakah Anda ingin mendownload fitur Resep Pertumbuhan?"
"price_prediction_feature" -> "Apakah Anda ingin mendownload fitur Prediksi Harga Komoditas?"
"plant_disease_detection_feature" -> "Apakah Anda ingin mendownload fitur Deteksi Penyakit Tanaman?"
else -> "Apakah Anda ingin mendownload modul ini?"
}
AlertDialog( AlertDialog(
icon = { icon = {
@ -28,23 +37,27 @@ fun DownloadModuleConfirmationDialog(state: MutableState<Boolean>, onClickConfir
modifier = Modifier.height(70.dp), modifier = Modifier.height(70.dp),
) )
}, },
title = { Text(stringResource(id = R.string.download_module_title)) },
text = { Text(stringResource(id = R.string.download_module_message)) }, title = {
onDismissRequest = { state.value = false }, Text(
stringResource(id = R.string.download_module_title),
style = textTheme.bodyLarge,
)
},
text = { Text(message) },
onDismissRequest = onDismiss,
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
onClickConfirm.invoke() onClickConfirm()
state.value = false onDismiss()
} }
) { ) {
Text(stringResource(id = R.string.download)) Text(stringResource(id = R.string.download))
} }
}, },
dismissButton = { dismissButton = {
TextButton( TextButton(onClick = onDismiss) {
onClick = { state.value = false }
) {
Text(stringResource(id = R.string.cancel)) Text(stringResource(id = R.string.cancel))
} }
} }
@ -54,12 +67,10 @@ fun DownloadModuleConfirmationDialog(state: MutableState<Boolean>, onClickConfir
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun PreviewDownloadModuleConfirmationDialog() { fun PreviewDownloadModuleConfirmationDialog() {
// Use remember just for preview context
val showDialog = remember { mutableStateOf(true) }
// Provide fake implementation for confirmation click
DownloadModuleConfirmationDialog( DownloadModuleConfirmationDialog(
state = showDialog, moduleName = "price_prediction_feature",
onClickConfirm = { /* No-op for preview */ } onClickConfirm = {},
onDismiss = {}
) )
} }

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro package com.syaroful.agrilinkvocpro.core.utils
sealed class DownloadState { sealed class DownloadState {
data object Idle : DownloadState() data object Idle : DownloadState()

View File

@ -1,4 +1,4 @@
package com.syaroful.agrilinkvocpro.di package com.syaroful.agrilinkvocpro.data.repository
import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.Task
import com.google.android.play.core.splitinstall.SplitInstallManager import com.google.android.play.core.splitinstall.SplitInstallManager

View File

@ -1,12 +1,12 @@
package com.syaroful.agrilinkvocpro.viewModel package com.syaroful.agrilinkvocpro.presentation.dynamicModule
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.google.android.play.core.splitinstall.SplitInstallException import com.google.android.play.core.splitinstall.SplitInstallException
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener 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.SplitInstallErrorCode
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
import com.syaroful.agrilinkvocpro.DownloadState import com.syaroful.agrilinkvocpro.core.utils.DownloadState
import com.syaroful.agrilinkvocpro.di.DynamicModuleRepository import com.syaroful.agrilinkvocpro.data.repository.DynamicModuleRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow

View File

@ -1,79 +0,0 @@
package com.syaroful.agrilinkvocpro.ui.pages
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.components.MenuItemButton
import com.syaroful.agrilinkvocpro.core.components.textTheme
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
@Composable
fun GreenHouseInformationSection() {
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
IconButton(
onClick = {},
modifier = Modifier.align(Alignment.CenterVertically)
) {
Icon(
imageVector = Icons.Default.Person,
contentDescription = "profile",
tint = MainGreen
)
}
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(20.dp)
.fillMaxWidth()
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(text = "2 Komoditas", color = MainGreen, style = textTheme.bodyMedium)
Spacer(modifier = Modifier.height(24.dp))
Text(text = "Green House Bumiaji", style = textTheme.bodyLarge)
Text(
text = "Jl. Kopral Kasdi 2, Bulukerto, Kec. Bumiaji, Kota Batu, Jawa Timur 65334 ",
style = textTheme.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Spacer(modifier = Modifier.width(24.dp))
Image(
painter = painterResource(id = R.drawable.green_house_image),
contentDescription = "profile",
modifier = Modifier
.size(90.dp)
.clip(RoundedCornerShape(10.dp))
)
}
}

View File

@ -0,0 +1,236 @@
package com.syaroful.agrilinkvocpro.ui.screen.home
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
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.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 com.syaroful.agrilinkvocpro.R
import com.syaroful.agrilinkvocpro.core.components.MenuItemButton
import com.syaroful.agrilinkvocpro.core.components.textTheme
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?>
) {
AgrilinkVocproTheme {
Scaffold { padding ->
Column(
modifier = Modifier
.padding(padding)
.fillMaxWidth()
) {
GreenHouseInformationSection()
Spacer(modifier = Modifier.height(20.dp))
DynamicFeatureSection(
onControlFeatureClick = onControlFeatureClick,
onRecipeFeatureClick = onRecipeFeatureClick,
onPricePredictionFeatureClick = onPricePredictionFeatureClick,
onPlantDiseaseDetectionFeatureClick = onPlantDiseaseDetectionFeatureClick
)
Spacer(modifier = Modifier.height(32.dp))
Text(
modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth(),
text = "Data Sensor Green House",
textAlign = TextAlign.Center,
style = textTheme.bodyMedium
)
BetDataComponent()
BetDataComponent()
BetDataComponent()
}
}
}
}
@Composable
private fun BetDataComponent() {
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)
}
Spacer(modifier = Modifier.height(4.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.border(
width = 1.dp,
color = MainGreen.copy(alpha = 0.4f),
shape = RoundedCornerShape(16.dp)
)
.padding(horizontal = 16.dp, vertical = 8.dp)
)
{
SensorDataItem(
icon = "N",
label = "Nitrogen",
value = 230.toString()
)
SensorDataItem(
icon = "P",
label = "Phosphor",
value = 50.toString()
)
SensorDataItem(
icon = "K",
label = "Potassium",
value = 40.toString()
)
Image(
modifier = Modifier.height(60.dp),
painter = painterResource(id = R.drawable.kabocha),
contentDescription = "Commodity"
)
}
}
}
@Composable
private fun SensorDataItem(
icon: String = "-",
label: String,
value: String = "0"
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = icon, color = MainGreen, style = textTheme.headlineMedium)
Text(text = label, color = LightGrey, style = textTheme.bodySmall)
Text(text = value, style = textTheme.headlineSmall)
}
}
@Composable
fun DynamicFeatureSection(
onControlFeatureClick: () -> Unit,
onRecipeFeatureClick: () -> Unit,
onPricePredictionFeatureClick: () -> Unit,
onPlantDiseaseDetectionFeatureClick: () -> Unit
) {
Row(
modifier = Modifier
.padding(horizontal = 20.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
MenuItemButton(
label = "Kontrol\nAktuator",
icon = painterResource(id = R.drawable.control_actuator_icon),
onClick = onControlFeatureClick
)
MenuItemButton(
label = "Resep\nPertumbuhan",
icon = painterResource(id = R.drawable.growth_recipe_icon),
onClick = onRecipeFeatureClick
)
MenuItemButton(
label = "Harga\nKomoditas",
icon = painterResource(id = R.drawable.commodity_price_prediction_icon),
onClick = onPricePredictionFeatureClick
)
MenuItemButton(
label = "Deteksi\nPenyakit",
icon = painterResource(id = R.drawable.plant_disease_detection_icon),
onClick = onPlantDiseaseDetectionFeatureClick
)
}
}
@Composable
fun GreenHouseInformationSection() {
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
IconButton(
onClick = {},
modifier = Modifier.align(Alignment.CenterVertically)
) {
Icon(
imageVector = Icons.Default.Person,
contentDescription = "profile",
tint = MainGreen
)
}
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(20.dp)
.fillMaxWidth()
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(text = "2 Komoditas", color = MainGreen, style = textTheme.bodyMedium)
Spacer(modifier = Modifier.height(24.dp))
Text(text = "Green House Bumiaji", style = textTheme.bodyLarge)
Text(
text = "Jl. Kopral Kasdi 2, Bulukerto, Kec. Bumiaji, Kota Batu, Jawa Timur 65334 ",
style = textTheme.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Spacer(modifier = Modifier.width(24.dp))
Image(
painter = painterResource(id = R.drawable.green_house_image),
contentDescription = "profile",
modifier = Modifier
.size(90.dp)
.clip(RoundedCornerShape(10.dp))
)
}
}
@Preview(showBackground = true, name = "Light Mode")
@Preview(showBackground = true, name = "Dark Mode", uiMode = UI_MODE_NIGHT_YES)
@Composable
fun HomePreview() {
val state = remember { mutableStateOf<String?>("control_feature") }
HomeScreen(
onControlFeatureClick = {},
onRecipeFeatureClick = {},
onPricePredictionFeatureClick = {},
onPlantDiseaseDetectionFeatureClick = {},
dialogState = state
)
}