Hydration: water intake from the food log, drop the "+вода" button
Intake is what you already log (liquids count via water_pct), so there is nothing to tap mid-ride and no guessing an amount. At session start JS seeds the native deficit from the food log — need-so-far for the time of day minus logged intake — via set_hydration_config(start_deficit_ml); the service then accrues sweat on top. Removes the +вода button, hydration_drink and water_drink.txt.
This commit is contained in:
parent
6183f0c529
commit
9a8ef04230
10 changed files with 56 additions and 86 deletions
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
|
|
@ -139,7 +139,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|||
|
||||
[[package]]
|
||||
name = "baldav"
|
||||
version = "0.1.519"
|
||||
version = "0.1.520"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"btleplug",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "baldav"
|
||||
version = "0.1.519"
|
||||
version = "0.1.520"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@ import org.json.JSONObject
|
|||
object Hydration {
|
||||
private const val TAG = "Hydration"
|
||||
private const val CH = "baldav_hydration"
|
||||
private var accruedMl = 0.0 // need accrued since session start, minus what was drunk
|
||||
private var accruedMl = 0.0 // running water deficit: start-of-session shortfall + sweat
|
||||
private var startDeficitMl = 0.0 // how far behind you were at session start (from the food log)
|
||||
private var pendingStart = false // apply startDeficitMl on the first tick after reset()
|
||||
private var lastTickMs = 0L
|
||||
private var lastBeepMs = 0L
|
||||
private var lastCfgMs = 0L
|
||||
|
|
@ -41,37 +43,27 @@ object Hydration {
|
|||
sweatMlPerMin = j.optInt("sweatMlPerHour", 600).coerceIn(60, 2000) / 60.0
|
||||
alertMl = j.optInt("alertMl", 600).coerceIn(100, 3000).toDouble()
|
||||
reAlertMs = j.optInt("reAlertMin", 15).coerceIn(2, 120) * 60_000L
|
||||
startDeficitMl = j.optInt("startDeficitMl", 0).coerceIn(0, 5000).toDouble()
|
||||
} else enabled = false
|
||||
} catch (e: Exception) { Log.e(TAG, "cfg: ${e.message}"); enabled = false }
|
||||
}
|
||||
|
||||
// Call at session start: clear the deficit so a fresh ride/workout starts from zero.
|
||||
fun reset() { accruedMl = 0.0; lastTickMs = 0L; lastBeepMs = 0L }
|
||||
// Call at session start: next tick re-reads the config and seeds the deficit from the food log.
|
||||
fun reset() { lastTickMs = 0L; lastBeepMs = 0L; pendingStart = true }
|
||||
|
||||
// "+вода" writes the drunk ml here (Rust hydration_drink); subtract and consume.
|
||||
private fun drainDrinks(ctx: Context) {
|
||||
try {
|
||||
val f = File(ctx.filesDir.parentFile ?: ctx.filesDir, "water_drink.txt")
|
||||
if (f.exists()) {
|
||||
val ml = f.readText().trim().toDoubleOrNull() ?: 0.0
|
||||
f.delete()
|
||||
accruedMl -= ml
|
||||
if (accruedMl < 0) accruedMl = 0.0
|
||||
}
|
||||
} catch (e: Exception) { Log.e(TAG, "drink: ${e.message}") }
|
||||
}
|
||||
|
||||
// Call often (every 10-30 s) from any running service. Accrues need, honours drinks, beeps only
|
||||
// when behind. At most one beep per re-alert window across all callers (process-wide singleton).
|
||||
// Call often (every 10-30 s) from any running service. Deficit = start-of-session shortfall (from
|
||||
// logged intake vs need-so-far) + accrued sweat. Beeps only when behind, at most once per re-alert
|
||||
// window across all callers (process-wide singleton). Water intake comes from what you log, not a
|
||||
// button — nothing to tap mid-ride; you top up and log it as usual, next session re-seeds.
|
||||
fun tick(ctx: Context, handler: Handler) {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastCfgMs > 30_000L) { readCfg(ctx); lastCfgMs = now }
|
||||
if (!enabled) return
|
||||
if (pendingStart) { accruedMl = startDeficitMl; pendingStart = false }
|
||||
if (lastTickMs == 0L) { lastTickMs = now; return }
|
||||
val dtMin = (now - lastTickMs) / 60_000.0
|
||||
lastTickMs = now
|
||||
accruedMl += sweatMlPerMin * dtMin
|
||||
drainDrinks(ctx)
|
||||
if (accruedMl >= alertMl && now - lastBeepMs >= reAlertMs) {
|
||||
lastBeepMs = now
|
||||
beep(handler); vibrate(ctx); notify(ctx, accruedMl.toInt())
|
||||
|
|
|
|||
|
|
@ -498,32 +498,18 @@ fn gps_service_command(app: tauri::AppHandle, cmd: String) -> Result<String, Str
|
|||
|
||||
// Write hydration.json to the app root, where the Hydration singleton reads it: the level-based
|
||||
// drink reminder (accrue sweat during a session, beep when the deficit exceeds alert_ml).
|
||||
// start_deficit_ml: how far behind on water you were at session start (from logged intake vs
|
||||
// need-so-far), computed in JS and seeded into the native deficit. From the settings screen it is 0.
|
||||
#[tauri::command]
|
||||
fn set_hydration_config(app: tauri::AppHandle, enabled: bool, alert_ml: u32, sweat_ml_per_hour: u32, re_alert_min: u32) -> Result<String, String> {
|
||||
fn set_hydration_config(app: tauri::AppHandle, enabled: bool, alert_ml: u32, sweat_ml_per_hour: u32, re_alert_min: u32, start_deficit_ml: u32) -> Result<String, String> {
|
||||
let json = format!(
|
||||
"{{\"enabled\":{},\"alertMl\":{},\"sweatMlPerHour\":{},\"reAlertMin\":{}}}",
|
||||
enabled, alert_ml.clamp(100, 3000), sweat_ml_per_hour.clamp(60, 2000), re_alert_min.clamp(2, 120)
|
||||
"{{\"enabled\":{},\"alertMl\":{},\"sweatMlPerHour\":{},\"reAlertMin\":{},\"startDeficitMl\":{}}}",
|
||||
enabled, alert_ml.clamp(100, 3000), sweat_ml_per_hour.clamp(60, 2000), re_alert_min.clamp(2, 120), start_deficit_ml.clamp(0, 5000)
|
||||
);
|
||||
write_app_root_file(&app, "hydration.json", &json);
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
// "+вода": add drunk ml to water_drink.txt (read-add-write), which the Hydration singleton consumes
|
||||
// on its next tick to reduce the deficit. Lands in both candidate app-root paths so Kotlin finds it.
|
||||
#[tauri::command]
|
||||
fn hydration_drink(app: tauri::AppHandle, ml: u32) -> Result<String, String> {
|
||||
// Read the current pending amount from the explicit app root (= Kotlin filesDir.parentFile),
|
||||
// the same file the Hydration singleton deletes, so the running total stays consistent.
|
||||
let explicit_root = app.path().app_data_dir().ok()
|
||||
.and_then(|d| d.parent().and_then(|p| p.parent()).map(|p| p.to_path_buf()));
|
||||
let cur: f64 = explicit_root.as_ref()
|
||||
.and_then(|r| std::fs::read_to_string(r.join("water_drink.txt")).ok())
|
||||
.and_then(|s| s.trim().parse().ok()).unwrap_or(0.0);
|
||||
let total = cur + ml as f64;
|
||||
write_app_root_file(&app, "water_drink.txt", &format!("{}", total));
|
||||
Ok(format!("{}", total))
|
||||
}
|
||||
|
||||
// Write a small file to the app root — both where native_root resolves and the explicit
|
||||
// app_data_dir/../.. (= Kotlin filesDir.parentFile) — so the Kotlin side always finds it.
|
||||
fn write_app_root_file(app: &tauri::AppHandle, name: &str, contents: &str) {
|
||||
|
|
@ -1222,7 +1208,7 @@ pub fn run() {
|
|||
.add_migrations("sqlite:baldav.db", migrations)
|
||||
.build(),
|
||||
)
|
||||
.invoke_handler(tauri::generate_handler![get_safe_insets, import_seed_products, import_tiles, download_file, get_points_filename, delete_points_file, gps_set_session_id, list_points_files, read_points_file, pick_file_open, get_picked_file, debug_native_paths, rebuild_roads, get_road_points, get_ride_graph, get_app_info, gps_service_command, set_hydration_config, hydration_drink, gps_service_read_points, brouter_route, brouter_download_tile, brouter_download_status, brouter_list_tiles, save_file, export_image, read_file_base64, export_db, import_db, restart_app, get_update_info, http_request, chzn_http, ble::ble_scan, ble::ble_scan_results, ble::ble_connect, ble::ble_disconnect, ble::ble_get_hr, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_read_hr_log, ble::ble_read_strap_uptime, ble::ble_reset_strap_uptime, ble::ble_request_enable, read_state_file])
|
||||
.invoke_handler(tauri::generate_handler![get_safe_insets, import_seed_products, import_tiles, download_file, get_points_filename, delete_points_file, gps_set_session_id, list_points_files, read_points_file, pick_file_open, get_picked_file, debug_native_paths, rebuild_roads, get_road_points, get_ride_graph, get_app_info, gps_service_command, set_hydration_config, gps_service_read_points, brouter_route, brouter_download_tile, brouter_download_status, brouter_list_tiles, save_file, export_image, read_file_base64, export_db, import_db, restart_app, get_update_info, http_request, chzn_http, ble::ble_scan, ble::ble_scan_results, ble::ble_connect, ble::ble_disconnect, ble::ble_get_hr, ble::ble_is_connected, ble::ble_check_bluetooth, ble::ble_save_device, ble::ble_clear_hr, ble::ble_read_hr_log, ble::ble_read_strap_uptime, ble::ble_reset_strap_uptime, ble::ble_request_enable, read_state_file])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/nicegui/tauri-v2/refs/heads/main/tooling/cli/schema.json",
|
||||
"productName": "BalDav",
|
||||
"version": "0.1.519",
|
||||
"version": "0.1.520",
|
||||
"identifier": "dev.baldav.app",
|
||||
"build": {
|
||||
"frontendDist": "../frontend"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue