diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index cca7557..557f0e3 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -13,6 +13,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8f20281..f09406f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -27,6 +27,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
+ signingConfig = signingConfigs.getByName("debug")
}
}
compileOptions {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0b84693..32678e5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -50,6 +50,11 @@
android:name=".FullScreenImageActivity"
android:exported="false"
android:theme="@style/Theme.AnimalRating" />
+
+
diff --git a/app/src/main/java/com/example/animalrating/CameraProcessor.kt b/app/src/main/java/com/example/animalrating/CameraProcessor.kt
index c2ac78c..5335e0c 100644
--- a/app/src/main/java/com/example/animalrating/CameraProcessor.kt
+++ b/app/src/main/java/com/example/animalrating/CameraProcessor.kt
@@ -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") {
@@ -77,16 +86,56 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
findViewById(R.id.btnExit).setOnClickListener {
finish()
}
+
+ val btnShutter = findViewById(R.id.btnShutter)
+ val btnToggle = findViewById(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)
- loadSavedMask()
+ // 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(R.id.btnShutter)
+ val btnToggle = findViewById(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
@@ -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
}
@@ -133,17 +189,31 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
}
}
+
+ val retakePath = intent.getStringExtra("RETAKE_IMAGE_PATH")
+ if (!retakePath.isNullOrEmpty()) {
+ val oldFile = File(retakePath)
+ if (oldFile.exists()) {
+ oldFile.delete()
+ }
+ }
- // Save to public gallery
- saveToGallery(file)
-
- 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)
- val greenMask = applyGreenColor(savedBitmap)
- savedMaskOverlay.setImageBitmap(greenMask)
- savedMaskOverlay.alpha = 0.5f
-
- // Scale to 640x480 (or flipped for portrait) for comparison
- 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)
+ if (savedBitmap != null) {
+ // Apply green color filter for visualization
+ if (isMaskDisplayEnabled) {
+ val greenMask = applyGreenColor(savedBitmap)
+
+ // 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
- segmentationOverlay.setImageBitmap(result.mask)
+ if (isMaskDisplayEnabled) {
+ segmentationOverlay.setImageBitmap(result.mask)
+ } else {
+ segmentationOverlay.setImageDrawable(null)
+ }
}
}
- if (result.isMatch) {
+ if (isAutoCapture && result.isMatch) {
takePhoto()
}
}
@@ -293,4 +370,4 @@ class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
Log.e("CameraProcessor", "Frame processing error", e)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/example/animalrating/CowSelectionActivity.kt b/app/src/main/java/com/example/animalrating/CowSelectionActivity.kt
index 1cb5ac0..5a58df8 100644
--- a/app/src/main/java/com/example/animalrating/CowSelectionActivity.kt
+++ b/app/src/main/java/com/example/animalrating/CowSelectionActivity.kt
@@ -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()
+ private val initialImagePaths = mutableSetOf()
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(R.id.tvToolbarTitle)?.text = StringProvider.getString("title_cow_selection")
- findViewById(R.id.tvAddCowDetails)?.text = StringProvider.getString("title_add_cow_details")
-
- findViewById(R.id.tilSpecies)?.hint = StringProvider.getString("hint_species")
- findViewById(R.id.tilBreed)?.hint = StringProvider.getString("hint_breed")
- findViewById(R.id.tilAge)?.hint = StringProvider.getString("hint_age")
- findViewById(R.id.tilMilk)?.hint = StringProvider.getString("hint_milk_yield")
- findViewById(R.id.tilCalving)?.hint = StringProvider.getString("hint_calving_number")
- findViewById(R.id.tilDescription)?.hint = StringProvider.getString("hint_description")
-
- findViewById(R.id.tvReproductiveStatus)?.text = StringProvider.getString("label_reproductive_status")
- findViewById(R.id.rbPregnant)?.text = StringProvider.getString("radio_pregnant")
- findViewById(R.id.rbCalved)?.text = StringProvider.getString("radio_calved")
- findViewById(R.id.rbNone)?.text = StringProvider.getString("radio_none")
-
- findViewById(R.id.tvUploadPhotos)?.text = StringProvider.getString("label_upload_photos")
- findViewById