v1 commit
This commit is contained in:
parent
7f24f1cae1
commit
1861d14a7f
|
|
@ -13,6 +13,9 @@
|
|||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="release">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -27,6 +27,7 @@ android {
|
|||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@
|
|||
android:exported="false"
|
||||
android:theme="@style/Theme.AnimalRating" />
|
||||
|
||||
<activity
|
||||
android:name=".RatingActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.AnimalRating" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -2,6 +2,7 @@ package com.example.animalrating
|
|||
|
||||
import android.Manifest
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
|
|
@ -12,10 +13,12 @@ import android.os.Bundle
|
|||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.ExperimentalGetImage
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageCaptureException
|
||||
|
|
@ -49,11 +52,15 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
private var isPhotoTaken = false
|
||||
private var matchThreshold = 75
|
||||
private var algorithm = HomeActivity.ALGORITHM_HAMMING
|
||||
private var isAutoCapture = true
|
||||
private var isMaskDisplayEnabled = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
StringProvider.initialize(this)
|
||||
|
||||
cowName = intent.getStringExtra("COW_NAME")
|
||||
orientation = intent.getStringExtra("ORIENTATION")
|
||||
|
||||
|
|
@ -61,6 +68,8 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
||||
matchThreshold = prefs.getInt("THRESHOLD", 75)
|
||||
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
|
||||
if (orientation == "front" || orientation == "back") {
|
||||
|
|
@ -78,16 +87,56 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
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()
|
||||
|
||||
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
|
||||
overlay.setSilhouette(silhouetteId)
|
||||
|
||||
// Need to wait for layout to get width/height for scaling mask correctly
|
||||
savedMaskOverlay.post {
|
||||
loadSavedMask()
|
||||
}
|
||||
|
||||
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() {
|
||||
if (isPhotoTaken) return
|
||||
isPhotoTaken = true
|
||||
|
|
@ -96,15 +145,22 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
|
||||
val name = cowName ?: "unknown"
|
||||
val side = orientation ?: "unknown"
|
||||
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
|
||||
|
||||
// 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")
|
||||
}
|
||||
val count = (existingFiles?.size ?: 0) + 1
|
||||
|
||||
if (!cowFolder.exists()) {
|
||||
cowFolder.mkdirs()
|
||||
}
|
||||
|
||||
val filename = "${name}_${side}_${count}.jpg"
|
||||
val file = File(filesDir, filename)
|
||||
val file = File(cowFolder, filename)
|
||||
|
||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()
|
||||
|
||||
|
|
@ -113,8 +169,8 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
ContextCompat.getMainExecutor(this),
|
||||
object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onError(exc: ImageCaptureException) {
|
||||
Log.e("MainActivity", "Photo capture failed: ${exc.message}", exc)
|
||||
Toast.makeText(baseContext, "Photo capture failed", Toast.LENGTH_SHORT).show()
|
||||
Log.e("CameraProcessor", "Photo capture failed: ${exc.message}", exc)
|
||||
Toast.makeText(baseContext, StringProvider.getString("toast_capture_failed"), Toast.LENGTH_SHORT).show()
|
||||
isPhotoTaken = false
|
||||
}
|
||||
|
||||
|
|
@ -134,16 +190,30 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
}
|
||||
}
|
||||
|
||||
// Save to public gallery
|
||||
saveToGallery(file)
|
||||
val retakePath = intent.getStringExtra("RETAKE_IMAGE_PATH")
|
||||
if (!retakePath.isNullOrEmpty()) {
|
||||
val oldFile = File(retakePath)
|
||||
if (oldFile.exists()) {
|
||||
oldFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
val msg = "Saved as $filename"
|
||||
val msg = "${StringProvider.getString("toast_saved_as")} $filename"
|
||||
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()
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("MainActivity", "Error saving image", e)
|
||||
Toast.makeText(baseContext, "Error saving image", Toast.LENGTH_SHORT).show()
|
||||
Log.e("CameraProcessor", "Error saving image", e)
|
||||
Toast.makeText(baseContext, StringProvider.getString("toast_error_saving_image"), Toast.LENGTH_SHORT).show()
|
||||
isPhotoTaken = false
|
||||
}
|
||||
}
|
||||
|
|
@ -152,38 +222,7 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
}
|
||||
|
||||
private fun saveToGallery(file: File) {
|
||||
val values = ContentValues().apply {
|
||||
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)
|
||||
}
|
||||
// Removed
|
||||
}
|
||||
|
||||
private fun loadSavedMask() {
|
||||
|
|
@ -195,17 +234,50 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
try {
|
||||
val savedBitmap = BitmapFactory.decodeFile(file.absolutePath)
|
||||
|
||||
// Apply green color filter for visualization (on original size)
|
||||
if (savedBitmap != null) {
|
||||
// Apply green color filter for visualization
|
||||
if (isMaskDisplayEnabled) {
|
||||
val greenMask = applyGreenColor(savedBitmap)
|
||||
savedMaskOverlay.setImageBitmap(greenMask)
|
||||
savedMaskOverlay.alpha = 0.5f
|
||||
|
||||
// Scale to 640x480 (or flipped for portrait) for comparison
|
||||
// Calculate scale to match FIT_CENTER logic of SilhouetteOverlay
|
||||
val viewW = savedMaskOverlay.width.toFloat()
|
||||
val viewH = savedMaskOverlay.height.toFloat()
|
||||
|
||||
if (viewW > 0 && viewH > 0) {
|
||||
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) {
|
||||
Log.e("CameraProcessor", "Error loading saved mask", e)
|
||||
|
|
@ -269,6 +341,7 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
}, ContextCompat.getMainExecutor(this))
|
||||
}
|
||||
|
||||
@ExperimentalGetImage
|
||||
override fun onFrame(imageProxy: ImageProxy) {
|
||||
if (isPhotoTaken) {
|
||||
imageProxy.close()
|
||||
|
|
@ -282,10 +355,14 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
|
|||
runOnUiThread {
|
||||
if (result.mask != null) {
|
||||
currentMask = result.mask
|
||||
if (isMaskDisplayEnabled) {
|
||||
segmentationOverlay.setImageBitmap(result.mask)
|
||||
} else {
|
||||
segmentationOverlay.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
if (result.isMatch) {
|
||||
}
|
||||
if (isAutoCapture && result.isMatch) {
|
||||
takePhoto()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import android.view.View
|
|||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.Button
|
||||
import android.widget.GridLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RadioButton
|
||||
|
|
@ -34,51 +33,21 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
private var currentCowName: String? = null
|
||||
private lateinit var imagesContainer: LinearLayout
|
||||
private val storagePermissionCode = 101
|
||||
private val orientationViews = mutableMapOf<String, View>()
|
||||
private val initialImagePaths = mutableSetOf<String>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_cow_selection)
|
||||
|
||||
// Initialize StringProvider
|
||||
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)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
|
||||
// NOTE: The navigation icon is set in XML (@drawable/ic_back_arrow).
|
||||
// We handle the click here.
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
@ -87,20 +56,24 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
|
||||
imagesContainer = findViewById(R.id.currentCowImagesContainer)
|
||||
|
||||
// Restore cow name if available, or generate new if requested
|
||||
currentCowName = savedInstanceState?.getString("COW_NAME") ?: intent.getStringExtra("COW_NAME")
|
||||
|
||||
if (currentCowName == null) {
|
||||
generateNewCowName()
|
||||
}
|
||||
|
||||
// Try to load existing data if we are editing an existing cow
|
||||
loadInitialImages()
|
||||
|
||||
if (intent.hasExtra("COW_NAME")) {
|
||||
loadCowDetails(currentCowName!!)
|
||||
}
|
||||
|
||||
updateCowNameDisplay()
|
||||
refreshCowImages()
|
||||
orientationViews["left"] = findViewById(R.id.btnLeft)
|
||||
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 {
|
||||
if (checkStoragePermissions()) {
|
||||
|
|
@ -111,29 +84,69 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
findViewById<Button>(R.id.btnCancel).setOnClickListener {
|
||||
deleteSessionImages()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
val buttons = mapOf(
|
||||
R.id.btnLeft to Pair(R.drawable.left, "left"),
|
||||
R.id.btnRight to Pair(R.drawable.right, "right"),
|
||||
R.id.btnTop to Pair(R.drawable.angle, "angle"),
|
||||
R.id.btnFront to Pair(R.drawable.front, "front"),
|
||||
R.id.btnBack to Pair(R.drawable.back, "back")
|
||||
)
|
||||
|
||||
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 loadInitialImages() {
|
||||
val name = currentCowName ?: return
|
||||
val cowFolder = StorageUtils.getCowImageFolder(name)
|
||||
if (cowFolder.exists()) {
|
||||
cowFolder.listFiles()?.forEach { file ->
|
||||
if (file.isFile) {
|
||||
initialImagePaths.add(file.absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||
|
|
@ -166,15 +179,9 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun saveProfile() {
|
||||
// Get displayed values
|
||||
val speciesDisplay = findViewById<AutoCompleteTextView>(R.id.spinnerSpecies).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 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 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 {
|
||||
val fileExists = csvFile.exists()
|
||||
|
|
@ -208,7 +216,6 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
lines.add(csvHeader.trim())
|
||||
}
|
||||
|
||||
// Check if we are updating
|
||||
val existingIndex = lines.indexOfFirst { it.startsWith("$currentCowName,") }
|
||||
|
||||
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()
|
||||
finish()
|
||||
} 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) {
|
||||
val csvFile = File(filesDir, "cow_profiles.csv")
|
||||
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||
val csvFile = File(docsFolder, "cow_profiles.csv")
|
||||
if (!csvFile.exists()) return
|
||||
|
||||
try {
|
||||
val lines = csvFile.readLines()
|
||||
// Removed unused header variable
|
||||
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) {
|
||||
// Data is stored in English. Find key for English value, then get display string for that key.
|
||||
val storedSpecies = record[1]
|
||||
val speciesKey = StringProvider.getKeyForEnglishValue(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])
|
||||
|
||||
val storedStatus = record[6]
|
||||
// Find which key matches this English value
|
||||
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) {
|
||||
"radio_pregnant" -> findViewById<RadioButton>(R.id.rbPregnant).isChecked = true
|
||||
"radio_calved" -> findViewById<RadioButton>(R.id.rbCalved).isChecked = true
|
||||
"radio_none" -> findViewById<RadioButton>(R.id.rbNone).isChecked = true
|
||||
// Fallback if stored directly as text or unknown key
|
||||
else -> {
|
||||
// If stored directly as English "Pregnant" etc.
|
||||
when (storedStatus) {
|
||||
"Pregnant" -> findViewById<RadioButton>(R.id.rbPregnant).isChecked = true
|
||||
"Calved" -> findViewById<RadioButton>(R.id.rbCalved).isChecked = true
|
||||
|
|
@ -348,7 +317,9 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
"right" to R.drawable.right,
|
||||
"angle" to R.drawable.angle,
|
||||
"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) ->
|
||||
|
|
@ -379,10 +350,8 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
for (i in pixels.indices) {
|
||||
val alpha = (pixels[i] shr 24) and 0xFF
|
||||
if (alpha > 0) {
|
||||
// Original was visible -> make transparent
|
||||
pixels[i] = Color.TRANSPARENT
|
||||
} else {
|
||||
// Original was transparent -> make black (opaque)
|
||||
pixels[i] = Color.BLACK
|
||||
}
|
||||
}
|
||||
|
|
@ -394,87 +363,153 @@ class CowSelectionActivity : AppCompatActivity() {
|
|||
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() {
|
||||
imagesContainer.removeAllViews()
|
||||
val name = currentCowName ?: return
|
||||
|
||||
val files = filesDir.listFiles { _, fname -> fname.startsWith("${name}_") && fname.endsWith(".jpg") }
|
||||
if (files.isNullOrEmpty()) return
|
||||
val orientations = mapOf(
|
||||
"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 filesByOrientation = files.groupBy { file ->
|
||||
val parts = file.name.split("_")
|
||||
if (parts.size >= 3) parts[2] else "unknown"
|
||||
val cowImagesFolder = StorageUtils.getCowImageFolder(name)
|
||||
|
||||
orientations.forEach { (orientation, pair) ->
|
||||
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) ->
|
||||
addOrientationSection(orientation, orientationFiles)
|
||||
}
|
||||
}
|
||||
val latestFile = files?.maxByOrNull { it.lastModified() }
|
||||
|
||||
private fun addOrientationSection(orientationStr: String, files: List<File>) {
|
||||
// Orientation Header
|
||||
val orientationHeader = TextView(this).apply {
|
||||
val key = when(orientationStr.lowercase(Locale.getDefault())) {
|
||||
val container = findViewById<LinearLayout>(viewId)
|
||||
container.removeAllViews()
|
||||
|
||||
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 orientationStr.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
|
||||
text = label
|
||||
val label = if (key.isNotEmpty()) StringProvider.getString(key) else orientation
|
||||
|
||||
textSize = 16f
|
||||
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
setPadding(16, 8, 0, 8)
|
||||
}
|
||||
imagesContainer.addView(orientationHeader)
|
||||
|
||||
// Grid for thumbnails
|
||||
val gridLayout = GridLayout(this).apply {
|
||||
columnCount = 3
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
if (latestFile != null && latestFile.exists()) {
|
||||
val frameLayout = android.widget.FrameLayout(this)
|
||||
frameLayout.layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins(16, 0, 16, 16)
|
||||
LinearLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
val imageView = ImageView(this)
|
||||
imageView.layoutParams = android.widget.FrameLayout.LayoutParams(
|
||||
android.widget.FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
android.widget.FrameLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
val bitmap = BitmapFactory.decodeFile(latestFile.absolutePath)
|
||||
imageView.setImageBitmap(bitmap)
|
||||
|
||||
val deleteBtn = ImageView(this)
|
||||
val btnSize = (24 * resources.displayMetrics.density).toInt()
|
||||
val btnParams = android.widget.FrameLayout.LayoutParams(btnSize, btnSize)
|
||||
btnParams.gravity = android.view.Gravity.TOP or android.view.Gravity.END
|
||||
val margin = (4 * resources.displayMetrics.density).toInt()
|
||||
btnParams.setMargins(margin, margin, margin, margin)
|
||||
deleteBtn.layoutParams = btnParams
|
||||
deleteBtn.setImageResource(android.R.drawable.ic_menu_close_clear_cancel)
|
||||
deleteBtn.setColorFilter(Color.RED)
|
||||
deleteBtn.setBackgroundColor(Color.parseColor("#80FFFFFF"))
|
||||
deleteBtn.isClickable = true
|
||||
deleteBtn.setOnClickListener {
|
||||
if (latestFile.delete()) {
|
||||
val remainingFiles = cowImagesFolder.listFiles()
|
||||
if (remainingFiles == null || remainingFiles.isEmpty()) {
|
||||
cowImagesFolder.delete()
|
||||
}
|
||||
refreshCowImages()
|
||||
}
|
||||
}
|
||||
|
||||
// Images for this orientation
|
||||
files.forEach { file ->
|
||||
val thumbnailView = layoutInflater.inflate(R.layout.item_image_thumbnail, gridLayout, false)
|
||||
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
|
||||
|
||||
val imageView = thumbnailView.findViewById<ImageView>(R.id.ivThumbnail)
|
||||
val deleteButton = thumbnailView.findViewById<View>(R.id.btnDelete)
|
||||
frameLayout.addView(imageView)
|
||||
frameLayout.addView(labelView)
|
||||
frameLayout.addView(deleteBtn)
|
||||
|
||||
imageView.setImageBitmap(BitmapFactory.decodeFile(file.absolutePath))
|
||||
container.addView(frameLayout)
|
||||
|
||||
imageView.setOnClickListener {
|
||||
val intent = Intent(this@CowSelectionActivity, FullScreenImageActivity::class.java)
|
||||
intent.putExtra("IMAGE_PATH", file.absolutePath)
|
||||
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)
|
||||
}
|
||||
|
||||
deleteButton.setOnClickListener {
|
||||
if (file.delete()) {
|
||||
Toast.makeText(this@CowSelectionActivity, StringProvider.getString("toast_image_deleted"), Toast.LENGTH_SHORT).show()
|
||||
refreshCowImages()
|
||||
} else {
|
||||
Toast.makeText(this@CowSelectionActivity, StringProvider.getString("toast_error_deleting_image"), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
container.setOnClickListener(null)
|
||||
container.isClickable = false
|
||||
|
||||
gridLayout.addView(thumbnailView)
|
||||
} 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
|
||||
}
|
||||
}
|
||||
imagesContainer.addView(gridLayout)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
|
|
|||
|
|
@ -93,11 +93,19 @@ class FrameProcessor {
|
|||
|
||||
val rawBitmap = Bitmap.createBitmap(colors, fullWidth, fullHeight, Bitmap.Config.ARGB_8888)
|
||||
|
||||
// Rotate if needed
|
||||
// Rotate and Scale if needed
|
||||
bitmapMask = if (isPortrait) {
|
||||
try {
|
||||
val matrix = Matrix()
|
||||
// Rotate 90 degrees
|
||||
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)
|
||||
} catch (e: Exception) {
|
||||
Log.e("FrameProcessor", "Error rotating mask", e)
|
||||
|
|
@ -127,7 +135,17 @@ class FrameProcessor {
|
|||
}
|
||||
|
||||
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_JACCARD -> calculateJaccardSimilarity(savedMask, comparisonMask, thresholdPercent)
|
||||
else -> calculateHammingDistance(savedMask, comparisonMask, thresholdPercent)
|
||||
|
|
@ -229,19 +247,13 @@ class FrameProcessor {
|
|||
var union = 0
|
||||
|
||||
for (i in pixels1.indices) {
|
||||
// mask1 is Saved Mask (Inverse: Subject is Transparent/0, Background is Black/255)
|
||||
// NOTE: Wait, the saved mask was inverted originally?
|
||||
// Let's stick to generic "isSet" logic.
|
||||
// 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
|
||||
|
||||
// mask1 is Saved Mask (Likely Inverted in StorageUtils: Subject is Transparent/0, Background is Black/255)
|
||||
// But check how it's loaded. In CameraProcessor: loadSavedMask() -> decodeFile().
|
||||
// If we assume saved mask is inverted (subject transparent), then alpha < 128 is subject.
|
||||
val alpha1 = (pixels1[i] ushr 24) and 0xFF
|
||||
val isSubject1 = alpha1 < 128
|
||||
|
||||
// mask2 is Live Mask (Subject is Magenta/180 alpha)
|
||||
val alpha2 = (pixels2[i] ushr 24) and 0xFF
|
||||
val isSubject2 = alpha2 > 0
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.example.animalrating
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import java.io.File
|
||||
|
|
@ -13,7 +16,14 @@ class FullScreenImageActivity : AppCompatActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_full_screen_image)
|
||||
|
||||
StringProvider.initialize(this)
|
||||
|
||||
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) {
|
||||
val file = File(imagePath)
|
||||
if (file.exists()) {
|
||||
|
|
@ -22,8 +32,31 @@ class FullScreenImageActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
findViewById<Button>(R.id.btnBack).setOnClickListener {
|
||||
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
|
||||
}
|
||||
|
||||
val btnBack = findViewById<ImageButton>(R.id.btnBack)
|
||||
btnBack.contentDescription = StringProvider.getString("content_desc_back")
|
||||
btnBack.setOnClickListener {
|
||||
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.graphics.BitmapFactory
|
||||
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.cardview.widget.CardView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.util.Locale
|
||||
|
||||
class GalleryActivity : AppCompatActivity() {
|
||||
|
|
@ -22,17 +25,14 @@ class GalleryActivity : AppCompatActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_gallery)
|
||||
|
||||
// Initialize StringProvider
|
||||
StringProvider.initialize(this)
|
||||
|
||||
// Setup Back Button
|
||||
val toolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
supportActionBar?.setDisplayShowTitleEnabled(false)
|
||||
|
||||
// Set Toolbar title
|
||||
findViewById<TextView>(R.id.tvToolbarTitle)?.text = StringProvider.getString("title_gallery")
|
||||
|
||||
toolbar.setNavigationOnClickListener {
|
||||
|
|
@ -52,15 +52,14 @@ class GalleryActivity : AppCompatActivity() {
|
|||
private fun refreshGallery() {
|
||||
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 groupedFiles = files?.groupBy { file ->
|
||||
val parts = file.name.split("_")
|
||||
if (parts.size >= 2) "${parts[0]}_${parts[1]}" else "unknown"
|
||||
} ?: emptyMap()
|
||||
val cowNamesFromFolders = cowFolders.map { it.name }
|
||||
|
||||
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||
val csvFile = File(docsFolder, "cow_profiles.csv")
|
||||
|
||||
val csvFile = File(filesDir, "cow_profiles.csv")
|
||||
val cowDetails = if (csvFile.exists()) {
|
||||
csvFile.readLines().associate { line ->
|
||||
val parts = line.split(",")
|
||||
|
|
@ -70,21 +69,51 @@ class GalleryActivity : AppCompatActivity() {
|
|||
emptyMap()
|
||||
}
|
||||
|
||||
groupedFiles.forEach { (cowName, cowFiles) ->
|
||||
val allCowNames = (cowDetails.keys + cowNamesFromFolders).filter { it.isNotEmpty() && it != "CowID" }.distinct()
|
||||
|
||||
allCowNames.forEach { cowName ->
|
||||
val details = cowDetails[cowName] ?: emptyList()
|
||||
val cowImageFolder = StorageUtils.getCowImageFolder(cowName)
|
||||
val cowFiles = cowImageFolder.listFiles { _, name -> name.endsWith(".jpg") }?.toList() ?: emptyList()
|
||||
|
||||
addCowSection(cowName, cowFiles, details)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addCowSection(cowName: String, cowFiles: List<File>, details: List<String>) {
|
||||
// Cow Name Header and Retake Button
|
||||
val headerLayout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.HORIZONTAL
|
||||
// Main Card
|
||||
val card = CardView(this).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,14 +122,74 @@ class GalleryActivity : AppCompatActivity() {
|
|||
textSize = 20f
|
||||
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
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(
|
||||
0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
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")
|
||||
textSize = 12f
|
||||
setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
|
|
@ -109,9 +198,11 @@ class GalleryActivity : AppCompatActivity() {
|
|||
backgroundTintList = ColorStateList.valueOf(android.graphics.Color.parseColor("#EFEBE9"))
|
||||
strokeWidth = (1 * resources.displayMetrics.density).toInt()
|
||||
strokeColor = ColorStateList.valueOf(android.graphics.Color.parseColor("#5D4037"))
|
||||
layoutParams = buttonParams
|
||||
insetTop = 0
|
||||
insetBottom = 0
|
||||
minHeight = 0
|
||||
minimumHeight = 0
|
||||
setPadding(24, 20, 24, 20)
|
||||
|
||||
setOnClickListener {
|
||||
val intent = Intent(this@GalleryActivity, CowSelectionActivity::class.java)
|
||||
|
|
@ -120,63 +211,142 @@ class GalleryActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
headerLayout.addView(nameView)
|
||||
headerLayout.addView(retakeButton)
|
||||
container.addView(headerLayout)
|
||||
val rateButton = MaterialButton(this).apply {
|
||||
text = "Rate" // Add string if needed, skipping as per instruction to be concise unless requested
|
||||
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.parseColor("#6D4C41"))
|
||||
layoutParams = buttonParams
|
||||
insetTop = 0
|
||||
insetBottom = 0
|
||||
minHeight = 0
|
||||
minimumHeight = 0
|
||||
|
||||
// Display details if available
|
||||
// Header: CowID,Species,Breed,Age,MilkYield,CalvingNumber,ReproductiveStatus,Description
|
||||
if (details.size >= 7) {
|
||||
val detailsLayout = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
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(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins(0, 0, 0, 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"))
|
||||
setMargins(16, 0, 16, 16)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
// Sort images or keep them in order. The user asked for 3 in each row.
|
||||
cowFiles.forEach { file ->
|
||||
val thumbnailView = layoutInflater.inflate(R.layout.item_image_thumbnail, gridLayout, false)
|
||||
|
||||
infoText.append("${StringProvider.getString("label_species")} ${details.getOrElse(1) { "-" }} ")
|
||||
infoText.append("${StringProvider.getString("label_breed")} ${details.getOrElse(2) { "-" }}\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")} ${details.getOrElse(6) { "-" }}")
|
||||
val imageView = thumbnailView.findViewById<ImageView>(R.id.ivThumbnail)
|
||||
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()
|
||||
textSize = 14f
|
||||
setTextColor(android.graphics.Color.parseColor("#5D4037"))
|
||||
setLineSpacing(10f, 1f)
|
||||
// Optionally set layout params for thumbnailView to ensure 3 per row
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val screenWidth = displayMetrics.widthPixels
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
detailsLayout.addView(detailsView)
|
||||
container.addView(detailsLayout)
|
||||
}
|
||||
|
||||
// Group files by orientation (e.g., left, right, etc.)
|
||||
val filesByOrientation = cowFiles.groupBy { file ->
|
||||
// Extract orientation from filename
|
||||
val parts = file.name.split("_")
|
||||
if (parts.size >= 3) parts[2] else "unknown"
|
||||
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}_") } ?: ""
|
||||
}
|
||||
|
||||
filesByOrientation.forEach { (orientation, orientationFiles) ->
|
||||
addOrientationSection(orientation, orientationFiles)
|
||||
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)
|
||||
}
|
||||
|
||||
// Separator
|
||||
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)
|
||||
|
||||
val separator = android.view.View(this).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
|
|
@ -188,64 +358,39 @@ class GalleryActivity : AppCompatActivity() {
|
|||
}
|
||||
container.addView(separator)
|
||||
}
|
||||
|
||||
private fun addOrientationSection(orientationStr: String, files: List<File>) {
|
||||
// Orientation Header
|
||||
val orientationHeader = TextView(this).apply {
|
||||
val key = when(orientationStr.lowercase(Locale.getDefault())) {
|
||||
"front" -> "text_front_view"
|
||||
"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)
|
||||
|
||||
// Grid for thumbnails
|
||||
val gridLayout = android.widget.GridLayout(this).apply {
|
||||
columnCount = 3
|
||||
layoutParams = LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins(16, 0, 16, 16)
|
||||
private fun deleteCow(cowName: String) {
|
||||
// Delete Images
|
||||
val imageFolder = StorageUtils.getCowImageFolder(cowName)
|
||||
if (imageFolder.exists()) {
|
||||
imageFolder.deleteRecursively()
|
||||
}
|
||||
|
||||
val docsFolder = StorageUtils.getDocumentsFolder()
|
||||
|
||||
// Delete Profile
|
||||
val profileFile = File(docsFolder, "cow_profiles.csv")
|
||||
if (profileFile.exists()) {
|
||||
val lines = profileFile.readLines()
|
||||
val newLines = lines.filter { !it.startsWith("$cowName,") }
|
||||
FileWriter(profileFile).use { writer ->
|
||||
newLines.forEach { writer.write(it + "\n") }
|
||||
}
|
||||
}
|
||||
|
||||
// Images for this orientation
|
||||
files.forEach { file ->
|
||||
val thumbnailView = layoutInflater.inflate(R.layout.item_image_thumbnail, gridLayout, false)
|
||||
|
||||
val imageView = thumbnailView.findViewById<ImageView>(R.id.ivThumbnail)
|
||||
val deleteButton = thumbnailView.findViewById<android.view.View>(R.id.btnDelete)
|
||||
|
||||
imageView.setImageBitmap(BitmapFactory.decodeFile(file.absolutePath))
|
||||
|
||||
imageView.setOnClickListener {
|
||||
val intent = Intent(this@GalleryActivity, FullScreenImageActivity::class.java)
|
||||
intent.putExtra("IMAGE_PATH", file.absolutePath)
|
||||
startActivity(intent)
|
||||
// Delete Ratings
|
||||
val ratingsFile = File(docsFolder, "cow_ratings.csv")
|
||||
if (ratingsFile.exists()) {
|
||||
val lines = ratingsFile.readLines()
|
||||
val newLines = lines.filter { !it.startsWith("$cowName,") }
|
||||
FileWriter(ratingsFile).use { writer ->
|
||||
newLines.forEach { writer.write(it + "\n") }
|
||||
}
|
||||
}
|
||||
|
||||
deleteButton.setOnClickListener {
|
||||
if (file.delete()) {
|
||||
Toast.makeText(this@GalleryActivity, StringProvider.getString("toast_image_deleted"), Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(this, "Cow profile 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)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
|
|||
|
|
@ -2,20 +2,27 @@ package com.example.animalrating
|
|||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.app.ActivityCompat
|
||||
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.switchmaterial.SwitchMaterial
|
||||
|
||||
class HomeActivity : AppCompatActivity() {
|
||||
|
||||
|
|
@ -25,8 +32,14 @@ class HomeActivity : AppCompatActivity() {
|
|||
const val ALGORITHM_JACCARD = "Jaccard Similarity"
|
||||
private const val PERMISSION_REQUEST_CODE = 101
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_home)
|
||||
|
|
@ -44,23 +57,27 @@ class HomeActivity : AppCompatActivity() {
|
|||
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) {
|
||||
permissions.add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
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()) {
|
||||
|
|
@ -69,6 +86,15 @@ class HomeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
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
|
||||
val languageSpinner = findViewById<Spinner>(R.id.spinnerLanguage)
|
||||
val languages = StringProvider.getLanguages()
|
||||
|
|
@ -76,7 +102,6 @@ class HomeActivity : AppCompatActivity() {
|
|||
languageAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
languageSpinner.adapter = languageAdapter
|
||||
|
||||
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
||||
val savedLang = prefs.getString("LANGUAGE", "English")
|
||||
languageSpinner.setSelection(languages.indexOf(savedLang))
|
||||
|
||||
|
|
@ -99,7 +124,8 @@ class HomeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
// 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.btnSelectCow).text = StringProvider.getString("btn_select_cow")
|
||||
findViewById<TextView>(R.id.tvAlgorithmLabel).text = StringProvider.getString("label_algorithm")
|
||||
|
|
@ -120,19 +146,81 @@ class HomeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
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
|
||||
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)
|
||||
spinner.adapter = adapter
|
||||
|
||||
// Set default selection from preferences or intent
|
||||
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
|
||||
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
|
||||
|
|
@ -149,6 +237,35 @@ class HomeActivity : AppCompatActivity() {
|
|||
override fun onStartTrackingTouch(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) {
|
||||
|
|
@ -171,22 +288,13 @@ class HomeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
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 resId = resources.getIdentifier(resName, "drawable", packageName)
|
||||
|
||||
if (resId != 0) {
|
||||
imageView.setImageResource(resId)
|
||||
} else {
|
||||
// Fallback to default if specific one not found
|
||||
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)
|
||||
if (defaultId != 0) imageView.setImageResource(defaultId)
|
||||
}
|
||||
|
|
@ -196,22 +304,32 @@ class HomeActivity : AppCompatActivity() {
|
|||
private fun saveSettings() {
|
||||
val spinner = findViewById<Spinner>(R.id.spinnerAlgorithm)
|
||||
val seekBar = findViewById<SeekBar>(R.id.seekBarThreshold)
|
||||
val showSegmentationMask = findViewById<SwitchMaterial>(R.id.switchMaskDisplay)
|
||||
|
||||
if (spinner != null && seekBar != null && showSegmentationMask != null) {
|
||||
val selectedIndex = spinner.selectedItemPosition
|
||||
val selectedAlgorithm = if (selectedIndex >= 0 && selectedIndex < internalAlgorithms.size) {
|
||||
internalAlgorithms[selectedIndex]
|
||||
} else {
|
||||
ALGORITHM_HAMMING
|
||||
}
|
||||
|
||||
if (spinner != null && seekBar != null) {
|
||||
val selectedAlgorithm = spinner.selectedItem?.toString() ?: ALGORITHM_HAMMING
|
||||
val threshold = seekBar.progress
|
||||
|
||||
// Save to preferences
|
||||
val prefs = getSharedPreferences("AnimalRatingPrefs", MODE_PRIVATE)
|
||||
val showMask = prefs.getBoolean(PREF_MASK_DISPLAY, false)
|
||||
prefs.edit().apply {
|
||||
putString("ALGORITHM", selectedAlgorithm)
|
||||
putInt("THRESHOLD", threshold)
|
||||
putBoolean(PREF_MASK_DISPLAY, showMask)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSettingsAndStart() {
|
||||
if(!IS_RELEASE_BUILD)
|
||||
saveSettings()
|
||||
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 bmpH = bmp.height.toFloat()
|
||||
|
||||
val viewAspect = viewW / viewH
|
||||
val bmpAspect = bmpW / bmpH
|
||||
// Calculate scale to fit (FIT_CENTER)
|
||||
val scale = kotlin.math.min(viewW / bmpW, viewH / bmpH)
|
||||
|
||||
val cropRect: RectF
|
||||
val scaledW = bmpW * scale
|
||||
val scaledH = bmpH * scale
|
||||
|
||||
if (bmpAspect > viewAspect) {
|
||||
// Bitmap is wider → crop left & right
|
||||
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)
|
||||
}
|
||||
val left = (viewW - scaledW) / 2f
|
||||
val top = (viewH - scaledH) / 2f
|
||||
|
||||
// Scale to exactly fill screen
|
||||
val destRect = RectF(0f, 0f, viewW, viewH)
|
||||
val destRect = RectF(left, top, left + scaledW, top + scaledH)
|
||||
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"/>
|
||||
|
||||
<!-- 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
|
||||
android:id="@+id/tilSpecies"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -56,8 +65,7 @@
|
|||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusBottomEnd="12dp"
|
||||
app:boxStrokeColor="#6D4C41"
|
||||
android:hint="Species">
|
||||
app:boxStrokeColor="#6D4C41">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/spinnerSpecies"
|
||||
|
|
@ -66,6 +74,14 @@
|
|||
android:inputType="none"/>
|
||||
</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
|
||||
android:id="@+id/tilBreed"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -76,8 +92,7 @@
|
|||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusBottomEnd="12dp"
|
||||
app:boxStrokeColor="#6D4C41"
|
||||
android:hint="Breed">
|
||||
app:boxStrokeColor="#6D4C41">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/spinnerBreed"
|
||||
|
|
@ -93,19 +108,31 @@
|
|||
android:baselineAligned="false"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilAge"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
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_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
app:boxCornerRadiusTopStart="12dp"
|
||||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusBottomEnd="12dp"
|
||||
app:boxStrokeColor="#6D4C41"
|
||||
android:hint="Age (Years)">
|
||||
app:boxStrokeColor="#6D4C41">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/etAge"
|
||||
|
|
@ -113,20 +140,33 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:inputType="number"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilMilk"
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<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_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
app:boxCornerRadiusTopStart="12dp"
|
||||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusBottomEnd="12dp"
|
||||
app:boxStrokeColor="#6D4C41"
|
||||
android:hint="Milk Yield (L)">
|
||||
app:boxStrokeColor="#6D4C41">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -134,6 +174,15 @@
|
|||
android:inputType="numberDecimal"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</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
|
||||
android:id="@+id/tilCalving"
|
||||
|
|
@ -145,8 +194,7 @@
|
|||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusBottomEnd="12dp"
|
||||
app:boxStrokeColor="#6D4C41"
|
||||
android:hint="Calving Number">
|
||||
app:boxStrokeColor="#6D4C41">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -190,6 +238,14 @@
|
|||
android:text="None"/>
|
||||
</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
|
||||
android:id="@+id/tilDescription"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -200,8 +256,7 @@
|
|||
app:boxCornerRadiusTopEnd="12dp"
|
||||
app:boxCornerRadiusBottomStart="12dp"
|
||||
app:boxCornerRadiusBottomEnd="12dp"
|
||||
app:boxStrokeColor="#6D4C41"
|
||||
android:hint="Description">
|
||||
app:boxStrokeColor="#6D4C41">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -225,7 +280,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="2"
|
||||
android:rowCount="3"
|
||||
android:rowCount="4"
|
||||
android:alignmentMode="alignMargins"
|
||||
android:columnOrderPreserved="false"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
|
@ -362,12 +417,77 @@
|
|||
</LinearLayout>
|
||||
</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
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_columnWeight="1"
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
|
|
|||
|
|
@ -25,4 +25,14 @@
|
|||
android:scaleType="centerInside"
|
||||
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>
|
||||
|
|
@ -1,6 +1,13 @@
|
|||
<?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:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#F7F7F7"
|
||||
|
|
@ -13,19 +20,21 @@
|
|||
android:padding="24dp"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<!-- Language Selection (Top Right) -->
|
||||
<!-- Menu Button (Top Right) to Open Drawer -->
|
||||
<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"/>
|
||||
<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 -->
|
||||
|
|
@ -114,6 +123,7 @@
|
|||
|
||||
<!-- Settings Card -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/viewSettings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="16dp"
|
||||
|
|
@ -128,6 +138,7 @@
|
|||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHeaderSettings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Settings"
|
||||
|
|
@ -190,9 +201,87 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:max="100"
|
||||
android:progressTint="#6D4C41"
|
||||
android:thumbTint="#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>
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
|
@ -51,4 +51,51 @@
|
|||
app:iconPadding="0dp"
|
||||
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>
|
||||
|
|
@ -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">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
|
|
@ -20,6 +20,18 @@
|
|||
|
||||
</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
|
||||
android:id="@+id/btnDelete"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
|
|
@ -39,6 +51,7 @@
|
|||
android:insetBottom="0dp"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="0dp"
|
||||
android:gravity="center"/>
|
||||
android:gravity="center"
|
||||
android:elevation="6dp"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
@ -5,15 +5,34 @@
|
|||
"title_gallery": "Saved Cow Profiles",
|
||||
"title_add_cow_details": "Add Cow Details",
|
||||
"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_select_cow": "Select a Cow",
|
||||
"btn_select_cow": "Add New Cow Profile",
|
||||
"label_algorithm": "Algorithm",
|
||||
"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_rate": "Rate",
|
||||
"btn_delete": "Delete",
|
||||
"btn_save_rating": "Save Rating",
|
||||
"btn_retake": "Retake Photo",
|
||||
"btn_exit": "Exit",
|
||||
"hint_search_profiles": "Search profiles...",
|
||||
"hint_comments": "Comments",
|
||||
"toast_image_deleted": "Image deleted",
|
||||
"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_no_details": "No details available",
|
||||
"label_species": "Species:",
|
||||
"label_breed": "Breed:",
|
||||
"label_age": "Age:",
|
||||
|
|
@ -38,6 +57,8 @@
|
|||
"text_left_side": "Left Side",
|
||||
"text_right_side": "Right Side",
|
||||
"text_angle_view": "Angle View",
|
||||
"text_left_angle": "Left Angle",
|
||||
"text_right_angle": "Right Angle",
|
||||
"btn_save_profile": "Save Profile",
|
||||
"btn_cancel": "Cancel",
|
||||
"toast_profile_saved": "Profile Saved!",
|
||||
|
|
@ -55,7 +76,33 @@
|
|||
"breed_gir": "Gir",
|
||||
"breed_red_sindhi": "Red Sindhi",
|
||||
"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": {
|
||||
"app_name": "पशु रेटिंग",
|
||||
|
|
@ -63,15 +110,34 @@
|
|||
"title_gallery": "सहेजे गए गाय प्रोफाइल",
|
||||
"title_add_cow_details": "गाय का विवरण जोड़ें",
|
||||
"title_cow_selection": "गाय का चयन",
|
||||
"title_rate_cow": "गाय का मूल्यांकन",
|
||||
"header_photos": "तस्वीरें",
|
||||
"header_cow_details": "गाय का विवरण",
|
||||
"header_feature_ratings": "विशेषता रेटिंग",
|
||||
"header_settings": "सेटिंग्स",
|
||||
"subtitle_home": "गाय की जानकारी आसानी से रिकॉर्ड और प्रबंधित करें।",
|
||||
"btn_view_gallery": "सहेजे गए प्रोफाइल देखें",
|
||||
"btn_select_cow": "एक गाय का चयन करें",
|
||||
"btn_select_cow": "नई गाय प्रोफ़ाइल जोड़ें",
|
||||
"label_algorithm": "एल्गोरिदम",
|
||||
"label_match_threshold": "मैच थ्रेसहोल्ड",
|
||||
"label_auto_capture": "ऑटो कैप्चर",
|
||||
"label_enable_debug": "डिबग सक्षम करें",
|
||||
"label_mask_display": "सेगमेंटेशन मास्क दिखाएं",
|
||||
"btn_edit": "संपादित करें",
|
||||
"btn_rate": "रेट करें",
|
||||
"btn_delete": "हटाएँ",
|
||||
"btn_save_rating": "रेटिंग सहेजें",
|
||||
"btn_retake": "फोटो फिर से लें",
|
||||
"btn_exit": "बाहर जाएं",
|
||||
"hint_search_profiles": "प्रोफ़ाइल खोजें...",
|
||||
"hint_comments": "टिप्पणियाँ",
|
||||
"toast_image_deleted": "छवि हटा दी गई",
|
||||
"toast_error_deleting_image": "छवि हटाने में त्रुटि",
|
||||
"toast_ratings_saved": "रेटिंग सहेजी गई!",
|
||||
"toast_error_saving_ratings": "रेटिंग सहेजने में त्रुटि",
|
||||
"toast_profile_deleted": "गाय प्रोफ़ाइल हटाई गई",
|
||||
"text_cow_id": "गाय आईडी:",
|
||||
"text_no_details": "कोई विवरण उपलब्ध नहीं",
|
||||
"label_species": "प्रजाति:",
|
||||
"label_breed": "नस्ल:",
|
||||
"label_age": "आयु:",
|
||||
|
|
@ -96,6 +162,8 @@
|
|||
"text_left_side": "बाईं ओर",
|
||||
"text_right_side": "दाईं ओर",
|
||||
"text_angle_view": "कोणीय दृश्य",
|
||||
"text_left_angle": "बायां कोण",
|
||||
"text_right_angle": "दायां कोण",
|
||||
"btn_save_profile": "प्रोफ़ाइल सहेजें",
|
||||
"btn_cancel": "रद्द करें",
|
||||
"toast_profile_saved": "प्रोफ़ाइल सहेजा गया!",
|
||||
|
|
@ -113,6 +181,32 @@
|
|||
"breed_gir": "गिर",
|
||||
"breed_red_sindhi": "लाल सिंधी",
|
||||
"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