This commit is contained in:
Ibnu Naz'm Ar-rosyid
2025-07-18 20:05:17 +07:00
commit bea44c1b7c
234 changed files with 5681 additions and 0 deletions

1
TomatoLeafCare/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,100 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id("kotlin-parcelize")
}
android {
namespace = "com.example.tomatoleafcare"
compileSdk = 35
defaultConfig {
applicationId = "com.example.tomatoleafcare"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
aaptOptions {
noCompress += "tflite"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildFeatures {
viewBinding = true
dataBinding = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.2")
implementation("androidx.navigation:navigation-ui-ktx:2.7.2")
implementation("androidx.camera:camera-core:1.3.0")
implementation("androidx.camera:camera-camera2:1.3.0")
implementation("androidx.camera:camera-lifecycle:1.3.0")
implementation("androidx.camera:camera-view:1.3.0")
implementation("com.google.android.material:material:1.9.0")
implementation ("org.tensorflow:tensorflow-lite:2.13.0")
implementation ("org.tensorflow:tensorflow-lite-support:0.3.1")
implementation ("com.squareup.okhttp3:okhttp:4.12.0")
implementation ("com.squareup.picasso:picasso:2.8")
implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation ("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
}

21
TomatoLeafCare/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package com.example.tomatoleafcare
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.example.tomatoleafcare", appContext.packageName)
}
}

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.TomatoLeafCare"
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity
android:name=".ui.splash.SplashActivity"
android:screenOrientation="portrait"
android:exported="true"
android:theme="@style/Theme.TomatoLeafCare.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".ui.home.MainActivity"
android:screenOrientation="portrait"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.TomatoLeafCare.Splash"/>
<activity android:name=".DiseaseDetailFragment" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,48 @@
package com.example.tomatoleafcare.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.model.Disease
class DiseaseAdapter(
private val diseases: List<Disease>,
private val listener: OnItemClickListener
) : RecyclerView.Adapter<DiseaseAdapter.DiseaseViewHolder>() {
interface OnItemClickListener {
fun onItemClick(disease: Disease)
}
inner class DiseaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val diseaseName: TextView = itemView.findViewById(R.id.diseaseName)
private val diseaseImage: ImageView = itemView.findViewById(R.id.diseaseImage)
private val diseaseDescription: TextView = itemView.findViewById((R.id.diseaseDescription))
fun bind(disease: Disease) {
diseaseName.text = disease.name
diseaseImage.setImageResource(disease.imageResId)
diseaseDescription.text = disease.description
itemView.setOnClickListener {
listener.onItemClick(disease)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DiseaseViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.disease_item, parent, false)
return DiseaseViewHolder(view)
}
override fun onBindViewHolder(holder: DiseaseViewHolder, position: Int) {
holder.bind(diseases[position])
}
override fun getItemCount(): Int = diseases.size
}

View File

@ -0,0 +1,59 @@
package com.example.tomatoleafcare.ui.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.recyclerview.widget.RecyclerView
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.model.History
import com.squareup.picasso.Picasso
class HistoryAdapter(
private val context: Context,
private var historyList: List<History>,
private val onItemClick: (History) -> Unit,
private val onDeleteClick: (History) -> Unit
) : RecyclerView.Adapter<HistoryAdapter.HistoryViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.history_item, parent, false)
return HistoryViewHolder(view)
}
override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
val history = historyList[position]
holder.bind(history)
holder.itemView.setOnClickListener {
onItemClick(history)
}
holder.btnDelete.setOnClickListener {
onDeleteClick(history)
}
}
override fun getItemCount(): Int = historyList.size
fun updateData(newList: List<History>) {
historyList = newList
notifyDataSetChanged()
}
inner class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val diseaseName: TextView = itemView.findViewById(R.id.txtDiseaseName)
private val date: TextView = itemView.findViewById(R.id.txtDate)
private val image: ImageView = itemView.findViewById(R.id.imageHistory)
val btnDelete: ImageButton = itemView.findViewById(R.id.btnDelete)
fun bind(history: History) {
diseaseName.text = history.diseaseName
date.text = history.date
Picasso.get().load(history.imagePath)
.placeholder(R.drawable.placeholder)
.into(image)
}
}
}

View File

@ -0,0 +1,18 @@
package com.example.tomatoleafcare.adapter
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.example.tomatoleafcare.ui.home.CardSlide1Fragment
import com.example.tomatoleafcare.ui.home.CardSlide2Fragment
class SliderAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> CardSlide1Fragment()
1 -> CardSlide2Fragment()
else -> CardSlide1Fragment()
}
}
}

View File

@ -0,0 +1,66 @@
package com.example.tomatoleafcare.helper
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.example.tomatoleafcare.model.History
class HistoryDatabaseHelper(context: Context) :
SQLiteOpenHelper(context, "history.db", null, 1) {
override fun onCreate(db: SQLiteDatabase) {
val createTable = """
CREATE TABLE history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
diseaseName TEXT,
date TEXT,
imagePath TEXT
)
""".trimIndent()
db.execSQL(createTable)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS history")
onCreate(db)
}
fun insertHistory(item: History): Long {
val db = writableDatabase
val values = ContentValues().apply {
put("diseaseName", item.diseaseName)
put("date", item.date)
put("imagePath", item.imagePath)
}
return db.insert("history", null, values)
}
fun getAllHistory(): List<History> {
val db = readableDatabase
val cursor = db.rawQuery("SELECT * FROM history ORDER BY id DESC", null)
val items = mutableListOf<History>()
while (cursor.moveToNext()) {
items.add(
History(
id = cursor.getLong(cursor.getColumnIndexOrThrow("id")),
diseaseName = cursor.getString(cursor.getColumnIndexOrThrow("diseaseName")),
date = cursor.getString(cursor.getColumnIndexOrThrow("date")),
imagePath = cursor.getString(cursor.getColumnIndexOrThrow("imagePath"))
)
)
}
cursor.close()
return items
}
fun deleteHistory(id: Long): Boolean {
val db = this.writableDatabase
val result = db.delete("history", "id=?", arrayOf(id.toString()))
db.close()
return result > 0
}
}

View File

@ -0,0 +1,15 @@
package com.example.tomatoleafcare.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Disease(
val name: String,
val description: String,
val cause: String,
val symptoms: String,
val impact: String,
val solution: String,
val imageResId: Int
): Parcelable

View File

@ -0,0 +1,11 @@
package com.example.tomatoleafcare.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class History(
val id: Long = 0,
val diseaseName: String,
val date: String,
val imagePath: String
) : Parcelable

View File

@ -0,0 +1,99 @@
package com.example.tomatoleafcare.repository
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.model.Disease
object DiseaseRepository {
val classList = listOf(
Disease(
"Bercak Daun Septoria",
"Menyebabkan bercak coklat pada daun, pencegahan dengan fungisida dan sirkulasi udara yang baik.",
"Jamur Septoria lycopersici, jamur ini sering ditemukan di tanah yang basah dan menyebar melalui percikan air.",
"Bercak-bercak kecil berwarna coklat tua atau hitam pada daun yang dimulai dari bagian bawah dan dapat membesar.",
"Daun menguning dan rontok., mengurangi produktivitas buah, dan membuat tanaman rentan terhadap penyakit lain.",
"Penggunaan fungisida, memastikan sirkulasi udara yang baik, dan menghindari penyiraman berlebihan.",
R.drawable.bercakdaunseptoria
),
Disease(
"Virus Mozaik",
"Menyebabkan daun terdistorsi dan mosaik, cegah dengan benih sehat dan menghapus tanaman terinfeksi.",
"Tomato Mosaic Virus (ToMV) yang menyebar melalui kontak tanaman terinfeksi, alat pertanian, dan serangga.",
"Pola mosaik bercak hijau muda dan gelap pada daun, daun menjadi keriput atau terdistorsi",
"Menghambat fotosintesis, menyebabkan pertumbuhan yang tidak optimal, serta menurunkan kualitas dan kuantitas buah.",
"Menggunakan benih sehat, menjaga kebersihan alat, dan menghilangkan tanaman yang terinfeksi.",
R.drawable.virusmozaik
),
Disease(
"Jamur Daun",
"Menyebabkan bercak kuning dan lapisan beludru di bawah daun, cegah dengan kelembapan rendah dan fungisida.",
"Jamur Cladosporium fulvum yang berkembang di lingkungan lembap dan sirkulasi udara buruk.",
"Bercak kuning pada bagian atas daun dan lapisan beludru hijau atau coklat pada bagian bawah daun",
"Mengurangi kemampuan fotosintesis, dapat menyebabkan daun mengering dan rontok.",
"Menjaga kelembapan rendah, meningkatkan sirkulasi udara, menghindari penyiraman langsung pada daun, dan menggunakan fungisida.",
R.drawable.jamurdaun
),
Disease(
"Virus Keriting Daun",
"Menyebabkan daun keriting dan menguning, cegah dengan insektisida dan penghapusan tanaman terinfeksi.",
"Virus yang ditularkan oleh kutu kebul (Bemisia tabaci).",
"Daun menjadi keriting, tebal, dan berubah warna menjadi kuning.",
"Menghambat pertumbuhan tanaman, menurunkan kualitas dan kuantitas buah.",
"Menggunakan insektisida, dan segera hapus tanaman yang terinfeksi.",
R.drawable.viruskeriting
),
Disease(
"Hawar Daun",
"Menyebabkan bercak hitam, pembusukan buah, cegah dengan fungisida dan rotasi tanaman.",
"Jamur Phytophthora infestans, yang menyebar cepat dalam kondisi lembap dan dingin.",
"Bercak coklat gelap atau hitam menyebar cepat pada daun, dengan lapisan putih pada bagian bawah daun.",
"Bisa dengan cepat menghancurkan seluruh tanaman jika tidak segera diatasi, buah juga bisa membusuk.",
"Gunakan fungisida terutama saat musim hujan, dan lakukan rotasi tanaman untuk mencegah infeksi ulang",
R.drawable.lateblight
),
Disease(
"Bercak Bakteri",
"Menyebabkan bercak hitam pada daun dan buah, cegah dengan benih sehat dan sanitasi.",
"Bakteri Xanthomonas campestris pv. vesicatoria, yang menyebar melalui air, angin, dan alat pertanian.",
"Bercak hitam kecil muncul pada daun, batang, dan buah.",
"Merusak jaringan daun, Menghambat fotositesis dan Menurunkan kualitas buah,",
"Gunakan benih sehat, jaga sanitasi, dan gunakan bakterisida.",
R.drawable.bercakbakteri
),
Disease(
"Tanaman Sehat",
"Tidak ditemukan penyakit pada daun ini.",
"tidak ada",
"tidak ada",
"tidak ada",
"tidak ada",
R.drawable.sehat
)
)
fun getData(): List<Disease> {
return classList
}
fun findDiseaseByName(name: String): Disease? {
return classList.find { it.name.equals(name, ignoreCase = true) }
}
}
object DiseaseMatcher {
fun matchDisease(className: String): Disease? {
return when (className) {
"bacterial_spot" -> DiseaseRepository.classList.find { it.name.contains("Bercak Bakteri", ignoreCase = true) }
"healthy" -> DiseaseRepository.classList.find { it.name.contains("Tanaman Sehat", ignoreCase = true) }
"late_blight" -> DiseaseRepository.classList.find { it.name.contains("Hawar Daun", ignoreCase = true) }
"leaf_curl_virus" -> DiseaseRepository.classList.find { it.name.contains("Virus Keriting Daun", ignoreCase = true) }
"leaf_mold" -> DiseaseRepository.classList.find { it.name.contains("Jamur Daun", ignoreCase = true) }
"mosaic_virus" -> DiseaseRepository.classList.find { it.name.contains("Virus Mozaik", ignoreCase = true) }
"septoria_leaf_spot" -> DiseaseRepository.classList.find { it.name.contains("Bercak Daun Septoria", ignoreCase = true) }
else -> null
}
}
}

View File

@ -0,0 +1,431 @@
package com.example.tomatoleafcare.ui.camera
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Debug
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import com.example.tomatoleafcare.DiseaseDetailFragment
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.repository.DiseaseMatcher
import com.example.tomatoleafcare.model.History
import com.example.tomatoleafcare.databinding.CameraFragmentBinding
import com.example.tomatoleafcare.helper.HistoryDatabaseHelper
import okhttp3.*
import org.json.JSONObject
import java.io.*
import java.nio.channels.FileChannel
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import android.os.Handler
import android.os.Looper
import androidx.core.content.ContextCompat
import android.util.Base64
import android.util.Log
import okhttp3.RequestBody.Companion.toRequestBody
import org.tensorflow.lite.Interpreter
import java.nio.MappedByteBuffer
import kotlin.math.roundToInt
class CameraFragment : Fragment() {
private var _binding: CameraFragmentBinding? = null
private val binding get() = _binding ?: throw IllegalStateException("Binding belum diinisialisasi")
private lateinit var imageUri: Uri
private lateinit var photoFile: File
private val classLabels = arrayOf(
"bacterial_spot",
"healthy",
"late_blight",
"leaf_curl_virus",
"leaf_mold",
"mosaic_virus",
"septoria_leaf_spot"
)
private val labelKelas = arrayOf(
"Bercak Bakteri",
"Daun Sehat",
"Hawar Daun",
"Virus Keriting",
"Jamur Daun",
"Virus Mozaik",
"Bercak Daun Septoria"
)
private var apiAvailable = false
private val credentials = "rahasia:tomat"
private val basicAuth = "Basic " + Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP)
private val galleryLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
try {
val inputStream = requireContext().contentResolver.openInputStream(it)
val tempFile = File.createTempFile("gallery_", ".jpg", requireContext().cacheDir)
val outputStream = FileOutputStream(tempFile)
inputStream?.copyTo(outputStream)
inputStream?.close()
outputStream.close()
imageUri = Uri.fromFile(tempFile)
binding.imageView.setImageURI(imageUri)
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(requireContext(), "Gagal memuat gambar dari galeri", Toast.LENGTH_SHORT).show()
}
}
}
private val cameraLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
binding.imageView.setImageURI(imageUri)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = CameraFragmentBinding.inflate(inflater, container, false)
binding.btnKamera.setOnClickListener {
requestCameraPermissionLauncher.launch(android.Manifest.permission.CAMERA)
}
binding.btnGaleri.setOnClickListener {
openGallery()
}
binding.btnPindai.setOnClickListener {
if (::imageUri.isInitialized) {
if (apiAvailable) {
sendImageToApi(imageUri)
} else {
runLocalModel(imageUri, requireContext())
}
} else {
Toast.makeText(requireContext(), "Silakan pilih gambar terlebih dahulu", Toast.LENGTH_SHORT).show()
}
}
handler.post(updateNetworkStatusRunnable)
return binding.root
}
private fun openGallery() {
galleryLauncher.launch("image/*")
}
private fun openCamera() {
try {
photoFile = File.createTempFile("photo_", ".jpg", requireContext().cacheDir)
imageUri = FileProvider.getUriForFile(requireContext(), "${requireContext().packageName}.fileprovider", photoFile)
cameraLauncher.launch(imageUri)
} catch (e: IOException) {
Toast.makeText(requireContext(), "Gagal membuat file foto", Toast.LENGTH_SHORT).show()
e.printStackTrace()
}
}
private val requestCameraPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
openCamera()
} else {
Toast.makeText(requireContext(), "Izin kamera diperlukan untuk fitur ini", Toast.LENGTH_SHORT).show()
}
}
private fun runLocalModel(uri: Uri, context: Context) {
// val startTime = System.currentTimeMillis()
// val beforeMemory = Debug.getNativeHeapAllocatedSize() / 1024.0
val model = Interpreter(loadModelFile("ResNet-50_tomato-leaf-disease.tflite", context))
val inputStream = context.contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(inputStream)
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, 224, 224, true)
val input = Array(1) { Array(224) { Array(224) { FloatArray(3) } } }
for (y in 0 until 224) {
for (x in 0 until 224) {
val pixel = resizedBitmap.getPixel(x, y)
input[0][y][x][0] = Color.red(pixel).toFloat()
input[0][y][x][1] = Color.green(pixel).toFloat()
input[0][y][x][2] = Color.blue(pixel).toFloat()
}
}
val output = Array(1) { FloatArray(classLabels.size) }
model.run(input, output)
val probabilities = output[0]
// Softmax normalization (optional, if model doesn't do it)
val expValues = probabilities.map { Math.exp(it.toDouble()) }
val sumExp = expValues.sum()
val softmax = expValues.map { (it / sumExp).toFloat() }
val predictedIndex = probabilities.indices.maxByOrNull { probabilities[it] } ?: -1
val prediction = classLabels[predictedIndex]
val resultBuilder = StringBuilder()
resultBuilder.append("Confidence:\n")
for (i in labelKelas.indices) {
val label = labelKelas[i]
val confidence = softmax[i] * 100
resultBuilder.append("$label: ${"%.2f".format(confidence)}%\n")
}
// val endTime = System.currentTimeMillis()
// val afterMemory = Debug.getNativeHeapAllocatedSize() / 1024.0
// val duration = endTime - startTime
// val memoryUsed = (afterMemory - beforeMemory).roundToInt()
// Log.d("MODEL_RESULT", resultBuilder.toString())
val confidenceText = resultBuilder.toString()
//Toast.makeText(context, "Local: $prediction\nTime: $duration ms\nMemory: $memoryUsed KB", Toast.LENGTH_LONG).show()
// Toast.makeText(context, "Local: $prediction", Toast.LENGTH_LONG).show()
// Log.d("MODEL_METRICS", "Local - Time: $duration ms, Memory: $memoryUsed KB")
navigateToNoteFragment(prediction, uri, confidenceText)
}
private fun loadModelFile(modelName: String, context: Context): MappedByteBuffer {
val fileDescriptor = context.assets.openFd(modelName)
val inputStream = FileInputStream(fileDescriptor.fileDescriptor)
val fileChannel = inputStream.channel
val startOffset = fileDescriptor.startOffset
val declaredLength = fileDescriptor.declaredLength
return fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength)
}
private fun sendImageToApi(uri: Uri) {
// val startTime = System.currentTimeMillis()
// val beforeMemory = Debug.getNativeHeapAllocatedSize() / 1024.0
val contentResolver = requireContext().contentResolver
val inputStream = contentResolver.openInputStream(uri) ?: return
val imageBytes = inputStream.readBytes()
val requestBody = imageBytes.toRequestBody("image/*".toMediaTypeOrNull(), 0)
val body = MultipartBody.Part.createFormData("image", "image.jpg", requestBody)
val requestBodyMultipart = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(body)
.build()
val client = OkHttpClient()
val request = Request.Builder()
.url("http://robotika.upnvj.ac.id:8000/tomatoleafcare/classify")
// .url("http://api.simdoks.web.id:5000/tomatoleafcare/classify")
.post(requestBodyMultipart)
.addHeader("Authorization", basicAuth)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
requireActivity().runOnUiThread {
Toast.makeText(requireContext(), "Gagal terhubung ke server", Toast.LENGTH_SHORT).show()
}
}
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body?.string()
if (!response.isSuccessful || responseBody == null) {
requireActivity().runOnUiThread {
Toast.makeText(requireContext(), "Respons tidak valid dari server", Toast.LENGTH_SHORT).show()
}
return
}
try {
val jsonObject = JSONObject(responseBody)
val predictedClassObj = jsonObject.getJSONObject("predictedClass")
val prediction = predictedClassObj.getString("class")
val confidencesArray = predictedClassObj.getJSONArray("confidences")
val confidenceTextBuilder = StringBuilder()
confidenceTextBuilder.append("\nConfidence:\n")
for (i in 0 until confidencesArray.length()) {
val item = confidencesArray.getJSONObject(i)
val className = item.getString("class")
val confidence = item.getDouble("confidence")
confidenceTextBuilder.append("$className: ${"%.2f".format(confidence)}%\n")
}
val confidenceText = confidenceTextBuilder.toString()
// val endTime = System.currentTimeMillis()
// val duration = endTime - startTime
// val afterMemory = Debug.getNativeHeapAllocatedSize() / 1024.0
// val memoryUsed = (afterMemory - beforeMemory).roundToInt()
requireActivity().runOnUiThread {
// Toast.makeText(requireContext(), "Online: $prediction\nTime: $duration ms\nMemory: $memoryUsed KB", Toast.LENGTH_LONG).show()
// Toast.makeText(requireContext(), "Online: $prediction", Toast.LENGTH_LONG).show()
// Log.d("MODEL_METRICS", "Online - Time: $duration ms, Memory: $memoryUsed KB")
navigateToNoteFragment(prediction, uri, confidenceText)
}
} catch (e: Exception) {
Log.e("PARSE_ERROR", e.message ?: "Unknown error")
Log.e("RAW_JSON", responseBody ?: "No body")
e.printStackTrace()
requireActivity().runOnUiThread {
Toast.makeText(requireContext(), "Gagal parsing data", Toast.LENGTH_SHORT).show()
}
}
}
})
}
private val handler = Handler(Looper.getMainLooper())
private val updateInterval: Long = 1000
private val updateNetworkStatusRunnable = object : Runnable {
override fun run() {
checkApiAvailability()
handler.postDelayed(this, updateInterval)
}
}
private fun checkApiAvailability() {
val client = OkHttpClient()
val request = Request.Builder()
.url("http://robotika.upnvj.ac.id:8000/tomatoleafcare/check")
// .url("http://api.simdoks.web.id:5000/tomatoleafcare/check")
.get()
.addHeader("Authorization", basicAuth)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
apiAvailable = false
updateApiStatus()
Log.e("API_RESPONSE", "Request failed: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
apiAvailable = response.isSuccessful
updateApiStatus()
}
})
}
private fun updateApiStatus() {
if (_binding == null || !isAdded) return
requireActivity().runOnUiThread {
_binding?.let { binding ->
if (apiAvailable) {
binding.textStatus.text = getString(R.string.online)
binding.textStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.green))
binding.ledStatus.setBackgroundResource(R.drawable.bg_led_online)
} else {
binding.textStatus.text = getString(R.string.offline)
binding.textStatus.setTextColor(ContextCompat.getColor(requireContext(), R.color.blue))
binding.ledStatus.setBackgroundResource(R.drawable.bg_led_offline)
}
}
}
}
private fun navigateToNoteFragment(
className: String,
imageUri: Uri,
confidenceText: String
) {
confidenceText.lines().forEachIndexed { index, line ->
Log.d("CONFIDENCE_LINE", "[$index] $line")
}
val maxConfidence = confidenceText.lines()
.mapNotNull { line ->
Regex("[a-zA-Z_]+:\\s*([\\d.,]+)%").find(line)
?.groupValues?.get(1)
?.replace(",", ".")
?.toFloatOrNull()
}
.maxOrNull() ?: 0f
val isConfident = maxConfidence >= 70f
Log.d("ParsedConfidence", "confidence: $confidenceText")
Log.d("ParsedConfidence", "Max confidence: $maxConfidence")
// matchedDisease hanya dipakai jika confidence cukup
val matchedDisease = if (isConfident) DiseaseMatcher.matchDisease(className) else null
// Simpan ke database hanya jika confidence >= 70%
if (isConfident) {
val dbHelper = HistoryDatabaseHelper(requireContext())
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
val now = formatter.format(Date())
val historyItem = History(
diseaseName = matchedDisease?.name ?: "Unknown",
date = now,
imagePath = imageUri.toString()
)
dbHelper.insertHistory(historyItem)
}
// Siapkan Bundle untuk dikirim ke DiseaseDetailFragment
val bundle = Bundle().apply {
if (isConfident) {
putString("disease_name", matchedDisease?.name)
putString("disease_description", matchedDisease?.description)
putString("disease_symptoms", matchedDisease?.symptoms)
putString("disease_causes", matchedDisease?.cause)
putString("disease_impact", matchedDisease?.impact)
putString("disease_solution", matchedDisease?.solution)
} else {
putString("disease_name", "Penyakit daun tidak terdeteksi")
putString("disease_description", "-")
putString("disease_symptoms", "-")
putString("disease_causes", "-")
putString("disease_impact", "-")
putString("disease_solution", "-")
}
putString("image_uri", imageUri.toString())
putString("confidence_text", confidenceText)
}
// Navigasi ke DiseaseDetailFragment
val noteFragment = DiseaseDetailFragment().apply {
arguments = bundle
}
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, noteFragment)
.addToBackStack(null)
.commit()
}
override fun onDestroyView() {
super.onDestroyView()
handler.removeCallbacks(updateNetworkStatusRunnable)
_binding = null
}
}

View File

@ -0,0 +1,73 @@
package com.example.tomatoleafcare.ui.history
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.repository.DiseaseRepository
import com.example.tomatoleafcare.model.History
import com.example.tomatoleafcare.ui.home.MainActivity
import com.squareup.picasso.Picasso
class HistoryDetailFragment : Fragment() {
private var history: History? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.history_detail_fragment, container, false)
(activity as? MainActivity)?.showBottomNav(false)
val btnBack = view.findViewById<ImageView>(R.id.btnBack)
btnBack.setOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
history = arguments?.getParcelable<History>("history") as? History
history?.let {
showDetails(view, it)
}
return view
}
private fun showDetails(view: View, history: History) {
val detailDiseaseName = view.findViewById<TextView>(R.id.detailDiseaseName)
val detailDiseaseCauses = view.findViewById<TextView>(R.id.detailDiseaseCauses)
val detailDiseaseSymptoms = view.findViewById<TextView>(R.id.detailDiseaseSymptoms)
val detailDiseaseImpact = view.findViewById<TextView>(R.id.detailDiseaseImpact)
val detailDiseaseSolution = view.findViewById<TextView>(R.id.detailDiseaseSolution)
val detailDiseaseImage = view.findViewById<ImageView>(R.id.detailDiseaseImage)
Picasso.get().load(history.imagePath).into(detailDiseaseImage)
detailDiseaseName.text = history.diseaseName
val matchedDisease = DiseaseRepository.findDiseaseByName(history.diseaseName)
matchedDisease?.let {
detailDiseaseCauses.text = it.cause
detailDiseaseSymptoms.text = it.symptoms
detailDiseaseImpact.text = it.impact
detailDiseaseSolution.text = it.solution
} ?: run {
detailDiseaseCauses.text = "-"
detailDiseaseSymptoms.text = "-"
detailDiseaseImpact.text = "-"
detailDiseaseSolution.text = "-"
}
}
override fun onDestroyView() {
super.onDestroyView()
(activity as? MainActivity)?.showBottomNav(true)
}
}

View File

@ -0,0 +1,64 @@
package com.example.tomatoleafcare.ui.history
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.ui.adapter.HistoryAdapter
import com.example.tomatoleafcare.viewmodel.HistoryViewModel
class HistoryFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var historyAdapter: HistoryAdapter
private val viewModel: HistoryViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.history_fragment, container, false)
recyclerView = view.findViewById(R.id.recyclerViewHistory)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
historyAdapter = HistoryAdapter(
context = requireContext(),
historyList = listOf(),
onItemClick = { history ->
val bundle = Bundle()
bundle.putParcelable("history", history)
val detailFragment = HistoryDetailFragment().apply {
arguments = bundle
}
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, detailFragment)
.addToBackStack(null)
.commit()
},
onDeleteClick = { item ->
viewModel.deleteHistoryById(item.id.toInt())
}
)
recyclerView.adapter = historyAdapter
observeViewModel()
return view
}
private fun observeViewModel() {
viewModel.historyList.observe(viewLifecycleOwner, Observer { list ->
historyAdapter.updateData(list)
})
}
}

View File

@ -0,0 +1,14 @@
package com.example.tomatoleafcare.ui.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.tomatoleafcare.R
class CardSlide1Fragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.card_slide_1, container, false)
}
}

View File

@ -0,0 +1,14 @@
package com.example.tomatoleafcare.ui.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.tomatoleafcare.R
class CardSlide2Fragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.card_slide_2, container, false)
}
}

View File

@ -0,0 +1,123 @@
package com.example.tomatoleafcare.ui.home
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.example.tomatoleafcare.DiseaseDetailFragment
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.adapter.DiseaseAdapter
import com.example.tomatoleafcare.adapter.SliderAdapter
import com.example.tomatoleafcare.model.Disease
import com.example.tomatoleafcare.repository.DiseaseRepository
import com.example.tomatoleafcare.ui.note.NoteFragment
import com.google.android.material.bottomnavigation.BottomNavigationView
class HomeFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: DiseaseAdapter
private lateinit var viewPager: ViewPager2
private val sliderHandler = Handler(Looper.getMainLooper())
private val sliderRunnable = object : Runnable {
override fun run() {
val itemCount = viewPager.adapter?.itemCount ?: 0
if (itemCount > 0) {
val nextItem = (viewPager.currentItem + 1) % itemCount
viewPager.setCurrentItem(nextItem, true)
sliderHandler.postDelayed(this, 10000)
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.home_fragment, container, false)
val classList = DiseaseRepository.classList.filter { it.name != "Tanaman Sehat" }
val topThreeDiseases = classList.take(3)
viewPager = view.findViewById(R.id.viewPager)
viewPager.adapter = SliderAdapter(this)
val space = resources.getDimensionPixelSize(R.dimen.slider_gap)
viewPager.addItemDecoration(SliderItemDecoration(space))
recyclerView = view.findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
val seeMoreText: TextView = view.findViewById(R.id.seeMoreText)
seeMoreText.paintFlags = seeMoreText.paintFlags or Paint.UNDERLINE_TEXT_FLAG
seeMoreText.setOnClickListener {
val noteFragment = NoteFragment()
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, noteFragment)
.addToBackStack(null)
.commit()
val bottomNavigationView = requireActivity().findViewById<BottomNavigationView>(R.id.bottomNavigationView)
bottomNavigationView.selectedItemId = R.id.navigation_note
}
adapter = DiseaseAdapter(topThreeDiseases, object : DiseaseAdapter.OnItemClickListener {
override fun onItemClick(disease: Disease) {
val detailFragment = DiseaseDetailFragment().apply {
arguments = Bundle().apply {
putString("disease_name", disease.name)
putString("disease_description", disease.description)
putString("disease_symptoms", disease.symptoms)
putString("disease_causes", disease.cause)
putString("disease_impact", disease.impact)
putString("disease_solution", disease.solution)
putInt("disease_image", disease.imageResId)
}
}
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, detailFragment)
.addToBackStack(null)
.commit()
}
})
recyclerView.adapter = adapter
return view
}
override fun onResume() {
super.onResume()
sliderHandler.postDelayed(sliderRunnable, 1000)
}
override fun onPause() {
super.onPause()
sliderHandler.removeCallbacks(sliderRunnable)
}
class SliderItemDecoration(private val space: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
outRect.left = space
outRect.right = space
}
}
}

View File

@ -0,0 +1,49 @@
package com.example.tomatoleafcare.ui.home
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.databinding.ActivityMainBinding
import com.example.tomatoleafcare.ui.note.NoteFragment
import com.example.tomatoleafcare.ui.camera.CameraFragment
import com.example.tomatoleafcare.ui.history.HistoryFragment
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (savedInstanceState == null) {
replaceFragment(HomeFragment())
}
binding.bottomNavigationView.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> replaceFragment(HomeFragment())
R.id.navigation_camera -> replaceFragment(CameraFragment())
R.id.navigation_note -> replaceFragment(NoteFragment())
R.id.navigation_history -> replaceFragment(HistoryFragment())
}
true
}
}
fun showBottomNav(show: Boolean) {
val bottomNav = findViewById<View>(R.id.bottomNavigationView)
bottomNav.visibility = if (show) View.VISIBLE else View.GONE
}
private fun replaceFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
}
}

View File

@ -0,0 +1,104 @@
package com.example.tomatoleafcare
import android.app.AlertDialog
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.example.tomatoleafcare.ui.home.MainActivity
import com.example.tomatoleafcare.viewmodel.DiseaseViewModel
import com.google.android.material.imageview.ShapeableImageView
class DiseaseDetailFragment : Fragment() {
private val diseaseViewModel: DiseaseViewModel by activityViewModels()
private lateinit var imageView: ShapeableImageView
private lateinit var nameTextView: TextView
private lateinit var causesTextView: TextView
private lateinit var symptomsTextView: TextView
private lateinit var impactTextView: TextView
private lateinit var solutionTextView: TextView
private lateinit var backButton: ImageButton
private lateinit var infoConfidenceButton: ImageButton
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.disease_detail_fragment, container, false)
(activity as? MainActivity)?.showBottomNav(false)
imageView = view.findViewById(R.id.detailDiseaseImage)
nameTextView = view.findViewById(R.id.detailDiseaseName)
causesTextView = view.findViewById(R.id.detailDiseaseCauses)
symptomsTextView = view.findViewById(R.id.detailDiseaseSymptoms)
impactTextView = view.findViewById(R.id.detailDiseaseImpact)
solutionTextView = view.findViewById(R.id.detailDiseaseSolution)
backButton = view.findViewById(R.id.btnBack)
infoConfidenceButton = view.findViewById(R.id.infoConfidenceButton)
val diseaseName = arguments?.getString("disease_name")
val imageUriString = arguments?.getString("image_uri")
val confidenceText = arguments?.getString("confidence_text")
if (!imageUriString.isNullOrEmpty()) {
val imageUri = Uri.parse(imageUriString)
imageView.setImageURI(imageUri)
}
if (diseaseName?.trim()?.lowercase() == "penyakit daun tidak terdeteksi") {
// Jangan load data dari ViewModel
nameTextView.text = "Tidak terdeteksi"
causesTextView.text = "-"
symptomsTextView.text = "-"
impactTextView.text = "-"
solutionTextView.text = "-"
} else {
// Load dan tampilkan data penyakit
diseaseViewModel.loadDiseaseByName(diseaseName ?: "")
diseaseViewModel.disease.observe(viewLifecycleOwner, Observer { disease ->
if (disease != null) {
nameTextView.text = disease.name
causesTextView.text = disease.cause
symptomsTextView.text = disease.symptoms
impactTextView.text = disease.impact
solutionTextView.text = disease.solution
if (imageUriString.isNullOrEmpty()) {
imageView.setImageResource(disease.imageResId)
}
}
})
}
infoConfidenceButton.setOnClickListener {
AlertDialog.Builder(requireContext())
.setTitle("Confidence per Label")
.setMessage(confidenceText ?: "Confidence tidak tersedia.")
.show()
}
backButton.setOnClickListener {
requireActivity().supportFragmentManager.popBackStack()
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
(activity as? MainActivity)?.showBottomNav(true)
diseaseViewModel.clearDisease()
}
}

View File

@ -0,0 +1,50 @@
package com.example.tomatoleafcare.ui.note
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.tomatoleafcare.DiseaseDetailFragment
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.adapter.DiseaseAdapter
import com.example.tomatoleafcare.repository.DiseaseRepository
import com.example.tomatoleafcare.model.Disease
import com.example.tomatoleafcare.viewmodel.DiseaseViewModel
class NoteFragment : Fragment() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: DiseaseAdapter
private val viewModel: DiseaseViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.note_fragment, container, false)
val classList = DiseaseRepository.classList.filter { it.name != "Tanaman Sehat" }
recyclerView = view.findViewById(R.id.recyclerViewNote)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
adapter = DiseaseAdapter(classList, object : DiseaseAdapter.OnItemClickListener {
override fun onItemClick(disease: Disease) {
viewModel.loadDiseaseByName(disease.name)
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DiseaseDetailFragment())
.addToBackStack(null)
.commit()
}
})
recyclerView.adapter = adapter
return view
}
}

View File

@ -0,0 +1,21 @@
package com.example.tomatoleafcare.ui.splash
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
import com.example.tomatoleafcare.R
import com.example.tomatoleafcare.ui.home.MainActivity
class SplashActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.splash_screen)
Handler(Looper.getMainLooper()).postDelayed({
startActivity(Intent(this, MainActivity::class.java))
finish()
}, 2000)
}
}

View File

@ -0,0 +1,11 @@
package com.example.tomatoleafcare.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)

View File

@ -0,0 +1,58 @@
package com.example.tomatoleafcare.ui.theme
import android.app.Activity
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
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun TomatoLeafCareTheme(
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
)
}

View File

@ -0,0 +1,34 @@
package com.example.tomatoleafcare.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
)
*/
)

View File

@ -0,0 +1,25 @@
package com.example.tomatoleafcare.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.tomatoleafcare.model.Disease
import com.example.tomatoleafcare.repository.DiseaseRepository
class DiseaseViewModel : ViewModel() {
private val _disease = MutableLiveData<Disease?>()
val disease: MutableLiveData<Disease?> get() = _disease
fun loadDiseaseByName(name: String) {
val result = DiseaseRepository.findDiseaseByName(name)
result?.let {
_disease.value = it
}
}
fun clearDisease() {
_disease.value = null
}
}

View File

@ -0,0 +1,37 @@
package com.example.tomatoleafcare.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.tomatoleafcare.helper.HistoryDatabaseHelper
import com.example.tomatoleafcare.model.History
class HistoryViewModel(application: Application) : AndroidViewModel(application) {
private val dbHelper = HistoryDatabaseHelper(application)
private val _historyList = MutableLiveData<List<History>>()
val historyList: LiveData<List<History>> get() = _historyList
init {
loadAllHistory()
}
fun loadAllHistory() {
val allHistory = dbHelper.getAllHistory()
_historyList.postValue(allHistory)
}
fun deleteHistoryById(id: Int) {
val success = dbHelper.deleteHistory(id.toLong())
if (success) {
loadAllHistory()
}
}
fun insertHistory(item: History) {
dbHelper.insertHistory(item)
loadAllHistory()
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/active_color" android:state_checked="true"/>
<item android:color="@color/inactive_color"/>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#0000FF"/> <!-- Biru -->
<size android:width="16dp" android:height="16dp"/>
</shape>

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#00FF00"/> <!-- Hijau -->
<size android:width="16dp" android:height="16dp"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

View File

@ -0,0 +1,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white" />
<stroke
android:width="1dp"
android:color="@color/green_primary" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="16dp"/>
<solid android:color="@android:color/white"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="70dp"
app:menu="@menu/nav_menu"
app:itemIconTint="@color/nav_item_color"
app:itemBackground="@color/light_green"
app:itemActiveIndicatorStyle="@color/light_green"
/>
</LinearLayout>

View File

@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFDEB">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:layout_marginBottom="8dp">
<ImageView
android:id="@+id/logoImage"
android:layout_width="210dp"
android:layout_height="54dp"
android:src="@drawable/tomatoleafcare"
android:contentDescription="@string/logo"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"/>
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true">
<View
android:id="@+id/ledStatus"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="6dp"
android:background="@drawable/bg_led_offline"
android:layout_gravity="center_vertical"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp" />
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/offline"
android:textColor="@color/blue"
android:textSize="14sp"
android:textStyle="bold"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"/>
</LinearLayout>
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/judul"
android:textAlignment="center"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="#3C553E"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_corner_bg"
android:padding="12dp"
android:layout_marginBottom="12dp"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/penjelasan"
android:textSize="14sp"
android:justificationMode="inter_word"
android:textColor="#3C3C3C" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginBottom="12dp"
android:paddingHorizontal="12dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnKamera"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/kamera"
app:icon="@drawable/ic_camera"
app:iconPadding="8dp"
app:iconGravity="textStart"
app:iconTint="@color/black"
android:textColor="@color/black"
app:cornerRadius="12dp"
app:backgroundTint="@color/light_green"
android:layout_marginEnd="8dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnGaleri"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/galeri"
android:textColor="@color/black"
app:backgroundTint="@color/light_green"
app:cornerRadius="12dp"
app:icon="@drawable/ic_galeri"
app:iconGravity="textStart"
app:iconPadding="8dp"
app:iconTint="@color/black"
android:layout_marginStart="8dp" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:background="#D1D1D1">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/placeholder"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:contentDescription="@string/placeholder_gambar" />
</FrameLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPindai"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pindai"
app:icon="@drawable/ic_pindai"
app:iconPadding="8dp"
app:iconGravity="textStart"
app:iconTint="@color/black"
android:textColor="@color/black"
app:cornerRadius="12dp"
app:backgroundTint="@color/light_green"/>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:cardCornerRadius="12dp"
app:cardBackgroundColor="@color/yellow_accent"
android:layout_margin="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="180dp">
<TextView
android:id="@+id/textTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_marginBottom="8dp"
android:padding="20dp"
android:text="@string/tanam_rawat_nikmati"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textSubtitle"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_below="@id/textTitle"
android:layout_alignStart="@id/textTitle"
android:layout_marginStart="1dp"
android:layout_marginTop="-4dp"
android:padding="20dp"
android:text="@string/quotes"
android:textColor="@color/black"
android:textSize="14sp" />
<ImageView
android:id="@+id/imageTomato"
android:layout_width="148dp"
android:layout_height="120dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:contentDescription="@string/gambar_tomat"
android:scaleType="centerCrop"
android:src="@drawable/tomato" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="16dp"
android:background="@color/green_primary"
app:cardElevation="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:background="@color/green_primary">
<TextView
android:id="@+id/textTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tanam_rawat_panen"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
android:padding="10dp"
android:layout_marginBottom="10dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.cardview.widget.CardView
android:id="@+id/cardStep1"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="4dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:layout_constraintTop_toBottomOf="@id/textTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/cardStep2"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/yellow_accent"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tanam"
android:textColor="@android:color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Pilih bibit unggul untuk hasil terbaik"
android:textColor="@color/black"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/cardStep2"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:layout_constraintTop_toBottomOf="@id/textTitle"
app:layout_constraintStart_toEndOf="@id/cardStep1"
app:layout_constraintEnd_toStartOf="@+id/cardStep3"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/yellow_accent"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Jaga"
android:textColor="@android:color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Siram secara teratur dan rutin"
android:textColor="@color/black"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/cardStep3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:layout_constraintTop_toBottomOf="@id/textTitle"
app:layout_constraintStart_toEndOf="@id/cardStep2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/yellow_accent"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Panen"
android:textColor="@android:color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="Nikmati tomat segar dan sehat"
android:textColor="@color/black"
android:textSize="12sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/cream_background"
android:padding="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/logoImage"
android:layout_width="210dp"
android:layout_height="54dp"
android:src="@drawable/tomatoleafcare"
android:contentDescription="@string/logotomatoleafcare"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageButton
android:id="@+id/btnBack"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/kembali"
android:src="@drawable/ic_close"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/green_primary" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cream_background"
android:scrollbars="none"
android:padding="16dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/detailDiseaseImage"
android:layout_width="match_parent"
android:layout_height="300dp"
android:contentDescription="@string/disease_image"
android:scaleType="centerCrop"
app:shapeAppearanceOverlay="@style/CornerImageStyle" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp"
app:strokeColor="@color/green_primary"
app:strokeWidth="1dp"
android:backgroundTint="@android:color/white">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<!-- Nama Penyakit (Tetap di Tengah) -->
<TextView
android:id="@+id/detailDiseaseName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nama_penyakit"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/green_primary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Tombol Info (i) di Pojok Kanan Atas -->
<ImageButton
android:id="@+id/infoConfidenceButton"
android:layout_width="28dp"
android:layout_height="28dp"
android:background="@drawable/ic_circle_background"
android:src="@drawable/ic_info"
android:scaleType="centerInside"
android:tint="@color/green_primary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="UseAppTint" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/penyebab"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary" />
<TextView
android:id="@+id/detailDiseaseCauses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/penyebab"
android:justificationMode="inter_word"
android:textColor="@color/black" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gejala"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary"
android:paddingTop="12dp" />
<TextView
android:id="@+id/detailDiseaseSymptoms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:textColor="@color/black"
android:text="@string/gejala" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dampak"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary"
android:paddingTop="12dp" />
<TextView
android:id="@+id/detailDiseaseImpact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:textColor="@color/black"
android:text="@string/dampak" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/solusi"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary"
android:paddingTop="12dp" />
<TextView
android:id="@+id/detailDiseaseSolution"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:textColor="@color/black"
android:text="@string/solusi" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardBackgroundColor="@color/green_primary"
app:cardCornerRadius="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/diseaseImage"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginEnd="16dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder"
android:contentDescription="@string/gambar_penyakit_daun"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginEnd="2dp">
<TextView
android:id="@+id/diseaseName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nama_penyakit"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="@color/yellow_accent"/>
<TextView
android:id="@+id/diseaseDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deskripsi_singkat_penyakit"
android:justificationMode="inter_word"
android:textSize="12sp"
android:textColor="@color/white"
android:layout_marginTop="4dp"
android:paddingEnd="2dp"
android:layout_marginBottom="2dp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/cream_background"
android:padding="16dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/logoImage"
android:layout_width="210dp"
android:layout_height="54dp"
android:src="@drawable/tomatoleafcare"
android:contentDescription="@string/logotomatoleafcare"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageButton
android:id="@+id/btnBack"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/ic_close"
android:contentDescription="@string/kembali"
android:background="?attr/selectableItemBackgroundBorderless"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/green_primary" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cream_background"
android:scrollbars="none"
android:padding="16dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/detailDiseaseImage"
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="centerCrop"
android:contentDescription="@string/disease_image"
app:shapeAppearanceOverlay="@style/CornerImageStyle" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp"
app:strokeColor="@color/green_primary"
app:strokeWidth="1dp"
android:backgroundTint="@android:color/white">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/detailDiseaseName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/nama_penyakit"
android:textSize="22sp"
android:textStyle="bold"
android:textAlignment="center"
android:textColor="@color/green_primary" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/penyebab"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary"
android:paddingTop="12dp" />
<TextView
android:id="@+id/detailDiseaseCauses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/penyebab"
android:justificationMode="inter_word"
android:textColor="@color/black" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gejala"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary"
android:paddingTop="12dp" />
<TextView
android:id="@+id/detailDiseaseSymptoms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:textColor="@color/black"
android:text="@string/gejala" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dampak"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary"
android:paddingTop="12dp" />
<TextView
android:id="@+id/detailDiseaseImpact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:textColor="@color/black"
android:text="@string/dampak" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/solusi"
android:textStyle="bold"
android:textSize="18sp"
android:textColor="@color/green_primary"
android:paddingTop="12dp" />
<TextView
android:id="@+id/detailDiseaseSolution"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:justificationMode="inter_word"
android:textColor="@color/black"
android:text="@string/solusi" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/history_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/cream_background"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="15dp">
<ImageView
android:layout_width="210dp"
android:layout_height="54dp"
android:src="@drawable/tomatoleafcare"
android:contentDescription="@string/logotomatoleafcare" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewHistory"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardBackgroundColor="@color/green_primary"
app:cardCornerRadius="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:id="@+id/imageHistory"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="12dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder"
android:contentDescription="@string/gambar_riwayat_penyakit" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/txtDiseaseName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nama_penyakit"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="@color/yellow_accent" />
<TextView
android:id="@+id/txtDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tanggal"
android:textSize="12sp"
android:textColor="@color/white"
android:layout_marginTop="4dp" />
</LinearLayout>
<ImageButton
android:id="@+id/btnDelete"
android:layout_marginEnd="10dp"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_delete"
android:contentDescription="@string/hapus_riwayat"
android:background="@android:color/transparent"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
app:tint="@android:color/white" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/cream_background"
tools:context=".ui.home.HomeFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="210dp"
android:layout_height="54dp"
android:src="@drawable/tomatoleafcare"
android:contentDescription="@string/logotomatoleafcare" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:gravity="center_vertical"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/jenis_penyakit_daun_tomat"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/seeMoreText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/selengkapnya"
android:clickable="true"
android:focusable="true"
android:textColor="@color/green_primary"
android:textSize="14sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingRight="8dp"
android:paddingLeft="8dp"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cream_background"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="210dp"
android:layout_height="54dp"
android:src="@drawable/tomatoleafcare"
android:contentDescription="@string/logotomatoleafcare" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewNote"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</LinearLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/green_primary"
android:gravity="center"
android:paddingBottom="20dp">
<androidx.cardview.widget.CardView
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center"
app:cardCornerRadius="20dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/yellow_accent"/>
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:contentDescription="@string/logo"
android:src="@drawable/logo"
android:elevation="8dp"/>
</FrameLayout>

View File

@ -0,0 +1,21 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home"
android:title="Home" />
<item
android:id="@+id/navigation_camera"
android:icon="@drawable/ic_camera"
android:title="Scan" />
<item
android:id="@+id/navigation_note"
android:icon="@drawable/ic_note"
android:title="Note" />
<item
android:id="@+id/navigation_history"
android:icon="@drawable/ic_history"
android:title="History"/>
</menu>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="green_primary">#626F47</color>
<color name="light_green">#A4B465</color>
<color name="yellow_accent">#FFCF50</color>
<color name="cream_background">#FEFAE0</color>
<color name="active_color">#FFCF50</color>
<color name="inactive_color">#626F47</color>
<color name="green">#00FF00</color>
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="slider_gap">16dp</dimen>
</resources>

View File

@ -0,0 +1,32 @@
<resources>
<string name="app_name">Tomato Leaf Care</string>
<string name="placeholder_gambar">Placeholder gambar</string>
<string name="penjelasan">Pastikan foto yang anda kirimkan memiliki kualitas yang cukup jelas dan hindari foto yang buram, terpotong atau terlalu gelap sehingga sulit untuk mengidentifikasi penyakit daun dengan jelas.</string>
<string name="kamera">Kamera</string>
<string name="galeri">Galeri</string>
<string name="logo">Logo</string>
<string name="judul">Masukkan gambar penyakit daun\nyang ingin dideteksi</string>
<string name="pindai">Pindai</string>
<string name="logotomatoleafcare">LogoTomatoLeafCare</string>
<string name="disease_image">Disease Image</string>
<string name="nama_penyakit">Nama Penyakit</string>
<string name="penyebab">Penyebab</string>
<string name="gejala">Gejala</string>
<string name="dampak">Dampak</string>
<string name="solusi">Solusi</string>
<string name="gambar_penyakit_daun">gambar penyakit daun</string>
<string name="deskripsi_singkat_penyakit">Deskripsi singkat penyakit</string>
<string name="gambar_riwayat_penyakit">Gambar riwayat penyakit</string>
<string name="tanggal">Tanggal</string>
<string name="hapus_riwayat">Hapus riwayat</string>
<string name="tanam_rawat_nikmati">🌱 Tanam, Rawat, Nikmati! 🍅</string>
<string name="tanam_rawat_panen">🌱 Tanam, Rawat, Panen! 🍅</string>
<string name="quotes">Rawat tanaman tomatmu dan panen hasil terbaik!</string>
<string name="jenis_penyakit_daun_tomat">Jenis Penyakit Daun Tomat</string>
<string name="online">Online</string>
<string name="offline">Offline</string>
<string name="kembali">backbutton</string>
<string name="gambar_tomat">gambar-tomat</string>
<string name="lihat_lebih_banyak">Lihat lebih banyak</string>
<string name="selengkapnya">selengkapnya</string>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CornerImageStyle">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">16dp</item>
</style>
</resources>

View File

@ -0,0 +1,14 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.TomatoLeafCare" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/green_primary</item>
<item name="colorSecondary">@color/yellow_accent</item>
<item name="android:colorBackground">@color/white</item>
<item name="android:statusBarColor">?attr/colorPrimary</item>
</style>
<style name="Theme.TomatoLeafCare.Splash" parent="Theme.Material3.Light.NoActionBar">
<item name="android:statusBarColor">@color/green_primary</item>
</style>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path
name="cache"
path="." />
</paths>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">robotika.upnvj.ac.id</domain>
</domain-config>
</network-security-config>

View File

@ -0,0 +1,17 @@
package com.example.tomatoleafcare
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)
}
}