Animal Rating

This commit is contained in:
SaiD 2025-11-25 20:35:23 +05:30
commit 3e5a8772d7
68 changed files with 2388 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-11-24T18:35:29.331278100Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=10BF45100J001X5" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

13
.idea/deviceManager.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/studiobot.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="StudioBotProjectSettings">
<option name="shareContext" value="OptedIn" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

74
app/build.gradle.kts Normal file
View File

@ -0,0 +1,74 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.example.animalrating"
compileSdk {
version = release(36)
}
defaultConfig {
applicationId = "com.example.animalrating"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
}
dependencies {
val cameraxVersion = "1.5.1"
implementation("androidx.appcompat:appcompat:1.7.0")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.camera:camera-core:$cameraxVersion")
implementation("androidx.camera:camera-camera2:$cameraxVersion")
implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
implementation("androidx.camera:camera-view:$cameraxVersion")
implementation("androidx.camera:camera-mlkit-vision:$cameraxVersion")
// ML Kit Object Detection
implementation("com.google.mlkit:object-detection:17.0.2")
// ML Kit Subject Segmentation (Google Play Services version)
implementation("com.google.android.gms:play-services-mlkit-subject-segmentation:16.0.0-beta1")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}

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

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

View File

@ -0,0 +1,24 @@
package com.example.animalrating
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.animalrating", appContext.packageName)
}
}

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AnimalRating">
<activity
android:name=".HomeActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AnimalRating">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".CowSelectionActivity"
android:exported="false"
android:theme="@style/Theme.AnimalRating" />
<activity
android:name=".CameraProcessor"
android:exported="false"
android:theme="@style/Theme.AnimalRating" />
<activity
android:name=".GalleryActivity"
android:exported="false"
android:theme="@style/Theme.AnimalRating" />
<activity
android:name=".FullScreenImageActivity"
android:exported="false"
android:theme="@style/Theme.AnimalRating" />
</application>
</manifest>

View File

@ -0,0 +1,301 @@
package com.example.animalrating
import android.Manifest
import android.content.pm.ActivityInfo
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Matrix
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.ImageView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import com.example.animalrating.ml.CowAnalyzer
import com.example.animalrating.ui.SilhouetteOverlay
import java.io.File
import java.io.FileOutputStream
import java.util.concurrent.Executors
class CameraProcessor : AppCompatActivity(), CowAnalyzer.CowListener {
private lateinit var previewView: PreviewView
private lateinit var overlay: SilhouetteOverlay
private lateinit var segmentationOverlay: ImageView
private lateinit var savedMaskOverlay: ImageView
private var imageCapture: ImageCapture? = null
private lateinit var frameProcessor: FrameProcessor
private val cameraExecutor = Executors.newSingleThreadExecutor()
private var cowName: String? = null
private var orientation: String? = null
private var currentMask: Bitmap? = null
private var savedMaskBitmap: Bitmap? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
cowName = intent.getStringExtra("COW_NAME")
orientation = intent.getStringExtra("ORIENTATION")
// Set orientation based on selected view
if (orientation == "front" || orientation == "back") {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
previewView = findViewById(R.id.cameraPreview)
overlay = findViewById(R.id.silhouetteOverlay)
segmentationOverlay = findViewById(R.id.segmentationOverlay)
savedMaskOverlay = findViewById(R.id.savedMaskOverlay)
frameProcessor = FrameProcessor { maskBitmap ->
runOnUiThread {
if (maskBitmap != null && (orientation == "front" || orientation == "back")) {
try {
val matrix = Matrix()
matrix.postRotate(90f)
val rotatedBitmap = Bitmap.createBitmap(
maskBitmap, 0, 0, maskBitmap.width, maskBitmap.height, matrix, true
)
currentMask = rotatedBitmap
segmentationOverlay.setImageBitmap(rotatedBitmap)
} catch (e: Exception) {
Log.e("CameraProcessor", "Error rotating mask", e)
currentMask = maskBitmap
segmentationOverlay.setImageBitmap(maskBitmap)
}
} else {
currentMask = maskBitmap
segmentationOverlay.setImageBitmap(maskBitmap)
}
}
}
val btnSave = findViewById<Button>(R.id.btnSave)
btnSave.setOnClickListener {
saveBitmask()
}
val silhouetteId = intent.getIntExtra("SILHOUETTE_ID", 0)
overlay.setSilhouette(silhouetteId)
loadSavedMask()
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
private fun takePhoto() {
val imageCapture = imageCapture ?: return
val name = cowName ?: "unknown"
val side = orientation ?: "unknown"
// Find current count for this cow and orientation
val existingFiles = filesDir.listFiles { _, fname ->
fname.startsWith("${name}_${side}_") && fname.endsWith(".jpg")
}
val count = (existingFiles?.size ?: 0) + 1
val filename = "${name}_${side}_${count}.jpg"
val file = File(filesDir, filename)
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()
imageCapture.takePicture(
outputOptions,
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()
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
// Correct rotation based on current orientation
try {
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
val matrix = Matrix()
val rotation = if (orientation == "front" || orientation == "back") 90f else 0f
if (rotation != 0f) {
matrix.postRotate(rotation)
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
FileOutputStream(file).use { out ->
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
}
}
val msg = "Saved as $filename"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
finish()
} catch (e: Exception) {
Log.e("MainActivity", "Error rotating image", e)
Toast.makeText(baseContext, "Error saving image", Toast.LENGTH_SHORT).show()
}
}
}
)
}
private fun loadSavedMask() {
val side = orientation ?: "unknown"
val filename = "${side}_mask.png"
val file = File(filesDir, filename)
if (file.exists()) {
try {
val savedBitmap = BitmapFactory.decodeFile(file.absolutePath)
savedMaskBitmap = savedBitmap // Store reference for comparison
// Apply green color filter or process pixels
val greenMask = applyGreenColor(savedBitmap)
savedMaskOverlay.setImageBitmap(greenMask)
savedMaskOverlay.alpha = 0.5f
} catch (e: Exception) {
Log.e("CameraProcessor", "Error loading saved mask", e)
}
}
}
private fun applyGreenColor(original: Bitmap): Bitmap {
val width = original.width
val height = original.height
val pixels = IntArray(width * height)
original.getPixels(pixels, 0, width, 0, 0, width, height)
for (i in pixels.indices) {
val alpha = (pixels[i] shr 24) and 0xff
if (alpha > 10) {
// Set to Green with original alpha or fixed alpha
pixels[i] = Color.argb(alpha, 0, 255, 0)
}
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
}
private fun saveBitmask() {
val mask = currentMask
if (mask == null) {
Toast.makeText(this, "No mask available to save", Toast.LENGTH_SHORT).show()
return
}
val side = orientation ?: "unknown"
val filename = "${side}_mask.png"
val file = File(filesDir, filename)
try {
FileOutputStream(file).use { out ->
mask.compress(Bitmap.CompressFormat.PNG, 100, out)
}
Toast.makeText(this, "Bitmask saved as $filename", Toast.LENGTH_SHORT).show()
// Reload to show the newly saved mask immediately
loadSavedMask()
} catch (e: Exception) {
Log.e("CameraProcessor", "Error saving bitmask", e)
Toast.makeText(this, "Error saving bitmask", Toast.LENGTH_SHORT).show()
}
}
// Function to calculate Hamming distance between two bitmasks
fun calculateHammingDistance(mask1: Bitmap, mask2: Bitmap, threshold: Int): Boolean {
if (mask1.width != mask2.width || mask1.height != mask2.height) {
Log.w("CameraProcessor", "Masks dimensions mismatch: ${mask1.width}x${mask1.height} vs ${mask2.width}x${mask2.height}")
return false
}
val width = mask1.width
val height = mask1.height
val pixels1 = IntArray(width * height)
val pixels2 = IntArray(width * height)
mask1.getPixels(pixels1, 0, width, 0, 0, width, height)
mask2.getPixels(pixels2, 0, width, 0, 0, width, height)
var distance = 0
for (i in pixels1.indices) {
val isSet1 = (pixels1[i] ushr 24) > 0
val isSet2 = (pixels2[i] ushr 24) > 0
if (isSet1 != isSet2) {
distance++
}
}
Log.d("CameraProcessor", "Hamming distance: $distance")
return distance <= threshold
}
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) startCamera()
}
private fun startCamera() {
val providerFuture = ProcessCameraProvider.getInstance(this)
providerFuture.addListener({
val cameraProvider = providerFuture.get()
val preview = androidx.camera.core.Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
val analyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(
ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST
)
.build()
.also {
it.setAnalyzer(cameraExecutor, CowAnalyzer(this))
}
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this,
androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture,
analyzer
)
}, ContextCompat.getMainExecutor(this))
}
override fun onFrame(imageProxy: ImageProxy) {
val current = frameProcessor.processFrame(imageProxy)
val savedMask = savedMaskBitmap
val threshold = 80000
Log.d("MatchingMasks", "Comparing masks: ${current?.width} ${savedMask?.width}")
if (savedMask != null && current != null) {
// Perform comparison
Log.d("MatchingMasks", "Comparing masks")
if (calculateHammingDistance(savedMask, current, threshold)) {
takePhoto()
} else {
Toast.makeText(this, "Masks do not match. Please align better.", Toast.LENGTH_SHORT).show()
}
}
}
}

View File

@ -0,0 +1,214 @@
package com.example.animalrating
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class CowSelectionActivity : AppCompatActivity() {
private var currentCowName: String? = null
private lateinit var imagesContainer: LinearLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cow_selection)
initializeDefaultMasks()
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()
}
updateCowNameDisplay()
refreshCowImages()
findViewById<Button>(R.id.btnNewCow).setOnClickListener {
generateNewCowName()
updateCowNameDisplay()
refreshCowImages()
}
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"), // Placeholder if no image yet
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
findViewById<Button>(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)
}
}
}
override fun onResume() {
super.onResume()
refreshCowImages()
}
private fun initializeDefaultMasks() {
val orientationResources = mapOf(
"left" to R.drawable.left,
"right" to R.drawable.right,
"angle" to R.drawable.angle,
"front" to R.drawable.front,
"back" to R.drawable.back
)
orientationResources.forEach { (orientation, resId) ->
val filename = "${orientation}_mask.png"
val file = File(filesDir, filename)
if (!file.exists()) {
try {
val original = BitmapFactory.decodeResource(resources, resId)
if (original != null) {
val inverted = createInverseBitmask(original)
FileOutputStream(file).use { out ->
inverted.compress(Bitmap.CompressFormat.PNG, 100, out)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
private fun createInverseBitmask(src: Bitmap): Bitmap {
val width = src.width
val height = src.height
val pixels = IntArray(width * height)
src.getPixels(pixels, 0, width, 0, 0, width, height)
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
}
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888)
}
private fun generateNewCowName() {
val sdf = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
currentCowName = "cow_${sdf.format(Date())}"
}
private fun updateCowNameDisplay() {
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
// Group files by orientation
val filesByOrientation = files.groupBy { file ->
val parts = file.name.split("_")
if (parts.size >= 3) parts[2] else "unknown"
}
filesByOrientation.forEach { (orientation, orientationFiles) ->
addOrientationSection(orientation, orientationFiles)
}
}
private fun addOrientationSection(orientationStr: String, files: List<File>) {
// Orientation Header
val orientationHeader = TextView(this).apply {
text = orientationStr.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
textSize = 16f
setTypeface(null, android.graphics.Typeface.BOLD)
setPadding(16, 8, 0, 8)
}
imagesContainer.addView(orientationHeader)
// Images for this orientation
files.forEach { file ->
val imageRow = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
200 // Smaller height for images
).apply {
setMargins(16, 0, 0, 16)
}
}
val imageView = ImageView(this).apply {
layoutParams = LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.MATCH_PARENT,
1f
)
scaleType = ImageView.ScaleType.CENTER_CROP
setImageBitmap(BitmapFactory.decodeFile(file.absolutePath))
setOnClickListener {
val intent = Intent(context, FullScreenImageActivity::class.java)
intent.putExtra("IMAGE_PATH", file.absolutePath)
startActivity(intent)
}
}
val deleteButton = Button(this).apply {
text = "Delete"
textSize = 12f
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(16,0,0,0)
}
setOnClickListener {
if (file.delete()) {
Toast.makeText(context, "Image deleted", Toast.LENGTH_SHORT).show()
refreshCowImages()
} else {
Toast.makeText(context, "Error deleting image", Toast.LENGTH_SHORT).show()
}
}
}
imageRow.addView(imageView)
imageRow.addView(deleteButton)
imagesContainer.addView(imageRow)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("COW_NAME", currentCowName)
}
}

View File

@ -0,0 +1,96 @@
package com.example.animalrating
import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageProxy
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.segmentation.subject.SubjectSegmentation
import com.google.mlkit.vision.segmentation.subject.SubjectSegmenterOptions
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
class FrameProcessor(private val onMaskReady: (Bitmap?) -> Unit) {
private val isProcessing = AtomicBoolean(false)
private val processingExecutor = Executors.newSingleThreadExecutor()
private val options = SubjectSegmenterOptions.Builder()
.enableMultipleSubjects(
SubjectSegmenterOptions.SubjectResultOptions.Builder()
.enableConfidenceMask()
.build()
)
.build()
private val segmenter = SubjectSegmentation.getClient(options)
@ExperimentalGetImage
fun processFrame(imageProxy: ImageProxy): Bitmap? {
Log.d("MatchingMasks", "Here")
if (!isProcessing.compareAndSet(false, true)) {
imageProxy.close()
return null
}
val mediaImage = imageProxy.image
if (mediaImage == null) {
isProcessing.set(false)
imageProxy.close()
return null
}
val inputImage = InputImage.fromMediaImage(mediaImage, 0)
var bitmapMask: Bitmap? = null
segmenter.process(inputImage)
.addOnSuccessListener(processingExecutor) { result ->
val subject = result.subjects.firstOrNull()
val mask = subject?.confidenceMask
if (mask != null) {
val startX = subject.startX
val startY = subject.startY
val maskWidth = subject.width
val maskHeight = subject.height
val fullWidth = inputImage.width
val fullHeight = inputImage.height
// Ensure buffer has enough data
if (mask.remaining() >= maskWidth * maskHeight) {
val colors = IntArray(fullWidth * fullHeight) // Initialized to 0 (TRANSPARENT)
mask.rewind()
for (y in 0 until maskHeight) {
for (x in 0 until maskWidth) {
if (mask.get() > 0.5f) {
val destX = startX + x
val destY = startY + y
if (destX < fullWidth && destY < fullHeight) {
colors[destY * fullWidth + destX] = Color.argb(180, 255, 0, 255)
}
}
}
}
bitmapMask = Bitmap.createBitmap(colors, fullWidth, fullHeight, Bitmap.Config.ARGB_8888)
onMaskReady(bitmapMask)
} else {
Log.e("FrameProcessor", "Mask buffer size mismatch. Expected ${maskWidth * maskHeight}, got ${mask.remaining()}")
onMaskReady(null)
}
} else {
onMaskReady(null)
}
}
.addOnFailureListener { e ->
Log.e("FrameProcessor", "Subject Segmentation failed", e)
onMaskReady(null)
}
.addOnCompleteListener { _ ->
isProcessing.set(false)
imageProxy.close()
}
return bitmapMask
}
}

View File

@ -0,0 +1,29 @@
package com.example.animalrating
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import java.io.File
class FullScreenImageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_full_screen_image)
val imagePath = intent.getStringExtra("IMAGE_PATH")
if (imagePath != null) {
val file = File(imagePath)
if (file.exists()) {
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
findViewById<ImageView>(R.id.fullScreenImageView).setImageBitmap(bitmap)
}
}
findViewById<Button>(R.id.btnBack).setOnClickListener {
finish()
}
}
}

View File

@ -0,0 +1,167 @@
package com.example.animalrating
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.util.Locale
class GalleryActivity : AppCompatActivity() {
private lateinit var container: LinearLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
container = findViewById(R.id.galleryContainer)
refreshGallery()
}
private fun refreshGallery() {
container.removeAllViews()
val files = filesDir.listFiles { _, name -> name.startsWith("cow_") && name.endsWith(".jpg") }
// 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()
groupedFiles.forEach { (cowName, cowFiles) ->
addCowSection(cowName, cowFiles)
}
}
private fun addCowSection(cowName: String, cowFiles: List<File>) {
// Cow Name Header and Retake Button
val headerLayout = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(0, 32, 0, 16)
}
}
val nameView = TextView(this).apply {
text = cowName
textSize = 20f
layoutParams = LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.WRAP_CONTENT,
1f
)
}
val retakeButton = Button(this).apply {
text = "Retake"
setOnClickListener {
val intent = Intent(context, CowSelectionActivity::class.java)
intent.putExtra("COW_NAME", cowName)
startActivity(intent)
}
}
headerLayout.addView(nameView)
headerLayout.addView(retakeButton)
container.addView(headerLayout)
// Group files by orientation (e.g., left, right, etc.)
val filesByOrientation = cowFiles.groupBy { file ->
val parts = file.name.split("_")
if (parts.size >= 3) parts[2] else "unknown"
}
filesByOrientation.forEach { (orientation, orientationFiles) ->
addOrientationSection(orientation, orientationFiles)
}
// Separator
val separator = android.view.View(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
2
).apply {
setMargins(0, 16, 0, 16)
}
setBackgroundColor(android.graphics.Color.LTGRAY)
}
container.addView(separator)
}
private fun addOrientationSection(orientationStr: String, files: List<File>) {
// Orientation Header
val orientationHeader = TextView(this).apply {
text = orientationStr.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
textSize = 16f
setTypeface(null, android.graphics.Typeface.BOLD)
setPadding(16, 8, 0, 8)
}
container.addView(orientationHeader)
// Images for this orientation
files.forEach { file ->
val imageRow = LinearLayout(this).apply {
orientation = LinearLayout.HORIZONTAL
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
200
).apply {
setMargins(32, 0, 0, 16)
}
}
val imageView = ImageView(this).apply {
layoutParams = LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.MATCH_PARENT,
1f
)
scaleType = ImageView.ScaleType.CENTER_CROP
setImageBitmap(BitmapFactory.decodeFile(file.absolutePath))
setOnClickListener {
val intent = Intent(context, FullScreenImageActivity::class.java)
intent.putExtra("IMAGE_PATH", file.absolutePath)
startActivity(intent)
}
}
val deleteButton = Button(this).apply {
text = "Delete"
textSize = 12f
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
setMargins(16,0,0,0)
}
setOnClickListener {
if (file.delete()) {
Toast.makeText(context, "Image deleted", Toast.LENGTH_SHORT).show()
refreshGallery()
} else {
Toast.makeText(context, "Error deleting image", Toast.LENGTH_SHORT).show()
}
}
}
imageRow.addView(imageView)
imageRow.addView(deleteButton)
container.addView(imageRow)
}
}
override fun onResume() {
super.onResume()
refreshGallery()
}
}

View File

@ -0,0 +1,21 @@
package com.example.animalrating
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
class HomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
findViewById<Button>(R.id.btnViewGallery).setOnClickListener {
startActivity(Intent(this, GalleryActivity::class.java))
}
findViewById<Button>(R.id.btnSelectCow).setOnClickListener {
startActivity(Intent(this, CowSelectionActivity::class.java))
}
}
}

View File

@ -0,0 +1,18 @@
package com.example.animalrating.ml
import android.graphics.Bitmap
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
class CowAnalyzer(
private val listener: CowListener
) : ImageAnalysis.Analyzer {
interface CowListener {
fun onFrame(imageProxy: ImageProxy)
}
override fun analyze(image: ImageProxy) {
listener.onFrame(image)
}
}

View File

@ -0,0 +1,43 @@
package com.example.animalrating.ml
import android.graphics.*
import androidx.camera.core.ImageProxy
import java.io.ByteArrayOutputStream
/**
* Convert ImageProxy YUV_420_888 to Bitmap
*/
fun ImageProxy.toBitmap(): Bitmap {
val yBuffer = planes[0].buffer // Y
val uBuffer = planes[1].buffer // U
val vBuffer = planes[2].buffer // V
val ySize = yBuffer.remaining()
val uSize = uBuffer.remaining()
val vSize = vBuffer.remaining()
val nv21 = ByteArray(ySize + uSize + vSize)
// U and V are swapped, so we place V first then U
yBuffer.get(nv21, 0, ySize)
vBuffer.get(nv21, ySize, vSize)
uBuffer.get(nv21, ySize + vSize, uSize)
val yuvImage = YuvImage(
nv21,
ImageFormat.NV21,
width,
height,
null
)
val out = ByteArrayOutputStream()
yuvImage.compressToJpeg(
Rect(0, 0, width, height),
90,
out
)
val jpegBytes = out.toByteArray()
return BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
}

View File

@ -0,0 +1,24 @@
package com.example.animalrating.ui
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
class MaskOverlay(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private var maskBitmap: Bitmap? = null
private var matrix: Matrix = Matrix()
fun updateMask(bitmap: Bitmap?, transform: Matrix?) {
maskBitmap = bitmap
matrix = transform ?: Matrix()
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val bmp = maskBitmap ?: return
canvas.drawBitmap(bmp, matrix, null)
}
}

View File

@ -0,0 +1,74 @@
package com.example.animalrating.ui
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
class SilhouetteOverlay(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private val paint = Paint().apply {
color = Color.GREEN
style = Paint.Style.STROKE
strokeWidth = 5f
}
private val silhouettePaint = Paint().apply {
alpha = 128 // 50% opacity
}
private var silhouette: Bitmap? = null
fun setSilhouette(drawableId: Int) {
try {
if (drawableId != 0) {
silhouette = BitmapFactory.decodeResource(resources, drawableId)
} else {
silhouette = null
}
invalidate()
} catch (e: Exception) {
Log.e("SilhouetteOverlay", "Error loading silhouette", e)
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
silhouette?.let { bmp ->
val viewW = width.toFloat()
val viewH = height.toFloat()
val bmpW = bmp.width.toFloat()
val bmpH = bmp.height.toFloat()
val viewAspect = viewW / viewH
val bmpAspect = bmpW / bmpH
val cropRect: RectF
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)
}
// Scale to exactly fill screen
val destRect = RectF(0f, 0f, viewW, viewH)
canvas.drawBitmap(bmp, cropRect.toRect(), destRect, silhouettePaint)
}
}
// Helper
private fun RectF.toRect(): Rect {
return Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())
}
}

View File

@ -0,0 +1,11 @@
package com.example.animalrating.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,58 @@
package com.example.animalrating.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun AnimalRatingTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.example.animalrating.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/tvCowName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#333333"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnNewCow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Capture New Cow"
android:backgroundTint="#2196F3"
android:textColor="#FFFFFF"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Select Orientation"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2"
android:rowCount="3"
android:alignmentMode="alignMargins"
android:columnOrderPreserved="false"
android:layout_marginBottom="32dp">
<!-- Left -->
<Button
android:id="@+id/btnLeft"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:text="Left"
android:layout_margin="8dp"
android:backgroundTint="#FF5722"
android:textColor="#FFFFFF"/>
<!-- Right -->
<Button
android:id="@+id/btnRight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:text="Right"
android:layout_margin="8dp"
android:backgroundTint="#FF5722"
android:textColor="#FFFFFF"/>
<!-- Front -->
<Button
android:id="@+id/btnFront"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:text="Front"
android:layout_margin="8dp"
android:backgroundTint="#FF5722"
android:textColor="#FFFFFF"/>
<!-- Back -->
<Button
android:id="@+id/btnBack"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:text="Back"
android:layout_margin="8dp"
android:backgroundTint="#FF5722"
android:textColor="#FFFFFF"/>
<!-- Top (Centered in last row) -->
<Button
android:id="@+id/btnTop"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_gravity="fill_horizontal"
android:layout_columnSpan="2"
android:text="Angle"
android:layout_margin="8dp"
android:backgroundTint="#FF5722"
android:textColor="#FFFFFF"/>
</GridLayout>
<!-- Container for displaying current cow's images -->
<LinearLayout
android:id="@+id/currentCowImagesContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"/>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<ImageView
android:id="@+id/fullScreenImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:scaleType="fitCenter"
android:contentDescription="Full Screen Image" />
<Button
android:id="@+id/btnBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="32dp"
android:text="Back" />
</RelativeLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5">
<LinearLayout
android:id="@+id/galleryContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Captured Images"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="#333333"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="24dp"/>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,64 @@
<?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:gravity="center"
android:padding="16dp"
android:background="#F5F5F5">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Animal Rating"
android:textSize="32sp"
android:textStyle="bold"
android:textColor="#333333"
android:layout_marginBottom="48dp"/>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:contentPadding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start New Rating"
android:textSize="18sp"
android:textColor="#666666"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnSelectCow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Select Cow"
android:textSize="16sp"
android:padding="12dp"
android:backgroundTint="#FF5722"
android:textColor="#FFFFFF"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/btnViewGallery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="View Collected Images"
android:textSize="16sp"
android:padding="12dp"
android:backgroundTint="#2196F3"
android:textColor="#FFFFFF"/>
</LinearLayout>

View File

@ -0,0 +1,40 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.camera.view.PreviewView
android:id="@+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.animalrating.ui.SilhouetteOverlay
android:id="@+id/silhouetteOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- ImageView to display the saved mask (Green) -->
<ImageView
android:id="@+id/savedMaskOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:elevation="5dp"/>
<!-- ImageView to display the segmentation mask (Live) -->
<ImageView
android:id="@+id/segmentationOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:alpha="0.9"
android:elevation="10dp"/>
<Button
android:id="@+id/btnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Save"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="32dp"/>
</FrameLayout>

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">AnimalRating</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.AnimalRating" parent="Theme.AppCompat.Light.NoActionBar" />
</resources>

View File

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

View File

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

View File

@ -0,0 +1,17 @@
package com.example.animalrating
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

6
build.gradle.kts Normal file
View File

@ -0,0 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}

23
gradle.properties Normal file
View File

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

32
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,32 @@
[versions]
agp = "8.13.1"
kotlin = "2.0.21"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.10.0"
activityCompose = "1.12.0"
composeBom = "2024.09.00"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,8 @@
#Sun Nov 23 20:12:35 IST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
gradlew vendored Normal file
View File

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
gradlew.bat vendored Normal file
View File

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

24
settings.gradle.kts Normal file
View File

@ -0,0 +1,24 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "AnimalRating"
include(":app")