feat: implement dynamic feature module for control actuator
This commit is contained in:
parent
812cd4b23b
commit
05b707fbdd
|
|
@ -12,6 +12,7 @@
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
<option value="$PROJECT_DIR$/control_feature" />
|
<option value="$PROJECT_DIR$/control_feature" />
|
||||||
|
<option value="$PROJECT_DIR$/diseasedetection_feature" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveExternalAnnotations" value="false" />
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.0.0" />
|
<option name="version" value="2.0.21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -3,6 +3,8 @@ plugins {
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
id("com.google.gms.google-services")
|
id("com.google.gms.google-services")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
|
id("com.google.dagger.hilt.android")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -13,8 +15,8 @@ android {
|
||||||
applicationId = "com.syaroful.agrilinkvocpro"
|
applicationId = "com.syaroful.agrilinkvocpro"
|
||||||
minSdk = 29
|
minSdk = 29
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 2
|
||||||
versionName = "1.0"
|
versionName = "1.0.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
@ -29,42 +31,59 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "1.8"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
dynamicFeatures += setOf(":control_feature")
|
dynamicFeatures += setOf(":control_feature", ":diseasedetection_feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
// Android Core and Lifecycle
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
|
||||||
|
// Jetpack Compose UI
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom)) // BOM for consistent Compose versions
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
|
|
||||||
|
// Testing Dependencies
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom)) // BOM for consistent Compose test versions
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
|
||||||
|
// Debugging Dependencies
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
|
||||||
// firebase service
|
// Firebase Services
|
||||||
implementation(platform(libs.firebase.bom))
|
implementation(platform(libs.firebase.bom)) // BOM for consistent Firebase versions
|
||||||
implementation(libs.firebase.database)
|
implementation(libs.firebase.database)
|
||||||
|
|
||||||
|
// ViewModel
|
||||||
// viewModel
|
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
|
|
||||||
|
// Dynamic Feature Modules
|
||||||
|
implementation(libs.feature.delivery)
|
||||||
|
implementation(libs.feature.delivery.ktx)
|
||||||
|
|
||||||
|
// Dependency Injection
|
||||||
|
implementation(libs.hilt.android)
|
||||||
|
ksp(libs.hilt.android.compiler)
|
||||||
|
|
||||||
|
// navigation with compose
|
||||||
|
implementation(libs.androidx.navigation.compose)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MyApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.syaroful.agrilinkvocpro
|
||||||
|
|
||||||
|
sealed class DownloadState {
|
||||||
|
data object Idle : DownloadState()
|
||||||
|
data object Starting : DownloadState()
|
||||||
|
data object Downloading : DownloadState()
|
||||||
|
data object Downloaded : DownloadState()
|
||||||
|
data object Installed : DownloadState()
|
||||||
|
data class Failed(val message: String) : DownloadState()
|
||||||
|
data class DownloadingWithProgress(val progress: Float) : DownloadState()
|
||||||
|
}
|
||||||
|
|
@ -1,68 +1,196 @@
|
||||||
package com.syaroful.agrilinkvocpro
|
package com.syaroful.agrilinkvocpro
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
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.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.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.material3.Text
|
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.Composable
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
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.text.input.KeyboardType
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import com.syaroful.agrilinkvocpro.core.components.DownloadModuleConfirmationDialog
|
||||||
import com.syaroful.agrilinkvocpro.core.components.AppButton
|
import com.syaroful.agrilinkvocpro.core.components.DownloadProgressDialog
|
||||||
import com.syaroful.agrilinkvocpro.core.components.AppPasswordField
|
import com.syaroful.agrilinkvocpro.core.components.MenuItemButton
|
||||||
import com.syaroful.agrilinkvocpro.core.components.AppTextField
|
import com.syaroful.agrilinkvocpro.ui.pages.GreenHouseInformationSection
|
||||||
import com.syaroful.agrilinkvocpro.ui.pages.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 kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
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 var dialogState = mutableStateOf(false)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
AgrilinkVocproTheme {
|
AgrilinkVocproTheme {
|
||||||
HomeScreen()
|
HomeScreen()
|
||||||
|
DownloadProgressDialog(
|
||||||
|
showDialog = showProgressDialog.value,
|
||||||
|
message = progressMessage.value,
|
||||||
|
progress = progressPercent.floatValue,
|
||||||
|
onDismiss = { showProgressDialog.value = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
private fun delayAndDismissDialog() {
|
||||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
lifecycleScope.launch {
|
||||||
|
kotlinx.coroutines.delay(1500)
|
||||||
Column {
|
showProgressDialog.value = false
|
||||||
Text(
|
openControlFeature()
|
||||||
text = "Hello $name!, silahkan Login",
|
}
|
||||||
modifier = modifier.fillMaxWidth(),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
AppTextField(
|
|
||||||
hint = "Enter Your Email",
|
|
||||||
leadingIcon = painterResource(R.drawable.icon_email),
|
|
||||||
keyboardType = KeyboardType.Email
|
|
||||||
)
|
|
||||||
AppPasswordField(
|
|
||||||
hint = "Enter your password",
|
|
||||||
keyboardType = KeyboardType.Password
|
|
||||||
)
|
|
||||||
AppButton(
|
|
||||||
label = "Login",
|
|
||||||
onClick = {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true, name = "Light Mode")
|
|
||||||
@Preview(showBackground = true, name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
|
||||||
@Composable
|
|
||||||
fun GreetingPreview() {
|
|
||||||
AgrilinkVocproTheme {
|
|
||||||
HomeScreen()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Composable
|
||||||
|
fun HomeScreen() {
|
||||||
|
AgrilinkVocproTheme {
|
||||||
|
Scaffold { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(padding)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
GreenHouseInformationSection()
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
DynamicFeatureSection()
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
DownloadModuleConfirmationDialog(dialogState, ::downloadDynamicModule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.syaroful.agrilinkvocpro
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class MyApplication : Application() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -7,8 +7,15 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
|
@ -55,8 +62,8 @@ fun AppTextField(
|
||||||
colors = TextFieldDefaults.colors(
|
colors = TextFieldDefaults.colors(
|
||||||
focusedContainerColor = Color.Transparent,
|
focusedContainerColor = Color.Transparent,
|
||||||
unfocusedContainerColor = Color.Transparent,
|
unfocusedContainerColor = Color.Transparent,
|
||||||
focusedIndicatorColor = MainGreen, // Warna stroke saat TextField aktif
|
focusedIndicatorColor = MainGreen,
|
||||||
unfocusedIndicatorColor = LightGrey // Warna stroke saat TextField tidak aktif
|
unfocusedIndicatorColor = LightGrey
|
||||||
),
|
),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
placeholder = {
|
placeholder = {
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,12 @@ import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||||
@Composable
|
@Composable
|
||||||
fun MenuItemButton(
|
fun MenuItemButton(
|
||||||
label: String,
|
label: String,
|
||||||
icon: Painter
|
icon: Painter,
|
||||||
|
onClick: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
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),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.syaroful.agrilinkvocpro.ui.pages
|
package com.syaroful.agrilinkvocpro.ui.pages
|
||||||
|
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -16,7 +15,6 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
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.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -24,84 +22,58 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
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.ui.theme.AgrilinkVocproTheme
|
|
||||||
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
import com.syaroful.agrilinkvocpro.ui.theme.MainGreen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen() {
|
fun GreenHouseInformationSection() {
|
||||||
AgrilinkVocproTheme {
|
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
|
||||||
Scaffold { padding ->
|
IconButton(
|
||||||
Column(
|
onClick = {},
|
||||||
modifier = Modifier
|
modifier = Modifier.align(Alignment.CenterVertically)
|
||||||
.padding(padding)
|
) {
|
||||||
.fillMaxWidth()
|
Icon(
|
||||||
) {
|
imageVector = Icons.Default.Person,
|
||||||
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
|
contentDescription = "profile",
|
||||||
IconButton(
|
tint = MainGreen
|
||||||
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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
|
||||||
Row(modifier = Modifier.padding(horizontal = 20.dp).fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
MenuItemButton(label = "Kontrol\nAktuator",icon = painterResource(id = R.drawable.control_actuator_icon))
|
|
||||||
MenuItemButton(label = "Resep\nPertumbuhan",icon = painterResource(id = R.drawable.growth_recipe_icon))
|
|
||||||
MenuItemButton(label = "Harga\nKomoditas",icon = painterResource(id = R.drawable.commodity_price_prediction_icon))
|
|
||||||
MenuItemButton(label = "Deteksi\nPenyakit",icon = painterResource(id = R.drawable.plant_disease_detection_icon))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showBackground = true, name = "Light Mode")
|
Row(
|
||||||
@Preview(showBackground = true, name = "Dark Mode", uiMode = UI_MODE_NIGHT_YES)
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
@Composable
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
fun HomeScreenPreview() {
|
modifier = Modifier
|
||||||
HomeScreen()
|
.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))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,12 @@ val Purple40 = Color(0xFF6650a4)
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
val Pink40 = Color(0xFF7D5260)
|
val Pink40 = Color(0xFF7D5260)
|
||||||
|
|
||||||
|
val Grey10 = Color(0xFFE7E7E7)
|
||||||
val LightGrey = Color(0xFFAAB3D0)
|
val LightGrey = Color(0xFFAAB3D0)
|
||||||
|
val DividerColor = Color(0xFFC2C2C2)
|
||||||
val DarkGrey = Color(0xFF6C707E)
|
val DarkGrey = Color(0xFF6C707E)
|
||||||
|
|
||||||
val MainGreen = Color(0xFF179678)
|
val MainGreen = Color(0xFF179678)
|
||||||
|
val DarkGreen = Color(0xFF0A3732)
|
||||||
|
val LightGreen = Color(0xFFE2FFF8)
|
||||||
|
val LemonGreen = Color(0xFFC9F000)
|
||||||
|
|
|
||||||
BIN
agrilinkvocpro/app/src/main/res/drawable/play_store.png
Normal file
BIN
agrilinkvocpro/app/src/main/res/drawable/play_store.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -2,4 +2,15 @@
|
||||||
<string name="app_name">Agrilink Vocpro</string>
|
<string name="app_name">Agrilink Vocpro</string>
|
||||||
<string name="title_control_feature">Control Module</string>
|
<string name="title_control_feature">Control Module</string>
|
||||||
<string name="title_activity_detail_control_screen">DetailControlScreen</string>
|
<string name="title_activity_detail_control_screen">DetailControlScreen</string>
|
||||||
|
<string name="title_diseasedetection_feature">Disease Detection Module</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="control_feature_label">Kontrol Aktuator</string>
|
||||||
|
<string name="play_store_icon_desc">Google Play Store</string>
|
||||||
|
<string name="download_module_title">Unduh Modul Fitur Dinamis</string>
|
||||||
|
<string name="download_module_message">anda perlu mengunduh modul fitur dinamis agar fitur ini dapat digunakan</string>
|
||||||
|
<string name="download">Download</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="title_activity_control_actuator">ControlActuatorActivity</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -5,4 +5,6 @@ plugins {
|
||||||
alias(libs.plugins.kotlin.compose) apply false
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
id("com.google.gms.google-services") version "4.4.2" apply false
|
id("com.google.gms.google-services") version "4.4.2" apply false
|
||||||
alias(libs.plugins.android.dynamic.feature) apply false
|
alias(libs.plugins.android.dynamic.feature) apply false
|
||||||
|
id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
|
||||||
|
id("com.google.dagger.hilt.android") version "2.56.2" apply false
|
||||||
}
|
}
|
||||||
|
|
@ -16,8 +16,7 @@ android {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
"proguard-rules-dynamic-features.pro"
|
||||||
"proguard-rules.pro"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +34,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":app"))
|
implementation(project(":app"))
|
||||||
|
// UI and Compose
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.runtime.android)
|
implementation(libs.androidx.runtime.android)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
|
@ -44,18 +44,22 @@ dependencies {
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
|
|
||||||
|
// ViewModel
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
|
|
||||||
|
// Firebase
|
||||||
|
implementation(platform(libs.firebase.bom))
|
||||||
|
implementation(libs.firebase.database)
|
||||||
|
|
||||||
|
// Testing
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
|
||||||
|
// Debugging
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
|
||||||
// firebase
|
|
||||||
implementation(platform(libs.firebase.bom))
|
|
||||||
implementation(libs.firebase.database)
|
|
||||||
|
|
||||||
// viewModel
|
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,11 @@
|
||||||
</dist:module>
|
</dist:module>
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
|
<activity
|
||||||
|
android:name=".ControlActuatorActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/title_activity_control_actuator"
|
||||||
|
android:theme="@style/Theme.AgrilinkVocpro" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.control_feature
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import com.syaroful.agrilinkvocpro.control_feature.page.ControlActuatorScreen
|
||||||
|
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.AgrilinkVocproTheme
|
||||||
|
|
||||||
|
class ControlActuatorActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
setContent {
|
||||||
|
AgrilinkVocproTheme {
|
||||||
|
ControlActuatorScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.control_feature.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.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.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
|
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.graphics.Color
|
||||||
|
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.DividerColor
|
||||||
|
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.Grey10
|
||||||
|
import com.syaroful.agrilinkvocpro.control_feature.ui.theme.MainGreen
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ControlCard(
|
||||||
|
iconRes: Int,
|
||||||
|
label: String,
|
||||||
|
isOn: Boolean,
|
||||||
|
onToggle: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
|
val backgroundColor = if (isOn) DarkGreen else Color.White
|
||||||
|
val iconTint = if (isOn) Color(0xFFB2FF59) else Color(0xFF4CAF50)
|
||||||
|
val textColor = if (isOn) MainGreen else MainGreen
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
.border(1.dp, Grey10, RoundedCornerShape(12.dp))
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.background(
|
||||||
|
color = iconTint.copy(alpha = 0.1f),
|
||||||
|
shape = CircleShape
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = iconRes),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconTint,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Switch(
|
||||||
|
checked = isOn,
|
||||||
|
onCheckedChange = onToggle,
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = DarkGreen,
|
||||||
|
uncheckedThumbColor = Color.White,
|
||||||
|
uncheckedBorderColor = DividerColor,
|
||||||
|
checkedTrackColor = MainGreen,
|
||||||
|
uncheckedTrackColor = DividerColor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(label, color = if (isOn) Color.White else Color.Black)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
if (isOn) "On" else "Off",
|
||||||
|
color = textColor,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,14 @@
|
||||||
package com.syaroful.agrilinkvocpro.control_feature.page
|
package com.syaroful.agrilinkvocpro.control_feature.page
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
|
@ -24,8 +18,6 @@ import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.SwitchDefaults
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -33,13 +25,10 @@ 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.graphics.Color
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
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 com.syaroful.agrilinkvocpro.control_feature.R
|
import com.syaroful.agrilinkvocpro.control_feature.R
|
||||||
|
import com.syaroful.agrilinkvocpro.control_feature.components.ControlCard
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -47,14 +36,19 @@ fun ControlActuatorScreen() {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Control Actuator") },
|
title = {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Text("Control Actuator", style = MaterialTheme.typography.titleMedium)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = { /* TODO: handle back */ }) {
|
IconButton(onClick = { }) {
|
||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = { /* TODO: handle history */ }) {
|
IconButton(onClick = { }) {
|
||||||
Icon(Icons.Filled.Info, contentDescription = "History")
|
Icon(Icons.Filled.Info, contentDescription = "History")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,63 +107,6 @@ fun ControlGrid(iconRes: Int, items: List<String>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ControlCard(
|
|
||||||
iconRes: Int,
|
|
||||||
label: String,
|
|
||||||
isOn: Boolean,
|
|
||||||
onToggle: (Boolean) -> Unit
|
|
||||||
) {
|
|
||||||
val backgroundColor = if (isOn) Color(0xFF00332C) else Color.White
|
|
||||||
val iconTint = if (isOn) Color(0xFFB2FF59) else Color(0xFF4CAF50)
|
|
||||||
val textColor = if (isOn) Color(0xFF00E676) else Color(0xFF4CAF50)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(backgroundColor)
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(32.dp)
|
|
||||||
.background(color = if (isOn) Color(0xFF00332C) else Color(0xFFE0F2F1), shape = CircleShape),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = iconRes),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = iconTint,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Switch(
|
|
||||||
checked = isOn,
|
|
||||||
onCheckedChange = onToggle,
|
|
||||||
colors = SwitchDefaults.colors(
|
|
||||||
checkedThumbColor = Color(0xFF00E676),
|
|
||||||
uncheckedThumbColor = Color.Gray
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
Text(label, color = Color.Black)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
if (isOn) "On" else "Off",
|
|
||||||
color = textColor,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.control_feature.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
|
val Purple40 = Color(0xFF6650a4)
|
||||||
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
|
val Pink40 = Color(0xFF7D5260)
|
||||||
|
|
||||||
|
val Grey10 = Color(0xFFE7E7E7)
|
||||||
|
val LightGrey = Color(0xFFAAB3D0)
|
||||||
|
val DividerColor = Color(0xFFC2C2C2)
|
||||||
|
val DarkGrey = Color(0xFF6C707E)
|
||||||
|
|
||||||
|
val MainGreen = Color(0xFF179678)
|
||||||
|
val DarkGreen = Color(0xFF0A3732)
|
||||||
|
val LightGreen = Color(0xFFE2FFF8)
|
||||||
|
val LemonGreen = Color(0xFFC9F000)
|
||||||
|
|
||||||
|
val BackgroundLight = Color(0xFFF8F8F8)
|
||||||
|
val BackgroundDark = Color(0xFF222222)
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.control_feature.ui.theme
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
|
private val DarkColorScheme = darkColorScheme(
|
||||||
|
primary = Purple80,
|
||||||
|
secondary = PurpleGrey80,
|
||||||
|
tertiary = Pink80,
|
||||||
|
background = BackgroundDark
|
||||||
|
)
|
||||||
|
|
||||||
|
private val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Purple40,
|
||||||
|
secondary = PurpleGrey40,
|
||||||
|
tertiary = Pink40,
|
||||||
|
background = BackgroundLight
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Other default colors to override
|
||||||
|
|
||||||
|
surface = Color(0xFFFFFBFE),
|
||||||
|
onPrimary = Color.White,
|
||||||
|
onSecondary = Color.White,
|
||||||
|
onTertiary = Color.White,
|
||||||
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
onSurface = Color(0xFF1C1B1F),
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AgrilinkVocproTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
// Dynamic color is available on Android 12+
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkTheme -> DarkColorScheme
|
||||||
|
else -> LightColorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.control_feature.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Set of Material typography styles to start with
|
||||||
|
val Typography = Typography(
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
/* Other default text styles to override
|
||||||
|
titleLarge = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
lineHeight = 28.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Theme.AgrilinkVocpro" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
|
</resources>
|
||||||
1
agrilinkvocpro/diseasedetection_feature/.gitignore
vendored
Normal file
1
agrilinkvocpro/diseasedetection_feature/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
37
agrilinkvocpro/diseasedetection_feature/build.gradle.kts
Normal file
37
agrilinkvocpro/diseasedetection_feature/build.gradle.kts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.dynamic.feature)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
}
|
||||||
|
android {
|
||||||
|
namespace = "com.syaroful.agrilinkvocpro.diseasedetection_feature"
|
||||||
|
compileSdk = 35
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 29
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
"proguard-rules-dynamic-features.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":app"))
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.diseasedetection_feature
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.syaroful.agrilinkvocpro.diseasedetection_feature", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:dist="http://schemas.android.com/apk/distribution">
|
||||||
|
|
||||||
|
<dist:module
|
||||||
|
dist:instant="false"
|
||||||
|
dist:title="@string/title_diseasedetection_feature">
|
||||||
|
<dist:delivery>
|
||||||
|
<dist:on-demand />
|
||||||
|
</dist:delivery>
|
||||||
|
<dist:fusing dist:include="true" />
|
||||||
|
</dist:module>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.diseasedetection_feature.pages
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.diseasedetection_feature
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
fun addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,31 @@
|
||||||
[versions]
|
[versions]
|
||||||
agp = "8.8.0"
|
agp = "8.8.2"
|
||||||
|
featureDelivery = "2.1.0"
|
||||||
firebaseBom = "33.13.0"
|
firebaseBom = "33.13.0"
|
||||||
kotlin = "2.0.0"
|
hiltAndroid = "2.56.2"
|
||||||
coreKtx = "1.15.0"
|
hiltAndroidCompiler = "2.56.2"
|
||||||
|
kotlin = "2.0.21"
|
||||||
|
coreKtx = "1.16.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.2.1"
|
junitVersion = "1.2.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
lifecycleRuntimeKtx = "2.8.7"
|
lifecycleRuntimeKtx = "2.9.0"
|
||||||
activityCompose = "1.9.3"
|
activityCompose = "1.10.1"
|
||||||
composeBom = "2024.04.01"
|
composeBom = "2025.05.00"
|
||||||
lifecycleViewmodelCompose = "2.8.7"
|
lifecycleViewmodelCompose = "2.9.0"
|
||||||
runtimeAndroid = "1.8.0"
|
navigationCompose = "2.9.0"
|
||||||
|
runtimeAndroid = "1.8.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
|
||||||
|
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||||
|
feature-delivery = { module = "com.google.android.play:feature-delivery", version.ref = "featureDelivery" }
|
||||||
|
feature-delivery-ktx = { module = "com.google.android.play:feature-delivery-ktx", version.ref = "featureDelivery" }
|
||||||
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
||||||
firebase-database = { module = "com.google.firebase:firebase-database" }
|
firebase-database = { module = "com.google.firebase:firebase-database" }
|
||||||
|
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
|
||||||
|
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroidCompiler" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#Wed Jan 08 14:02:58 WIB 2025
|
#Fri May 09 13:08:34 WIB 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,4 @@ dependencyResolutionManagement {
|
||||||
rootProject.name = "Agrilink Vocpro"
|
rootProject.name = "Agrilink Vocpro"
|
||||||
include(":app")
|
include(":app")
|
||||||
include(":control_feature")
|
include(":control_feature")
|
||||||
|
include(":diseasedetection_feature")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user