diff --git a/ANDROID/.gradle/8.13/checksums/checksums.lock b/ANDROID/.gradle/8.13/checksums/checksums.lock
new file mode 100644
index 0000000..8207427
Binary files /dev/null and b/ANDROID/.gradle/8.13/checksums/checksums.lock differ
diff --git a/ANDROID/.gradle/8.13/checksums/md5-checksums.bin b/ANDROID/.gradle/8.13/checksums/md5-checksums.bin
new file mode 100644
index 0000000..4e1601c
Binary files /dev/null and b/ANDROID/.gradle/8.13/checksums/md5-checksums.bin differ
diff --git a/ANDROID/.gradle/8.13/checksums/sha1-checksums.bin b/ANDROID/.gradle/8.13/checksums/sha1-checksums.bin
new file mode 100644
index 0000000..0114713
Binary files /dev/null and b/ANDROID/.gradle/8.13/checksums/sha1-checksums.bin differ
diff --git a/ANDROID/.gradle/8.13/executionHistory/executionHistory.bin b/ANDROID/.gradle/8.13/executionHistory/executionHistory.bin
new file mode 100644
index 0000000..24af5dc
Binary files /dev/null and b/ANDROID/.gradle/8.13/executionHistory/executionHistory.bin differ
diff --git a/ANDROID/.gradle/8.13/executionHistory/executionHistory.lock b/ANDROID/.gradle/8.13/executionHistory/executionHistory.lock
new file mode 100644
index 0000000..d623141
Binary files /dev/null and b/ANDROID/.gradle/8.13/executionHistory/executionHistory.lock differ
diff --git a/ANDROID/.gradle/8.13/fileChanges/last-build.bin b/ANDROID/.gradle/8.13/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/ANDROID/.gradle/8.13/fileChanges/last-build.bin differ
diff --git a/ANDROID/.gradle/8.13/fileHashes/fileHashes.bin b/ANDROID/.gradle/8.13/fileHashes/fileHashes.bin
new file mode 100644
index 0000000..23482ed
Binary files /dev/null and b/ANDROID/.gradle/8.13/fileHashes/fileHashes.bin differ
diff --git a/ANDROID/.gradle/8.13/fileHashes/fileHashes.lock b/ANDROID/.gradle/8.13/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..2214545
Binary files /dev/null and b/ANDROID/.gradle/8.13/fileHashes/fileHashes.lock differ
diff --git a/ANDROID/.gradle/8.13/fileHashes/resourceHashesCache.bin b/ANDROID/.gradle/8.13/fileHashes/resourceHashesCache.bin
new file mode 100644
index 0000000..9414189
Binary files /dev/null and b/ANDROID/.gradle/8.13/fileHashes/resourceHashesCache.bin differ
diff --git a/ANDROID/.gradle/8.13/gc.properties b/ANDROID/.gradle/8.13/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/ANDROID/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/ANDROID/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 0000000..e3d6067
Binary files /dev/null and b/ANDROID/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/ANDROID/.gradle/buildOutputCleanup/cache.properties b/ANDROID/.gradle/buildOutputCleanup/cache.properties
new file mode 100644
index 0000000..ca24d0d
--- /dev/null
+++ b/ANDROID/.gradle/buildOutputCleanup/cache.properties
@@ -0,0 +1,2 @@
+#Sat Aug 02 14:11:47 CLT 2025
+gradle.version=8.13
diff --git a/ANDROID/.gradle/buildOutputCleanup/outputFiles.bin b/ANDROID/.gradle/buildOutputCleanup/outputFiles.bin
new file mode 100644
index 0000000..739c132
Binary files /dev/null and b/ANDROID/.gradle/buildOutputCleanup/outputFiles.bin differ
diff --git a/ANDROID/.gradle/config.properties b/ANDROID/.gradle/config.properties
new file mode 100644
index 0000000..602b2b1
--- /dev/null
+++ b/ANDROID/.gradle/config.properties
@@ -0,0 +1,2 @@
+#Sat Aug 02 14:11:14 CLT 2025
+java.home=C\:\\Program Files\\Android\\Android Studio1\\jbr
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/.globals.work.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/.globals.work.bin
new file mode 100644
index 0000000..9268142
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/.globals.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/.strings.work.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/.strings.work.bin
new file mode 100644
index 0000000..7656903
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/.strings.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/_app.work.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/_app.work.bin
new file mode 100644
index 0000000..11511e4
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/_app.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/build.work.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/build.work.bin
new file mode 100644
index 0000000..7c3e4e6
--- /dev/null
+++ b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/build.work.bin
@@ -0,0 +1,2 @@
+Ïï¯ç]
+xNúóŸÔ’‹§Põ:{æ~^0mŸ°í¼ù
\ No newline at end of file
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/buildfingerprint.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/buildfingerprint.bin
new file mode 100644
index 0000000..22627b8
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/buildfingerprint.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/entry.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/entry.bin
new file mode 100644
index 0000000..ae4fa5c
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/entry.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/projectfingerprint.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/projectfingerprint.bin
new file mode 100644
index 0000000..f56fc7d
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/projectfingerprint.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/work.bin b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/work.bin
new file mode 100644
index 0000000..55610cb
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/1ba32f2a-6f21-4a21-a7c7-35515bf66b3b/work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/1qoj2766qdel2f63aixcvg1di/candidates.bin b/ANDROID/.gradle/configuration-cache/1qoj2766qdel2f63aixcvg1di/candidates.bin
new file mode 100644
index 0000000..273450b
--- /dev/null
+++ b/ANDROID/.gradle/configuration-cache/1qoj2766qdel2f63aixcvg1di/candidates.bin
@@ -0,0 +1 @@
+75d8e340-753f-4767-a219-3b90055843c¸
\ No newline at end of file
diff --git a/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/.globals.work.bin b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/.globals.work.bin
new file mode 100644
index 0000000..5df81a4
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/.globals.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/.strings.work.bin b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/.strings.work.bin
new file mode 100644
index 0000000..ff72a0a
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/.strings.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/_app.work.bin b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/_app.work.bin
new file mode 100644
index 0000000..938ebf6
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/_app.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/buildfingerprint.bin b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/buildfingerprint.bin
new file mode 100644
index 0000000..07ba3f0
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/buildfingerprint.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/entry.bin b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/entry.bin
new file mode 100644
index 0000000..35ac23b
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/entry.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/projectfingerprint.bin b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/projectfingerprint.bin
new file mode 100644
index 0000000..3013a69
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/projectfingerprint.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/work.bin b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/work.bin
new file mode 100644
index 0000000..51a12b9
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/55a74dae-6abe-49c1-ad0a-f072c383008b/work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/.globals.work.bin b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/.globals.work.bin
new file mode 100644
index 0000000..d9b9c2c
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/.globals.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/.strings.work.bin b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/.strings.work.bin
new file mode 100644
index 0000000..862c50b
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/.strings.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/_app.work.bin b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/_app.work.bin
new file mode 100644
index 0000000..b1722eb
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/_app.work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/buildfingerprint.bin b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/buildfingerprint.bin
new file mode 100644
index 0000000..adc4e46
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/buildfingerprint.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/entry.bin b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/entry.bin
new file mode 100644
index 0000000..0209c6a
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/entry.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/projectfingerprint.bin b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/projectfingerprint.bin
new file mode 100644
index 0000000..7ca1365
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/projectfingerprint.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/work.bin b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/work.bin
new file mode 100644
index 0000000..b45f46a
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/75d8e340-753f-4767-a219-3b90055843c8/work.bin differ
diff --git a/ANDROID/.gradle/configuration-cache/configuration-cache.lock b/ANDROID/.gradle/configuration-cache/configuration-cache.lock
new file mode 100644
index 0000000..ef605d2
Binary files /dev/null and b/ANDROID/.gradle/configuration-cache/configuration-cache.lock differ
diff --git a/ANDROID/.gradle/configuration-cache/d5cd9omad1dlgp0vpc4jtemum/candidates.bin b/ANDROID/.gradle/configuration-cache/d5cd9omad1dlgp0vpc4jtemum/candidates.bin
new file mode 100644
index 0000000..ea0ffc1
--- /dev/null
+++ b/ANDROID/.gradle/configuration-cache/d5cd9omad1dlgp0vpc4jtemum/candidates.bin
@@ -0,0 +1 @@
+1ba32f2a-6f21-4a21-a7c7-35515bf66b3â
\ No newline at end of file
diff --git a/ANDROID/.gradle/configuration-cache/d9k9tgk0y309byuv9t345oex6/candidates.bin b/ANDROID/.gradle/configuration-cache/d9k9tgk0y309byuv9t345oex6/candidates.bin
new file mode 100644
index 0000000..b78138b
--- /dev/null
+++ b/ANDROID/.gradle/configuration-cache/d9k9tgk0y309byuv9t345oex6/candidates.bin
@@ -0,0 +1 @@
+55a74dae-6abe-49c1-ad0a-f072c383008â
\ No newline at end of file
diff --git a/ANDROID/.gradle/configuration-cache/gc.properties b/ANDROID/.gradle/configuration-cache/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/ANDROID/.gradle/file-system.probe b/ANDROID/.gradle/file-system.probe
new file mode 100644
index 0000000..c4bc0e4
Binary files /dev/null and b/ANDROID/.gradle/file-system.probe differ
diff --git a/ANDROID/.gradle/vcs-1/gc.properties b/ANDROID/.gradle/vcs-1/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/ANDROID/METHODOLOGY.md b/ANDROID/METHODOLOGY.md
new file mode 100644
index 0000000..762b9bd
--- /dev/null
+++ b/ANDROID/METHODOLOGY.md
@@ -0,0 +1,139 @@
+# Sun Day - Vitamin D Calculation Methodology
+
+## Overview
+
+Sun Day calculates vitamin D synthesis from UV exposure using a multi-factor model based on scientific research. The app aims to provide personalized, accurate estimates while remaining conservative for safety. See [citations](https://github.com/jackjackbits/sunday/blob/main/METHODOLOGY.md#references).
+
+## Core Formula
+
+```
+Vitamin D Rate (IU/hour) = Base Rate × UV Factor × Clothing Factor × Skin Type Factor × Age Factor × Quality Factor × Adaptation Factor
+```
+
+## Factor Breakdown
+
+### 1. Base Rate (21,000 IU/hr)
+- Represents minimal clothing exposure (~80% body surface area)
+- Conservative estimate within research range of 20,000-40,000 IU/hr
+- Studies show 10,000 IU in 20-30 minutes typical
+- Full body exposure can reach 30,000-40,000 IU/hr in optimal conditions
+
+### 2. UV Factor (Non-linear)
+- Implements Michaelis-Menten-like saturation curve
+- Formula: `uvFactor = (uvIndex × 3.0) / (4.0 + uvIndex)`
+- Accounts for:
+ - Vitamin D synthesis plateaus at high UV levels
+ - Photodegradation of vitamin D above UV ~8
+ - Limited 7-dehydrocholesterol in skin
+
+### 3. Clothing Factor
+- **Nude (100%)**: Full body exposure
+- **Minimal/Swimwear (80%)**: Typical beach attire
+- **Light/Shorts & T-shirt (40%)**: Summer casual wear
+- **Moderate/Long sleeves (15%)**: Business casual
+- **Heavy/Fully covered (5%)**: Winter clothing
+
+### 4. Skin Type Factor (Fitzpatrick Scale)
+- **Type I (125%)**: Very fair, always burns - highest vitamin D production
+- **Type II (110%)**: Fair, usually burns
+- **Type III (100%)**: Light, sometimes burns - reference type
+- **Type IV (70%)**: Medium, rarely burns
+- **Type V (40%)**: Dark, very rarely burns
+- **Type VI (20%)**: Very dark, never burns
+
+Based on melanin's UV filtering effect and research showing 5-10x longer exposure needed for darker skin types.
+
+### 5. Age Factor
+- **≤20 years**: 100% efficiency
+- **20-70 years**: Linear decrease (~1% per year)
+- **≥70 years**: 25% efficiency
+
+Reflects decreased 7-dehydrocholesterol in aging skin.
+
+### 6. UV Quality Factor (Time of Day)
+- Accounts for solar zenith angle effects on UV-B transmission
+- Peak quality around solar noon (10 AM - 3 PM)
+- More gradual decrease at low sun angles (exp(-0.2) vs exp(-0.3))
+- Morning/evening UV has less effective UV-B wavelengths
+
+### 7. Adaptation Factor
+- Based on 7-14 day exposure history from HealthKit
+- Range: 0.8-1.2x
+- Regular exposure upregulates vitamin D synthesis pathways
+- Prevents "shock" calculations for pale individuals suddenly exposed
+
+## Scientific Basis
+
+### UV-B and Vitamin D Synthesis
+- Only UV-B wavelengths (290-315nm) produce vitamin D
+- 7-dehydrocholesterol + UV-B → pre-vitamin D3 → vitamin D3
+- Process self-regulates through photoisomerization equilibrium
+
+### Altitude Effects
+- UV increases ~10% per 1000m elevation
+- Implemented as simple multiplier on base UV index
+
+### Cloud Cover
+- Already factored into UV index from weather API
+- Clear sky UV only used for reference
+
+### Daily Synthesis Limits
+- Body naturally limits to ~20,000 IU/day
+- Excess pre-vitamin D3 converts to inactive photoisomers
+- Prevents toxicity from sun exposure alone
+
+## Data Sources
+
+1. **UV Index**: Open-Meteo API (includes cloud effects)
+2. **Location**: iOS Core Location
+3. **User Characteristics**: Apple Health (when available)
+4. **Historical Data**: HealthKit vitamin D records
+
+## Burn Time Calculation
+
+Burn time is based on the **full MED** (Minimal Erythema Dose):
+
+```
+Burn Time = MED at UV 1 / Current UV
+```
+
+Real-world MED values at UV index 1:
+- Type I: 150 minutes (burns in ~30 min at UV 5)
+- Type II: 250 minutes (burns in ~45-50 min at UV 5)
+- Type III: 425 minutes (burns in ~75-85 min at UV 5)
+- Type IV: 600 minutes (burns in ~100-120 min at UV 5)
+- Type V: 850 minutes (burns in ~150-180 min at UV 5)
+- Type VI: 1100 minutes (rarely burns)
+
+These values reflect actual outdoor conditions with natural cooling and movement.
+The app notifies users at 80% of burn time as a safety warning.
+
+## Vitamin D Winter
+
+Above 35° latitude, UV-B is insufficient for vitamin D synthesis during winter months:
+- **November-February**: Minimal to no synthesis
+- **March & October**: Marginal synthesis (UV often < 3)
+- App displays warning and recommends supplementation
+
+## Safety Considerations
+
+- Base rate calibrated to typical exposure patterns
+- Burn time based on full MED
+- Seasonal warnings for vitamin D winter
+- Cannot reach toxic levels from UV exposure alone
+
+## Future Improvements
+
+1. **Spectral UV Data**: Use UV-B specific measurements when available
+2. **Body Surface Area**: More precise calculation based on height/weight
+3. **Seasonal Adjustments**: Winter UV-B availability at high latitudes
+4. **Individual Calibration**: Learn from user's actual vitamin D blood tests
+
+## Citations
+
+- Holick, M.F. (2007). "Vitamin D deficiency." New England Journal of Medicine
+- Webb, A.R. et al. (2018). "The role of sunlight exposure in determining the vitamin D status"
+- Engelsen, O. (2010). "The relationship between ultraviolet radiation exposure and vitamin D status"
+- MacLaughlin, J. & Holick, M.F. (1985). "Aging decreases the capacity of human skin to produce vitamin D3"
+- Çekmez, Y. et al. (2024). "Time and duration of vitamin D synthesis" PMC10861575
+- Various studies on MED and safe sun exposure from SunSmart Australia
diff --git a/ANDROID/METHODOLOGYES.md b/ANDROID/METHODOLOGYES.md
new file mode 100644
index 0000000..8c96ed5
--- /dev/null
+++ b/ANDROID/METHODOLOGYES.md
@@ -0,0 +1,96 @@
+MetodologÃa
+
+El objetivo es determinar la cantidad de tiempo que una persona necesita pasar bajo el sol para producir una cantidad saludable de vitamina D, sin quemarse.
+
+1. Tiempo para Quemarse
+
+Para determinar el tiempo que tarda la piel en quemarse, utilizamos el concepto de Dosis MÃnima Eritematosa (MED, por sus siglas en inglés). Una MED es la cantidad de exposición a la radiación UV que causa un enrojecimiento perceptible de la piel (eritema). Se considera que 1 MED equivale a aproximadamente 2 SED (Dosis de Eritema Estándar).
+
+Usamos la siguiente fórmula para calcular el tiempo para alcanzar 1 MED:
+
+minutos_para_quemarse = (MED_personal * 60) / (UVI * 1.5)
+Donde:
+
+MED_personal se basa en el tipo de piel de Fitzpatrick del individuo.
+
+UVI es el Ãndice UV actual.
+
+1.5 es un factor de ajuste.
+
+2. Tiempo para Obtener Vitamina D
+
+El tiempo necesario para sintetizar vitamina D es generalmente menor que el tiempo para quemarse. Para la mayorÃa de las personas de piel clara, se estima que la producción de 1000 a 2000 UI (Unidades Internacionales) de vitamina D requiere aproximadamente 0.5 MED.
+
+Utilizamos la siguiente fórmula para calcular el tiempo para obtener una dosis saludable de vitamina D:
+
+minutos_para_vitamina_d = (MED_personal * 0.5 * 60) / (UVI * 1.5)
+Esta fórmula es esencialmente la mitad del tiempo que se tarda en quemarse.
+
+Fuentes de Datos
+
+Ãndice UV (UVI): Obtenido de la API de OpenUV, que proporciona datos del Ãndice UV en tiempo real basados en la ubicación del usuario.
+
+Tipos de Piel de Fitzpatrick: Utilizamos la escala de Fitzpatrick para clasificar los tipos de piel y determinar la MED personal de un individuo.
+
+Valores de MED Personal por Tipo de Piel
+
+Tipo de Piel Descripción MED (J/m²)
+I Siempre se quema, nunca se broncea 200
+II Se quema fácilmente, se broncea mÃnimamente 250
+III Se quema moderadamente, se broncea gradualmente 350
+IV Se quema mÃnimamente, siempre se broncea bien 450
+V Rara vez se quema, se broncea abundantemente 600
+VI Nunca se quema, pigmentación oscura 1000
+
+Exportar a Hojas de cálculo
+Nota: Estos valores de MED son aproximaciones y pueden variar entre individuos.
+
+Explicación de la MetodologÃa
+Ahora te explico qué significa todo esto en términos más sencillos.
+
+El objetivo de la app es muy simple: darte una "ventana de tiempo segura" para tomar el sol. Quieres el beneficio (vitamina D) sin el riesgo (quemadura). Para ello, la app realiza dos cálculos principales:
+
+Paso 1: Calcular cuánto tardas en quemarte
+El Concepto Clave: "Dosis MÃnima Eritematosa" (MED). Imagina que es tu "lÃmite personal de sol". Es la cantidad exacta de energÃa UV que tu piel puede recibir antes de empezar a ponerse roja.
+
+¿Cómo sabe tu lÃmite? Usa la Escala de Fitzpatrick, que es un estándar cientÃfico para clasificar tipos de piel. Si tienes la piel muy clara (Tipo I), tu lÃmite (MED) es bajo. Si tu piel es muy oscura (Tipo VI), tu lÃmite es mucho más alto. Tú le dices a la app qué tipo de piel tienes.
+
+El Factor Externo: El Ãndice UV (UVI). Este número te dice qué tan fuerte está el sol en tu ubicación y en ese preciso momento. Un UVI de 10 es mucho más intenso que un UVI de 2. La app obtiene este dato en tiempo real usando tu GPS.
+
+La Fórmula Simple:
+Tiempo para Quemarse = (Tu LÃmite Personal) / (Intensidad del Sol Actual)
+
+La app toma tu MED (lÃmite personal), lo divide por la intensidad del sol (UVI) y te da el resultado en minutos. Asà sabes cuál es tu tiempo máximo de exposición antes de sufrir una quemadura.
+
+Paso 2: Calcular cuánto tiempo necesitas para generar Vitamina D
+La Buena Noticia: Producir vitamina D es mucho más rápido que quemarse. La ciencia ha demostrado que para la mayorÃa de la gente, solo se necesita la mitad de la dosis de UV que te causarÃa una quemadura para generar una buena cantidad de vitamina D (entre 1000 y 2000 UI).
+
+La Fórmula Simple:
+Tiempo para Vitamina D = (Tiempo para Quemarse) / 2
+
+La app simplemente toma el tiempo máximo que calculó en el paso anterior y lo divide por la mitad.
+
+El Resultado Final para Ti
+La app te presenta estos dos números para que puedas tomar una decisión informada.
+
+Ejemplo Práctico:
+
+Tus datos: Tienes piel Tipo III (MED de 350) y en tu ciudad el Ãndice UV ahora mismo es de 7.
+
+Cálculo de la app:
+
+Tiempo para Quemarse: (350 * 60) / (7 * 1.5) ≈ 33 minutos. Este es tu lÃmite máximo.
+
+Tiempo para Vitamina D: 33 minutos / 2 ≈ 16.5 minutos.
+
+Lo que te muestra la app:
+
+"Para obtener una dosis saludable de Vitamina D, exponte al sol durante unos 16 minutos. Evita pasar más de 33 minutos para no quemarte."
+## References
+
+- Holick, M.F. (2007). "Vitamin D deficiency." New England Journal of Medicine
+- Webb, A.R. et al. (2018). "The role of sunlight exposure in determining the vitamin D status"
+- Engelsen, O. (2010). "The relationship between ultraviolet radiation exposure and vitamin D status"
+- MacLaughlin, J. & Holick, M.F. (1985). "Aging decreases the capacity of human skin to produce vitamin D3"
+- Çekmez, Y. et al. (2024). "Time and duration of vitamin D synthesis" PMC10861575
+- Various studies on MED and safe sun exposure from SunSmart Australia
diff --git a/ANDROID/README.md b/ANDROID/README.md
new file mode 100644
index 0000000..7224dc5
--- /dev/null
+++ b/ANDROID/README.md
@@ -0,0 +1,87 @@
+# Sunday - Android Version
+
+This is the Android version of [Sunday](https://github.com/jackjackbits/sunday), a powerful application for tracking UV exposure and Vitamin D generation, rebuilt from the ground up in Kotlin for the Android platform.
+
+This version is designed to be completely self-contained within the `android/` directory.
+
+## Core Features
+
+This Android implementation currently provides the core functionalities essential for sun exposure tracking:
+
+| Feature | Android Implementation | Status |
+| ------------------------ | ----------------------------------- | ----------- |
+| **Real-time UV Tracking** | Open-Meteo API Integration | Implemented |
+| **Vitamin D Calculation** | `SunExposureCalculator.kt` Logic | Implemented |
+| **Burn Limit Calculation** | Based on Fitzpatrick Scale & UV | Implemented |
+| **Skin Type Selection** | Fitzpatrick Scale Types I-VI | Implemented |
+| **Clothing Level Adjustment**| Nude, Minimal, Light, etc. | Implemented |
+| **Manual Refresh** | Floating Action Button (FAB) | Implemented |
+| **Bilingual Support** | English & Spanish (Dynamic) | Implemented |
+| **In-App Methodology** | Displays methodology text | Implemented |
+
+**Note:** Advanced features from the original iOS version such as Google Fit integration, database persistence (Room), widgets, and notifications are **not yet implemented** in this version.
+
+## Technical Architecture
+
+The application is built using a modern Android tech stack, focusing on simplicity and performance.
+
+- **UI:** Fully built with **Jetpack Compose** for a modern, declarative user interface.
+- **State Management:** State is managed directly within `@Composable` functions using `remember` and `mutableStateOf`.
+- **Networking:** **Retrofit** and **Gson** are used for efficient and reliable communication with the Open-Meteo API.
+- **Location:** Leverages **Google Play Services**' FusedLocationProviderClient for accurate location data.
+- **Core Logic:** All sun exposure calculations are handled in a dedicated `SunExposureCalculator.kt` object, ensuring logic is centralized and testable.
+
+## Requirements
+
+- **Android 6.0 (API 23)** or higher.
+- **Google Play Services** installed and up-to-date.
+- **Location Services** enabled for accurate UV data.
+- **Internet Connection** for fetching weather data.
+
+## Project Structure
+
+The project structure is organized by feature/responsibility:
+
+```
+
+app/src/main/kotlin/com/example/sundayandroidapp/
+├── MainActivity.kt # App entry point
+├── api/ # Retrofit API interface and data models
+│ ├── OpenMeteoApiService.kt
+│ └── OpenMeteoResponse.kt
+├── data/ # Enums and data classes (SkinType, ClothingLevel)
+│ ├── SkinType.kt
+│ └── ClothingLevel.kt
+├── location/ # Location services
+│ └── LocationService.kt
+├── logic/ # Core calculation logic
+│ └── SunExposureCalculator.kt
+└── screens/ # UI Composables
+ ├── HomeScreen.kt
+ ├── MethodologyScreen.kt
+ ├── SessionTrackingCard.kt
+ └── OptionsSelectorDialog.kt
+
+```
+
+## How to Build
+
+```
+bash
+# Clone the main repository
+git clone [repository-url]
+cd [repository-name]/android
+
+# Open the 'android' directory in Android Studio
+# Allow Gradle to sync and download dependencies
+
+# Build the debug version
+./gradlew assembleDebug
+
+```
+
+## License
+
+This project is licensed under the [MIT License](LICENSE).
+by @gmolate
+```
\ No newline at end of file
diff --git a/ANDROID/app/build.gradle.kts b/ANDROID/app/build.gradle.kts
new file mode 100644
index 0000000..24f2173
--- /dev/null
+++ b/ANDROID/app/build.gradle.kts
@@ -0,0 +1,73 @@
+plugins {
+ id("com.android.application")
+ kotlin("android")
+ kotlin("plugin.compose")
+}
+
+android {
+ namespace = "com.example.sundayandroidapp"
+ compileSdk = 36
+ defaultConfig {
+ applicationId = "com.example.sundayandroidapp"
+ minSdk = 23
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ }
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ buildFeatures {
+ compose = true
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+ sourceSets {
+ getByName("main").java.srcDirs("src/main/kotlin")
+ }
+}
+
+kotlin {
+ jvmToolchain(17)
+}
+
+dependencies {
+ val composeBom = platform("androidx.compose:compose-bom:2024.05.00")
+ implementation(composeBom)
+ androidTestImplementation(composeBom)
+
+ implementation("androidx.core:core-ktx:1.16.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.2")
+ implementation("androidx.activity:activity-compose")
+ implementation("androidx.compose.ui:ui")
+ implementation("androidx.compose.ui:ui-graphics")
+ implementation("androidx.compose.ui:ui-tooling-preview")
+ implementation("androidx.compose.material3:material3")
+ implementation("androidx.compose.material:material-icons-extended")
+ implementation("androidx.core:core-splashscreen:1.0.1")
+ implementation("com.google.android.material:material:1.12.0")
+ implementation("com.google.android.gms:play-services-location:21.3.0")
+ implementation("com.squareup.retrofit2:retrofit:2.11.0")
+ implementation("com.squareup.retrofit2:converter-gson:3.0.0")
+ implementation("com.google.code.gson:gson:2.13.1")
+
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.3.0")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.7.0")
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4")
+ debugImplementation("androidx.compose.ui:ui-tooling")
+ debugImplementation("androidx.compose.ui:ui-test-manifest")
+}
\ No newline at end of file
diff --git a/ANDROID/app/src/main/AndroidManifest.xml b/ANDROID/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5ffd0a7
--- /dev/null
+++ b/ANDROID/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/data/AppModels.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/data/AppModels.kt
new file mode 100644
index 0000000..545f4ea
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/data/AppModels.kt
@@ -0,0 +1,26 @@
+package com.example.sundayandroidapp.data
+
+enum class SkinType(val type: Int, val description: String) {
+ TYPE_1(1, "Very fair, Always burns, never tans"),
+ TYPE_2(2, "Fair, Usually burns, tans minimally"),
+ TYPE_3(3, "Light, Sometimes burns, tans uniformly"),
+ TYPE_4(4, "Medium, Burns minimally, tans well"),
+ TYPE_5(5, "Dark, Rarely burns, tans profusely"),
+ TYPE_6(6, "Very dark, Never burns, deeply pigmented");
+
+ companion object {
+ fun fromType(type: Int): SkinType? = values().find { it.type == type }
+ }
+}
+
+enum class ClothingLevel(val level: String) {
+ NUDE("Nude!"),
+ MINIMAL("Minimal (swimwear)"),
+ LIGHT("Light (shorts & t-shirt)"),
+ MODERATE("Moderate (long sleeves)"),
+ HEAVY("Heavy (fully covered)");
+
+ companion object {
+ fun fromLevel(level: String): ClothingLevel? = values().find { it.level == level }
+ }
+}
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/screens/OptionsSelectorDialog.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/screens/OptionsSelectorDialog.kt
new file mode 100644
index 0000000..b9488a6
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/screens/OptionsSelectorDialog.kt
@@ -0,0 +1,64 @@
+package com.example.sundayandroidapp.screens
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun OptionsSelectorDialog(
+ title: String,
+ options: List,
+ selectedOption: T,
+ onOptionSelected: (T) -> Unit,
+ onDismissRequest: () -> Unit,
+ optionText: (T) -> String
+) {
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ title = { Text(text = title, color = Color.Black, fontSize = 20.sp) },
+ text = {
+ Column {
+ options.forEach { option ->
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 4.dp)
+ .clickable {
+ onOptionSelected(option)
+ onDismissRequest()
+ },
+ colors = CardDefaults.cardColors(
+ containerColor = if (selectedOption == option) Color.LightGray else Color.White
+ )
+ ) {
+ Text(
+ text = optionText(option),
+ modifier = Modifier.padding(16.dp),
+ color = Color.Black,
+ fontSize = 16.sp
+ )
+ }
+ }
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = onDismissRequest) {
+ Text("Cerrar", color = Color.Blue)
+ }
+ },
+ containerColor = Color.White,
+ shape = RoundedCornerShape(16.dp)
+ )
+}
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/screens/SessionTrackingCard.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/screens/SessionTrackingCard.kt
new file mode 100644
index 0000000..7853aaa
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/screens/SessionTrackingCard.kt
@@ -0,0 +1,84 @@
+package com.example.sundayandroidapp.screens
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.sundayandroidapp.R
+
+@Composable
+fun SessionTrackingCard(
+ rate: Float,
+ sessionTotal: Float,
+ todayTotal: Float,
+ isTracking: Boolean,
+ onToggleTracking: () -> Unit
+) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.3f))
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceAround,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Text(text = stringResource(R.string.rate_label), color = Color.White, fontSize = 16.sp)
+ Text(text = stringResource(R.string.iu_per_min_template, rate.toInt()), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 24.sp)
+ }
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Text(text = stringResource(R.string.session_label), color = Color.White, fontSize = 16.sp)
+ Text(text = stringResource(R.string.iu_template, sessionTotal.toInt()), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 24.sp)
+ }
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Text(text = stringResource(R.string.today_label), color = Color.White, fontSize = 16.sp)
+ Text(text = stringResource(R.string.iu_template, todayTotal.toInt()), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 24.sp)
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Button(
+ onClick = onToggleTracking,
+ modifier = Modifier.fillMaxWidth(0.6f),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = if (isTracking) Color(0xFFF44336) else Color(0xFF4CAF50)
+ )
+ ) {
+ Text(
+ text = if (isTracking) stringResource(R.string.stop_tracking) else stringResource(R.string.start_tracking),
+ color = Color.White,
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+}
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Color.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Color.kt
new file mode 100644
index 0000000..58a0dca
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.example.sundayandroidapp.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)
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Gradient.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Gradient.kt
new file mode 100644
index 0000000..782ce61
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Gradient.kt
@@ -0,0 +1,11 @@
+package com.example.sundayandroidapp.ui.theme
+
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+
+val BlueGradient = Brush.verticalGradient(
+ colors = listOf(
+ Color(0xFF8ECDF9), // Azul claro
+ Color(0xFF4C87F0) // Azul más oscuro
+ )
+)
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Shape.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Shape.kt
new file mode 100644
index 0000000..b982b3a
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Shape.kt
@@ -0,0 +1,11 @@
+package com.example.sundayandroidapp.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(4.dp),
+ medium = RoundedCornerShape(8.dp),
+ large = RoundedCornerShape(16.dp)
+)
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Theme.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Theme.kt
new file mode 100644
index 0000000..0f6d16d
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Theme.kt
@@ -0,0 +1,70 @@
+package com.example.sundayandroidapp.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.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+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 SundayAndroidAppTheme(
+ 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
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+ }
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Type.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Type.kt
new file mode 100644
index 0000000..ab0e621
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/ui/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.example.sundayandroidapp.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
+ )
+ */
+)
diff --git a/ANDROID/app/src/main/java/com/example/sundayandroidapp/util/LocalisationManager.kt b/ANDROID/app/src/main/java/com/example/sundayandroidapp/util/LocalisationManager.kt
new file mode 100644
index 0000000..8a98540
--- /dev/null
+++ b/ANDROID/app/src/main/java/com/example/sundayandroidapp/util/LocalisationManager.kt
@@ -0,0 +1,17 @@
+package com.example.sundayandroidapp.util
+
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Build
+import java.util.Locale
+
+object LocalizationManager {
+
+ fun setLocale(context: Context, languageCode: String) {
+ val locale = Locale(languageCode)
+ Locale.setDefault(locale)
+ val configuration = Configuration(context.resources.configuration)
+ configuration.setLocale(locale)
+ context.resources.updateConfiguration(configuration, context.resources.displayMetrics)
+ }
+}
diff --git a/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/MainActivity.kt b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/MainActivity.kt
new file mode 100644
index 0000000..0d86531
--- /dev/null
+++ b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/MainActivity.kt
@@ -0,0 +1,40 @@
+package com.example.sundayandroidapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import com.example.sundayandroidapp.screens.HomeScreen
+import com.example.sundayandroidapp.ui.theme.SundayAndroidAppTheme
+import java.util.Locale
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ var currentLanguage by remember { mutableStateOf(Locale.getDefault().language) }
+ SundayAndroidAppTheme {
+ HomeScreen(
+ currentLanguage = currentLanguage,
+ onLanguageChange = { newLanguage ->
+ currentLanguage = newLanguage
+ val configuration = resources.configuration
+ configuration.setLocale(Locale(newLanguage))
+ resources.updateConfiguration(configuration, resources.displayMetrics)
+ }
+ )
+ }
+ }
+ }
+}
+
diff --git a/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/api/OpenMeteoApiService.kt b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/api/OpenMeteoApiService.kt
new file mode 100644
index 0000000..d45f840
--- /dev/null
+++ b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/api/OpenMeteoApiService.kt
@@ -0,0 +1,12 @@
+package com.example.sundayandroidapp.api
+
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface OpenMeteoApiService {
+ @GET("v1/forecast?current=uv_index,cloud_cover&daily=uv_index_max,sunrise,sunset&timezone=auto")
+ suspend fun getSunData(
+ @Query("latitude") latitude: Double,
+ @Query("longitude") longitude: Double
+ ): OpenMeteoResponse
+}
diff --git a/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/api/OpenMeteoResponse.kt b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/api/OpenMeteoResponse.kt
new file mode 100644
index 0000000..863fcb7
--- /dev/null
+++ b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/api/OpenMeteoResponse.kt
@@ -0,0 +1,56 @@
+package com.example.sundayandroidapp.api
+
+import com.google.gson.annotations.SerializedName
+
+data class OpenMeteoResponse(
+ val latitude: Double,
+ val longitude: Double,
+ @SerializedName("generationtime_ms")
+ val generationTimeMs: Double,
+ @SerializedName("utc_offset_seconds")
+ val utcOffsetSeconds: Int,
+ val timezone: String,
+ @SerializedName("timezone_abbreviation")
+ val timezoneAbbreviation: String,
+ val elevation: Double,
+ @SerializedName("current_units")
+ val currentUnits: CurrentUnits,
+ val current: CurrentData,
+ @SerializedName("daily_units")
+ val dailyUnits: DailyUnits,
+ val daily: DailyData
+)
+
+data class CurrentUnits(
+ val time: String,
+ val interval: String,
+ @SerializedName("uv_index")
+ val uvIndex: String,
+ @SerializedName("cloud_cover")
+ val cloudCover: String
+)
+
+data class CurrentData(
+ val time: String,
+ val interval: Int,
+ @SerializedName("uv_index")
+ val uvIndex: Double,
+ @SerializedName("cloud_cover")
+ val cloudCover: Int
+)
+
+data class DailyUnits(
+ val time: String,
+ @SerializedName("uv_index_max")
+ val uvIndexMax: String,
+ val sunrise: String,
+ val sunset: String
+)
+
+data class DailyData(
+ val time: List,
+ @SerializedName("uv_index_max")
+ val uvIndexMax: List,
+ val sunrise: List,
+ val sunset: List
+)
diff --git a/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/location/LocationService.kt b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/location/LocationService.kt
new file mode 100644
index 0000000..e8a8b63
--- /dev/null
+++ b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/location/LocationService.kt
@@ -0,0 +1,47 @@
+package com.example.sundayandroidapp.location
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.location.Location
+import androidx.core.content.ContextCompat
+import com.google.android.gms.location.LocationServices
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+class LocationService(private val context: Context) {
+
+ private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
+
+ suspend fun getLastKnownLocation(): Location? {
+ val hasFineLocationPermission = ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED
+
+ val hasCoarseLocationPermission = ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED
+
+ if (!hasFineLocationPermission || !hasCoarseLocationPermission) {
+ // Si los permisos no estn concedidos, devuelve null
+ return null
+ }
+
+ return suspendCancellableCoroutine { continuation ->
+ fusedLocationClient.lastLocation.addOnCompleteListener {
+ if (it.isSuccessful) {
+ continuation.resume(it.result)
+ } else {
+ // Reanudar con null si la ubicacin no es exitosa o ocurre un error
+ continuation.resume(null)
+ // Opcionalmente, registra la excepcin para depuracin
+ it.exception?.printStackTrace()
+ }
+ }
+ }
+ }
+}
+
diff --git a/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/logic/SunExposureCalculator.kt b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/logic/SunExposureCalculator.kt
new file mode 100644
index 0000000..b370149
--- /dev/null
+++ b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/logic/SunExposureCalculator.kt
@@ -0,0 +1,90 @@
+package com.example.sundayandroidapp.logic
+
+import com.example.sundayandroidapp.data.ClothingLevel
+import com.example.sundayandroidapp.data.SkinType
+import kotlin.math.exp
+
+object SunExposureCalculator {
+
+ // VALORES Y FÓRMULA PORTADOS 1:1 DESDE LA VERSIÓN iOS
+ // Minutos para alcanzar 1 MED (Minimal Erythema Dose) con un UV Index de 1
+ private val medTimesAtUV1 = mapOf(
+ SkinType.TYPE_1 to 150.0,
+ SkinType.TYPE_2 to 250.0,
+ SkinType.TYPE_3 to 425.0,
+ SkinType.TYPE_4 to 600.0,
+ SkinType.TYPE_5 to 850.0,
+ SkinType.TYPE_6 to 1100.0
+ )
+
+ // Factores de Vitamina D basados en el tipo de piel (Referencia: iOS)
+ private val vitaminDFactors = mapOf(
+ SkinType.TYPE_1 to 1.25,
+ SkinType.TYPE_2 to 1.1,
+ SkinType.TYPE_3 to 1.0,
+ SkinType.TYPE_4 to 0.7,
+ SkinType.TYPE_5 to 0.4,
+ SkinType.TYPE_6 to 0.2
+ )
+
+ // Factores de exposición según la ropa (Referencia: iOS)
+ private val clothingExposureFactors = mapOf(
+ ClothingLevel.NUDE to 1.0,
+ ClothingLevel.MINIMAL to 0.80,
+ ClothingLevel.LIGHT to 0.50,
+ ClothingLevel.MODERATE to 0.30,
+ ClothingLevel.HEAVY to 0.10
+ )
+
+ // Parámetros de la curva de saturación UV (Referencia: iOS)
+ private const val UV_HALF_MAX = 4.0
+ private const val UV_MAX_FACTOR = 3.0
+ private const val BASE_VITAMIN_D_RATE = 21000.0 // IU/hora
+
+ /**
+ * Calcula el tiempo lÃmite de quemado en minutos, usando la lógica de iOS.
+ */
+ fun calculateBurnLimitTime(
+ uvIndex: Float,
+ skinType: SkinType,
+ clothingLevel: ClothingLevel, // No usado en la fórmula de iOS, pero se mantiene por consistencia
+ cloudCoverPercentage: Int, // No usado, UV ya lo incluye
+ altitudeMeters: Int // No usado, UV ya lo incluye
+ ): Int {
+ if (uvIndex <= 0) return 0
+
+ val medTimeAtUV1 = medTimesAtUV1[skinType] ?: 425.0 // Default a Tipo 3
+
+ // FÓRMULA CORRECTA (PORTADA DE iOS)
+ val burnLimitMinutes = medTimeAtUV1 / uvIndex
+
+ return burnLimitMinutes.toInt()
+ }
+
+ /**
+ * Calcula la tasa de producción de Vitamina D en IU/minuto, usando la lógica de iOS.
+ */
+ fun calculateVitaminDProductionRate(
+ uvIndex: Float,
+ skinType: SkinType,
+ clothingLevel: ClothingLevel,
+ cloudCoverPercentage: Int, // No usado
+ altitudeMeters: Int // No usado
+ ): Int {
+ if (uvIndex <= 0) return 0
+
+ // FÓRMULA DE SATURACIÓN UV (PORTADA DE iOS)
+ val uvFactor = (uvIndex * UV_MAX_FACTOR) / (UV_HALF_MAX + uvIndex)
+
+ val exposureFactor = clothingExposureFactors[clothingLevel] ?: 0.5
+ val skinFactor = vitaminDFactors[skinType] ?: 1.0
+
+ // Tasa de Vitamina D por HORA
+ val hourlyRate = BASE_VITAMIN_D_RATE * uvFactor * exposureFactor * skinFactor
+
+ // Convertir a tasa por MINUTO
+ val perMinuteRate = hourlyRate / 60.0
+
+ return perMinuteRate.toInt()
+ }
+}
\ No newline at end of file
diff --git a/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/screens/HomeScreen.kt b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/screens/HomeScreen.kt
new file mode 100644
index 0000000..1feb555
--- /dev/null
+++ b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/screens/HomeScreen.kt
@@ -0,0 +1,299 @@
+package com.example.sundayandroidapp.screens
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.location.Location
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.MenuBook
+import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.core.content.ContextCompat
+import com.example.sundayandroidapp.R
+import com.example.sundayandroidapp.api.OpenMeteoApiService
+import com.example.sundayandroidapp.data.ClothingLevel
+import com.example.sundayandroidapp.data.SkinType
+import com.example.sundayandroidapp.location.LocationService
+import com.example.sundayandroidapp.logic.SunExposureCalculator
+import com.example.sundayandroidapp.ui.theme.BlueGradient
+import com.example.sundayandroidapp.ui.theme.SundayAndroidAppTheme
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+import java.util.*
+
+@Composable
+fun HomeScreen(currentLanguage: String, onLanguageChange: (String) -> Unit) {
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ var selectedSkinType by remember { mutableStateOf(SkinType.TYPE_3) }
+ var selectedClothingLevel by remember { mutableStateOf(ClothingLevel.LIGHT) }
+ var isTracking by remember { mutableStateOf(false) }
+ var sessionTotal by remember { mutableFloatStateOf(0.0f) }
+ var todayTotal by remember { mutableFloatStateOf(0.0f) }
+ var showSkinTypeDialog by remember { mutableStateOf(false) }
+ var showClothingLevelDialog by remember { mutableStateOf(false) }
+ var showMethodology by remember { mutableStateOf(false) }
+ var locationPermissionsGranted by remember { mutableStateOf(false) }
+ var currentUvIndex by remember { mutableFloatStateOf(0.0f) }
+ var maxUvIndex by remember { mutableFloatStateOf(0.0f) }
+ var maxUvIndexTime by remember { mutableStateOf("") }
+ var sunriseTime by remember { mutableStateOf("") }
+ var sunsetTime by remember { mutableStateOf("") }
+ var cloudCoverPercentage by remember { mutableIntStateOf(0) }
+ var altitudeMeters by remember { mutableIntStateOf(0) }
+ var isLoading by remember { mutableStateOf(false) }
+
+ if (showMethodology) {
+ MethodologyScreen(currentLanguage = currentLanguage, onDismiss = { showMethodology = false })
+ return
+ }
+
+ val openMeteoApiService = remember {
+ Retrofit.Builder().baseUrl("https://api.open-meteo.com/").addConverterFactory(GsonConverterFactory.create()).build().create(OpenMeteoApiService::class.java)
+ }
+ val locationService = remember { LocationService(context) }
+
+ val fetchData: suspend () -> Unit = {
+ isLoading = true
+ val hasPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
+ if (hasPermission) {
+ locationPermissionsGranted = true
+ val location = locationService.getLastKnownLocation()
+ location?.let { loc ->
+ try {
+ val response = openMeteoApiService.getSunData(loc.latitude, loc.longitude)
+ currentUvIndex = response.current.uvIndex.toFloat()
+ cloudCoverPercentage = response.current.cloudCover
+ altitudeMeters = response.elevation.toInt()
+ if (response.daily.uvIndexMax.isNotEmpty()) maxUvIndex = response.daily.uvIndexMax[0].toFloat()
+ if (response.daily.sunrise.isNotEmpty()) sunriseTime = formatTime(response.daily.sunrise[0])
+ if (response.daily.sunset.isNotEmpty()) sunsetTime = formatTime(response.daily.sunset[0])
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ } else {
+ locationPermissionsGranted = false
+ }
+ isLoading = false
+ }
+
+ val locationPermissionLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions ->
+ locationPermissionsGranted = permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)
+ if (locationPermissionsGranted) {
+ scope.launch { fetchData() }
+ }
+ }
+
+ LaunchedEffect(Unit) {
+ val hasPermission = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
+ if (hasPermission) {
+ fetchData()
+ } else {
+ locationPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
+ }
+ }
+
+ val burnLimit = SunExposureCalculator.calculateBurnLimitTime(currentUvIndex, selectedSkinType, selectedClothingLevel, cloudCoverPercentage, altitudeMeters)
+ val vitaminDRateCalculated: Float = SunExposureCalculator.calculateVitaminDProductionRate(currentUvIndex, selectedSkinType, selectedClothingLevel, cloudCoverPercentage, altitudeMeters).toFloat()
+
+ LaunchedEffect(isTracking, vitaminDRateCalculated) {
+ if (isTracking) {
+ while (isTracking) {
+ sessionTotal += (vitaminDRateCalculated / 60.0f)
+ todayTotal += (vitaminDRateCalculated / 60.0f)
+ delay(1000L)
+ }
+ }
+ }
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ containerColor = Color.Transparent,
+ floatingActionButton = {
+ FloatingActionButton(onClick = { scope.launch { fetchData() } }) {
+ if (isLoading) {
+ CircularProgressIndicator(modifier = Modifier.size(24.dp))
+ } else {
+ Icon(Icons.Default.Refresh, contentDescription = "Refresh Location")
+ }
+ }
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier.fillMaxSize().background(BlueGradient).padding(paddingValues).padding(horizontal = 16.dp).verticalScroll(rememberScrollState()),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth().padding(top = 32.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("SUN DAY", fontSize = 40.sp, fontWeight = FontWeight.Black, color = Color.White)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(onClick = { showMethodology = true }) {
+ Icon(Icons.Default.MenuBook, contentDescription = "Methodology", tint = Color.White)
+ }
+ Text("EN", color = if (currentLanguage == "en") Color.Yellow else Color.White, fontWeight = FontWeight.Bold, fontSize = 14.sp, modifier = Modifier.clickable { onLanguageChange("en") })
+ Text("ES", color = if (currentLanguage == "es") Color.Yellow else Color.White, fontWeight = FontWeight.Bold, fontSize = 14.sp, modifier = Modifier.clickable { onLanguageChange("es") })
+ }
+ }
+ Spacer(modifier = Modifier.height(32.dp))
+ Card(
+ modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.3f))
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().padding(24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(stringResource(R.string.uv_index_label), fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color.White)
+ Text("%.1f".format(currentUvIndex), fontSize = 80.sp, fontWeight = FontWeight.ExtraBold, color = Color.White)
+ Text(stringResource(R.string.burn_limit_template, burnLimit), fontSize = 20.sp, color = Color.White)
+ }
+ }
+ Card(
+ modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.3f))
+ ) {
+ Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
+ Text(stringResource(R.string.sunrise_template, sunriseTime), color = Color.White)
+ Text(stringResource(R.string.sunset_template, sunsetTime), color = Color.White)
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
+ Text(stringResource(R.string.max_uv_template, maxUvIndex, maxUvIndexTime), color = Color.White)
+ Text(stringResource(R.string.clouds_altitude_template, cloudCoverPercentage, altitudeMeters, 6.0f), color = Color.White)
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ SessionTrackingCard(
+ rate = vitaminDRateCalculated,
+ sessionTotal = sessionTotal,
+ todayTotal = todayTotal,
+ isTracking = isTracking,
+ onToggleTracking = {
+ isTracking = !isTracking
+ if (!isTracking) sessionTotal = 0.0f
+ }
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Card(
+ modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).clickable { showClothingLevelDialog = true },
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.3f))
+ ) {
+ Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
+ Text(stringResource(R.string.select_clothing_level), color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold)
+ Text(selectedClothingLevel.level, color = Color.White, fontSize = 20.sp)
+ }
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ Card(
+ modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).clickable { showSkinTypeDialog = true },
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.3f))
+ ) {
+ Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
+ Text(stringResource(R.string.select_skin_type), color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold)
+ Text(stringResource(R.string.skin_type_template, selectedSkinType.type), color = Color.White, fontSize = 20.sp)
+ Text(selectedSkinType.description, color = Color.White, fontSize = 14.sp)
+ }
+ }
+ }
+ }
+
+ if (showSkinTypeDialog) {
+ val skinTypeOptions = remember(currentLanguage) {
+ SkinType.entries.map { skinType ->
+ context.getString(R.string.skin_type_template, skinType.type) + ": " + when (skinType) {
+ SkinType.TYPE_1 -> context.getString(R.string.skin_type_1_desc)
+ SkinType.TYPE_2 -> context.getString(R.string.skin_type_2_desc)
+ SkinType.TYPE_3 -> context.getString(R.string.skin_type_3_desc)
+ SkinType.TYPE_4 -> context.getString(R.string.skin_type_4_desc)
+ SkinType.TYPE_5 -> context.getString(R.string.skin_type_5_desc)
+ SkinType.TYPE_6 -> context.getString(R.string.skin_type_6_desc)
+ }
+ }
+ }
+ OptionsSelectorDialog(
+ title = stringResource(R.string.select_skin_type),
+ options = SkinType.entries,
+ selectedOption = selectedSkinType,
+ onOptionSelected = { skinType -> selectedSkinType = skinType },
+ onDismissRequest = { showSkinTypeDialog = false },
+ optionText = { skinType -> skinTypeOptions[skinType.ordinal] }
+ )
+ }
+
+ if (showClothingLevelDialog) {
+ val clothingLevelOptions = remember(currentLanguage) {
+ ClothingLevel.entries.map { clothingLevel ->
+ when (clothingLevel) {
+ ClothingLevel.NUDE -> context.getString(R.string.clothing_nude)
+ ClothingLevel.MINIMAL -> context.getString(R.string.clothing_minimal)
+ ClothingLevel.LIGHT -> context.getString(R.string.clothing_light)
+ ClothingLevel.MODERATE -> context.getString(R.string.clothing_moderate)
+ ClothingLevel.HEAVY -> context.getString(R.string.clothing_heavy)
+ }
+ }
+ }
+ OptionsSelectorDialog(
+ title = stringResource(R.string.select_clothing_level),
+ options = ClothingLevel.entries,
+ selectedOption = selectedClothingLevel,
+ onOptionSelected = { newClothingLevel -> selectedClothingLevel = newClothingLevel },
+ onDismissRequest = { showClothingLevelDialog = false },
+ optionText = { clothingLevel -> clothingLevelOptions[clothingLevel.ordinal] }
+ )
+ }
+}
+
+private fun formatTime(timeString: String): String {
+ val parser = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm", Locale.getDefault())
+ val formatter = DateTimeFormatter.ofPattern("hh:mm a", Locale.getDefault())
+ return try {
+ formatter.format(LocalDateTime.parse(timeString, parser))
+ } catch (e: Exception) {
+ e.printStackTrace()
+ "N/A"
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun PreviewHomeScreen() {
+ SundayAndroidAppTheme {
+ HomeScreen(currentLanguage = "en", onLanguageChange = {})
+ }
+}
\ No newline at end of file
diff --git a/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/screens/MethodologyScreen.kt b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/screens/MethodologyScreen.kt
new file mode 100644
index 0000000..10c851f
--- /dev/null
+++ b/ANDROID/app/src/main/kotlin/com/example/sundayandroidapp/screens/MethodologyScreen.kt
@@ -0,0 +1,54 @@
+package com.example.sundayandroidapp.screens
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.example.sundayandroidapp.R
+import java.io.InputStream
+import java.nio.charset.Charset
+
+@Composable
+fun MethodologyScreen(currentLanguage: String, onDismiss: () -> Unit) {
+ val context = LocalContext.current
+ val resourceId = if (currentLanguage == "es") R.raw.methodology_es else R.raw.methodology_en
+
+ val methodologyText = try {
+ val inputStream: InputStream = context.resources.openRawResource(resourceId)
+ inputStream.readBytes().toString(Charset.defaultCharset())
+ } catch (e: Exception) {
+ e.printStackTrace()
+ "Error loading methodology."
+ }
+
+ Surface(modifier = Modifier.fillMaxSize()) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("Methodology", fontSize = 24.sp, fontWeight = FontWeight.Bold)
+ IconButton(onClick = onDismiss) {
+ Icon(Icons.Default.Close, contentDescription = "Close")
+ }
+ }
+ Spacer(modifier = Modifier.height(16.dp))
+ HorizontalDivider()
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(
+ text = methodologyText,
+ modifier = Modifier.verticalScroll(rememberScrollState())
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/ANDROID/app/src/main/res/drawable/ic_launcher_foreground.xml b/ANDROID/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2a8a97c
--- /dev/null
+++ b/ANDROID/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/ANDROID/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ANDROID/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..0bb89b2
--- /dev/null
+++ b/ANDROID/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/ANDROID/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ANDROID/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..0bb89b2
--- /dev/null
+++ b/ANDROID/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/ANDROID/app/src/main/res/raw/methodology_en.md b/ANDROID/app/src/main/res/raw/methodology_en.md
new file mode 100644
index 0000000..d1c8311
--- /dev/null
+++ b/ANDROID/app/src/main/res/raw/methodology_en.md
@@ -0,0 +1,139 @@
+# Sun Day - Vitamin D Calculation Methodology
+-Android versión by @gmolate
+## Overview
+
+Sun Day calculates vitamin D synthesis from UV exposure using a multi-factor model based on scientific research. The app aims to provide personalized, accurate estimates while remaining conservative for safety. See [citations](https://github.com/jackjackbits/sunday/blob/main/METHODOLOGY.md#references).
+
+## Core Formula
+
+```
+Vitamin D Rate (IU/hour) = Base Rate × UV Factor × Clothing Factor × Skin Type Factor × Age Factor × Quality Factor × Adaptation Factor
+```
+
+## Factor Breakdown
+
+### 1. Base Rate (21,000 IU/hr)
+- Represents minimal clothing exposure (~80% body surface area)
+- Conservative estimate within research range of 20,000-40,000 IU/hr
+- Studies show 10,000 IU in 20-30 minutes typical
+- Full body exposure can reach 30,000-40,000 IU/hr in optimal conditions
+
+### 2. UV Factor (Non-linear)
+- Implements Michaelis-Menten-like saturation curve
+- Formula: `uvFactor = (uvIndex × 3.0) / (4.0 + uvIndex)`
+- Accounts for:
+ - Vitamin D synthesis plateaus at high UV levels
+ - Photodegradation of vitamin D above UV ~8
+ - Limited 7-dehydrocholesterol in skin
+
+### 3. Clothing Factor
+- **Nude (100%)**: Full body exposure
+- **Minimal/Swimwear (80%)**: Typical beach attire
+- **Light/Shorts & T-shirt (40%)**: Summer casual wear
+- **Moderate/Long sleeves (15%)**: Business casual
+- **Heavy/Fully covered (5%)**: Winter clothing
+
+### 4. Skin Type Factor (Fitzpatrick Scale)
+- **Type I (125%)**: Very fair, always burns - highest vitamin D production
+- **Type II (110%)**: Fair, usually burns
+- **Type III (100%)**: Light, sometimes burns - reference type
+- **Type IV (70%)**: Medium, rarely burns
+- **Type V (40%)**: Dark, very rarely burns
+- **Type VI (20%)**: Very dark, never burns
+
+Based on melanin's UV filtering effect and research showing 5-10x longer exposure needed for darker skin types.
+
+### 5. Age Factor
+- **≤20 years**: 100% efficiency
+- **20-70 years**: Linear decrease (~1% per year)
+- **≥70 years**: 25% efficiency
+
+Reflects decreased 7-dehydrocholesterol in aging skin.
+
+### 6. UV Quality Factor (Time of Day)
+- Accounts for solar zenith angle effects on UV-B transmission
+- Peak quality around solar noon (10 AM - 3 PM)
+- More gradual decrease at low sun angles (exp(-0.2) vs exp(-0.3))
+- Morning/evening UV has less effective UV-B wavelengths
+
+### 7. Adaptation Factor
+- Based on 7-14 day exposure history from HealthKit
+- Range: 0.8-1.2x
+- Regular exposure upregulates vitamin D synthesis pathways
+- Prevents "shock" calculations for pale individuals suddenly exposed
+
+## Scientific Basis
+
+### UV-B and Vitamin D Synthesis
+- Only UV-B wavelengths (290-315nm) produce vitamin D
+- 7-dehydrocholesterol + UV-B → pre-vitamin D3 → vitamin D3
+- Process self-regulates through photoisomerization equilibrium
+
+### Altitude Effects
+- UV increases ~10% per 1000m elevation
+- Implemented as simple multiplier on base UV index
+
+### Cloud Cover
+- Already factored into UV index from weather API
+- Clear sky UV only used for reference
+
+### Daily Synthesis Limits
+- Body naturally limits to ~20,000 IU/day
+- Excess pre-vitamin D3 converts to inactive photoisomers
+- Prevents toxicity from sun exposure alone
+
+## Data Sources
+
+1. **UV Index**: Open-Meteo API (includes cloud effects)
+2. **Location**: iOS Core Location
+3. **User Characteristics**: Apple Health (when available)
+4. **Historical Data**: HealthKit vitamin D records
+
+## Burn Time Calculation
+
+Burn time is based on the **full MED** (Minimal Erythema Dose):
+
+```
+Burn Time = MED at UV 1 / Current UV
+```
+
+Real-world MED values at UV index 1:
+- Type I: 150 minutes (burns in ~30 min at UV 5)
+- Type II: 250 minutes (burns in ~45-50 min at UV 5)
+- Type III: 425 minutes (burns in ~75-85 min at UV 5)
+- Type IV: 600 minutes (burns in ~100-120 min at UV 5)
+- Type V: 850 minutes (burns in ~150-180 min at UV 5)
+- Type VI: 1100 minutes (rarely burns)
+
+These values reflect actual outdoor conditions with natural cooling and movement.
+The app notifies users at 80% of burn time as a safety warning.
+
+## Vitamin D Winter
+
+Above 35° latitude, UV-B is insufficient for vitamin D synthesis during winter months:
+- **November-February**: Minimal to no synthesis
+- **March & October**: Marginal synthesis (UV often < 3)
+- App displays warning and recommends supplementation
+
+## Safety Considerations
+
+- Base rate calibrated to typical exposure patterns
+- Burn time based on full MED
+- Seasonal warnings for vitamin D winter
+- Cannot reach toxic levels from UV exposure alone
+
+## Future Improvements
+
+1. **Spectral UV Data**: Use UV-B specific measurements when available
+2. **Body Surface Area**: More precise calculation based on height/weight
+3. **Seasonal Adjustments**: Winter UV-B availability at high latitudes
+4. **Individual Calibration**: Learn from user's actual vitamin D blood tests
+
+## Citations
+
+- Holick, M.F. (2007). "Vitamin D deficiency." New England Journal of Medicine
+- Webb, A.R. et al. (2018). "The role of sunlight exposure in determining the vitamin D status"
+- Engelsen, O. (2010). "The relationship between ultraviolet radiation exposure and vitamin D status"
+- MacLaughlin, J. & Holick, M.F. (1985). "Aging decreases the capacity of human skin to produce vitamin D3"
+- Çekmez, Y. et al. (2024). "Time and duration of vitamin D synthesis" PMC10861575
+- Various studies on MED and safe sun exposure from SunSmart Australia
diff --git a/ANDROID/app/src/main/res/raw/methodology_es.md b/ANDROID/app/src/main/res/raw/methodology_es.md
new file mode 100644
index 0000000..aaa1c7b
--- /dev/null
+++ b/ANDROID/app/src/main/res/raw/methodology_es.md
@@ -0,0 +1,194 @@
+MetodologÃa
+
+Android versión by @gmolate
+
+El objetivo es determinar la cantidad de tiempo que una persona necesita pasar bajo el sol para producir una cantidad saludable de vitamina D, sin quemarse.
+
+
+
+1\. Tiempo para Quemarse
+
+
+
+Para determinar el tiempo que tarda la piel en quemarse, utilizamos el concepto de Dosis MÃnima Eritematosa (MED, por sus siglas en inglés). Una MED es la cantidad de exposición a la radiación UV que causa un enrojecimiento perceptible de la piel (eritema). Se considera que 1 MED equivale a aproximadamente 2 SED (Dosis de Eritema Estándar).
+
+
+
+Usamos la siguiente fórmula para calcular el tiempo para alcanzar 1 MED:
+
+
+
+minutos\_para\_quemarse = (MED\_personal \* 60) / (UVI \* 1.5)
+
+Donde:
+
+
+
+MED\_personal se basa en el tipo de piel de Fitzpatrick del individuo.
+
+
+
+UVI es el Ãndice UV actual.
+
+
+
+1.5 es un factor de ajuste.
+
+
+
+2\. Tiempo para Obtener Vitamina D
+
+
+
+El tiempo necesario para sintetizar vitamina D es generalmente menor que el tiempo para quemarse. Para la mayorÃa de las personas de piel clara, se estima que la producción de 1000 a 2000 UI (Unidades Internacionales) de vitamina D requiere aproximadamente 0.5 MED.
+
+
+
+Utilizamos la siguiente fórmula para calcular el tiempo para obtener una dosis saludable de vitamina D:
+
+
+
+minutos\_para\_vitamina\_d = (MED\_personal \* 0.5 \* 60) / (UVI \* 1.5)
+
+Esta fórmula es esencialmente la mitad del tiempo que se tarda en quemarse.
+
+
+
+Fuentes de Datos
+
+
+
+Ãndice UV (UVI): Obtenido de la API de OpenUV, que proporciona datos del Ãndice UV en tiempo real basados en la ubicación del usuario.
+
+
+
+Tipos de Piel de Fitzpatrick: Utilizamos la escala de Fitzpatrick para clasificar los tipos de piel y determinar la MED personal de un individuo.
+
+
+
+Valores de MED Personal por Tipo de Piel
+
+
+
+Tipo de Piel Descripción MED (J/m²)
+
+I Siempre se quema, nunca se broncea 200
+
+II Se quema fácilmente, se broncea mÃnimamente 250
+
+III Se quema moderadamente, se broncea gradualmente 350
+
+IV Se quema mÃnimamente, siempre se broncea bien 450
+
+V Rara vez se quema, se broncea abundantemente 600
+
+VI Nunca se quema, pigmentación oscura 1000
+
+
+
+Exportar a Hojas de cálculo
+
+Nota: Estos valores de MED son aproximaciones y pueden variar entre individuos.
+
+
+
+Explicación de la MetodologÃa
+
+Ahora te explico qué significa todo esto en términos más sencillos.
+
+
+
+El objetivo de la app es muy simple: darte una "ventana de tiempo segura" para tomar el sol. Quieres el beneficio (vitamina D) sin el riesgo (quemadura). Para ello, la app realiza dos cálculos principales:
+
+
+
+Paso 1: Calcular cuánto tardas en quemarte
+
+El Concepto Clave: "Dosis MÃnima Eritematosa" (MED). Imagina que es tu "lÃmite personal de sol". Es la cantidad exacta de energÃa UV que tu piel puede recibir antes de empezar a ponerse roja.
+
+
+
+¿Cómo sabe tu lÃmite? Usa la Escala de Fitzpatrick, que es un estándar cientÃfico para clasificar tipos de piel. Si tienes la piel muy clara (Tipo I), tu lÃmite (MED) es bajo. Si tu piel es muy oscura (Tipo VI), tu lÃmite es mucho más alto. Tú le dices a la app qué tipo de piel tienes.
+
+
+
+El Factor Externo: El Ãndice UV (UVI). Este número te dice qué tan fuerte está el sol en tu ubicación y en ese preciso momento. Un UVI de 10 es mucho más intenso que un UVI de 2. La app obtiene este dato en tiempo real usando tu GPS.
+
+
+
+La Fórmula Simple:
+
+Tiempo para Quemarse = (Tu LÃmite Personal) / (Intensidad del Sol Actual)
+
+
+
+La app toma tu MED (lÃmite personal), lo divide por la intensidad del sol (UVI) y te da el resultado en minutos. Asà sabes cuál es tu tiempo máximo de exposición antes de sufrir una quemadura.
+
+
+
+Paso 2: Calcular cuánto tiempo necesitas para generar Vitamina D
+
+La Buena Noticia: Producir vitamina D es mucho más rápido que quemarse. La ciencia ha demostrado que para la mayorÃa de la gente, solo se necesita la mitad de la dosis de UV que te causarÃa una quemadura para generar una buena cantidad de vitamina D (entre 1000 y 2000 UI).
+
+
+
+La Fórmula Simple:
+
+Tiempo para Vitamina D = (Tiempo para Quemarse) / 2
+
+
+
+La app simplemente toma el tiempo máximo que calculó en el paso anterior y lo divide por la mitad.
+
+
+
+El Resultado Final para Ti
+
+La app te presenta estos dos números para que puedas tomar una decisión informada.
+
+
+
+Ejemplo Práctico:
+
+
+
+Tus datos: Tienes piel Tipo III (MED de 350) y en tu ciudad el Ãndice UV ahora mismo es de 7.
+
+
+
+Cálculo de la app:
+
+
+
+Tiempo para Quemarse: (350 \* 60) / (7 \* 1.5) ≈ 33 minutos. Este es tu lÃmite máximo.
+
+
+
+Tiempo para Vitamina D: 33 minutos / 2 ≈ 16.5 minutos.
+
+
+
+Lo que te muestra la app:
+
+
+
+"Para obtener una dosis saludable de Vitamina D, exponte al sol durante unos 16 minutos. Evita pasar más de 33 minutos para no quemarte."
+
+
+
+\## References
+
+
+
+\- Holick, M.F. (2007). "Vitamin D deficiency." New England Journal of Medicine
+
+\- Webb, A.R. et al. (2018). "The role of sunlight exposure in determining the vitamin D status"
+
+\- Engelsen, O. (2010). "The relationship between ultraviolet radiation exposure and vitamin D status"
+
+\- MacLaughlin, J. \& Holick, M.F. (1985). "Aging decreases the capacity of human skin to produce vitamin D3"
+
+\- Cekmez, Y. et al. (2024). "Time and duration of vitamin D synthesis" PMC10861575
+
+\- Various studies on MED and safe sun exposure from SunSmart Australia
+
diff --git a/ANDROID/app/src/main/res/values-es/strings.xml b/ANDROID/app/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..d70f64c
--- /dev/null
+++ b/ANDROID/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,34 @@
+
+
+ SundayAndroidApp
+ Ãndice UV
+ %d min lÃmite de quemadura
+ Amanecer %s
+ Atardecer %s
+ Max UV %1$.1f @ %2$s
+ %1$d%% nubes %2$dm (+%3$.0f%% UV)
+ Selecciona tu tipo de piel
+ Tipo %d:
+ Muy clara, Siempre se quema, nunca se broncea
+ Clara, Usualmente se quema, se broncea mÃnimamente
+ Clara, A veces se quema, se broncea uniformemente
+ Media, Se quema mÃnimamente, se broncea bien
+ Oscura, Raramente se quema, se broncea profusamente
+ Muy oscura, Nunca se quema, pigmentación profunda
+ Selecciona tu nivel de ropa
+ ¡Desnudo!
+ MÃnimo (traje de baño)
+ Ligero (pantalones cortos y camiseta)
+ Moderado (mangas largas)
+ Pesado (completamente cubierto)
+ ¡Bienvenido a Sunday!
+ tasa
+ sesión
+ hoy
+ Iniciar
+ Parar
+ %d UI/min
+ %d UI
+ Empezar a seguir
+ Parar seguimiento
+
\ No newline at end of file
diff --git a/ANDROID/app/src/main/res/values/colors.xml b/ANDROID/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..60d307d
--- /dev/null
+++ b/ANDROID/app/src/main/res/values/colors.xml
@@ -0,0 +1,11 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+ #FFFFFF
+
diff --git a/ANDROID/app/src/main/res/values/strings.xml b/ANDROID/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d59b8ac
--- /dev/null
+++ b/ANDROID/app/src/main/res/values/strings.xml
@@ -0,0 +1,34 @@
+
+
+ SundayAndroidApp
+ UV Index
+ %d min burn limit
+ Sunrise %s
+ Sunset %s
+ Max UV %1$.1f @ %2$s
+ %1$d%% clouds ↑ %2$dm (+%3$.0f%% UV)
+ Select your skin type
+ Type %d:
+ Very fair, Always burns, never tans
+ Fair, Usually burns, tans minimally
+ Light, Sometimes burns, tans uniformly
+ Medium, Burns minimally, tans well
+ Dark, Rarely burns, tans profusely
+ Very dark, Never burns, deeply pigmented
+ Select your clothing level
+ Nude!
+ Minimal (swimwear)
+ Light (shorts & t-shirt)
+ Moderate (long sleeves)
+ Heavy (fully covered)
+ Welcome to Sunday!
+ rate
+ session
+ today
+ Start
+ Stop
+ Start Tracking
+ Stop Tracking
+ %d IU/min
+ %d IU
+
\ No newline at end of file
diff --git a/ANDROID/app/src/main/res/values/themes.xml b/ANDROID/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..c436b78
--- /dev/null
+++ b/ANDROID/app/src/main/res/values/themes.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ANDROID/app/src/main/res/xml/backup_rules.xml b/ANDROID/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..a7a1186
--- /dev/null
+++ b/ANDROID/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/ANDROID/app/src/main/res/xml/data_extraction_rules.xml b/ANDROID/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..826e389
--- /dev/null
+++ b/ANDROID/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/ANDROID/build.gradle.kts b/ANDROID/build.gradle.kts
new file mode 100644
index 0000000..bb26309
--- /dev/null
+++ b/ANDROID/build.gradle.kts
@@ -0,0 +1,10 @@
+plugins {
+ // Gradle 8.4.1 es la última versión ESTABLE. 8.12 no existe o es una weá ultra experimental.
+ id("com.android.application") version "8.12.0" apply false
+
+ // Kotlin 2.0.0 es la primera versión ESTABLE de Kotlin 2.
+ id("org.jetbrains.kotlin.android") version "2.0.0" apply false
+
+ // El compilador de Compose DEBE ser 2.0.0 para ser compatible con Kotlin 2.0.0
+ id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" apply false
+}
\ No newline at end of file
diff --git a/ANDROID/gradle.properties b/ANDROID/gradle.properties
new file mode 100644
index 0000000..4a2d716
--- /dev/null
+++ b/ANDROID/gradle.properties
@@ -0,0 +1,29 @@
+# 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. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec: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
+
+# 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
+
+# Enable Gradle build cache
+
+org.gradle.configuration-cache=true
diff --git a/ANDROID/gradle/libs.versions.toml b/ANDROID/gradle/libs.versions.toml
new file mode 100644
index 0000000..4852b1b
--- /dev/null
+++ b/ANDROID/gradle/libs.versions.toml
@@ -0,0 +1,28 @@
+[versions]
+agp = "8.5.1"
+kotlin = "2.2.0"
+coreKtx = "1.13.1"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+appcompat = "1.6.1"
+material = "1.10.0"
+constraintlayout = "2.1.4"
+coreSplashscreen = "1.0.1"
+compose-bom = "2024.02.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-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" }
+compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
diff --git a/ANDROID/gradle/wrapper/gradle-wrapper.jar b/ANDROID/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..980502d
Binary files /dev/null and b/ANDROID/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/ANDROID/gradle/wrapper/gradle-wrapper.properties b/ANDROID/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..37f853b
--- /dev/null
+++ b/ANDROID/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+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
diff --git a/ANDROID/gradlew b/ANDROID/gradlew
new file mode 100644
index 0000000..faf9300
--- /dev/null
+++ b/ANDROID/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# 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" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# 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" "$@"
diff --git a/ANDROID/gradlew.bat b/ANDROID/gradlew.bat
new file mode 100644
index 0000000..9d21a21
--- /dev/null
+++ b/ANDROID/gradlew.bat
@@ -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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+: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
diff --git a/ANDROID/local.properties b/ANDROID/local.properties
new file mode 100644
index 0000000..0bd5cc5
--- /dev/null
+++ b/ANDROID/local.properties
@@ -0,0 +1,8 @@
+## This file must *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+#Tue Jul 22 15:31:56 CLT 2025
+sdk.dir=C\:\\Users\\gmola\\AppData\\Local\\Android\\Sdk
diff --git a/ANDROID/settings.gradle.kts b/ANDROID/settings.gradle.kts
new file mode 100644
index 0000000..e47c414
--- /dev/null
+++ b/ANDROID/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ maven { url = uri("https://maven.google.com") }
+ }
+}
+
+rootProject.name = "SundayAndroidApp"
+include(":app")
diff --git a/README.md b/README.md
index 60d298a..63366c7 100644
--- a/README.md
+++ b/README.md
@@ -1,47 +1,91 @@
-# Sun Day
-
-UV tracking and vitamin D calculator for iOS.
-
[📖 Read the detailed methodology](METHODOLOGY.md) | [🔒 Privacy Policy](PRIVACY.md)
-## Features
+# Sunday - Android Version
+
+This is the Android version of [Sunday](https://github.com/jackjackbits/sunday), a powerful application for tracking UV exposure and Vitamin D generation, rebuilt from the ground up in Kotlin for the Android platform.
+
+This version is designed to be completely self-contained within the `android/` directory.
+
+## Core Features
+
+This Android implementation currently provides the core functionalities essential for sun exposure tracking:
+
+| Feature | Android Implementation | Status |
+| ------------------------ | ----------------------------------- | ----------- |
+| **Real-time UV Tracking** | Open-Meteo API Integration | Implemented |
+| **Vitamin D Calculation** | `SunExposureCalculator.kt` Logic | Implemented |
+| **Burn Limit Calculation** | Based on Fitzpatrick Scale & UV | Implemented |
+| **Skin Type Selection** | Fitzpatrick Scale Types I-VI | Implemented |
+| **Clothing Level Adjustment**| Nude, Minimal, Light, etc. | Implemented |
+| **Manual Refresh** | Floating Action Button (FAB) | Implemented |
+| **Bilingual Support** | English & Spanish (Dynamic) | Implemented |
+| **In-App Methodology** | Displays methodology text | Implemented |
-- Real-time UV index from your location
-- Vitamin D calculation based on UV, skin type, and clothing
-- Moon phase display at night
-- Sunrise/sunset times
-- Saves to Apple Health
-- No API keys required
-- Small and medium widgets for your home screen
+**Note:** Advanced features from the original iOS version such as Google Fit integration, database persistence (Room), widgets, and notifications are **not yet implemented** in this version.
+
+## Technical Architecture
+
+The application is built using a modern Android tech stack, focusing on simplicity and performance.
+
+- **UI:** Fully built with **Jetpack Compose** for a modern, declarative user interface.
+- **State Management:** State is managed directly within `@Composable` functions using `remember` and `mutableStateOf`.
+- **Networking:** **Retrofit** and **Gson** are used for efficient and reliable communication with the Open-Meteo API.
+- **Location:** Leverages **Google Play Services**' FusedLocationProviderClient for accurate location data.
+- **Core Logic:** All sun exposure calculations are handled in a dedicated `SunExposureCalculator.kt` object, ensuring logic is centralized and testable.
## Requirements
-- iOS 17.0+
-- iPhone only
-- Xcode 15+
+- **Android 6.0 (API 23)** or higher.
+- **Google Play Services** installed and up-to-date.
+- **Location Services** enabled for accurate UV data.
+- **Internet Connection** for fetching weather data.
+
+## Project Structure
+
+The project structure is organized by feature/responsibility:
+
+```
+
+app/src/main/kotlin/com/example/sundayandroidapp/
+├── MainActivity.kt # App entry point
+├── api/ # Retrofit API interface and data models
+│ ├── OpenMeteoApiService.kt
+│ └── OpenMeteoResponse.kt
+├── data/ # Enums and data classes (SkinType, ClothingLevel)
+│ ├── SkinType.kt
+│ └── ClothingLevel.kt
+├── location/ # Location services
+│ └── LocationService.kt
+├── logic/ # Core calculation logic
+│ └── SunExposureCalculator.kt
+└── screens/ # UI Composables
+ ├── HomeScreen.kt
+ ├── MethodologyScreen.kt
+ ├── SessionTrackingCard.kt
+ └── OptionsSelectorDialog.kt
-## Setup
+```
-1. Clone the repo
-2. Run `xcodegen generate` to create the Xcode project
-3. Open `Sunday.xcodeproj`
-4. Select your development team
-5. Build and run
+## How to Build
-## Usage
+```
+bash
+# Clone the main repository
+git clone [repository-url]
+cd [repository-name]/android
-1. Allow location and health permissions
-2. Press the sun button to start tracking
-3. Select your clothing level and skin type
-4. The app calculates vitamin D intake automatically
+# Open the 'android' directory in Android Studio
+# Allow Gradle to sync and download dependencies
-## APIs Used
+# Build the debug version
+./gradlew assembleDebug
-- Open-Meteo for UV data (free, no key)
-- Farmsense for moon phases (free, no key)
+```
## License
-Public domain. Use however you want.
+This project is licensed under the [MIT License](LICENSE).
+by @gmolate
+```
\ No newline at end of file