v1 commit
This commit is contained in:
parent
7f24f1cae1
commit
1861d14a7f
|
|
@ -13,6 +13,9 @@
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
<DialogSelection />
|
<DialogSelection />
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="release">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -27,6 +27,7 @@ android {
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,11 @@
|
||||||
android:name=".FullScreenImageActivity"
|
android:name=".FullScreenImageActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.AnimalRating" />
|
android:theme="@style/Theme.AnimalRating" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".RatingActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Theme.AnimalRating" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.example.animalrating
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
|
@ -12,10 +13,12 @@ import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Size
|
import android.util.Size
|
||||||
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.camera.core.ExperimentalGetImage
|
||||||
import androidx.camera.core.ImageAnalysis
|
import androidx.camera.core.ImageAnalysis
|
||||||
import androidx.camera.core.ImageCapture
|
import androidx.camera.core.ImageCapture
|
||||||
import androidx.camera.core.ImageCaptureException
|
import androidx.camera.core.ImageCaptureException
|
||||||
|
|
@ -49,11 +52,15 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
private var isPhotoTaken = false
|
private var isPhotoTaken = false
|
||||||
private var matchThreshold = 75
|
private var matchThreshold = 75
|
||||||
private var algorithm = HomeActivity.ALGORITHM_HAMMING
|
private var algorithm = HomeActivity.ALGORITHM_HAMMING
|
||||||
|
private var isAutoCapture = true
|
||||||
|
private var isMaskDisplayEnabled = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
|
StringProvider.initialize(this)
|
||||||
|
|
||||||
cowName = intent.getStringExtra("COW_NAME")
|
cowName = intent.getStringExtra("COW_NAME")
|
||||||
orientation = intent.getStringExtra("ORIENTATION")
|
orientation = intent.getStringExtra("ORIENTATION")
|
||||||
|
|
||||||
|
|
@ -61,6 +68,8 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
||||||
matchThreshold = prefs.getInt("THRESHOLD", 75)
|
matchThreshold = prefs.getInt("THRESHOLD", 75)
|
||||||
algorithm = prefs.getString("ALGORITHM", HomeActivity.ALGORITHM_HAMMING) ?: HomeActivity.ALGORITHM_HAMMING
|
algorithm = prefs.getString("ALGORITHM", HomeActivity.ALGORITHM_HAMMING) ?: HomeActivity.ALGORITHM_HAMMING
|
||||||
|
isAutoCapture = prefs.getBoolean(HomeActivity.PREF_AUTO_CAPTURE, true)
|
||||||
|
isMaskDisplayEnabled = prefs.getBoolean(HomeActivity.PREF_MASK_DISPLAY, false)
|
||||||
|
|
||||||
// Set orientation based on selected view
|
// Set orientation based on selected view
|
||||||
if (orientation == "front" || orientation == "back") {
|
if (orientation == "front" || orientation == "back") {
|
||||||
|
|
@ -77,16 +86,56 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
findViewById<MaterialButton>(R.id.btnExit).setOnClickListener {
|
findViewById<MaterialButton>(R.id.btnExit).setOnClickListener {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val btnShutter = findViewById<MaterialButton>(R.id.btnShutter)
|
||||||
|
val btnToggle = findViewById<MaterialButton>(R.id.btnToggleCaptureMode)
|
||||||
|
|
||||||
|
btnShutter.setOnClickListener {
|
||||||
|
takePhoto()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCaptureModeUI()
|
||||||
|
|
||||||
|
btnToggle.setOnClickListener {
|
||||||
|
isAutoCapture = !isAutoCapture
|
||||||
|
// Save preference for persistence if desired, or just toggle for session
|
||||||
|
prefs.edit().putBoolean(HomeActivity.PREF_AUTO_CAPTURE, isAutoCapture).apply()
|
||||||
|
updateCaptureModeUI()
|
||||||
|
}
|
||||||
|
|
||||||
frameProcessor = FrameProcessor()
|
frameProcessor = FrameProcessor()
|
||||||
|
|
||||||
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
|
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
|
||||||
overlay.setSilhouette(silhouetteId)
|
overlay.setSilhouette(silhouetteId)
|
||||||
|
|
||||||
loadSavedMask()
|
// Need to wait for layout to get width/height for scaling mask correctly
|
||||||
|
savedMaskOverlay.post {
|
||||||
|
loadSavedMask()
|
||||||
|
}
|
||||||
|
|
||||||
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
|
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateCaptureModeUI() {
|
||||||
|
val btnShutter = findViewById<MaterialButton>(R.id.btnShutter)
|
||||||
|
val btnToggle = findViewById<MaterialButton>(R.id.btnToggleCaptureMode)
|
||||||
|
|
||||||
|
if (isAutoCapture) {
|
||||||
|
btnShutter.visibility = View.GONE
|
||||||
|
// Auto Mode: Eye Icon, Dark Background
|
||||||
|
btnToggle.setIconResource(android.R.drawable.ic_menu_view)
|
||||||
|
btnToggle.setIconTintResource(android.R.color.white)
|
||||||
|
btnToggle.setBackgroundColor(Color.parseColor("#6D4C41"))
|
||||||
|
btnToggle.alpha = 1.0f
|
||||||
|
} else {
|
||||||
|
btnShutter.visibility = View.VISIBLE
|
||||||
|
// Manual Mode: Eye Icon (Grey/Transparent) - Requested to keep Eye icon
|
||||||
|
btnToggle.setIconResource(android.R.drawable.ic_menu_view)
|
||||||
|
btnToggle.setIconTintResource(android.R.color.darker_gray)
|
||||||
|
btnToggle.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
btnToggle.alpha = 0.7f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun takePhoto() {
|
private fun takePhoto() {
|
||||||
if (isPhotoTaken) return
|
if (isPhotoTaken) return
|
||||||
|
|
@ -96,15 +145,22 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
|
|
||||||
val name = cowName ?: "unknown"
|
val name = cowName ?: "unknown"
|
||||||
val side = orientation ?: "unknown"
|
val side = orientation ?: "unknown"
|
||||||
|
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
|
||||||
|
|
||||||
// Find current count for this cow and orientation
|
// Find current count for this cow and orientation
|
||||||
val existingFiles = filesDir.listFiles { _, fname ->
|
val cowFolder = StorageUtils.getCowImageFolder(name)
|
||||||
|
|
||||||
|
val existingFiles = cowFolder.listFiles { _, fname ->
|
||||||
fname.startsWith("${name}_${side}_") && fname.endsWith(".jpg")
|
fname.startsWith("${name}_${side}_") && fname.endsWith(".jpg")
|
||||||
}
|
}
|
||||||
val count = (existingFiles?.size ?: 0) + 1
|
val count = (existingFiles?.size ?: 0) + 1
|
||||||
|
|
||||||
|
if (!cowFolder.exists()) {
|
||||||
|
cowFolder.mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
val filename = "${name}_${side}_${count}.jpg"
|
val filename = "${name}_${side}_${count}.jpg"
|
||||||
val file = File(filesDir, filename)
|
val file = File(cowFolder, filename)
|
||||||
|
|
||||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()
|
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()
|
||||||
|
|
||||||
|
|
@ -113,8 +169,8 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
ContextCompat.getMainExecutor(this),
|
ContextCompat.getMainExecutor(this),
|
||||||
object : ImageCapture.OnImageSavedCallback {
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
override fun onError(exc: ImageCaptureException) {
|
override fun onError(exc: ImageCaptureException) {
|
||||||
Log.e("MainActivity", "Photo capture failed: ${exc.message}", exc)
|
Log.e("CameraProcessor", "Photo capture failed: ${exc.message}", exc)
|
||||||
Toast.makeText(baseContext, "Photo capture failed", Toast.LENGTH_SHORT).show()
|
Toast.makeText(baseContext, StringProvider.getString("toast_capture_failed"), Toast.LENGTH_SHORT).show()
|
||||||
isPhotoTaken = false
|
isPhotoTaken = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,17 +189,31 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
|
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val retakePath = intent.getStringExtra("RETAKE_IMAGE_PATH")
|
||||||
|
if (!retakePath.isNullOrEmpty()) {
|
||||||
|
val oldFile = File(retakePath)
|
||||||
|
if (oldFile.exists()) {
|
||||||
|
oldFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save to public gallery
|
val msg = "${StringProvider.getString("toast_saved_as")} $filename"
|
||||||
saveToGallery(file)
|
|
||||||
|
|
||||||
val msg = "Saved as $filename"
|
|
||||||
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
|
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
// Navigate to FullScreenImageActivity
|
||||||
|
val intent = Intent(this@CameraProcessor, FullScreenImageActivity::class.java)
|
||||||
|
intent.putExtra("IMAGE_PATH", file.absolutePath)
|
||||||
|
intent.putExtra("ALLOW_RETAKE", true)
|
||||||
|
intent.putExtra("COW_NAME", cowName)
|
||||||
|
intent.putExtra("ORIENTATION", orientation)
|
||||||
|
intent.putExtra("SILHOUETTE_ID", silhouetteId)
|
||||||
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("MainActivity", "Error saving image", e)
|
Log.e("CameraProcessor", "Error saving image", e)
|
||||||
Toast.makeText(baseContext, "Error saving image", Toast.LENGTH_SHORT).show()
|
Toast.makeText(baseContext, StringProvider.getString("toast_error_saving_image"), Toast.LENGTH_SHORT).show()
|
||||||
isPhotoTaken = false
|
isPhotoTaken = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -152,38 +222,7 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveToGallery(file: File) {
|
private fun saveToGallery(file: File) {
|
||||||
val values = ContentValues().apply {
|
// Removed
|
||||||
put(MediaStore.Images.Media.DISPLAY_NAME, file.name)
|
|
||||||
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/AnimalRating")
|
|
||||||
put(MediaStore.Images.Media.IS_PENDING, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
|
||||||
} else {
|
|
||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val uri = contentResolver.insert(collection, values)
|
|
||||||
uri?.let {
|
|
||||||
contentResolver.openOutputStream(it)?.use { out ->
|
|
||||||
FileInputStream(file).use { input ->
|
|
||||||
input.copyTo(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
values.clear()
|
|
||||||
values.put(MediaStore.Images.Media.IS_PENDING, 0)
|
|
||||||
contentResolver.update(it, values, null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("CameraProcessor", "Error saving to gallery", e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadSavedMask() {
|
private fun loadSavedMask() {
|
||||||
|
|
@ -195,17 +234,50 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
try {
|
try {
|
||||||
val savedBitmap = BitmapFactory.decodeFile(file.absolutePath)
|
val savedBitmap = BitmapFactory.decodeFile(file.absolutePath)
|
||||||
|
|
||||||
// Apply green color filter for visualization (on original size)
|
if (savedBitmap != null) {
|
||||||
val greenMask = applyGreenColor(savedBitmap)
|
// Apply green color filter for visualization
|
||||||
savedMaskOverlay.setImageBitmap(greenMask)
|
if (isMaskDisplayEnabled) {
|
||||||
savedMaskOverlay.alpha = 0.5f
|
val greenMask = applyGreenColor(savedBitmap)
|
||||||
|
|
||||||
// Scale to 640x480 (or flipped for portrait) for comparison
|
// Calculate scale to match FIT_CENTER logic of SilhouetteOverlay
|
||||||
val isPortrait = (side == "front" || side == "back")
|
val viewW = savedMaskOverlay.width.toFloat()
|
||||||
val width = if (isPortrait) 480 else 640
|
val viewH = savedMaskOverlay.height.toFloat()
|
||||||
val height = if (isPortrait) 640 else 480
|
|
||||||
|
if (viewW > 0 && viewH > 0) {
|
||||||
savedMaskBitmap = Bitmap.createScaledBitmap(savedBitmap, width, height, true)
|
val bmpW = greenMask.width.toFloat()
|
||||||
|
val bmpH = greenMask.height.toFloat()
|
||||||
|
|
||||||
|
val scale = kotlin.math.min(viewW / bmpW, viewH / bmpH)
|
||||||
|
val scaledW = (bmpW * scale).toInt()
|
||||||
|
val scaledH = (bmpH * scale).toInt()
|
||||||
|
|
||||||
|
if (scaledW > 0 && scaledH > 0) {
|
||||||
|
val scaledBitmap = Bitmap.createScaledBitmap(greenMask, scaledW, scaledH, true)
|
||||||
|
savedMaskOverlay.setImageBitmap(scaledBitmap)
|
||||||
|
savedMaskOverlay.scaleType = ImageView.ScaleType.FIT_CENTER // Ensure it centers
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback if view size not ready yet, though post() should handle it
|
||||||
|
savedMaskOverlay.setImageBitmap(greenMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
savedMaskOverlay.alpha = 0.5f
|
||||||
|
} else {
|
||||||
|
savedMaskOverlay.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare mask for analysis (640x480 target)
|
||||||
|
// We should ideally match the visible part of the preview
|
||||||
|
// For simplicity, keeping original scaling logic for analysis as it might depend on full frame
|
||||||
|
// However, if the overlay is FIT_CENTER, the analysis should likely respect that aspect ratio too
|
||||||
|
// But for now, let's just fix the visual overlay as requested.
|
||||||
|
|
||||||
|
val isPortrait = (side == "front" || side == "back")
|
||||||
|
val width = if (isPortrait) 480 else 640
|
||||||
|
val height = if (isPortrait) 640 else 480
|
||||||
|
|
||||||
|
savedMaskBitmap = Bitmap.createScaledBitmap(savedBitmap, width, height, true)
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("CameraProcessor", "Error loading saved mask", e)
|
Log.e("CameraProcessor", "Error loading saved mask", e)
|
||||||
|
|
@ -269,6 +341,7 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
}, ContextCompat.getMainExecutor(this))
|
}, ContextCompat.getMainExecutor(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalGetImage
|
||||||
override fun onFrame(imageProxy: ImageProxy) {
|
override fun onFrame(imageProxy: ImageProxy) {
|
||||||
if (isPhotoTaken) {
|
if (isPhotoTaken) {
|
||||||
imageProxy.close()
|
imageProxy.close()
|
||||||
|
|
@ -282,10 +355,14 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (result.mask != null) {
|
if (result.mask != null) {
|
||||||
currentMask = result.mask
|
currentMask = result.mask
|
||||||
segmentationOverlay.setImageBitmap(result.mask)
|
if (isMaskDisplayEnabled) {
|
||||||
|
segmentationOverlay.setImageBitmap(result.mask)
|
||||||
|
} else {
|
||||||
|
segmentationOverlay.setImageDrawable(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result.isMatch) {
|
if (isAutoCapture && result.isMatch) {
|
||||||
takePhoto()
|
takePhoto()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -293,4 +370,4 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
||||||
Log.e("CameraProcessor", "Frame processing error", e)
|
Log.e("CameraProcessor", "Frame processing error", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import android.view.View
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.AutoCompleteTextView
|
import android.widget.AutoCompleteTextView
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.GridLayout
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
|
|
@ -34,51 +33,21 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
private var currentCowName: String? = null
|
private var currentCowName: String? = null
|
||||||
private lateinit var imagesContainer: LinearLayout
|
private lateinit var imagesContainer: LinearLayout
|
||||||
private val storagePermissionCode = 101
|
private val storagePermissionCode = 101
|
||||||
|
private val orientationViews = mutableMapOf<String, View>()
|
||||||
|
private val initialImagePaths = mutableSetOf<String>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_cow_selection)
|
setContentView(R.layout.activity_cow_selection)
|
||||||
|
|
||||||
// Initialize StringProvider
|
|
||||||
StringProvider.initialize(this)
|
StringProvider.initialize(this)
|
||||||
|
setupUIStrings()
|
||||||
|
|
||||||
// Set UI text from StringProvider
|
|
||||||
findViewById<TextView>(R.id.tvToolbarTitle)?.text = StringProvider.getString("title_cow_selection")
|
|
||||||
findViewById<TextView>(R.id.tvAddCowDetails)?.text = StringProvider.getString("title_add_cow_details")
|
|
||||||
|
|
||||||
findViewById<TextInputLayout>(R.id.tilSpecies)?.hint = StringProvider.getString("hint_species")
|
|
||||||
findViewById<TextInputLayout>(R.id.tilBreed)?.hint = StringProvider.getString("hint_breed")
|
|
||||||
findViewById<TextInputLayout>(R.id.tilAge)?.hint = StringProvider.getString("hint_age")
|
|
||||||
findViewById<TextInputLayout>(R.id.tilMilk)?.hint = StringProvider.getString("hint_milk_yield")
|
|
||||||
findViewById<TextInputLayout>(R.id.tilCalving)?.hint = StringProvider.getString("hint_calving_number")
|
|
||||||
findViewById<TextInputLayout>(R.id.tilDescription)?.hint = StringProvider.getString("hint_description")
|
|
||||||
|
|
||||||
findViewById<TextView>(R.id.tvReproductiveStatus)?.text = StringProvider.getString("label_reproductive_status")
|
|
||||||
findViewById<RadioButton>(R.id.rbPregnant)?.text = StringProvider.getString("radio_pregnant")
|
|
||||||
findViewById<RadioButton>(R.id.rbCalved)?.text = StringProvider.getString("radio_calved")
|
|
||||||
findViewById<RadioButton>(R.id.rbNone)?.text = StringProvider.getString("radio_none")
|
|
||||||
|
|
||||||
findViewById<TextView>(R.id.tvUploadPhotos)?.text = StringProvider.getString("label_upload_photos")
|
|
||||||
findViewById<Button>(R.id.btnNewCow)?.text = StringProvider.getString("btn_save_profile")
|
|
||||||
findViewById<Button>(R.id.btnCancel)?.text = StringProvider.getString("btn_cancel")
|
|
||||||
|
|
||||||
findViewById<TextView>(R.id.tvFrontView)?.text = StringProvider.getString("text_front_view")
|
|
||||||
findViewById<TextView>(R.id.tvRearView)?.text = StringProvider.getString("text_rear_view")
|
|
||||||
findViewById<TextView>(R.id.tvLeftSide)?.text = StringProvider.getString("text_left_side")
|
|
||||||
findViewById<TextView>(R.id.tvRightSide)?.text = StringProvider.getString("text_right_side")
|
|
||||||
findViewById<TextView>(R.id.tvAngleView)?.text = StringProvider.getString("text_angle_view")
|
|
||||||
|
|
||||||
// Setup Toolbar Navigation Click (Exit instead of Back)
|
|
||||||
val toolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
|
val toolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||||
|
|
||||||
// NOTE: The navigation icon is set in XML (@drawable/ic_back_arrow).
|
|
||||||
// We handle the click here.
|
|
||||||
toolbar.setNavigationOnClickListener {
|
toolbar.setNavigationOnClickListener {
|
||||||
// Exit behavior: Finish this activity.
|
|
||||||
// If "Exit" means close app, use finishAffinity() but user likely means exit this screen.
|
|
||||||
// Given "except home activity", finish() goes back to home or previous screen.
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,20 +56,24 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
|
|
||||||
imagesContainer = findViewById(R.id.currentCowImagesContainer)
|
imagesContainer = findViewById(R.id.currentCowImagesContainer)
|
||||||
|
|
||||||
// Restore cow name if available, or generate new if requested
|
|
||||||
currentCowName = savedInstanceState?.getString("COW_NAME") ?: intent.getStringExtra("COW_NAME")
|
currentCowName = savedInstanceState?.getString("COW_NAME") ?: intent.getStringExtra("COW_NAME")
|
||||||
|
|
||||||
if (currentCowName == null) {
|
if (currentCowName == null) {
|
||||||
generateNewCowName()
|
generateNewCowName()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load existing data if we are editing an existing cow
|
loadInitialImages()
|
||||||
|
|
||||||
if (intent.hasExtra("COW_NAME")) {
|
if (intent.hasExtra("COW_NAME")) {
|
||||||
loadCowDetails(currentCowName!!)
|
loadCowDetails(currentCowName!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCowNameDisplay()
|
orientationViews["left"] = findViewById(R.id.btnLeft)
|
||||||
refreshCowImages()
|
orientationViews["right"] = findViewById(R.id.btnRight)
|
||||||
|
orientationViews["angle"] = findViewById(R.id.btnTop)
|
||||||
|
orientationViews["front"] = findViewById(R.id.btnFront)
|
||||||
|
orientationViews["back"] = findViewById(R.id.btnBack)
|
||||||
|
orientationViews["left_angle"] = findViewById(R.id.btnLeftAngle)
|
||||||
|
orientationViews["right_angle"] = findViewById(R.id.btnRightAngle)
|
||||||
|
|
||||||
findViewById<Button>(R.id.btnNewCow).setOnClickListener {
|
findViewById<Button>(R.id.btnNewCow).setOnClickListener {
|
||||||
if (checkStoragePermissions()) {
|
if (checkStoragePermissions()) {
|
||||||
|
|
@ -111,30 +84,70 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewById<Button>(R.id.btnCancel).setOnClickListener {
|
findViewById<Button>(R.id.btnCancel).setOnClickListener {
|
||||||
|
deleteSessionImages()
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val buttons = mapOf(
|
|
||||||
R.id.btnLeft to Pair(R.drawable.left, "left"),
|
private fun loadInitialImages() {
|
||||||
R.id.btnRight to Pair(R.drawable.right, "right"),
|
val name = currentCowName ?: return
|
||||||
R.id.btnTop to Pair(R.drawable.angle, "angle"),
|
val cowFolder = StorageUtils.getCowImageFolder(name)
|
||||||
R.id.btnFront to Pair(R.drawable.front, "front"),
|
if (cowFolder.exists()) {
|
||||||
R.id.btnBack to Pair(R.drawable.back, "back")
|
cowFolder.listFiles()?.forEach { file ->
|
||||||
)
|
if (file.isFile) {
|
||||||
|
initialImagePaths.add(file.absolutePath)
|
||||||
buttons.forEach { (btnId, pair) ->
|
}
|
||||||
val (drawableId, orientation) = pair
|
|
||||||
// Cast to View generic, as it is now a LinearLayout (clickable) in XML
|
|
||||||
findViewById<View>(btnId).setOnClickListener {
|
|
||||||
val intent = Intent(this, CameraProcessor::class.java)
|
|
||||||
intent.putExtra("SILHOUETTE_ID", drawableId)
|
|
||||||
intent.putExtra("COW_NAME", currentCowName)
|
|
||||||
intent.putExtra("ORIENTATION", orientation)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deleteSessionImages() {
|
||||||
|
val name = currentCowName ?: return
|
||||||
|
val cowFolder = StorageUtils.getCowImageFolder(name)
|
||||||
|
if (cowFolder.exists()) {
|
||||||
|
cowFolder.listFiles()?.forEach { file ->
|
||||||
|
if (file.isFile && !initialImagePaths.contains(file.absolutePath)) {
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val remaining = cowFolder.listFiles()
|
||||||
|
if (remaining == null || remaining.isEmpty()) {
|
||||||
|
cowFolder.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUIStrings() {
|
||||||
|
findViewById<TextView>(R.id.tvToolbarTitle).text = StringProvider.getString("title_cow_selection")
|
||||||
|
findViewById<TextView>(R.id.tvAddCowDetails).text = StringProvider.getString("title_add_cow_details")
|
||||||
|
|
||||||
|
// Using hint_ keys for the new labels as they contain the appropriate text (e.g. "Species", "Breed")
|
||||||
|
findViewById<TextView>(R.id.tvLabelSpecies).text = StringProvider.getString("hint_species")
|
||||||
|
findViewById<TextView>(R.id.tvLabelBreed).text = StringProvider.getString("hint_breed")
|
||||||
|
findViewById<TextView>(R.id.tvLabelAge).text = StringProvider.getString("hint_age")
|
||||||
|
findViewById<TextView>(R.id.tvLabelMilk).text = StringProvider.getString("hint_milk_yield")
|
||||||
|
findViewById<TextView>(R.id.tvLabelCalving).text = StringProvider.getString("hint_calving_number")
|
||||||
|
findViewById<TextView>(R.id.tvLabelDescription).text = StringProvider.getString("hint_description")
|
||||||
|
|
||||||
|
findViewById<TextInputLayout>(R.id.tilSpecies).hint = null
|
||||||
|
findViewById<TextInputLayout>(R.id.tilBreed).hint = null
|
||||||
|
findViewById<TextInputLayout>(R.id.tilAge).hint = null
|
||||||
|
findViewById<TextInputLayout>(R.id.tilMilk).hint = null
|
||||||
|
findViewById<TextInputLayout>(R.id.tilCalving).hint = null
|
||||||
|
findViewById<TextInputLayout>(R.id.tilDescription).hint = null
|
||||||
|
|
||||||
|
findViewById<TextView>(R.id.tvReproductiveStatus).text = StringProvider.getString("label_reproductive_status")
|
||||||
|
findViewById<RadioButton>(R.id.rbPregnant).text = StringProvider.getString("radio_pregnant")
|
||||||
|
findViewById<RadioButton>(R.id.rbCalved).text = StringProvider.getString("radio_calved")
|
||||||
|
findViewById<RadioButton>(R.id.rbNone).text = StringProvider.getString("radio_none")
|
||||||
|
|
||||||
|
findViewById<TextView>(R.id.tvUploadPhotos).text = StringProvider.getString("label_upload_photos")
|
||||||
|
|
||||||
|
findViewById<Button>(R.id.btnNewCow).text = StringProvider.getString("btn_save_profile")
|
||||||
|
findViewById<Button>(R.id.btnCancel).text = StringProvider.getString("btn_cancel")
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkStoragePermissions(): Boolean {
|
private fun checkStoragePermissions(): Boolean {
|
||||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||||
android.os.Environment.isExternalStorageManager()
|
android.os.Environment.isExternalStorageManager()
|
||||||
|
|
@ -166,15 +179,9 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveProfile() {
|
private fun saveProfile() {
|
||||||
// Get displayed values
|
|
||||||
val speciesDisplay = findViewById<AutoCompleteTextView>(R.id.spinnerSpecies).text.toString()
|
val speciesDisplay = findViewById<AutoCompleteTextView>(R.id.spinnerSpecies).text.toString()
|
||||||
val breedDisplay = findViewById<AutoCompleteTextView>(R.id.spinnerBreed).text.toString()
|
val breedDisplay = findViewById<AutoCompleteTextView>(R.id.spinnerBreed).text.toString()
|
||||||
|
|
||||||
// Convert to English for storage if needed (using reverse lookup or StringProvider helper)
|
|
||||||
// Assuming keys like "species_cow" map to "Cow" in English and something else in Hindi
|
|
||||||
// We try to find the key for the displayed value, then get the English value for that key.
|
|
||||||
// If not found (e.g. custom input), fallback to displayed value.
|
|
||||||
|
|
||||||
val speciesKey = StringProvider.getKeyForValue(speciesDisplay)
|
val speciesKey = StringProvider.getKeyForValue(speciesDisplay)
|
||||||
val species = if (speciesKey != null) StringProvider.getStringEnglish(speciesKey) else speciesDisplay
|
val species = if (speciesKey != null) StringProvider.getStringEnglish(speciesKey) else speciesDisplay
|
||||||
|
|
||||||
|
|
@ -198,7 +205,8 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
val csvHeader = "CowID,Species,Breed,Age,MilkYield,CalvingNumber,ReproductiveStatus,Description\n"
|
val csvHeader = "CowID,Species,Breed,Age,MilkYield,CalvingNumber,ReproductiveStatus,Description\n"
|
||||||
val csvRow = "$currentCowName,$species,$breed,$ageInput,$milkInput,$calvingInput,$reproductiveStatus,$descriptionInput\n"
|
val csvRow = "$currentCowName,$species,$breed,$ageInput,$milkInput,$calvingInput,$reproductiveStatus,$descriptionInput\n"
|
||||||
|
|
||||||
val csvFile = File(filesDir, "cow_profiles.csv")
|
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||||
|
val csvFile = File(docsFolder, "cow_profiles.csv")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val fileExists = csvFile.exists()
|
val fileExists = csvFile.exists()
|
||||||
|
|
@ -208,7 +216,6 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
lines.add(csvHeader.trim())
|
lines.add(csvHeader.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we are updating
|
|
||||||
val existingIndex = lines.indexOfFirst { it.startsWith("$currentCowName,") }
|
val existingIndex = lines.indexOfFirst { it.startsWith("$currentCowName,") }
|
||||||
|
|
||||||
if (existingIndex != -1) {
|
if (existingIndex != -1) {
|
||||||
|
|
@ -223,9 +230,6 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also export to public storage if needed
|
|
||||||
saveCsvToPublicStorage(lines)
|
|
||||||
|
|
||||||
Toast.makeText(this, StringProvider.getString("toast_profile_saved"), Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, StringProvider.getString("toast_profile_saved"), Toast.LENGTH_SHORT).show()
|
||||||
finish()
|
finish()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -234,40 +238,16 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveCsvToPublicStorage(lines: List<String>) {
|
|
||||||
// Saving to public Documents folder
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
|
||||||
if (android.os.Environment.isExternalStorageManager()) {
|
|
||||||
val publicFile = File(android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOCUMENTS), "AnimalRating_Profiles.csv")
|
|
||||||
FileWriter(publicFile).use { writer ->
|
|
||||||
lines.forEach { line ->
|
|
||||||
writer.write(line + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val publicFile = File(android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOCUMENTS), "AnimalRating_Profiles.csv")
|
|
||||||
FileWriter(publicFile).use { writer ->
|
|
||||||
lines.forEach { line ->
|
|
||||||
writer.write(line + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadCowDetails(cowId: String) {
|
private fun loadCowDetails(cowId: String) {
|
||||||
val csvFile = File(filesDir, "cow_profiles.csv")
|
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||||
|
val csvFile = File(docsFolder, "cow_profiles.csv")
|
||||||
if (!csvFile.exists()) return
|
if (!csvFile.exists()) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val lines = csvFile.readLines()
|
val lines = csvFile.readLines()
|
||||||
// Removed unused header variable
|
|
||||||
val record = lines.find { it.startsWith("$cowId,") }?.split(",") ?: return
|
val record = lines.find { it.startsWith("$cowId,") }?.split(",") ?: return
|
||||||
|
|
||||||
// Simple mapping based on known order
|
|
||||||
// CowID,Species,Breed,Age,MilkYield,CalvingNumber,ReproductiveStatus,Description
|
|
||||||
if (record.size >= 8) {
|
if (record.size >= 8) {
|
||||||
// Data is stored in English. Find key for English value, then get display string for that key.
|
|
||||||
val storedSpecies = record[1]
|
val storedSpecies = record[1]
|
||||||
val speciesKey = StringProvider.getKeyForEnglishValue(storedSpecies)
|
val speciesKey = StringProvider.getKeyForEnglishValue(storedSpecies)
|
||||||
val displaySpecies = if (speciesKey != null) StringProvider.getString(speciesKey) else storedSpecies
|
val displaySpecies = if (speciesKey != null) StringProvider.getString(speciesKey) else storedSpecies
|
||||||
|
|
@ -283,24 +263,13 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
findViewById<TextInputLayout>(R.id.tilCalving).editText?.setText(record[5])
|
findViewById<TextInputLayout>(R.id.tilCalving).editText?.setText(record[5])
|
||||||
|
|
||||||
val storedStatus = record[6]
|
val storedStatus = record[6]
|
||||||
// Find which key matches this English value
|
|
||||||
val statusKey = StringProvider.getKeyForEnglishValue(storedStatus)
|
val statusKey = StringProvider.getKeyForEnglishValue(storedStatus)
|
||||||
|
|
||||||
// We need to check radio buttons.
|
|
||||||
// Radio buttons text is set from StringProvider in onCreate.
|
|
||||||
// So we need to find which RadioButton corresponds to 'statusKey'.
|
|
||||||
|
|
||||||
// "radio_pregnant" -> R.id.rbPregnant
|
|
||||||
// "radio_calved" -> R.id.rbCalved
|
|
||||||
// "radio_none" -> R.id.rbNone
|
|
||||||
|
|
||||||
when(statusKey) {
|
when(statusKey) {
|
||||||
"radio_pregnant" -> findViewById<RadioButton>(R.id.rbPregnant).isChecked = true
|
"radio_pregnant" -> findViewById<RadioButton>(R.id.rbPregnant).isChecked = true
|
||||||
"radio_calved" -> findViewById<RadioButton>(R.id.rbCalved).isChecked = true
|
"radio_calved" -> findViewById<RadioButton>(R.id.rbCalved).isChecked = true
|
||||||
"radio_none" -> findViewById<RadioButton>(R.id.rbNone).isChecked = true
|
"radio_none" -> findViewById<RadioButton>(R.id.rbNone).isChecked = true
|
||||||
// Fallback if stored directly as text or unknown key
|
|
||||||
else -> {
|
else -> {
|
||||||
// If stored directly as English "Pregnant" etc.
|
|
||||||
when (storedStatus) {
|
when (storedStatus) {
|
||||||
"Pregnant" -> findViewById<RadioButton>(R.id.rbPregnant).isChecked = true
|
"Pregnant" -> findViewById<RadioButton>(R.id.rbPregnant).isChecked = true
|
||||||
"Calved" -> findViewById<RadioButton>(R.id.rbCalved).isChecked = true
|
"Calved" -> findViewById<RadioButton>(R.id.rbCalved).isChecked = true
|
||||||
|
|
@ -348,7 +317,9 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
"right" to R.drawable.right,
|
"right" to R.drawable.right,
|
||||||
"angle" to R.drawable.angle,
|
"angle" to R.drawable.angle,
|
||||||
"front" to R.drawable.front,
|
"front" to R.drawable.front,
|
||||||
"back" to R.drawable.back
|
"back" to R.drawable.back,
|
||||||
|
"leftangle" to R.drawable.leftangle,
|
||||||
|
"rightangle" to R.drawable.rightangle
|
||||||
)
|
)
|
||||||
|
|
||||||
orientationResources.forEach { (orientation, resId) ->
|
orientationResources.forEach { (orientation, resId) ->
|
||||||
|
|
@ -379,10 +350,8 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
for (i in pixels.indices) {
|
for (i in pixels.indices) {
|
||||||
val alpha = (pixels[i] shr 24) and 0xFF
|
val alpha = (pixels[i] shr 24) and 0xFF
|
||||||
if (alpha > 0) {
|
if (alpha > 0) {
|
||||||
// Original was visible -> make transparent
|
|
||||||
pixels[i] = Color.TRANSPARENT
|
pixels[i] = Color.TRANSPARENT
|
||||||
} else {
|
} else {
|
||||||
// Original was transparent -> make black (opaque)
|
|
||||||
pixels[i] = Color.BLACK
|
pixels[i] = Color.BLACK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,91 +363,157 @@ class CowSelectionActivity : AppCompatActivity() {
|
||||||
currentCowName = "cow_${sdf.format(Date())}"
|
currentCowName = "cow_${sdf.format(Date())}"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCowNameDisplay() {
|
|
||||||
// Cow Name is now in Toolbar title if needed, but we hide title.
|
|
||||||
// findViewById<TextView>(R.id.tvCowName).text = "Current Cow: $currentCowName"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshCowImages() {
|
private fun refreshCowImages() {
|
||||||
imagesContainer.removeAllViews()
|
|
||||||
val name = currentCowName ?: return
|
val name = currentCowName ?: return
|
||||||
|
|
||||||
val files = filesDir.listFiles { _, fname -> fname.startsWith("${name}_") && fname.endsWith(".jpg") }
|
val orientations = mapOf(
|
||||||
if (files.isNullOrEmpty()) return
|
"left" to Pair(R.drawable.left, R.id.btnLeft),
|
||||||
|
"right" to Pair(R.drawable.right, R.id.btnRight),
|
||||||
|
"angle" to Pair(R.drawable.angle, R.id.btnTop),
|
||||||
|
"front" to Pair(R.drawable.front, R.id.btnFront),
|
||||||
|
"back" to Pair(R.drawable.back, R.id.btnBack),
|
||||||
|
"leftangle" to Pair(R.drawable.leftangle, R.id.btnLeftAngle),
|
||||||
|
"rightangle" to Pair(R.drawable.rightangle, R.id.btnRightAngle)
|
||||||
|
)
|
||||||
|
|
||||||
// Group files by orientation
|
val cowImagesFolder = StorageUtils.getCowImageFolder(name)
|
||||||
val filesByOrientation = files.groupBy { file ->
|
|
||||||
val parts = file.name.split("_")
|
orientations.forEach { (orientation, pair) ->
|
||||||
if (parts.size >= 3) parts[2] else "unknown"
|
val (drawableId, viewId) = pair
|
||||||
}
|
|
||||||
|
val files = if (cowImagesFolder.exists()) {
|
||||||
|
cowImagesFolder.listFiles { _, fname -> fname.startsWith("${name}_${orientation}_") && fname.endsWith(".jpg") }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
filesByOrientation.forEach { (orientation, orientationFiles) ->
|
val latestFile = files?.maxByOrNull { it.lastModified() }
|
||||||
addOrientationSection(orientation, orientationFiles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addOrientationSection(orientationStr: String, files: List<File>) {
|
val container = findViewById<LinearLayout>(viewId)
|
||||||
// Orientation Header
|
container.removeAllViews()
|
||||||
val orientationHeader = TextView(this).apply {
|
|
||||||
val key = when(orientationStr.lowercase(Locale.getDefault())) {
|
val key = when(orientation) {
|
||||||
"front" -> "text_front_view"
|
"front" -> "text_front_view"
|
||||||
"back" -> "text_rear_view"
|
"back" -> "text_rear_view"
|
||||||
"left" -> "text_left_side"
|
"left" -> "text_left_side"
|
||||||
"right" -> "text_right_side"
|
"right" -> "text_right_side"
|
||||||
"angle" -> "text_angle_view"
|
"angle" -> "text_angle_view"
|
||||||
|
"leftangle" -> "text_left_angle"
|
||||||
|
"rightangle" -> "text_right_angle"
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
val label = if (key.isNotEmpty()) StringProvider.getString(key) else orientationStr.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
|
val label = if (key.isNotEmpty()) StringProvider.getString(key) else orientation
|
||||||
text = label
|
|
||||||
|
|
||||||
textSize = 16f
|
|
||||||
setTypeface(null, android.graphics.Typeface.BOLD)
|
|
||||||
setPadding(16, 8, 0, 8)
|
|
||||||
}
|
|
||||||
imagesContainer.addView(orientationHeader)
|
|
||||||
|
|
||||||
// Grid for thumbnails
|
if (latestFile != null && latestFile.exists()) {
|
||||||
val gridLayout = GridLayout(this).apply {
|
val frameLayout = android.widget.FrameLayout(this)
|
||||||
columnCount = 3
|
frameLayout.layoutParams = LinearLayout.LayoutParams(
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
LinearLayout.LayoutParams.MATCH_PARENT
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
)
|
||||||
).apply {
|
|
||||||
setMargins(16, 0, 16, 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Images for this orientation
|
val imageView = ImageView(this)
|
||||||
files.forEach { file ->
|
imageView.layoutParams = android.widget.FrameLayout.LayoutParams(
|
||||||
val thumbnailView = layoutInflater.inflate(R.layout.item_image_thumbnail, gridLayout, false)
|
android.widget.FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
android.widget.FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
val imageView = thumbnailView.findViewById<ImageView>(R.id.ivThumbnail)
|
)
|
||||||
val deleteButton = thumbnailView.findViewById<View>(R.id.btnDelete)
|
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
val bitmap = BitmapFactory.decodeFile(latestFile.absolutePath)
|
||||||
imageView.setImageBitmap(BitmapFactory.decodeFile(file.absolutePath))
|
imageView.setImageBitmap(bitmap)
|
||||||
|
|
||||||
imageView.setOnClickListener {
|
val deleteBtn = ImageView(this)
|
||||||
val intent = Intent(this@CowSelectionActivity, FullScreenImageActivity::class.java)
|
val btnSize = (24 * resources.displayMetrics.density).toInt()
|
||||||
intent.putExtra("IMAGE_PATH", file.absolutePath)
|
val btnParams = android.widget.FrameLayout.LayoutParams(btnSize, btnSize)
|
||||||
startActivity(intent)
|
btnParams.gravity = android.view.Gravity.TOP or android.view.Gravity.END
|
||||||
}
|
val margin = (4 * resources.displayMetrics.density).toInt()
|
||||||
|
btnParams.setMargins(margin, margin, margin, margin)
|
||||||
deleteButton.setOnClickListener {
|
deleteBtn.layoutParams = btnParams
|
||||||
if (file.delete()) {
|
deleteBtn.setImageResource(android.R.drawable.ic_menu_close_clear_cancel)
|
||||||
Toast.makeText(this@CowSelectionActivity, StringProvider.getString("toast_image_deleted"), Toast.LENGTH_SHORT).show()
|
deleteBtn.setColorFilter(Color.RED)
|
||||||
refreshCowImages()
|
deleteBtn.setBackgroundColor(Color.parseColor("#80FFFFFF"))
|
||||||
} else {
|
deleteBtn.isClickable = true
|
||||||
Toast.makeText(this@CowSelectionActivity, StringProvider.getString("toast_error_deleting_image"), Toast.LENGTH_SHORT).show()
|
deleteBtn.setOnClickListener {
|
||||||
|
if (latestFile.delete()) {
|
||||||
|
val remainingFiles = cowImagesFolder.listFiles()
|
||||||
|
if (remainingFiles == null || remainingFiles.isEmpty()) {
|
||||||
|
cowImagesFolder.delete()
|
||||||
|
}
|
||||||
|
refreshCowImages()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
gridLayout.addView(thumbnailView)
|
|
||||||
}
|
|
||||||
imagesContainer.addView(gridLayout)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val labelView = TextView(this)
|
||||||
|
labelView.text = label
|
||||||
|
labelView.textSize = 12f
|
||||||
|
labelView.setTextColor(Color.WHITE)
|
||||||
|
labelView.setShadowLayer(3f, 0f, 0f, Color.BLACK)
|
||||||
|
val labelParams = android.widget.FrameLayout.LayoutParams(
|
||||||
|
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
android.widget.FrameLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
labelParams.gravity = android.view.Gravity.BOTTOM or android.view.Gravity.CENTER_HORIZONTAL
|
||||||
|
labelParams.bottomMargin = (4 * resources.displayMetrics.density).toInt()
|
||||||
|
labelView.layoutParams = labelParams
|
||||||
|
|
||||||
|
frameLayout.addView(imageView)
|
||||||
|
frameLayout.addView(labelView)
|
||||||
|
frameLayout.addView(deleteBtn)
|
||||||
|
|
||||||
|
container.addView(frameLayout)
|
||||||
|
|
||||||
|
imageView.setOnClickListener {
|
||||||
|
val intent = Intent(this, FullScreenImageActivity::class.java)
|
||||||
|
intent.putExtra("IMAGE_PATH", latestFile.absolutePath)
|
||||||
|
intent.putExtra("ALLOW_RETAKE", true)
|
||||||
|
intent.putExtra("COW_NAME", currentCowName)
|
||||||
|
intent.putExtra("ORIENTATION", orientation)
|
||||||
|
intent.putExtra("SILHOUETTE_ID", drawableId)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
container.setOnClickListener(null)
|
||||||
|
container.isClickable = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val iconView = ImageView(this)
|
||||||
|
val params = LinearLayout.LayoutParams(
|
||||||
|
(24 * resources.displayMetrics.density).toInt(),
|
||||||
|
(24 * resources.displayMetrics.density).toInt()
|
||||||
|
)
|
||||||
|
params.gravity = android.view.Gravity.CENTER_HORIZONTAL
|
||||||
|
iconView.layoutParams = params
|
||||||
|
iconView.setImageResource(android.R.drawable.ic_menu_camera)
|
||||||
|
iconView.setColorFilter(Color.parseColor("#5D4037"))
|
||||||
|
|
||||||
|
val textView = TextView(this)
|
||||||
|
val textParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
textParams.gravity = android.view.Gravity.CENTER_HORIZONTAL
|
||||||
|
textParams.topMargin = (4 * resources.displayMetrics.density).toInt()
|
||||||
|
textView.layoutParams = textParams
|
||||||
|
|
||||||
|
textView.text = label
|
||||||
|
textView.textSize = 12f
|
||||||
|
textView.setTextColor(Color.parseColor("#5D4037"))
|
||||||
|
|
||||||
|
container.addView(iconView)
|
||||||
|
container.addView(textView)
|
||||||
|
|
||||||
|
container.setOnClickListener {
|
||||||
|
val intent = Intent(this, CameraProcessor::class.java)
|
||||||
|
intent.putExtra("SILHOUETTE_ID", drawableId)
|
||||||
|
intent.putExtra("COW_NAME", currentCowName)
|
||||||
|
intent.putExtra("ORIENTATION", orientation)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
container.isClickable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
outState.putString("COW_NAME", currentCowName)
|
outState.putString("COW_NAME", currentCowName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,11 +93,19 @@ class FrameProcessor {
|
||||||
|
|
||||||
val rawBitmap = Bitmap.createBitmap(colors, fullWidth, fullHeight, Bitmap.Config.ARGB_8888)
|
val rawBitmap = Bitmap.createBitmap(colors, fullWidth, fullHeight, Bitmap.Config.ARGB_8888)
|
||||||
|
|
||||||
// Rotate if needed
|
// Rotate and Scale if needed
|
||||||
bitmapMask = if (isPortrait) {
|
bitmapMask = if (isPortrait) {
|
||||||
try {
|
try {
|
||||||
val matrix = Matrix()
|
val matrix = Matrix()
|
||||||
|
// Rotate 90 degrees
|
||||||
matrix.postRotate(90f)
|
matrix.postRotate(90f)
|
||||||
|
|
||||||
|
// Assuming previous scaling logic might still be relevant if masks are consistently off
|
||||||
|
// But generally, we trust the geometry.
|
||||||
|
// If scaling up is happening unexpectedly, one could use:
|
||||||
|
// matrix.postScale(0.9f, 0.9f)
|
||||||
|
// For now, stick to rotation unless requested otherwise.
|
||||||
|
|
||||||
Bitmap.createBitmap(rawBitmap, 0, 0, rawBitmap.width, rawBitmap.height, matrix, true)
|
Bitmap.createBitmap(rawBitmap, 0, 0, rawBitmap.width, rawBitmap.height, matrix, true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("FrameProcessor", "Error rotating mask", e)
|
Log.e("FrameProcessor", "Error rotating mask", e)
|
||||||
|
|
@ -127,7 +135,17 @@ class FrameProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comparisonMask != null) {
|
if (comparisonMask != null) {
|
||||||
isMatch = when (algorithm) {
|
// Map display strings to internal algorithm keys/logic
|
||||||
|
val algoKey = when(algorithm) {
|
||||||
|
StringProvider.getString("algo_euclidean") -> HomeActivity.ALGORITHM_EUCLIDEAN
|
||||||
|
StringProvider.getString("algo_jaccard") -> HomeActivity.ALGORITHM_JACCARD
|
||||||
|
else -> {
|
||||||
|
if (algorithm == HomeActivity.ALGORITHM_EUCLIDEAN || algorithm == HomeActivity.ALGORITHM_JACCARD) algorithm
|
||||||
|
else HomeActivity.ALGORITHM_HAMMING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMatch = when (algoKey) {
|
||||||
HomeActivity.ALGORITHM_EUCLIDEAN -> calculateEuclideanDistance(savedMask, comparisonMask, thresholdPercent)
|
HomeActivity.ALGORITHM_EUCLIDEAN -> calculateEuclideanDistance(savedMask, comparisonMask, thresholdPercent)
|
||||||
HomeActivity.ALGORITHM_JACCARD -> calculateJaccardSimilarity(savedMask, comparisonMask, thresholdPercent)
|
HomeActivity.ALGORITHM_JACCARD -> calculateJaccardSimilarity(savedMask, comparisonMask, thresholdPercent)
|
||||||
else -> calculateHammingDistance(savedMask, comparisonMask, thresholdPercent)
|
else -> calculateHammingDistance(savedMask, comparisonMask, thresholdPercent)
|
||||||
|
|
@ -229,19 +247,13 @@ class FrameProcessor {
|
||||||
var union = 0
|
var union = 0
|
||||||
|
|
||||||
for (i in pixels1.indices) {
|
for (i in pixels1.indices) {
|
||||||
// mask1 is Saved Mask (Inverse: Subject is Transparent/0, Background is Black/255)
|
// mask1 is Saved Mask (Likely Inverted in StorageUtils: Subject is Transparent/0, Background is Black/255)
|
||||||
// NOTE: Wait, the saved mask was inverted originally?
|
// But check how it's loaded. In CameraProcessor: loadSavedMask() -> decodeFile().
|
||||||
// Let's stick to generic "isSet" logic.
|
// If we assume saved mask is inverted (subject transparent), then alpha < 128 is subject.
|
||||||
// In previous turns, I assumed mask1 was "Inverse" and mask2 was "Normal".
|
|
||||||
// But for general algorithms, let's normalize.
|
|
||||||
// In CameraProcessor, we load the saved mask. It is likely the inverted one.
|
|
||||||
// Let's assume:
|
|
||||||
// Saved Mask (mask1): Alpha < 128 means subject (because it's inverted)
|
|
||||||
// Current Mask (mask2): Alpha > 0 means subject
|
|
||||||
|
|
||||||
val alpha1 = (pixels1[i] ushr 24) and 0xFF
|
val alpha1 = (pixels1[i] ushr 24) and 0xFF
|
||||||
val isSubject1 = alpha1 < 128
|
val isSubject1 = alpha1 < 128
|
||||||
|
|
||||||
|
// mask2 is Live Mask (Subject is Magenta/180 alpha)
|
||||||
val alpha2 = (pixels2[i] ushr 24) and 0xFF
|
val alpha2 = (pixels2[i] ushr 24) and 0xFF
|
||||||
val isSubject2 = alpha2 > 0
|
val isSubject2 = alpha2 > 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package com.example.animalrating
|
package com.example.animalrating
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -12,8 +15,15 @@ class FullScreenImageActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_full_screen_image)
|
setContentView(R.layout.activity_full_screen_image)
|
||||||
|
|
||||||
|
StringProvider.initialize(this)
|
||||||
|
|
||||||
val imagePath = intent.getStringExtra("IMAGE_PATH")
|
val imagePath = intent.getStringExtra("IMAGE_PATH")
|
||||||
|
val allowRetake = intent.getBooleanExtra("ALLOW_RETAKE", false)
|
||||||
|
val cowName = intent.getStringExtra("COW_NAME")
|
||||||
|
val orientation = intent.getStringExtra("ORIENTATION")
|
||||||
|
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
|
||||||
|
|
||||||
if (imagePath != null) {
|
if (imagePath != null) {
|
||||||
val file = File(imagePath)
|
val file = File(imagePath)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
|
|
@ -21,9 +31,32 @@ class FullScreenImageActivity : AppCompatActivity() {
|
||||||
findViewById<ImageView>(R.id.fullScreenImageView).setImageBitmap(bitmap)
|
findViewById<ImageView>(R.id.fullScreenImageView).setImageBitmap(bitmap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val btnRetake = findViewById<Button>(R.id.btnRetake)
|
||||||
|
if (allowRetake && imagePath != null) {
|
||||||
|
btnRetake.visibility = View.VISIBLE
|
||||||
|
btnRetake.text = "Retake Photo"
|
||||||
|
|
||||||
|
btnRetake.setOnClickListener {
|
||||||
|
// Launch camera to retake. Pass the current image path so CameraProcessor can overwrite/delete it on success.
|
||||||
|
val intent = Intent(this, CameraProcessor::class.java)
|
||||||
|
intent.putExtra("SILHOUETTE_ID", silhouetteId)
|
||||||
|
intent.putExtra("COW_NAME", cowName)
|
||||||
|
intent.putExtra("ORIENTATION", orientation)
|
||||||
|
intent.putExtra("RETAKE_IMAGE_PATH", imagePath) // Pass image path to replace
|
||||||
|
startActivity(intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
btnRetake.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
findViewById<Button>(R.id.btnBack).setOnClickListener {
|
val btnBack = findViewById<ImageButton>(R.id.btnBack)
|
||||||
|
btnBack.contentDescription = StringProvider.getString("content_desc_back")
|
||||||
|
btnBack.setOnClickListener {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findViewById<ImageView>(R.id.fullScreenImageView).contentDescription = StringProvider.getString("content_desc_full_screen_image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,17 @@ import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileWriter
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class GalleryActivity : AppCompatActivity() {
|
class GalleryActivity : AppCompatActivity() {
|
||||||
|
|
@ -22,17 +25,14 @@ class GalleryActivity : AppCompatActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_gallery)
|
setContentView(R.layout.activity_gallery)
|
||||||
|
|
||||||
// Initialize StringProvider
|
|
||||||
StringProvider.initialize(this)
|
StringProvider.initialize(this)
|
||||||
|
|
||||||
// Setup Back Button
|
|
||||||
val toolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
|
val toolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||||
|
|
||||||
// Set Toolbar title
|
|
||||||
findViewById<TextView>(R.id.tvToolbarTitle)?.text = StringProvider.getString("title_gallery")
|
findViewById<TextView>(R.id.tvToolbarTitle)?.text = StringProvider.getString("title_gallery")
|
||||||
|
|
||||||
toolbar.setNavigationOnClickListener {
|
toolbar.setNavigationOnClickListener {
|
||||||
|
|
@ -52,15 +52,14 @@ class GalleryActivity : AppCompatActivity() {
|
||||||
private fun refreshGallery() {
|
private fun refreshGallery() {
|
||||||
container.removeAllViews()
|
container.removeAllViews()
|
||||||
|
|
||||||
val files = filesDir.listFiles { _, name -> name.startsWith("cow_") && name.endsWith(".jpg") }
|
val imagesBaseFolder = StorageUtils.getImagesBaseFolder()
|
||||||
|
val cowFolders = imagesBaseFolder.listFiles { file -> file.isDirectory } ?: emptyArray()
|
||||||
|
|
||||||
// Group files by cow name
|
val cowNamesFromFolders = cowFolders.map { it.name }
|
||||||
val groupedFiles = files?.groupBy { file ->
|
|
||||||
val parts = file.name.split("_")
|
|
||||||
if (parts.size >= 2) "${parts[0]}_${parts[1]}" else "unknown"
|
|
||||||
} ?: emptyMap()
|
|
||||||
|
|
||||||
val csvFile = File(filesDir, "cow_profiles.csv")
|
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||||
|
val csvFile = File(docsFolder, "cow_profiles.csv")
|
||||||
|
|
||||||
val cowDetails = if (csvFile.exists()) {
|
val cowDetails = if (csvFile.exists()) {
|
||||||
csvFile.readLines().associate { line ->
|
csvFile.readLines().associate { line ->
|
||||||
val parts = line.split(",")
|
val parts = line.split(",")
|
||||||
|
|
@ -69,38 +68,128 @@ class GalleryActivity : AppCompatActivity() {
|
||||||
} else {
|
} else {
|
||||||
emptyMap()
|
emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val allCowNames = (cowDetails.keys + cowNamesFromFolders).filter { it.isNotEmpty() && it != "CowID" }.distinct()
|
||||||
|
|
||||||
groupedFiles.forEach { (cowName, cowFiles) ->
|
allCowNames.forEach { cowName ->
|
||||||
val details = cowDetails[cowName] ?: emptyList()
|
val details = cowDetails[cowName] ?: emptyList()
|
||||||
|
val cowImageFolder = StorageUtils.getCowImageFolder(cowName)
|
||||||
|
val cowFiles = cowImageFolder.listFiles { _, name -> name.endsWith(".jpg") }?.toList() ?: emptyList()
|
||||||
|
|
||||||
addCowSection(cowName, cowFiles, details)
|
addCowSection(cowName, cowFiles, details)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addCowSection(cowName: String, cowFiles: List<File>, details: List<String>) {
|
private fun addCowSection(cowName: String, cowFiles: List<File>, details: List<String>) {
|
||||||
// Cow Name Header and Retake Button
|
// Main Card
|
||||||
val headerLayout = LinearLayout(this).apply {
|
val card = CardView(this).apply {
|
||||||
orientation = LinearLayout.HORIZONTAL
|
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
).apply {
|
).apply {
|
||||||
setMargins(0, 32, 0, 16)
|
setMargins(0, 0, 0, 24)
|
||||||
|
}
|
||||||
|
radius = 16 * resources.displayMetrics.density
|
||||||
|
cardElevation = 2 * resources.displayMetrics.density
|
||||||
|
setCardBackgroundColor(android.graphics.Color.WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal Container (3:1 split)
|
||||||
|
val horizontalContainer = LinearLayout(this).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
weightSum = 4f
|
||||||
|
setPadding(24, 24, 24, 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left Layout (Info) - Weight 3
|
||||||
|
val leftLayout = LinearLayout(this).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
0,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
3f
|
||||||
|
).apply {
|
||||||
|
marginEnd = 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val nameView = TextView(this).apply {
|
val nameView = TextView(this).apply {
|
||||||
text = if (details.isNotEmpty()) "${StringProvider.getString("text_cow_id")} $cowName" else cowName
|
text = if (details.isNotEmpty()) "${StringProvider.getString("text_cow_id")} $cowName" else cowName
|
||||||
textSize = 20f
|
textSize = 20f
|
||||||
setTypeface(null, android.graphics.Typeface.BOLD)
|
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
setTextColor(android.graphics.Color.parseColor("#3E2723"))
|
setTextColor(android.graphics.Color.parseColor("#3E2723"))
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
).apply {
|
||||||
|
bottomMargin = 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leftLayout.addView(nameView)
|
||||||
|
|
||||||
|
if (details.size >= 7) {
|
||||||
|
// Translate values for display
|
||||||
|
val storedSpecies = details.getOrElse(1) { "-" }
|
||||||
|
val speciesKey = StringProvider.getKeyForEnglishValue(storedSpecies)
|
||||||
|
val displaySpecies = if (speciesKey != null) StringProvider.getString(speciesKey) else storedSpecies
|
||||||
|
|
||||||
|
val storedBreed = details.getOrElse(2) { "-" }
|
||||||
|
val breedKey = StringProvider.getKeyForEnglishValue(storedBreed)
|
||||||
|
val displayBreed = if (breedKey != null) StringProvider.getString(breedKey) else storedBreed
|
||||||
|
|
||||||
|
val storedStatus = details.getOrElse(6) { "-" }
|
||||||
|
val statusKey = StringProvider.getKeyForEnglishValue(storedStatus)
|
||||||
|
val displayStatus = if (statusKey != null) StringProvider.getString(statusKey) else storedStatus
|
||||||
|
|
||||||
|
val infoText = StringBuilder()
|
||||||
|
infoText.append("${StringProvider.getString("label_species")} $displaySpecies ")
|
||||||
|
infoText.append("${StringProvider.getString("label_breed")} $displayBreed\n")
|
||||||
|
infoText.append("${StringProvider.getString("label_age")} ${details.getOrElse(3) { "-" }} ${StringProvider.getString("unit_years")} ")
|
||||||
|
infoText.append("${StringProvider.getString("label_milk_yield")} ${details.getOrElse(4) { "-" }} ${StringProvider.getString("unit_liters")}\n")
|
||||||
|
infoText.append("${StringProvider.getString("label_calving_no")} ${details.getOrElse(5) { "-" }} ")
|
||||||
|
infoText.append("${StringProvider.getString("label_status")} $displayStatus")
|
||||||
|
|
||||||
|
val detailsView = TextView(this).apply {
|
||||||
|
text = infoText.toString()
|
||||||
|
textSize = 14f
|
||||||
|
setTextColor(android.graphics.Color.parseColor("#5D4037"))
|
||||||
|
setLineSpacing(10f, 1f)
|
||||||
|
}
|
||||||
|
leftLayout.addView(detailsView)
|
||||||
|
} else {
|
||||||
|
val detailsView = TextView(this).apply {
|
||||||
|
text = "No details available"
|
||||||
|
textSize = 14f
|
||||||
|
setTextColor(android.graphics.Color.parseColor("#5D4037"))
|
||||||
|
}
|
||||||
|
leftLayout.addView(detailsView)
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontalContainer.addView(leftLayout)
|
||||||
|
|
||||||
|
// Right Layout (Buttons) - Weight 1
|
||||||
|
val rightLayout = LinearLayout(this).apply {
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
0,
|
0,
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
1f
|
1f
|
||||||
)
|
)
|
||||||
|
gravity = Gravity.CENTER_VERTICAL
|
||||||
}
|
}
|
||||||
|
|
||||||
val retakeButton = MaterialButton(this).apply {
|
val buttonParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
(48 * resources.displayMetrics.density).toInt()
|
||||||
|
).apply {
|
||||||
|
bottomMargin = (8 * resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
val editButton = MaterialButton(this).apply {
|
||||||
text = StringProvider.getString("btn_edit")
|
text = StringProvider.getString("btn_edit")
|
||||||
textSize = 12f
|
textSize = 12f
|
||||||
setTypeface(null, android.graphics.Typeface.BOLD)
|
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
|
|
@ -109,9 +198,11 @@ class GalleryActivity : AppCompatActivity() {
|
||||||
backgroundTintList = ColorStateList.valueOf(android.graphics.Color.parseColor("#EFEBE9"))
|
backgroundTintList = ColorStateList.valueOf(android.graphics.Color.parseColor("#EFEBE9"))
|
||||||
strokeWidth = (1 * resources.displayMetrics.density).toInt()
|
strokeWidth = (1 * resources.displayMetrics.density).toInt()
|
||||||
strokeColor = ColorStateList.valueOf(android.graphics.Color.parseColor("#5D4037"))
|
strokeColor = ColorStateList.valueOf(android.graphics.Color.parseColor("#5D4037"))
|
||||||
|
layoutParams = buttonParams
|
||||||
|
insetTop = 0
|
||||||
|
insetBottom = 0
|
||||||
minHeight = 0
|
minHeight = 0
|
||||||
minimumHeight = 0
|
minimumHeight = 0
|
||||||
setPadding(24, 20, 24, 20)
|
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val intent = Intent(this@GalleryActivity, CowSelectionActivity::class.java)
|
val intent = Intent(this@GalleryActivity, CowSelectionActivity::class.java)
|
||||||
|
|
@ -119,137 +210,191 @@ class GalleryActivity : AppCompatActivity() {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
headerLayout.addView(nameView)
|
|
||||||
headerLayout.addView(retakeButton)
|
|
||||||
container.addView(headerLayout)
|
|
||||||
|
|
||||||
// Display details if available
|
val rateButton = MaterialButton(this).apply {
|
||||||
// Header: CowID,Species,Breed,Age,MilkYield,CalvingNumber,ReproductiveStatus,Description
|
text = "Rate" // Add string if needed, skipping as per instruction to be concise unless requested
|
||||||
if (details.size >= 7) {
|
textSize = 12f
|
||||||
val detailsLayout = LinearLayout(this).apply {
|
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
orientation = LinearLayout.VERTICAL
|
setTextColor(android.graphics.Color.WHITE)
|
||||||
|
cornerRadius = (12 * resources.displayMetrics.density).toInt()
|
||||||
|
backgroundTintList = ColorStateList.valueOf(android.graphics.Color.parseColor("#6D4C41"))
|
||||||
|
layoutParams = buttonParams
|
||||||
|
insetTop = 0
|
||||||
|
insetBottom = 0
|
||||||
|
minHeight = 0
|
||||||
|
minimumHeight = 0
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
val intent = Intent(this@GalleryActivity, RatingActivity::class.java)
|
||||||
|
intent.putExtra("COW_NAME", cowName)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val deleteButton = MaterialButton(this).apply {
|
||||||
|
text = "Delete"
|
||||||
|
textSize = 12f
|
||||||
|
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
|
setTextColor(android.graphics.Color.WHITE)
|
||||||
|
cornerRadius = (12 * resources.displayMetrics.density).toInt()
|
||||||
|
backgroundTintList = ColorStateList.valueOf(android.graphics.Color.RED)
|
||||||
|
layoutParams = buttonParams
|
||||||
|
insetTop = 0
|
||||||
|
insetBottom = 0
|
||||||
|
minHeight = 0
|
||||||
|
minimumHeight = 0
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
deleteCow(cowName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rightLayout.addView(editButton)
|
||||||
|
rightLayout.addView(rateButton)
|
||||||
|
rightLayout.addView(deleteButton)
|
||||||
|
horizontalContainer.addView(rightLayout)
|
||||||
|
card.addView(horizontalContainer)
|
||||||
|
|
||||||
|
container.addView(card)
|
||||||
|
|
||||||
|
// Orientation Images
|
||||||
|
if (cowFiles.isNotEmpty()) {
|
||||||
|
// Instead of separating by orientation, put all images in a grid
|
||||||
|
val gridLayout = android.widget.GridLayout(this).apply {
|
||||||
|
columnCount = 3
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
).apply {
|
).apply {
|
||||||
setMargins(0, 0, 0, 16)
|
setMargins(16, 0, 16, 16)
|
||||||
}
|
|
||||||
setPadding(24, 24, 24, 24)
|
|
||||||
background = android.graphics.drawable.GradientDrawable().apply {
|
|
||||||
setColor(android.graphics.Color.parseColor("#FAFAFA"))
|
|
||||||
cornerRadius = 16f
|
|
||||||
setStroke(2, android.graphics.Color.parseColor("#EEEEEE"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val infoText = StringBuilder()
|
|
||||||
// Note: Values like "Cow", "Holstein" stored in CSV are currently displayed as is.
|
|
||||||
// To translate stored values, we'd need a reverse lookup map or similar logic if the CSV stores English keys.
|
|
||||||
// For now, we translate the labels.
|
|
||||||
|
|
||||||
infoText.append("${StringProvider.getString("label_species")} ${details.getOrElse(1) { "-" }} ")
|
// Sort images or keep them in order. The user asked for 3 in each row.
|
||||||
infoText.append("${StringProvider.getString("label_breed")} ${details.getOrElse(2) { "-" }}\n")
|
cowFiles.forEach { file ->
|
||||||
infoText.append("${StringProvider.getString("label_age")} ${details.getOrElse(3) { "-" }} ${StringProvider.getString("unit_years")} ")
|
val thumbnailView = layoutInflater.inflate(R.layout.item_image_thumbnail, gridLayout, false)
|
||||||
infoText.append("${StringProvider.getString("label_milk_yield")} ${details.getOrElse(4) { "-" }} ${StringProvider.getString("unit_liters")}\n")
|
|
||||||
infoText.append("${StringProvider.getString("label_calving_no")} ${details.getOrElse(5) { "-" }} ")
|
val imageView = thumbnailView.findViewById<ImageView>(R.id.ivThumbnail)
|
||||||
infoText.append("${StringProvider.getString("label_status")} ${details.getOrElse(6) { "-" }}")
|
val labelView = thumbnailView.findViewById<TextView>(R.id.tvOrientationLabel)
|
||||||
|
val deleteButtonSmall = thumbnailView.findViewById<android.view.View>(R.id.btnDelete)
|
||||||
val detailsView = TextView(this).apply {
|
|
||||||
text = infoText.toString()
|
// Optionally set layout params for thumbnailView to ensure 3 per row
|
||||||
textSize = 14f
|
val displayMetrics = resources.displayMetrics
|
||||||
setTextColor(android.graphics.Color.parseColor("#5D4037"))
|
val screenWidth = displayMetrics.widthPixels
|
||||||
setLineSpacing(10f, 1f)
|
|
||||||
|
val itemWidth = (screenWidth - (48 * displayMetrics.density).toInt()) / 3
|
||||||
|
thumbnailView.layoutParams = android.widget.GridLayout.LayoutParams().apply {
|
||||||
|
width = itemWidth
|
||||||
|
height = itemWidth // Square
|
||||||
|
setMargins(4, 4, 4, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract orientation from filename
|
||||||
|
val parts = file.name.split("_")
|
||||||
|
var orientation = ""
|
||||||
|
if (parts.size >= 3) {
|
||||||
|
// format: cow_..._orientation_timestamp.jpg
|
||||||
|
// or cow_timestamp_orientation_...
|
||||||
|
// Let's guess. In CowSelectionActivity: ${name}_${orientation}_...
|
||||||
|
// name = cow_...
|
||||||
|
// so filename starts with name
|
||||||
|
|
||||||
|
// Let's just try to find a known orientation in the filename
|
||||||
|
val orientations = listOf("left", "right", "angle", "front", "back", "leftangle", "rightangle")
|
||||||
|
orientation = orientations.find { file.name.contains("_${it}_") } ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val key = when(orientation) {
|
||||||
|
"front" -> "text_front_view"
|
||||||
|
"back" -> "text_rear_view"
|
||||||
|
"left" -> "text_left_side"
|
||||||
|
"right" -> "text_right_side"
|
||||||
|
"angle" -> "text_angle_view"
|
||||||
|
"leftangle" -> "text_left_angle"
|
||||||
|
"rightangle" -> "text_right_angle"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
val label = if (key.isNotEmpty()) StringProvider.getString(key) else orientation
|
||||||
|
|
||||||
|
labelView.text = label
|
||||||
|
|
||||||
|
imageView.setImageBitmap(BitmapFactory.decodeFile(file.absolutePath))
|
||||||
|
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
|
||||||
|
imageView.setOnClickListener {
|
||||||
|
val intent = Intent(this@GalleryActivity, FullScreenImageActivity::class.java)
|
||||||
|
intent.putExtra("IMAGE_PATH", file.absolutePath)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteButtonSmall.setOnClickListener {
|
||||||
|
if (file.delete()) {
|
||||||
|
val parentDir = file.parentFile
|
||||||
|
if (parentDir != null && parentDir.exists()) {
|
||||||
|
val remaining = parentDir.listFiles()
|
||||||
|
if (remaining == null || remaining.isEmpty()) {
|
||||||
|
parentDir.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Toast.makeText(this@GalleryActivity, StringProvider.getString("toast_image_deleted"), Toast.LENGTH_SHORT).show()
|
||||||
|
refreshGallery()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this@GalleryActivity, StringProvider.getString("toast_error_deleting_image"), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gridLayout.addView(thumbnailView)
|
||||||
}
|
}
|
||||||
|
container.addView(gridLayout)
|
||||||
|
|
||||||
detailsLayout.addView(detailsView)
|
val separator = android.view.View(this).apply {
|
||||||
container.addView(detailsLayout)
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
}
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
2
|
||||||
// Group files by orientation (e.g., left, right, etc.)
|
).apply {
|
||||||
val filesByOrientation = cowFiles.groupBy { file ->
|
setMargins(0, 16, 0, 16)
|
||||||
val parts = file.name.split("_")
|
}
|
||||||
if (parts.size >= 3) parts[2] else "unknown"
|
setBackgroundColor(android.graphics.Color.LTGRAY)
|
||||||
}
|
|
||||||
|
|
||||||
filesByOrientation.forEach { (orientation, orientationFiles) ->
|
|
||||||
addOrientationSection(orientation, orientationFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separator
|
|
||||||
val separator = android.view.View(this).apply {
|
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
2
|
|
||||||
).apply {
|
|
||||||
setMargins(0, 16, 0, 16)
|
|
||||||
}
|
}
|
||||||
setBackgroundColor(android.graphics.Color.LTGRAY)
|
container.addView(separator)
|
||||||
}
|
}
|
||||||
container.addView(separator)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addOrientationSection(orientationStr: String, files: List<File>) {
|
private fun deleteCow(cowName: String) {
|
||||||
// Orientation Header
|
// Delete Images
|
||||||
val orientationHeader = TextView(this).apply {
|
val imageFolder = StorageUtils.getCowImageFolder(cowName)
|
||||||
val key = when(orientationStr.lowercase(Locale.getDefault())) {
|
if (imageFolder.exists()) {
|
||||||
"front" -> "text_front_view"
|
imageFolder.deleteRecursively()
|
||||||
"back" -> "text_rear_view"
|
|
||||||
"left" -> "text_left_side"
|
|
||||||
"right" -> "text_right_side"
|
|
||||||
"angle" -> "text_angle_view"
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
val label = if (key.isNotEmpty()) StringProvider.getString(key) else orientationStr.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
|
|
||||||
text = label
|
|
||||||
textSize = 16f
|
|
||||||
setTypeface(null, android.graphics.Typeface.BOLD)
|
|
||||||
setPadding(16, 8, 0, 8)
|
|
||||||
}
|
}
|
||||||
container.addView(orientationHeader)
|
|
||||||
|
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||||
// Grid for thumbnails
|
|
||||||
val gridLayout = android.widget.GridLayout(this).apply {
|
// Delete Profile
|
||||||
columnCount = 3
|
val profileFile = File(docsFolder, "cow_profiles.csv")
|
||||||
layoutParams = LinearLayout.LayoutParams(
|
if (profileFile.exists()) {
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
val lines = profileFile.readLines()
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
val newLines = lines.filter { !it.startsWith("$cowName,") }
|
||||||
).apply {
|
FileWriter(profileFile).use { writer ->
|
||||||
setMargins(16, 0, 16, 16)
|
newLines.forEach { writer.write(it + "\n") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Images for this orientation
|
// Delete Ratings
|
||||||
files.forEach { file ->
|
val ratingsFile = File(docsFolder, "cow_ratings.csv")
|
||||||
val thumbnailView = layoutInflater.inflate(R.layout.item_image_thumbnail, gridLayout, false)
|
if (ratingsFile.exists()) {
|
||||||
|
val lines = ratingsFile.readLines()
|
||||||
val imageView = thumbnailView.findViewById<ImageView>(R.id.ivThumbnail)
|
val newLines = lines.filter { !it.startsWith("$cowName,") }
|
||||||
val deleteButton = thumbnailView.findViewById<android.view.View>(R.id.btnDelete)
|
FileWriter(ratingsFile).use { writer ->
|
||||||
|
newLines.forEach { writer.write(it + "\n") }
|
||||||
imageView.setImageBitmap(BitmapFactory.decodeFile(file.absolutePath))
|
|
||||||
|
|
||||||
imageView.setOnClickListener {
|
|
||||||
val intent = Intent(this@GalleryActivity, FullScreenImageActivity::class.java)
|
|
||||||
intent.putExtra("IMAGE_PATH", file.absolutePath)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteButton.setOnClickListener {
|
|
||||||
if (file.delete()) {
|
|
||||||
Toast.makeText(this@GalleryActivity, StringProvider.getString("toast_image_deleted"), Toast.LENGTH_SHORT).show()
|
|
||||||
refreshGallery()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this@GalleryActivity, StringProvider.getString("toast_error_deleting_image"), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gridLayout.addView(thumbnailView)
|
|
||||||
}
|
}
|
||||||
container.addView(gridLayout)
|
|
||||||
|
Toast.makeText(this, "Cow profile deleted", Toast.LENGTH_SHORT).show()
|
||||||
|
refreshGallery()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
refreshGallery()
|
refreshGallery()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,20 +2,27 @@ package com.example.animalrating
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
|
||||||
class HomeActivity : AppCompatActivity() {
|
class HomeActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
|
@ -25,7 +32,13 @@ class HomeActivity : AppCompatActivity() {
|
||||||
const val ALGORITHM_JACCARD = "Jaccard Similarity"
|
const val ALGORITHM_JACCARD = "Jaccard Similarity"
|
||||||
private const val PERMISSION_REQUEST_CODE = 101
|
private const val PERMISSION_REQUEST_CODE = 101
|
||||||
private const val PREF_COW_ILLUSTRATION_INDEX = "COW_ILLUSTRATION_INDEX"
|
private const val PREF_COW_ILLUSTRATION_INDEX = "COW_ILLUSTRATION_INDEX"
|
||||||
|
const val PREF_AUTO_CAPTURE = "AUTO_CAPTURE_ENABLED"
|
||||||
|
const val PREF_DEBUG_ENABLE = "AUTO_DEBUG_ENABLED"
|
||||||
|
const val PREF_MASK_DISPLAY = "MASK_DISPLAY_ENABLED"
|
||||||
|
var IS_RELEASE_BUILD = true // Set to true for release
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val internalAlgorithms = listOf(ALGORITHM_HAMMING, ALGORITHM_EUCLIDEAN, ALGORITHM_JACCARD)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -44,23 +57,27 @@ class HomeActivity : AppCompatActivity() {
|
||||||
permissions.add(android.Manifest.permission.CAMERA)
|
permissions.add(android.Manifest.permission.CAMERA)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// Android 11+: Request All Files Access for managing external storage
|
||||||
|
if (!android.os.Environment.isExternalStorageManager()) {
|
||||||
|
try {
|
||||||
|
val intent = Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
val uri = android.net.Uri.fromParts("package", packageName, null)
|
||||||
|
intent.data = uri
|
||||||
|
startActivity(intent)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
val intent = Intent(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Android 10 and below
|
||||||
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
permissions.add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
permissions.add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
}
|
}
|
||||||
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
permissions.add(android.Manifest.permission.READ_EXTERNAL_STORAGE)
|
permissions.add(android.Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
permissions.add(android.Manifest.permission.READ_MEDIA_IMAGES)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
permissions.add(android.Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (permissions.isNotEmpty()) {
|
if (permissions.isNotEmpty()) {
|
||||||
|
|
@ -69,6 +86,15 @@ class HomeActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUI() {
|
private fun setupUI() {
|
||||||
|
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
||||||
|
|
||||||
|
// Menu Button Logic
|
||||||
|
val drawerLayout = findViewById<DrawerLayout>(R.id.drawer_layout)
|
||||||
|
val btnMenu = findViewById<ImageButton>(R.id.btnMenu)
|
||||||
|
btnMenu?.setOnClickListener {
|
||||||
|
drawerLayout.openDrawer(GravityCompat.START)
|
||||||
|
}
|
||||||
|
|
||||||
// Language Spinner
|
// Language Spinner
|
||||||
val languageSpinner = findViewById<Spinner>(R.id.spinnerLanguage)
|
val languageSpinner = findViewById<Spinner>(R.id.spinnerLanguage)
|
||||||
val languages = StringProvider.getLanguages()
|
val languages = StringProvider.getLanguages()
|
||||||
|
|
@ -76,7 +102,6 @@ class HomeActivity : AppCompatActivity() {
|
||||||
languageAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
languageAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
languageSpinner.adapter = languageAdapter
|
languageSpinner.adapter = languageAdapter
|
||||||
|
|
||||||
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
|
||||||
val savedLang = prefs.getString("LANGUAGE", "English")
|
val savedLang = prefs.getString("LANGUAGE", "English")
|
||||||
languageSpinner.setSelection(languages.indexOf(savedLang))
|
languageSpinner.setSelection(languages.indexOf(savedLang))
|
||||||
|
|
||||||
|
|
@ -99,17 +124,18 @@ class HomeActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set text from StringProvider
|
// Set text from StringProvider
|
||||||
findViewById<TextView>(R.id.tvTitle).text = StringProvider.getString("title_home")
|
findViewById<TextView>(R.id.tvTitle).text = StringProvider.getString("app_name")
|
||||||
|
findViewById<TextView>(R.id.tvSubtitle).text = StringProvider.getString("subtitle_home")
|
||||||
findViewById<MaterialButton>(R.id.btnViewGallery).text = StringProvider.getString("btn_view_gallery")
|
findViewById<MaterialButton>(R.id.btnViewGallery).text = StringProvider.getString("btn_view_gallery")
|
||||||
findViewById<MaterialButton>(R.id.btnSelectCow).text = StringProvider.getString("btn_select_cow")
|
findViewById<MaterialButton>(R.id.btnSelectCow).text = StringProvider.getString("btn_select_cow")
|
||||||
findViewById<TextView>(R.id.tvAlgorithmLabel).text = StringProvider.getString("label_algorithm")
|
findViewById<TextView>(R.id.tvAlgorithmLabel).text = StringProvider.getString("label_algorithm")
|
||||||
findViewById<TextView>(R.id.tvThresholdLabel).text = StringProvider.getString("label_match_threshold")
|
findViewById<TextView>(R.id.tvThresholdLabel).text = StringProvider.getString("label_match_threshold")
|
||||||
|
|
||||||
// Cow Illustration and Logic
|
// Cow Illustration and Logic
|
||||||
val ivCowIllustration = findViewById<ImageView>(R.id.ivCowIllustration)
|
val ivCowIllustration = findViewById<ImageView>(R.id.ivCowIllustration)
|
||||||
val savedIndex = prefs.getInt(PREF_COW_ILLUSTRATION_INDEX, 0)
|
val savedIndex = prefs.getInt(PREF_COW_ILLUSTRATION_INDEX, 0)
|
||||||
setCowIllustration(ivCowIllustration, savedIndex)
|
setCowIllustration(ivCowIllustration, savedIndex)
|
||||||
|
|
||||||
ivCowIllustration.setOnClickListener { view ->
|
ivCowIllustration.setOnClickListener { view ->
|
||||||
showIllustrationPopup(view, ivCowIllustration)
|
showIllustrationPopup(view, ivCowIllustration)
|
||||||
}
|
}
|
||||||
|
|
@ -120,28 +146,90 @@ class HomeActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewById<MaterialButton>(R.id.btnSelectCow).setOnClickListener {
|
findViewById<MaterialButton>(R.id.btnSelectCow).setOnClickListener {
|
||||||
saveSettingsAndStart()
|
// if (!IS_RELEASE_BUILD) { // This logic seemed wrong in previous thought, we want to start activity regardless?
|
||||||
|
// Ah, user said "dont show the settings panel". It doesn't mean disable "Add Cow".
|
||||||
|
// But previously I wrote logic to save settings before start.
|
||||||
|
if (!IS_RELEASE_BUILD) {
|
||||||
|
saveSettings()
|
||||||
|
}
|
||||||
|
startActivity(Intent(this, CowSelectionActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto Capture Toggle
|
||||||
|
val switchAutoCapture = findViewById<SwitchMaterial>(R.id.switchAutoCapture)
|
||||||
|
switchAutoCapture.text = StringProvider.getString("label_auto_capture")
|
||||||
|
val autoCaptureEnabled = prefs.getBoolean(PREF_AUTO_CAPTURE, false)
|
||||||
|
switchAutoCapture.isChecked = autoCaptureEnabled
|
||||||
|
|
||||||
|
val states = arrayOf(
|
||||||
|
intArrayOf(android.R.attr.state_checked),
|
||||||
|
intArrayOf(-android.R.attr.state_checked)
|
||||||
|
)
|
||||||
|
val thumbColors = intArrayOf(
|
||||||
|
Color.parseColor("#6D4C41"), // Dark Brown for On
|
||||||
|
Color.GRAY // Grey for Off
|
||||||
|
)
|
||||||
|
val trackColors = intArrayOf(
|
||||||
|
Color.parseColor("#8D6E63"), // Lighter Brown for On track
|
||||||
|
Color.LTGRAY // Light Grey for Off track
|
||||||
|
)
|
||||||
|
|
||||||
|
switchAutoCapture.thumbTintList = ColorStateList(states, thumbColors)
|
||||||
|
switchAutoCapture.trackTintList = ColorStateList(states, trackColors)
|
||||||
|
|
||||||
|
switchAutoCapture.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
prefs.edit().putBoolean(PREF_AUTO_CAPTURE, isChecked).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
val switchEnableDebug = findViewById<SwitchMaterial>(R.id.switchEnableDebug)
|
||||||
|
switchEnableDebug.text = StringProvider.getString("label_enable_debug")
|
||||||
|
val debugEnabled = prefs.getBoolean(PREF_DEBUG_ENABLE, false)
|
||||||
|
switchEnableDebug.isChecked = debugEnabled
|
||||||
|
IS_RELEASE_BUILD = !debugEnabled
|
||||||
|
|
||||||
|
switchEnableDebug.thumbTintList = ColorStateList(states, thumbColors)
|
||||||
|
switchEnableDebug.trackTintList = ColorStateList(states, trackColors)
|
||||||
|
|
||||||
|
switchEnableDebug.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
prefs.edit().putBoolean(PREF_DEBUG_ENABLE, isChecked).apply()
|
||||||
|
IS_RELEASE_BUILD = !isChecked
|
||||||
|
if (IS_RELEASE_BUILD) {
|
||||||
|
findViewById<CardView>(R.id.viewSettings).visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
saveSettings()
|
||||||
|
findViewById<CardView>(R.id.viewSettings).visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val headerSettings = findViewById<TextView>(R.id.tvHeaderSettings)
|
||||||
|
if (headerSettings != null) headerSettings.text = StringProvider.getString("header_settings")
|
||||||
|
|
||||||
// Algorithm Spinner
|
// Algorithm Spinner
|
||||||
val spinner = findViewById<Spinner>(R.id.spinnerAlgorithm)
|
val spinner = findViewById<Spinner>(R.id.spinnerAlgorithm)
|
||||||
val algorithms = listOf(ALGORITHM_HAMMING, ALGORITHM_EUCLIDEAN, ALGORITHM_JACCARD)
|
|
||||||
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, algorithms)
|
val displayAlgorithms = listOf(
|
||||||
|
StringProvider.getString("algo_hamming"),
|
||||||
|
StringProvider.getString("algo_euclidean"),
|
||||||
|
StringProvider.getString("algo_jaccard")
|
||||||
|
)
|
||||||
|
|
||||||
|
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, displayAlgorithms)
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
spinner.adapter = adapter
|
spinner.adapter = adapter
|
||||||
|
|
||||||
// Set default selection from preferences or intent
|
// Set default selection from preferences or intent
|
||||||
val savedAlg = prefs.getString("ALGORITHM", ALGORITHM_HAMMING)
|
val savedAlg = prefs.getString("ALGORITHM", ALGORITHM_HAMMING)
|
||||||
spinner.setSelection(algorithms.indexOf(savedAlg))
|
val index = internalAlgorithms.indexOf(savedAlg)
|
||||||
|
spinner.setSelection(if (index >= 0) index else 0)
|
||||||
|
|
||||||
// Threshold SeekBar
|
// Threshold SeekBar
|
||||||
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
|
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
|
||||||
val tvThreshold = findViewById<TextView>(R.id.tvThresholdValue)
|
val tvThreshold = findViewById<TextView>(R.id.tvThresholdValue)
|
||||||
|
|
||||||
val savedThreshold = prefs.getInt("THRESHOLD", 75)
|
val savedThreshold = prefs.getInt("THRESHOLD", 75)
|
||||||
seekBar.progress = savedThreshold
|
seekBar.progress = savedThreshold
|
||||||
tvThreshold.text = "$savedThreshold%"
|
tvThreshold.text = "$savedThreshold%"
|
||||||
|
|
||||||
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
|
||||||
tvThreshold.text = "$progress%"
|
tvThreshold.text = "$progress%"
|
||||||
|
|
@ -149,6 +237,35 @@ class HomeActivity : AppCompatActivity() {
|
||||||
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
|
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
|
||||||
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
|
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Mask Display Toggle
|
||||||
|
val switchMaskDisplay = findViewById<SwitchMaterial>(R.id.switchMaskDisplay)
|
||||||
|
switchMaskDisplay.text = StringProvider.getString("label_mask_display")
|
||||||
|
val maskDisplayEnabled = prefs.getBoolean(PREF_MASK_DISPLAY, false)
|
||||||
|
switchMaskDisplay.isChecked = maskDisplayEnabled
|
||||||
|
|
||||||
|
switchMaskDisplay.thumbTintList = ColorStateList(states, thumbColors)
|
||||||
|
switchMaskDisplay.trackTintList = ColorStateList(states, trackColors)
|
||||||
|
|
||||||
|
switchMaskDisplay.setOnCheckedChangeListener { _, isChecked ->
|
||||||
|
prefs.edit().putBoolean(PREF_MASK_DISPLAY, isChecked).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial visibility based on IS_RELEASE_BUILD
|
||||||
|
if (IS_RELEASE_BUILD) {
|
||||||
|
val settingsView = findViewById<CardView>(R.id.viewSettings)
|
||||||
|
settingsView.visibility = View.GONE
|
||||||
|
// Force defaults if needed, but logic above already sets defaults from prefs or code constants.
|
||||||
|
// If user wants to FORCE defaults every time release build runs:
|
||||||
|
/*
|
||||||
|
prefs.edit().putString("ALGORITHM", ALGORITHM_JACCARD).apply()
|
||||||
|
prefs.edit().putInt("THRESHOLD", 88).apply()
|
||||||
|
prefs.edit().putBoolean(PREF_MASK_DISPLAY, false).apply()
|
||||||
|
*/
|
||||||
|
// Leaving as is based on previous logic.
|
||||||
|
} else {
|
||||||
|
findViewById<CardView>(R.id.viewSettings).visibility = View.VISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showIllustrationPopup(anchor: View, imageView: ImageView) {
|
private fun showIllustrationPopup(anchor: View, imageView: ImageView) {
|
||||||
|
|
@ -171,48 +288,49 @@ class HomeActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCowIllustration(imageView: ImageView, index: Int) {
|
private fun setCowIllustration(imageView: ImageView, index: Int) {
|
||||||
// Map index to drawable resource
|
|
||||||
// We assume resources are named cow_illustration (for index 0 or default) or similar logic if strictly named
|
|
||||||
// But user said "cow_illustration_{n}, where n=0,1,2,3,4"
|
|
||||||
// However, currently we likely only have 'cow_illustration' in drawable from previous step, or user implies they exist.
|
|
||||||
// We will try to find resources dynamically.
|
|
||||||
|
|
||||||
val resName = "cow_illustration_$index"
|
val resName = "cow_illustration_$index"
|
||||||
val resId = resources.getIdentifier(resName, "drawable", packageName)
|
val resId = resources.getIdentifier(resName, "drawable", packageName)
|
||||||
|
|
||||||
if (resId != 0) {
|
if (resId != 0) {
|
||||||
imageView.setImageResource(resId)
|
imageView.setImageResource(resId)
|
||||||
} else {
|
} else {
|
||||||
// Fallback to default if specific one not found
|
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
// Maybe it's just "cow_illustration" without suffix if n=0 is base?
|
|
||||||
// Or user provided 5 distinct files. We try 'cow_illustration' as fallback.
|
|
||||||
val defaultId = resources.getIdentifier("cow_illustration", "drawable", packageName)
|
val defaultId = resources.getIdentifier("cow_illustration", "drawable", packageName)
|
||||||
if (defaultId != 0) imageView.setImageResource(defaultId)
|
if (defaultId != 0) imageView.setImageResource(defaultId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveSettings() {
|
private fun saveSettings() {
|
||||||
val spinner = findViewById<Spinner>(R.id.spinnerAlgorithm)
|
val spinner = findViewById<Spinner>(R.id.spinnerAlgorithm)
|
||||||
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
|
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
|
||||||
|
val showSegmentationMask = findViewById<SwitchMaterial>(R.id.switchMaskDisplay)
|
||||||
if (spinner != null && seekBar != null) {
|
|
||||||
val selectedAlgorithm = spinner.selectedItem?.toString() ?: ALGORITHM_HAMMING
|
if (spinner != null && seekBar != null && showSegmentationMask != null) {
|
||||||
|
val selectedIndex = spinner.selectedItemPosition
|
||||||
|
val selectedAlgorithm = if (selectedIndex >= 0 && selectedIndex < internalAlgorithms.size) {
|
||||||
|
internalAlgorithms[selectedIndex]
|
||||||
|
} else {
|
||||||
|
ALGORITHM_HAMMING
|
||||||
|
}
|
||||||
|
|
||||||
val threshold = seekBar.progress
|
val threshold = seekBar.progress
|
||||||
|
|
||||||
// Save to preferences
|
// Save to preferences
|
||||||
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
||||||
|
val showMask = prefs.getBoolean(PREF_MASK_DISPLAY, false)
|
||||||
prefs.edit().apply {
|
prefs.edit().apply {
|
||||||
putString("ALGORITHM", selectedAlgorithm)
|
putString("ALGORITHM", selectedAlgorithm)
|
||||||
putInt("THRESHOLD", threshold)
|
putInt("THRESHOLD", threshold)
|
||||||
|
putBoolean(PREF_MASK_DISPLAY, showMask)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveSettingsAndStart() {
|
private fun saveSettingsAndStart() {
|
||||||
saveSettings()
|
if(!IS_RELEASE_BUILD)
|
||||||
|
saveSettings()
|
||||||
startActivity(Intent(this, CowSelectionActivity::class.java))
|
startActivity(Intent(this, CowSelectionActivity::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,328 @@
|
||||||
|
package com.example.animalrating
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class RatingActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var currentCowName: String
|
||||||
|
private lateinit var ratingsContainer: LinearLayout
|
||||||
|
|
||||||
|
// Feature list (internal keys)
|
||||||
|
private val features = listOf(
|
||||||
|
"Stature", "Chest width", "Body depth", "Angularity",
|
||||||
|
"Rump angle", "Rump width", "Rear legs set", "Rear legs rear view",
|
||||||
|
"Foot angle", "Fore udder attachment", "Rear udder height",
|
||||||
|
"Central ligament", "Udder depth", "Front teat position",
|
||||||
|
"Teat length", "Rear teat position", "Locomotion",
|
||||||
|
"Body condition score", "Hock development", "Bone structure",
|
||||||
|
"Rear udder width", "Teat thickness", "Muscularity"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val ratingsMap = mutableMapOf<String, Int>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_rating)
|
||||||
|
|
||||||
|
StringProvider.initialize(this)
|
||||||
|
|
||||||
|
currentCowName = intent.getStringExtra("COW_NAME") ?: run {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val toolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||||
|
toolbar.setNavigationOnClickListener { finish() }
|
||||||
|
findViewById<TextView>(R.id.tvToolbarTitle)?.text = StringProvider.getString("title_rate_cow")
|
||||||
|
|
||||||
|
setupUIStrings()
|
||||||
|
loadCowDetails()
|
||||||
|
loadCowImages()
|
||||||
|
setupRatingSection()
|
||||||
|
loadExistingRatings()
|
||||||
|
|
||||||
|
findViewById<MaterialButton>(R.id.btnSaveRating).setOnClickListener {
|
||||||
|
saveRatings()
|
||||||
|
}
|
||||||
|
|
||||||
|
findViewById<MaterialButton>(R.id.btnCancelRating).setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUIStrings() {
|
||||||
|
findViewById<TextView>(R.id.tvHeaderPhotos)?.text = StringProvider.getString("header_photos")
|
||||||
|
findViewById<TextView>(R.id.tvHeaderCowDetails)?.text = StringProvider.getString("header_cow_details")
|
||||||
|
findViewById<TextView>(R.id.tvHeaderFeatureRatings)?.text = StringProvider.getString("header_feature_ratings")
|
||||||
|
findViewById<TextInputLayout>(R.id.tilComments)?.hint = StringProvider.getString("hint_comments")
|
||||||
|
findViewById<MaterialButton>(R.id.btnSaveRating)?.text = StringProvider.getString("btn_save_rating")
|
||||||
|
findViewById<MaterialButton>(R.id.btnCancelRating)?.text = StringProvider.getString("btn_cancel")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadCowDetails() {
|
||||||
|
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||||
|
val csvFile = File(docsFolder, "cow_profiles.csv")
|
||||||
|
if (!csvFile.exists()) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val lines = csvFile.readLines()
|
||||||
|
val record = lines.find { it.startsWith("$currentCowName,") }?.split(",") ?: return
|
||||||
|
|
||||||
|
if (record.size >= 8) {
|
||||||
|
// Translate values for display
|
||||||
|
val storedSpecies = record[1]
|
||||||
|
val speciesKey = StringProvider.getKeyForEnglishValue(storedSpecies)
|
||||||
|
val displaySpecies = if (speciesKey != null) StringProvider.getString(speciesKey) else storedSpecies
|
||||||
|
|
||||||
|
val storedBreed = record[2]
|
||||||
|
val breedKey = StringProvider.getKeyForEnglishValue(storedBreed)
|
||||||
|
val displayBreed = if (breedKey != null) StringProvider.getString(breedKey) else storedBreed
|
||||||
|
|
||||||
|
val storedStatus = record[6]
|
||||||
|
val statusKey = StringProvider.getKeyForEnglishValue(storedStatus)
|
||||||
|
val displayStatus = if (statusKey != null) StringProvider.getString(statusKey) else storedStatus
|
||||||
|
|
||||||
|
val infoText = StringBuilder()
|
||||||
|
infoText.append("${StringProvider.getString("label_species")} $displaySpecies\n")
|
||||||
|
infoText.append("${StringProvider.getString("label_breed")} $displayBreed\n")
|
||||||
|
infoText.append("${StringProvider.getString("label_age")} ${record[3]} ${StringProvider.getString("unit_years")}\n")
|
||||||
|
infoText.append("${StringProvider.getString("label_milk_yield")} ${record[4]} ${StringProvider.getString("unit_liters")}\n")
|
||||||
|
infoText.append("${StringProvider.getString("label_calving_no")} ${record[5]}\n")
|
||||||
|
infoText.append("${StringProvider.getString("label_status")} $displayStatus\n")
|
||||||
|
infoText.append("${StringProvider.getString("hint_description")}: ${record[7]}")
|
||||||
|
|
||||||
|
findViewById<TextView>(R.id.tvCowDetails).text = infoText.toString()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadCowImages() {
|
||||||
|
val container = findViewById<LinearLayout>(R.id.ratingImagesContainer) ?: return
|
||||||
|
container.removeAllViews()
|
||||||
|
|
||||||
|
val cowImagesFolder = StorageUtils.getCowImageFolder(currentCowName)
|
||||||
|
val files = cowImagesFolder.listFiles { _, name -> name.startsWith("${currentCowName}_") && name.endsWith(".jpg") } ?: return
|
||||||
|
|
||||||
|
val horizontalScroll = android.widget.HorizontalScrollView(this)
|
||||||
|
horizontalScroll.layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
horizontalScroll.isFillViewport = false
|
||||||
|
|
||||||
|
val imagesLayout = LinearLayout(this)
|
||||||
|
imagesLayout.orientation = LinearLayout.HORIZONTAL
|
||||||
|
|
||||||
|
files.forEach { file ->
|
||||||
|
val imageView = ImageView(this)
|
||||||
|
val size = (100 * resources.displayMetrics.density).toInt()
|
||||||
|
val params = LinearLayout.LayoutParams(size, size)
|
||||||
|
params.setMargins(0, 0, 16, 0)
|
||||||
|
imageView.layoutParams = params
|
||||||
|
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
|
||||||
|
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
|
||||||
|
imageView.setImageBitmap(bitmap)
|
||||||
|
|
||||||
|
imageView.setOnClickListener {
|
||||||
|
val intent = Intent(this, FullScreenImageActivity::class.java)
|
||||||
|
intent.putExtra("IMAGE_PATH", file.absolutePath)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesLayout.addView(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontalScroll.addView(imagesLayout)
|
||||||
|
container.addView(horizontalScroll)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupRatingSection() {
|
||||||
|
ratingsContainer = findViewById(R.id.ratingsContainer)
|
||||||
|
ratingsContainer.removeAllViews()
|
||||||
|
|
||||||
|
features.forEach { feature ->
|
||||||
|
val featureView = layoutInflater.inflate(R.layout.item_feature_rating, ratingsContainer, false)
|
||||||
|
|
||||||
|
// Localize feature name
|
||||||
|
val key = "feature_" + feature.lowercase(Locale.ROOT).replace(" ", "_")
|
||||||
|
val displayName = StringProvider.getString(key)
|
||||||
|
val finalName = if (displayName.isNotEmpty()) displayName else feature
|
||||||
|
|
||||||
|
featureView.findViewById<TextView>(R.id.tvFeatureName).text = finalName
|
||||||
|
|
||||||
|
val buttonContainer = featureView.findViewById<LinearLayout>(R.id.buttonContainer)
|
||||||
|
val segmentViews = mutableListOf<TextView>()
|
||||||
|
|
||||||
|
// Create 9 segments
|
||||||
|
for (i in 1..9) {
|
||||||
|
val tv = TextView(this)
|
||||||
|
val params = LinearLayout.LayoutParams(
|
||||||
|
0,
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
1f
|
||||||
|
)
|
||||||
|
tv.layoutParams = params
|
||||||
|
tv.text = i.toString()
|
||||||
|
tv.gravity = Gravity.CENTER
|
||||||
|
tv.setTextColor(ContextCompat.getColor(this, R.color.black))
|
||||||
|
tv.textSize = 14f
|
||||||
|
tv.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
|
||||||
|
tv.setOnClickListener {
|
||||||
|
val currentRating = ratingsMap[feature] ?: 0
|
||||||
|
if (currentRating == i) {
|
||||||
|
// Clicked already selected -> Clear selection
|
||||||
|
ratingsMap[feature] = 0
|
||||||
|
updateSegmentSelection(segmentViews, 0)
|
||||||
|
} else {
|
||||||
|
// Select new rating
|
||||||
|
ratingsMap[feature] = i
|
||||||
|
updateSegmentSelection(segmentViews, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentViews.add(tv)
|
||||||
|
buttonContainer.addView(tv)
|
||||||
|
}
|
||||||
|
|
||||||
|
featureView.tag = segmentViews
|
||||||
|
ratingsContainer.addView(featureView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSegmentSelection(segments: List<TextView>, selectedRating: Int) {
|
||||||
|
segments.forEachIndexed { index, tv ->
|
||||||
|
val rating = index + 1
|
||||||
|
if (rating == selectedRating) {
|
||||||
|
tv.setTextColor(Color.WHITE)
|
||||||
|
|
||||||
|
val radius = (8 * resources.displayMetrics.density)
|
||||||
|
val drawable = GradientDrawable()
|
||||||
|
drawable.setColor(Color.parseColor("#6D4C41"))
|
||||||
|
|
||||||
|
if (rating == 1) {
|
||||||
|
drawable.cornerRadii = floatArrayOf(radius, radius, 0f, 0f, 0f, 0f, radius, radius)
|
||||||
|
} else if (rating == 9) {
|
||||||
|
drawable.cornerRadii = floatArrayOf(0f, 0f, radius, radius, radius, radius, 0f, 0f)
|
||||||
|
} else {
|
||||||
|
drawable.cornerRadius = 0f
|
||||||
|
}
|
||||||
|
tv.background = drawable
|
||||||
|
} else {
|
||||||
|
tv.setTextColor(Color.parseColor("#5D4037"))
|
||||||
|
tv.background = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadExistingRatings() {
|
||||||
|
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||||
|
val ratingsFile = File(docsFolder, "cow_ratings.csv")
|
||||||
|
if (!ratingsFile.exists()) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val lines = ratingsFile.readLines()
|
||||||
|
// Format: CowID,Comments,Feature1,Feature2,...
|
||||||
|
val record = lines.find { it.startsWith("$currentCowName,") }?.split(",") ?: return
|
||||||
|
|
||||||
|
if (record.size >= 2) {
|
||||||
|
// Index 0: ID
|
||||||
|
// Index 1: Comments
|
||||||
|
val comments = record[1].replace(";", ",")
|
||||||
|
findViewById<TextInputLayout>(R.id.tilComments).editText?.setText(comments)
|
||||||
|
|
||||||
|
// Ratings start from index 2
|
||||||
|
features.forEachIndexed { index, feature ->
|
||||||
|
val ratingStr = record.getOrNull(index + 2)
|
||||||
|
val rating = ratingStr?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
|
if (rating > 0) {
|
||||||
|
ratingsMap[feature] = rating
|
||||||
|
|
||||||
|
// Find view and update by index
|
||||||
|
val featureView = ratingsContainer.getChildAt(index)
|
||||||
|
if (featureView != null) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val segments = featureView.tag as? List<TextView>
|
||||||
|
segments?.let { updateSegmentSelection(it, rating) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveRatings() {
|
||||||
|
val commentsInput = findViewById<TextInputLayout>(R.id.tilComments).editText?.text.toString()
|
||||||
|
val comments = commentsInput.replace(",", ";")
|
||||||
|
|
||||||
|
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||||
|
val ratingsFile = File(docsFolder, "cow_ratings.csv")
|
||||||
|
|
||||||
|
// Dynamically build header
|
||||||
|
val headerBuilder = StringBuilder("CowID,Comments")
|
||||||
|
features.forEach { headerBuilder.append(",$it") }
|
||||||
|
val header = headerBuilder.toString()
|
||||||
|
|
||||||
|
// Build row
|
||||||
|
val rowBuilder = StringBuilder()
|
||||||
|
rowBuilder.append("$currentCowName,$comments")
|
||||||
|
features.forEach { feature ->
|
||||||
|
val rating = ratingsMap[feature] ?: 0 // Defaults to 0 if not selected
|
||||||
|
rowBuilder.append(",$rating")
|
||||||
|
}
|
||||||
|
val newRow = rowBuilder.toString()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val lines = if (ratingsFile.exists()) ratingsFile.readLines().toMutableList() else mutableListOf()
|
||||||
|
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
lines.add(header)
|
||||||
|
} else if (lines[0] != header) {
|
||||||
|
// Header mismatch handling
|
||||||
|
}
|
||||||
|
|
||||||
|
val existingIndex = lines.indexOfFirst { it.startsWith("$currentCowName,") }
|
||||||
|
|
||||||
|
if (existingIndex != -1) {
|
||||||
|
lines[existingIndex] = newRow
|
||||||
|
} else {
|
||||||
|
lines.add(newRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
FileWriter(ratingsFile).use { writer ->
|
||||||
|
lines.forEach { line ->
|
||||||
|
writer.write(line + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(this, StringProvider.getString("toast_ratings_saved"), Toast.LENGTH_SHORT).show()
|
||||||
|
finish()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(this, StringProvider.getString("toast_error_saving_ratings"), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.example.animalrating
|
||||||
|
|
||||||
|
import android.os.Environment
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object StorageUtils {
|
||||||
|
|
||||||
|
private const val ROOT_FOLDER_NAME = "com.AnimalRating"
|
||||||
|
|
||||||
|
private fun getBaseFolder(): File {
|
||||||
|
// Changed to Android/media/com.AnimalRating as requested
|
||||||
|
val folder = File(Environment.getExternalStorageDirectory(), "Android/media/$ROOT_FOLDER_NAME")
|
||||||
|
if (!folder.exists()) {
|
||||||
|
folder.mkdirs()
|
||||||
|
}
|
||||||
|
return folder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDocumentsFolder(): File {
|
||||||
|
val folder = File(getBaseFolder(), "Documents")
|
||||||
|
if (!folder.exists()) {
|
||||||
|
folder.mkdirs()
|
||||||
|
}
|
||||||
|
return folder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImagesBaseFolder(): File {
|
||||||
|
val folder = File(getBaseFolder(), "Images")
|
||||||
|
if (!folder.exists()) {
|
||||||
|
folder.mkdirs()
|
||||||
|
}
|
||||||
|
return folder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCowImageFolder(cowId: String): File {
|
||||||
|
return File(getImagesBaseFolder(), cowId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVideosFolder(): File {
|
||||||
|
val folder = File(getBaseFolder(), "Videos")
|
||||||
|
if (!folder.exists()) {
|
||||||
|
folder.mkdirs()
|
||||||
|
}
|
||||||
|
return folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,32 +43,19 @@ class SilhouetteOverlay(context: Context, attrs: AttributeSet?) : View(context,
|
||||||
val bmpW = bmp.width.toFloat()
|
val bmpW = bmp.width.toFloat()
|
||||||
val bmpH = bmp.height.toFloat()
|
val bmpH = bmp.height.toFloat()
|
||||||
|
|
||||||
val viewAspect = viewW / viewH
|
// Calculate scale to fit (FIT_CENTER)
|
||||||
val bmpAspect = bmpW / bmpH
|
val scale = kotlin.math.min(viewW / bmpW, viewH / bmpH)
|
||||||
|
|
||||||
val cropRect: RectF
|
val scaledW = bmpW * scale
|
||||||
|
val scaledH = bmpH * scale
|
||||||
|
|
||||||
if (bmpAspect > viewAspect) {
|
val left = (viewW - scaledW) / 2f
|
||||||
// Bitmap is wider → crop left & right
|
val top = (viewH - scaledH) / 2f
|
||||||
val newW = bmpH * viewAspect
|
|
||||||
val left = (bmpW - newW) / 2f
|
|
||||||
cropRect = RectF(left, 0f, left + newW, bmpH)
|
|
||||||
} else {
|
|
||||||
// Bitmap is taller → crop top & bottom
|
|
||||||
val newH = bmpW / viewAspect
|
|
||||||
val top = (bmpH - newH) / 2f
|
|
||||||
cropRect = RectF(0f, top, bmpW, top + newH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale to exactly fill screen
|
val destRect = RectF(left, top, left + scaledW, top + scaledH)
|
||||||
val destRect = RectF(0f, 0f, viewW, viewH)
|
val srcRect = Rect(0, 0, bmp.width, bmp.height)
|
||||||
|
|
||||||
canvas.drawBitmap(bmp, cropRect.toRect(), destRect, silhouettePaint)
|
canvas.drawBitmap(bmp, srcRect, destRect, silhouettePaint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper
|
|
||||||
private fun RectF.toRect(): Rect {
|
|
||||||
return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#5D4037" />
|
||||||
|
<solid android:color="#FFFFFF" />
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<size android:width="1dp" />
|
||||||
|
<solid android:color="#5D4037" />
|
||||||
|
</shape>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
|
|
@ -46,6 +46,15 @@
|
||||||
android:layout_marginBottom="24dp"/>
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
<!-- Form Fields -->
|
<!-- Form Fields -->
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelSpecies"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Species"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/tilSpecies"
|
android:id="@+id/tilSpecies"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -56,8 +65,7 @@
|
||||||
app:boxCornerRadiusTopEnd="12dp"
|
app:boxCornerRadiusTopEnd="12dp"
|
||||||
app:boxCornerRadiusBottomStart="12dp"
|
app:boxCornerRadiusBottomStart="12dp"
|
||||||
app:boxCornerRadiusBottomEnd="12dp"
|
app:boxCornerRadiusBottomEnd="12dp"
|
||||||
app:boxStrokeColor="#6D4C41"
|
app:boxStrokeColor="#6D4C41">
|
||||||
android:hint="Species">
|
|
||||||
|
|
||||||
<AutoCompleteTextView
|
<AutoCompleteTextView
|
||||||
android:id="@+id/spinnerSpecies"
|
android:id="@+id/spinnerSpecies"
|
||||||
|
|
@ -66,6 +74,14 @@
|
||||||
android:inputType="none"/>
|
android:inputType="none"/>
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelBreed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Breed"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/tilBreed"
|
android:id="@+id/tilBreed"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -76,8 +92,7 @@
|
||||||
app:boxCornerRadiusTopEnd="12dp"
|
app:boxCornerRadiusTopEnd="12dp"
|
||||||
app:boxCornerRadiusBottomStart="12dp"
|
app:boxCornerRadiusBottomStart="12dp"
|
||||||
app:boxCornerRadiusBottomEnd="12dp"
|
app:boxCornerRadiusBottomEnd="12dp"
|
||||||
app:boxStrokeColor="#6D4C41"
|
app:boxStrokeColor="#6D4C41">
|
||||||
android:hint="Breed">
|
|
||||||
|
|
||||||
<AutoCompleteTextView
|
<AutoCompleteTextView
|
||||||
android:id="@+id/spinnerBreed"
|
android:id="@+id/spinnerBreed"
|
||||||
|
|
@ -93,48 +108,82 @@
|
||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:layout_marginBottom="16dp">
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<LinearLayout
|
||||||
android:id="@+id/tilAge"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
android:orientation="vertical">
|
||||||
app:boxCornerRadiusTopStart="12dp"
|
|
||||||
app:boxCornerRadiusTopEnd="12dp"
|
|
||||||
app:boxCornerRadiusBottomStart="12dp"
|
|
||||||
app:boxCornerRadiusBottomEnd="12dp"
|
|
||||||
app:boxStrokeColor="#6D4C41"
|
|
||||||
android:hint="Age (Years)">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<TextView
|
||||||
android:id="@+id/etAge"
|
android:id="@+id/tvLabelAge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Age (Years)"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilAge"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="number"/>
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
app:boxCornerRadiusTopStart="12dp"
|
||||||
|
app:boxCornerRadiusTopEnd="12dp"
|
||||||
|
app:boxCornerRadiusBottomStart="12dp"
|
||||||
|
app:boxCornerRadiusBottomEnd="12dp"
|
||||||
|
app:boxStrokeColor="#6D4C41">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/tilMilk"
|
android:id="@+id/etAge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
android:orientation="vertical">
|
||||||
app:boxCornerRadiusTopStart="12dp"
|
|
||||||
app:boxCornerRadiusTopEnd="12dp"
|
|
||||||
app:boxCornerRadiusBottomStart="12dp"
|
|
||||||
app:boxCornerRadiusBottomEnd="12dp"
|
|
||||||
app:boxStrokeColor="#6D4C41"
|
|
||||||
android:hint="Milk Yield (L)">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<TextView
|
||||||
|
android:id="@+id/tvLabelMilk"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Milk Yield (L)"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilMilk"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="numberDecimal"/>
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
app:boxCornerRadiusTopStart="12dp"
|
||||||
|
app:boxCornerRadiusTopEnd="12dp"
|
||||||
|
app:boxCornerRadiusBottomStart="12dp"
|
||||||
|
app:boxCornerRadiusBottomEnd="12dp"
|
||||||
|
app:boxStrokeColor="#6D4C41">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="numberDecimal"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelCalving"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Calving Number"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/tilCalving"
|
android:id="@+id/tilCalving"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -145,8 +194,7 @@
|
||||||
app:boxCornerRadiusTopEnd="12dp"
|
app:boxCornerRadiusTopEnd="12dp"
|
||||||
app:boxCornerRadiusBottomStart="12dp"
|
app:boxCornerRadiusBottomStart="12dp"
|
||||||
app:boxCornerRadiusBottomEnd="12dp"
|
app:boxCornerRadiusBottomEnd="12dp"
|
||||||
app:boxStrokeColor="#6D4C41"
|
app:boxStrokeColor="#6D4C41">
|
||||||
android:hint="Calving Number">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -190,6 +238,14 @@
|
||||||
android:text="None"/>
|
android:text="None"/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelDescription"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Description"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/tilDescription"
|
android:id="@+id/tilDescription"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -200,8 +256,7 @@
|
||||||
app:boxCornerRadiusTopEnd="12dp"
|
app:boxCornerRadiusTopEnd="12dp"
|
||||||
app:boxCornerRadiusBottomStart="12dp"
|
app:boxCornerRadiusBottomStart="12dp"
|
||||||
app:boxCornerRadiusBottomEnd="12dp"
|
app:boxCornerRadiusBottomEnd="12dp"
|
||||||
app:boxStrokeColor="#6D4C41"
|
app:boxStrokeColor="#6D4C41">
|
||||||
android:hint="Description">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -225,7 +280,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:columnCount="2"
|
android:columnCount="2"
|
||||||
android:rowCount="3"
|
android:rowCount="4"
|
||||||
android:alignmentMode="alignMargins"
|
android:alignmentMode="alignMargins"
|
||||||
android:columnOrderPreserved="false"
|
android:columnOrderPreserved="false"
|
||||||
android:layout_marginBottom="24dp">
|
android:layout_marginBottom="24dp">
|
||||||
|
|
@ -362,12 +417,77 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
<!-- Angle View -->
|
<!-- Left Angle -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:layout_columnWeight="1"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/btnLeftAngle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="#F5F5F5"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_menu_camera"
|
||||||
|
app:tint="#5D4037"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLeftAngle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Left Angle"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Right Angle -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:layout_columnWeight="1"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/btnRightAngle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="#F5F5F5"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@android:drawable/ic_menu_camera"
|
||||||
|
app:tint="#5D4037"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRightAngle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Right Angle"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Angle View (Top) - Fixed Size, No Span -->
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="100dp"
|
android:layout_height="100dp"
|
||||||
android:layout_columnWeight="1"
|
android:layout_columnWeight="1"
|
||||||
android:layout_columnSpan="2"
|
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
app:cardCornerRadius="8dp"
|
app:cardCornerRadius="8dp"
|
||||||
app:cardElevation="2dp">
|
app:cardElevation="2dp">
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,14 @@
|
||||||
android:scaleType="centerInside"
|
android:scaleType="centerInside"
|
||||||
android:contentDescription="Back" />
|
android:contentDescription="Back" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnRetake"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:text="Retake"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
@ -1,198 +1,287 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/drawer_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#F7F7F7"
|
tools:openDrawer="start">
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:background="#F7F7F7"
|
||||||
android:padding="24dp"
|
android:fillViewport="true">
|
||||||
android:gravity="center_horizontal">
|
|
||||||
|
|
||||||
<!-- Language Selection (Top Right) -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="end"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerLanguage"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:background="@drawable/bg_spinner_rounded"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Cow Illustration - Circular Logo -->
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:layout_width="180dp"
|
|
||||||
android:layout_height="180dp"
|
|
||||||
app:cardCornerRadius="90dp"
|
|
||||||
app:cardElevation="8dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
android:layout_marginBottom="24dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/ivCowIllustration"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:src="@drawable/cow_illustration_0"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:contentDescription="Cow Illustration"/>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<!-- Title -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Cow Data Collector"
|
|
||||||
android:textSize="28sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="#3E2723"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<!-- Subtitle -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvSubtitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Record and manage cow information easily."
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textColor="#6D4C41"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:layout_marginBottom="40dp"/>
|
|
||||||
|
|
||||||
<!-- Main Buttons Container -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_marginBottom="32dp">
|
android:padding="24dp"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
<!-- Add New Cow Profile Button -->
|
<!-- Menu Button (Top Right) to Open Drawer -->
|
||||||
<com.google.android.material.button.MaterialButton
|
<LinearLayout
|
||||||
android:id="@+id/btnSelectCow"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="72dp"
|
android:layout_height="wrap_content"
|
||||||
android:text="Add New Cow Profile"
|
android:gravity="end"
|
||||||
android:textSize="18sp"
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnMenu"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_sort_by_size"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Menu"
|
||||||
|
app:tint="#5D4037"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Cow Illustration - Circular Logo -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="180dp"
|
||||||
|
android:layout_height="180dp"
|
||||||
|
app:cardCornerRadius="90dp"
|
||||||
|
app:cardElevation="8dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF"
|
||||||
|
android:layout_marginBottom="24dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivCowIllustration"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:src="@drawable/cow_illustration_0"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:contentDescription="Cow Illustration"/>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<!-- Title -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Cow Data Collector"
|
||||||
|
android:textSize="28sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:cornerRadius="16dp"
|
android:textColor="#3E2723"
|
||||||
app:backgroundTint="#6D4C41"
|
android:textAlignment="center"
|
||||||
app:icon="@android:drawable/ic_input_add"
|
android:layout_marginBottom="8dp"/>
|
||||||
app:iconGravity="textStart"
|
|
||||||
app:iconPadding="12dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:elevation="4dp"/>
|
|
||||||
|
|
||||||
<!-- View Saved Profiles Button -->
|
<!-- Subtitle -->
|
||||||
<com.google.android.material.button.MaterialButton
|
<TextView
|
||||||
android:id="@+id/btnViewGallery"
|
android:id="@+id/tvSubtitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="72dp"
|
android:layout_height="wrap_content"
|
||||||
android:text="View Saved Profiles"
|
android:text="Record and manage cow information easily."
|
||||||
android:textSize="18sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold"
|
android:textColor="#6D4C41"
|
||||||
android:textColor="#5D4037"
|
android:textAlignment="center"
|
||||||
app:cornerRadius="16dp"
|
android:layout_marginBottom="40dp"/>
|
||||||
app:backgroundTint="#EFEBE9"
|
|
||||||
app:strokeColor="#5D4037"
|
|
||||||
app:strokeWidth="1dp"
|
|
||||||
app:icon="@android:drawable/ic_menu_gallery"
|
|
||||||
app:iconTint="#5D4037"
|
|
||||||
app:iconGravity="textStart"
|
|
||||||
app:iconPadding="12dp"
|
|
||||||
android:elevation="4dp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Settings Card -->
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:cardCornerRadius="16dp"
|
|
||||||
app:cardElevation="2dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
android:layout_marginBottom="24dp">
|
|
||||||
|
|
||||||
|
<!-- Main Buttons Container -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="20dp">
|
android:layout_marginBottom="32dp">
|
||||||
|
|
||||||
<TextView
|
<!-- Add New Cow Profile Button -->
|
||||||
android:layout_width="wrap_content"
|
<com.google.android.material.button.MaterialButton
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/btnSelectCow"
|
||||||
android:text="Settings"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:text="Add New Cow Profile"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="#3E2723"
|
app:cornerRadius="16dp"
|
||||||
android:layout_marginBottom="16dp"/>
|
app:backgroundTint="#6D4C41"
|
||||||
|
app:icon="@android:drawable/ic_input_add"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="12dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:elevation="4dp"/>
|
||||||
|
|
||||||
<!-- Algorithm Selection -->
|
<!-- View Saved Profiles Button -->
|
||||||
<TextView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/tvAlgorithmLabel"
|
android:id="@+id/btnViewGallery"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Algorithm"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textColor="#5D4037"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="72dp"
|
||||||
android:background="@drawable/bg_spinner_rounded"
|
android:text="View Saved Profiles"
|
||||||
android:padding="4dp"
|
android:textSize="18sp"
|
||||||
android:layout_marginBottom="20dp">
|
android:textStyle="bold"
|
||||||
<Spinner
|
android:textColor="#5D4037"
|
||||||
android:id="@+id/spinnerAlgorithm"
|
app:cornerRadius="16dp"
|
||||||
android:layout_width="match_parent"
|
app:backgroundTint="#EFEBE9"
|
||||||
android:layout_height="48dp"/>
|
app:strokeColor="#5D4037"
|
||||||
</FrameLayout>
|
app:strokeWidth="1dp"
|
||||||
|
app:icon="@android:drawable/ic_menu_gallery"
|
||||||
|
app:iconTint="#5D4037"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="12dp"
|
||||||
|
android:elevation="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Settings Card -->
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/viewSettings"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="16dp"
|
||||||
|
app:cardElevation="2dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF"
|
||||||
|
android:layout_marginBottom="24dp">
|
||||||
|
|
||||||
<!-- Threshold Slider -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:padding="20dp">
|
||||||
android:layout_marginBottom="8dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvThresholdLabel"
|
android:id="@+id/tvHeaderSettings"
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Match Threshold"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textColor="#5D4037"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvThresholdValue"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="75%"
|
android:text="Settings"
|
||||||
|
android:textSize="18sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="#3E2723"/>
|
android:textColor="#3E2723"
|
||||||
</LinearLayout>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<SeekBar
|
<!-- Algorithm Selection -->
|
||||||
android:id="@+id/seekBarThreshold"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/tvAlgorithmLabel"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:max="100"
|
android:layout_height="wrap_content"
|
||||||
android:progressTint="#6D4C41"
|
android:text="Algorithm"
|
||||||
android:thumbTint="#6D4C41"/>
|
android:textSize="14sp"
|
||||||
</LinearLayout>
|
android:textColor="#5D4037"
|
||||||
</androidx.cardview.widget.CardView>
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_spinner_rounded"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:layout_marginBottom="20dp">
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerAlgorithm"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<!-- Threshold Slider -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvThresholdLabel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Match Threshold"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#5D4037"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvThresholdValue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="75%"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#3E2723"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekBarThreshold"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:max="100"
|
||||||
|
android:progressTint="#6D4C41"
|
||||||
|
android:thumbTint="#6D4C41"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Mask Display Toggle -->
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switchMaskDisplay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Show Segmentation Mask"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:thumbTint="#6D4C41"
|
||||||
|
app:trackTint="#EFEBE9"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Navigation Drawer Content (Direct LinearLayout) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="280dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:background="#FFFFFF"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:elevation="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Menu"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#3E2723"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<!-- Language Selection -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Language"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerLanguage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="@drawable/bg_spinner_rounded"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<!-- Auto Capture Toggle -->
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switchAutoCapture"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Auto Capture"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:thumbTint="#6D4C41"
|
||||||
|
app:trackTint="#EFEBE9"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Enable Debug Toggle -->
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switchEnableDebug"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Enable Debug"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:thumbTint="#6D4C41"
|
||||||
|
app:trackTint="#EFEBE9"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
|
||||||
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<!-- ImageView to display the saved mask (Green) -->
|
<!-- ImageView to display the saved mask (Green)-->
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/savedMaskOverlay"
|
android:id="@+id/savedMaskOverlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:elevation="5dp"/>
|
android:elevation="5dp"/>
|
||||||
|
|
||||||
<!-- ImageView to display the segmentation mask (Live) -->
|
<!-- ImageView to display the segmentation mask (Live)-->
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/segmentationOverlay"
|
android:id="@+id/segmentationOverlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
@ -50,5 +50,52 @@
|
||||||
app:iconGravity="textStart"
|
app:iconGravity="textStart"
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
android:gravity="center"/>
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<!-- Toggle Auto/Manual Capture Button -->
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnToggleCaptureMode"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="top|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||||
|
app:icon="@android:drawable/ic_menu_camera"
|
||||||
|
app:iconSize="24dp"
|
||||||
|
app:iconTint="#FFFFFF"
|
||||||
|
app:backgroundTint="#80000000"
|
||||||
|
app:cornerRadius="24dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<!-- Shutter Button (Initially hidden if Auto) -->
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnShutter"
|
||||||
|
android:layout_width="72dp"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||||
|
app:icon="@android:drawable/ic_menu_camera"
|
||||||
|
app:iconSize="40dp"
|
||||||
|
app:iconTint="#FFFFFF"
|
||||||
|
app:backgroundTint="#D32F2F"
|
||||||
|
app:cornerRadius="36dp"
|
||||||
|
app:strokeColor="#FFFFFF"
|
||||||
|
app:strokeWidth="4dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<?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="#FFFFFF">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="#FFFFFF"
|
||||||
|
app:navigationIcon="@drawable/ic_round_back_button">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvToolbarTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Rate Cow"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#3E2723"/>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvHeaderPhotos"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Photos"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#3E2723"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ratingImagesContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:minHeight="100dp"/>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="2dp"
|
||||||
|
android:layout_marginBottom="24dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="#FAFAFA">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvHeaderCowDetails"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Cow Details"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCowDetails"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#3E2723"
|
||||||
|
android:lineSpacingExtra="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/tilComments"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
app:boxCornerRadiusTopStart="12dp"
|
||||||
|
app:boxCornerRadiusTopEnd="12dp"
|
||||||
|
app:boxCornerRadiusBottomStart="12dp"
|
||||||
|
app:boxCornerRadiusBottomEnd="12dp"
|
||||||
|
app:boxStrokeColor="#6D4C41"
|
||||||
|
android:hint="Comments">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:minLines="3"
|
||||||
|
android:gravity="top"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvHeaderFeatureRatings"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Feature Ratings"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="#3E2723"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ratingsContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginBottom="24dp"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnSaveRating"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:text="Save Rating"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="12dp"
|
||||||
|
app:backgroundTint="#6D4C41"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnCancelRating"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:text="Cancel"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:cornerRadius="12dp"
|
||||||
|
app:backgroundTint="#6D4C41"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvFeatureName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="#5D4037"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<!-- Rounded Rectangle Container for Segments -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/buttonContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@drawable/bg_rating_segment"
|
||||||
|
android:weightSum="9"
|
||||||
|
android:showDividers="middle"
|
||||||
|
android:divider="@drawable/divider_rating_segment"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
android:layout_margin="4dp">
|
android:layout_margin="4dp">
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
android:layout_width="100dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="100dp"
|
android:layout_height="match_parent"
|
||||||
app:cardCornerRadius="8dp"
|
app:cardCornerRadius="8dp"
|
||||||
app:cardElevation="2dp">
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
|
@ -19,6 +19,18 @@
|
||||||
android:background="#E0E0E0"/>
|
android:background="#E0E0E0"/>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvOrientationLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:background="#80000000"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:elevation="4dp"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnDelete"
|
android:id="@+id/btnDelete"
|
||||||
|
|
@ -39,6 +51,7 @@
|
||||||
android:insetBottom="0dp"
|
android:insetBottom="0dp"
|
||||||
app:iconGravity="textStart"
|
app:iconGravity="textStart"
|
||||||
app:iconPadding="0dp"
|
app:iconPadding="0dp"
|
||||||
android:gravity="center"/>
|
android:gravity="center"
|
||||||
|
android:elevation="6dp"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
@ -5,15 +5,34 @@
|
||||||
"title_gallery": "Saved Cow Profiles",
|
"title_gallery": "Saved Cow Profiles",
|
||||||
"title_add_cow_details": "Add Cow Details",
|
"title_add_cow_details": "Add Cow Details",
|
||||||
"title_cow_selection": "Cow Selection",
|
"title_cow_selection": "Cow Selection",
|
||||||
|
"title_rate_cow": "Rate Cow",
|
||||||
|
"header_photos": "Photos",
|
||||||
|
"header_cow_details": "Cow Details",
|
||||||
|
"header_feature_ratings": "Feature Ratings",
|
||||||
|
"header_settings": "Settings",
|
||||||
|
"subtitle_home": "Record and manage cow information easily.",
|
||||||
"btn_view_gallery": "View Saved Cow Profiles",
|
"btn_view_gallery": "View Saved Cow Profiles",
|
||||||
"btn_select_cow": "Select a Cow",
|
"btn_select_cow": "Add New Cow Profile",
|
||||||
"label_algorithm": "Algorithm",
|
"label_algorithm": "Algorithm",
|
||||||
"label_match_threshold": "Match Threshold",
|
"label_match_threshold": "Match Threshold",
|
||||||
|
"label_auto_capture": "Auto Capture",
|
||||||
|
"label_enable_debug": "Enable Debug",
|
||||||
|
"label_mask_display": "Show Segmentation Mask",
|
||||||
"btn_edit": "Edit",
|
"btn_edit": "Edit",
|
||||||
|
"btn_rate": "Rate",
|
||||||
|
"btn_delete": "Delete",
|
||||||
|
"btn_save_rating": "Save Rating",
|
||||||
|
"btn_retake": "Retake Photo",
|
||||||
|
"btn_exit": "Exit",
|
||||||
"hint_search_profiles": "Search profiles...",
|
"hint_search_profiles": "Search profiles...",
|
||||||
|
"hint_comments": "Comments",
|
||||||
"toast_image_deleted": "Image deleted",
|
"toast_image_deleted": "Image deleted",
|
||||||
"toast_error_deleting_image": "Error deleting image",
|
"toast_error_deleting_image": "Error deleting image",
|
||||||
|
"toast_ratings_saved": "Ratings Saved!",
|
||||||
|
"toast_error_saving_ratings": "Error saving ratings",
|
||||||
|
"toast_profile_deleted": "Cow profile deleted",
|
||||||
"text_cow_id": "Cow ID:",
|
"text_cow_id": "Cow ID:",
|
||||||
|
"text_no_details": "No details available",
|
||||||
"label_species": "Species:",
|
"label_species": "Species:",
|
||||||
"label_breed": "Breed:",
|
"label_breed": "Breed:",
|
||||||
"label_age": "Age:",
|
"label_age": "Age:",
|
||||||
|
|
@ -38,6 +57,8 @@
|
||||||
"text_left_side": "Left Side",
|
"text_left_side": "Left Side",
|
||||||
"text_right_side": "Right Side",
|
"text_right_side": "Right Side",
|
||||||
"text_angle_view": "Angle View",
|
"text_angle_view": "Angle View",
|
||||||
|
"text_left_angle": "Left Angle",
|
||||||
|
"text_right_angle": "Right Angle",
|
||||||
"btn_save_profile": "Save Profile",
|
"btn_save_profile": "Save Profile",
|
||||||
"btn_cancel": "Cancel",
|
"btn_cancel": "Cancel",
|
||||||
"toast_profile_saved": "Profile Saved!",
|
"toast_profile_saved": "Profile Saved!",
|
||||||
|
|
@ -55,7 +76,33 @@
|
||||||
"breed_gir": "Gir",
|
"breed_gir": "Gir",
|
||||||
"breed_red_sindhi": "Red Sindhi",
|
"breed_red_sindhi": "Red Sindhi",
|
||||||
"breed_murrah": "Murrah",
|
"breed_murrah": "Murrah",
|
||||||
"breed_surti": "Surti"
|
"breed_surti": "Surti",
|
||||||
|
"feature_stature": "Stature",
|
||||||
|
"feature_chest_width": "Chest width",
|
||||||
|
"feature_body_depth": "Body depth",
|
||||||
|
"feature_angularity": "Angularity",
|
||||||
|
"feature_rump_angle": "Rump angle",
|
||||||
|
"feature_rump_width": "Rump width",
|
||||||
|
"feature_rear_legs_set": "Rear legs set",
|
||||||
|
"feature_rear_legs_rear_view": "Rear legs rear view",
|
||||||
|
"feature_foot_angle": "Foot angle",
|
||||||
|
"feature_fore_udder_attachment": "Fore udder attachment",
|
||||||
|
"feature_rear_udder_height": "Rear udder height",
|
||||||
|
"feature_central_ligament": "Central ligament",
|
||||||
|
"feature_udder_depth": "Udder depth",
|
||||||
|
"feature_front_teat_position": "Front teat position",
|
||||||
|
"feature_teat_length": "Teat length",
|
||||||
|
"feature_rear_teat_position": "Rear teat position",
|
||||||
|
"feature_locomotion": "Locomotion",
|
||||||
|
"feature_body_condition_score": "Body condition score",
|
||||||
|
"feature_hock_development": "Hock development",
|
||||||
|
"feature_bone_structure": "Bone structure",
|
||||||
|
"feature_rear_udder_width": "Rear udder width",
|
||||||
|
"feature_teat_thickness": "Teat thickness",
|
||||||
|
"feature_muscularity": "Muscularity",
|
||||||
|
"algo_hamming": "Hamming Distance",
|
||||||
|
"algo_euclidean": "Euclidean Distance",
|
||||||
|
"algo_jaccard": "Jaccard Similarity"
|
||||||
},
|
},
|
||||||
"Hindi": {
|
"Hindi": {
|
||||||
"app_name": "पशु रेटिंग",
|
"app_name": "पशु रेटिंग",
|
||||||
|
|
@ -63,15 +110,34 @@
|
||||||
"title_gallery": "सहेजे गए गाय प्रोफाइल",
|
"title_gallery": "सहेजे गए गाय प्रोफाइल",
|
||||||
"title_add_cow_details": "गाय का विवरण जोड़ें",
|
"title_add_cow_details": "गाय का विवरण जोड़ें",
|
||||||
"title_cow_selection": "गाय का चयन",
|
"title_cow_selection": "गाय का चयन",
|
||||||
|
"title_rate_cow": "गाय का मूल्यांकन",
|
||||||
|
"header_photos": "तस्वीरें",
|
||||||
|
"header_cow_details": "गाय का विवरण",
|
||||||
|
"header_feature_ratings": "विशेषता रेटिंग",
|
||||||
|
"header_settings": "सेटिंग्स",
|
||||||
|
"subtitle_home": "गाय की जानकारी आसानी से रिकॉर्ड और प्रबंधित करें।",
|
||||||
"btn_view_gallery": "सहेजे गए प्रोफाइल देखें",
|
"btn_view_gallery": "सहेजे गए प्रोफाइल देखें",
|
||||||
"btn_select_cow": "एक गाय का चयन करें",
|
"btn_select_cow": "नई गाय प्रोफ़ाइल जोड़ें",
|
||||||
"label_algorithm": "एल्गोरिदम",
|
"label_algorithm": "एल्गोरिदम",
|
||||||
"label_match_threshold": "मैच थ्रेसहोल्ड",
|
"label_match_threshold": "मैच थ्रेसहोल्ड",
|
||||||
|
"label_auto_capture": "ऑटो कैप्चर",
|
||||||
|
"label_enable_debug": "डिबग सक्षम करें",
|
||||||
|
"label_mask_display": "सेगमेंटेशन मास्क दिखाएं",
|
||||||
"btn_edit": "संपादित करें",
|
"btn_edit": "संपादित करें",
|
||||||
|
"btn_rate": "रेट करें",
|
||||||
|
"btn_delete": "हटाएँ",
|
||||||
|
"btn_save_rating": "रेटिंग सहेजें",
|
||||||
|
"btn_retake": "फोटो फिर से लें",
|
||||||
|
"btn_exit": "बाहर जाएं",
|
||||||
"hint_search_profiles": "प्रोफ़ाइल खोजें...",
|
"hint_search_profiles": "प्रोफ़ाइल खोजें...",
|
||||||
|
"hint_comments": "टिप्पणियाँ",
|
||||||
"toast_image_deleted": "छवि हटा दी गई",
|
"toast_image_deleted": "छवि हटा दी गई",
|
||||||
"toast_error_deleting_image": "छवि हटाने में त्रुटि",
|
"toast_error_deleting_image": "छवि हटाने में त्रुटि",
|
||||||
|
"toast_ratings_saved": "रेटिंग सहेजी गई!",
|
||||||
|
"toast_error_saving_ratings": "रेटिंग सहेजने में त्रुटि",
|
||||||
|
"toast_profile_deleted": "गाय प्रोफ़ाइल हटाई गई",
|
||||||
"text_cow_id": "गाय आईडी:",
|
"text_cow_id": "गाय आईडी:",
|
||||||
|
"text_no_details": "कोई विवरण उपलब्ध नहीं",
|
||||||
"label_species": "प्रजाति:",
|
"label_species": "प्रजाति:",
|
||||||
"label_breed": "नस्ल:",
|
"label_breed": "नस्ल:",
|
||||||
"label_age": "आयु:",
|
"label_age": "आयु:",
|
||||||
|
|
@ -96,6 +162,8 @@
|
||||||
"text_left_side": "बाईं ओर",
|
"text_left_side": "बाईं ओर",
|
||||||
"text_right_side": "दाईं ओर",
|
"text_right_side": "दाईं ओर",
|
||||||
"text_angle_view": "कोणीय दृश्य",
|
"text_angle_view": "कोणीय दृश्य",
|
||||||
|
"text_left_angle": "बायां कोण",
|
||||||
|
"text_right_angle": "दायां कोण",
|
||||||
"btn_save_profile": "प्रोफ़ाइल सहेजें",
|
"btn_save_profile": "प्रोफ़ाइल सहेजें",
|
||||||
"btn_cancel": "रद्द करें",
|
"btn_cancel": "रद्द करें",
|
||||||
"toast_profile_saved": "प्रोफ़ाइल सहेजा गया!",
|
"toast_profile_saved": "प्रोफ़ाइल सहेजा गया!",
|
||||||
|
|
@ -113,6 +181,32 @@
|
||||||
"breed_gir": "गिर",
|
"breed_gir": "गिर",
|
||||||
"breed_red_sindhi": "लाल सिंधी",
|
"breed_red_sindhi": "लाल सिंधी",
|
||||||
"breed_murrah": "मुर्रा",
|
"breed_murrah": "मुर्रा",
|
||||||
"breed_surti": "सुरती"
|
"breed_surti": "सुरती",
|
||||||
|
"feature_stature": "कद",
|
||||||
|
"feature_chest_width": "छाती की चौड़ाई",
|
||||||
|
"feature_body_depth": "शरीर की गहराई",
|
||||||
|
"feature_angularity": "कोणीयता",
|
||||||
|
"feature_rump_angle": "रंप कोण",
|
||||||
|
"feature_rump_width": "रंप चौड़ाई",
|
||||||
|
"feature_rear_legs_set": "पिछले पैर सेट",
|
||||||
|
"feature_rear_legs_rear_view": "पिछले पैर पीछे का दृश्य",
|
||||||
|
"feature_foot_angle": "पैर का कोण",
|
||||||
|
"feature_fore_udder_attachment": "आगे का अडर जुड़ाव",
|
||||||
|
"feature_rear_udder_height": "पीछे के अडर की ऊंचाई",
|
||||||
|
"feature_central_ligament": "केंद्रीय लिगामेंट",
|
||||||
|
"feature_udder_depth": "अडर की गहराई",
|
||||||
|
"feature_front_teat_position": "सामने के थन की स्थिति",
|
||||||
|
"feature_teat_length": "थन की लंबाई",
|
||||||
|
"feature_rear_teat_position": "पीछे के थन की स्थिति",
|
||||||
|
"feature_locomotion": "चाल",
|
||||||
|
"feature_body_condition_score": "शारीरिक स्थिति स्कोर",
|
||||||
|
"feature_hock_development": "हॉक विकास",
|
||||||
|
"feature_bone_structure": "हड्डी की संरचना",
|
||||||
|
"feature_rear_udder_width": "पीछे के अडर की चौड़ाई",
|
||||||
|
"feature_teat_thickness": "थन की मोटाई",
|
||||||
|
"feature_muscularity": "मांसलता",
|
||||||
|
"algo_hamming": "हैमिंग दूरी",
|
||||||
|
"algo_euclidean": "यूक्लिडियन दूरी",
|
||||||
|
"algo_jaccard": "जैकार्ड समानता"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue