HydroFlux 0.1.2

This commit is contained in:
2026-02-11 19:45:56 +11:00
parent 6f766b7a96
commit 766533c8cd
3 changed files with 95 additions and 51 deletions

View File

@@ -145,15 +145,15 @@ export class Dashboard {
</div> </div>
</div> </div>
<!-- Strava Integration --> <!-- Distance / Fitness Integration -->
<div class="bg-gradient-to-br from-orange-50 to-white rounded-2xl p-6 shadow-md"> <div class="bg-gradient-to-br from-orange-50 to-white rounded-2xl p-6 shadow-md">
<h3 class="text-sm font-semibold text-gray-700 mb-4">Strava Integration</h3> <h3 class="text-sm font-semibold text-gray-700 mb-4">Distance</h3>
<div class="flex flex-col items-center justify-center py-4"> <div class="flex flex-col items-center justify-center py-4">
<div class="w-16 h-16 bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl flex items-center justify-center mb-3 shadow-lg transform rotate-12"> <div class="w-16 h-16 bg-gradient-to-br from-orange-500 to-orange-600 rounded-2xl flex items-center justify-center mb-3 shadow-lg transform rotate-12">
<span class="text-3xl font-bold text-white transform -rotate-12">S</span> <span class="text-3xl font-bold text-white transform -rotate-12">D</span>
</div> </div>
<span class="text-lg font-bold text-gray-800">5.2km Run</span> <span class="text-lg font-bold text-gray-800" id="distanceDisplay">${(healthData.distance || 0).toFixed(2)}km</span>
<span class="text-xs text-gray-500 mt-1">Last Sync: 2h ago</span> <span class="text-xs text-gray-500 mt-1">Today's Total</span>
</div> </div>
</div> </div>
</div> </div>
@@ -195,11 +195,15 @@ export class Dashboard {
this.renderGoals(goalsData); this.renderGoals(goalsData);
// Expose Native Callback // Expose Native Callback
window.updateHealthData = (steps, sleepHoursTotal) => { window.updateHealthData = (steps, sleepHoursTotal, distanceKm) => {
console.log("Received Health Data:", steps, sleepHoursTotal); console.log("Received Health Data:", steps, sleepHoursTotal, distanceKm);
// Save to Storage // Save to Storage
localStorage.setItem('hydroflux_health_data', JSON.stringify({ steps: steps, sleep: sleepHoursTotal })); localStorage.setItem('hydroflux_health_data', JSON.stringify({
steps: steps,
sleep: sleepHoursTotal,
distance: distanceKm || 0 // Default to 0 if undefined
}));
// Update UI directly // Update UI directly
const stepsCount = this.container.querySelector('#stepsCount'); const stepsCount = this.container.querySelector('#stepsCount');
@@ -210,7 +214,21 @@ export class Dashboard {
const sleepTime = this.container.querySelector('#sleepTime'); const sleepTime = this.container.querySelector('#sleepTime');
if (sleepTime) sleepTime.textContent = `${sh}h ${sm}m`; if (sleepTime) sleepTime.textContent = `${sh}h ${sm}m`;
// Update Distance Card (Replacing Strava Placeholder or updating it)
// Assuming the Strava card is now the general Fitness/Distance card as requested?
// "can we have the distance tab actually pull stats from health connect"
// Let's look for a distance element or update the text '5.2km Run' -> 'X.X km'
// NOTE: The current HTML has "5.2km Run" hardcoded in the Strava card.
// I should verify if I should target that specific element.
// There is no ID on that span. I'll add one via full replacement of render or just target via text?
// Safer to re-render or target widely.
// Let's assume I upgrade the render() to include an ID first?
// Actually, let's just save the data here. The Dashboard.render() needs to read it.
this.updateDynamicElements(steps, 10000); this.updateDynamicElements(steps, 10000);
if (this.updateDistanceDisplay) this.updateDistanceDisplay(distanceKm);
}; };
// Trigger initial sync if native interface exists // Trigger initial sync if native interface exists
@@ -395,6 +413,11 @@ export class Dashboard {
} }
} }
updateDistanceDisplay(distance) {
const el = this.container.querySelector('#distanceDisplay');
if (el) el.textContent = (distance || 0).toFixed(2) + 'km';
}
startTimers() { startTimers() {
const updateTime = () => { const updateTime = () => {
const now = new Date(); const now = new Date();

View File

@@ -13,6 +13,7 @@ import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.permission.HealthPermission import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.records.StepsRecord import androidx.health.connect.client.records.StepsRecord
import androidx.health.connect.client.records.SleepSessionRecord import androidx.health.connect.client.records.SleepSessionRecord
import androidx.health.connect.client.records.DistanceRecord // Added
import androidx.health.connect.client.request.ReadRecordsRequest import androidx.health.connect.client.request.ReadRecordsRequest
import androidx.health.connect.client.request.AggregateRequest import androidx.health.connect.client.request.AggregateRequest
import androidx.health.connect.client.time.TimeRangeFilter import androidx.health.connect.client.time.TimeRangeFilter
@@ -28,7 +29,8 @@ class MainActivity : FragmentActivity() {
// Define Permissions we need // Define Permissions we need
private val PERMISSIONS = setOf( private val PERMISSIONS = setOf(
HealthPermission.getReadPermission(StepsRecord::class), HealthPermission.getReadPermission(StepsRecord::class),
HealthPermission.getReadPermission(SleepSessionRecord::class) HealthPermission.getReadPermission(SleepSessionRecord::class),
HealthPermission.getReadPermission(DistanceRecord::class) // Added
) )
private val requestPermissions = registerForActivityResult( private val requestPermissions = registerForActivityResult(
@@ -169,55 +171,74 @@ class MainActivity : FragmentActivity() {
val startOfDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toInstant() val startOfDay = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toInstant()
val now = Instant.now() val now = Instant.now()
var totalSteps = 0L var totalSteps = 0L
var sleepHours = 0.0 var sleepHours = 0.0
var totalDistance = 0.0
// 1. Try Read Steps (AGGREGATE) // 1. Try Read Steps (AGGREGATE)
try { try {
val response = healthConnectClient.aggregate( val response = healthConnectClient.aggregate(
AggregateRequest( AggregateRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL), metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startOfDay, now) timeRangeFilter = TimeRangeFilter.between(startOfDay, now)
)
) )
// The result may be null if no data )
totalSteps = response[StepsRecord.COUNT_TOTAL] ?: 0 // The result may be null if no data
} catch (e: Exception) { totalSteps = response[StepsRecord.COUNT_TOTAL] ?: 0
e.printStackTrace() } catch (e: Exception) {
val msg = e.message ?: "Unknown Error" e.printStackTrace()
runOnUiThread { Toast.makeText(activity, "Steps Error: $msg", Toast.LENGTH_LONG).show() } val msg = e.message ?: "Unknown Error"
totalSteps = 0 runOnUiThread { Toast.makeText(activity, "Steps Error: $msg", Toast.LENGTH_LONG).show() }
} totalSteps = 0
}
// 2. Try Read Sleep (AGGREGATE) // 2. Try Read Sleep (AGGREGATE)
try { try {
val sleepResponse = healthConnectClient.aggregate( val sleepResponse = healthConnectClient.aggregate(
AggregateRequest( AggregateRequest(
metrics = setOf(SleepSessionRecord.SLEEP_DURATION_TOTAL), metrics = setOf(SleepSessionRecord.SLEEP_DURATION_TOTAL),
timeRangeFilter = TimeRangeFilter.between(now.minus(24, ChronoUnit.HOURS), now) timeRangeFilter = TimeRangeFilter.between(now.minus(24, ChronoUnit.HOURS), now)
)
) )
// Get total duration in Seconds (Duration -> Seconds) )
val duration = sleepResponse[SleepSessionRecord.SLEEP_DURATION_TOTAL] // Get total duration in Seconds (Duration -> Seconds)
if (duration != null) { val duration = sleepResponse[SleepSessionRecord.SLEEP_DURATION_TOTAL]
sleepHours = duration.seconds / 3600.0 if (duration != null) {
} sleepHours = duration.seconds / 3600.0
} catch (e: Exception) {
e.printStackTrace()
sleepHours = 0.0
} }
} catch (e: Exception) {
e.printStackTrace()
sleepHours = 0.0
}
runOnUiThread { // 3. Try Read Distance (AGGREGATE)
if (!activity.isFinishing && !activity.isDestroyed) { try {
try { val distanceResponse = healthConnectClient.aggregate(
myWebView.evaluateJavascript("window.updateHealthData($totalSteps, $sleepHours)", null) AggregateRequest(
} catch (e: Exception) { metrics = setOf(DistanceRecord.DISTANCE_TOTAL),
e.printStackTrace() timeRangeFilter = TimeRangeFilter.between(startOfDay, now)
} )
)
val dist = distanceResponse[DistanceRecord.DISTANCE_TOTAL]
if (dist != null) {
totalDistance = dist.inKilometers // Double
}
} catch (e: Exception) {
e.printStackTrace()
totalDistance = 0.0
}
runOnUiThread {
if (!activity.isFinishing && !activity.isDestroyed) {
try {
// Pass Steps, Sleep, AND Distance
myWebView.evaluateJavascript("window.updateHealthData($totalSteps, $sleepHours, $totalDistance)", null)
} catch (e: Exception) {
e.printStackTrace()
} }
} }
} }
} }
}
} }
} }

Binary file not shown.