Compare commits

..

3 Commits

Author SHA1 Message Date
Lina
a3fcde60e3 Fix for old devices (android less>9) 2026-02-11 20:59:22 +01:00
Lina
2db678cd17 Fixed: English translation :) 2026-02-11 18:01:42 +01:00
Lina
5575ba7f10 Fixed: English translation 2026-02-11 16:44:51 +01:00
6 changed files with 362 additions and 318 deletions

View File

@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-02-07T21:35:39.547765521Z"> <DropdownSelection timestamp="2026-02-11T19:42:19.363392546Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=M7GEJFYHBA554L7T" /> <DeviceId pluginId="LocalEmulator" identifier="path=/home/lina/.android/avd/Medium_Phone.avd" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport">
<title>WhatWeight 1.3.9</title> <title>WhatWeight 1.3.9</title>
<script src="chart.min.js"></script> <script src="chart.min.js"></script>
@ -131,49 +131,58 @@ body.loaded {
<header style="display: flex; align-items: center; margin: 10px 0 20px 5px;"> <header style="display: flex; align-items: center; margin: 10px 0 20px 5px;">
<div class="app-icon"> <div class="app-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24"> <svg height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(5.04 5.04) scale(0.58)"> <g transform="translate(5.04 5.04) scale(0.58)">
<path <path
fill="var(--primary)" d="M24,4c0,0.55 -0.45,1 -1,1h-1v1c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V5h-1c-0.55,0 -1,-0.45 -1,-1c0,-0.55 0.45,-1 1,-1h1V2c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v1h1C23.55,3 24,3.45 24,4zM21.52,8.95C21.83,9.91 22,10.94 22,12c0,5.52 -4.48,10 -10,10S2,17.52 2,12C2,6.48 6.48,2 12,2c1.5,0 2.92,0.34 4.2,0.94C16.08,3.27 16,3.62 16,4c0,1.35 0.9,2.5 2.13,2.87C18.5,8.1 19.65,9 21,9C21.18,9 21.35,8.98 21.52,8.95zM7,9.5C7,10.33 7.67,11 8.5,11S10,10.33 10,9.5S9.33,8 8.5,8S7,8.67 7,9.5zM16.31,14H7.69c-0.38,0 -0.63,0.42 -0.44,0.75C8.2,16.39 9.97,17.5 12,17.5s3.8,-1.11 4.75,-2.75C16.94,14.42 16.7,14 16.31,14zM17,9.5C17,8.67 16.33,8 15.5,8S14,8.67 14,9.5s0.67,1.5 1.5,1.5S17,10.33 17,9.5z"
d="M24,4c0,0.55 -0.45,1 -1,1h-1v1c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V5h-1c-0.55,0 -1,-0.45 -1,-1c0,-0.55 0.45,-1 1,-1h1V2c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v1h1C23.55,3 24,3.45 24,4zM21.52,8.95C21.83,9.91 22,10.94 22,12c0,5.52 -4.48,10 -10,10S2,17.52 2,12C2,6.48 6.48,2 12,2c1.5,0 2.92,0.34 4.2,0.94C16.08,3.27 16,3.62 16,4c0,1.35 0.9,2.5 2.13,2.87C18.5,8.1 19.65,9 21,9C21.18,9 21.35,8.98 21.52,8.95zM7,9.5C7,10.33 7.67,11 8.5,11S10,10.33 10,9.5S9.33,8 8.5,8S7,8.67 7,9.5zM16.31,14H7.69c-0.38,0 -0.63,0.42 -0.44,0.75C8.2,16.39 9.97,17.5 12,17.5s3.8,-1.11 4.75,-2.75C16.94,14.42 16.7,14 16.31,14zM17,9.5C17,8.67 16.33,8 15.5,8S14,8.67 14,9.5s0.67,1.5 1.5,1.5S17,10.33 17,9.5z"/> fill="var(--primary)"/>
</g> </g>
</svg> </svg>
</div> </div>
<b style="font-size: 20px; flex-grow: 1;" ><span style="color:var(--primary)">[ </span><span data-i18n="title-app">What-Weight</span><span style="color:var(--primary)"> ]</span></b> <b style="font-size: 20px; flex-grow: 1;"><span style="color:var(--primary)">[ </span><span
data-i18n="title-app">What-Weight</span><span style="color:var(--primary)"> ]</span></b>
<div id="trendDisplay" style="font-size: 10px; font-weight: 900; color: var(--sub);">...</div> <div id="trendDisplay" style="font-size: 10px; font-weight: 900; color: var(--sub);">...</div>
</header> </header>
<div class="card"> <div class="card">
<h2 data-i18n="card-input-title">Goal & Input</h2> <h2 data-i18n="card-input-title">Goal & Input</h2>
<div class="goals"> <div class="goals">
<div onclick="setGoal('keep')" id="g-keep" class="goal-btn" data-i18n="goal-keep">MAINTAIN</div> <div class="goal-btn" data-i18n="goal-keep" id="g-keep" onclick="setGoal('keep')">MAINTAIN
<div onclick="setGoal('lose')" id="g-lose" class="goal-btn" data-i18n="goal-lose">LOSE</div> </div>
<div onclick="setGoal('gain')" id="g-gain" class="goal-btn" data-i18n="goal-gain">GAIN</div> <div class="goal-btn" data-i18n="goal-lose" id="g-lose" onclick="setGoal('lose')">LOSE</div>
<div class="goal-btn" data-i18n="goal-gain" id="g-gain" onclick="setGoal('gain')">GAIN</div>
</div> </div>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div style="display:grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="number" id="targetWeight" data-i18n="placeholder-target" placeholder="Target kg" step="0.1"> <input data-i18n="placeholder-target" id="targetWeight" placeholder="Target kg"
<input type="number" id="wVal" data-i18n="placeholder-weight" placeholder="Weight kg" step="0.1" inputmode="decimal" oninput="validate(this, 30, 250)"> step="0.1" type="number">
<input data-i18n="placeholder-weight" id="wVal" inputmode="decimal" oninput="validate(this, 30, 250)"
placeholder="Weight kg" step="0.1" type="number">
</div> </div>
<input type="date" id="wDate"> <input id="wDate" type="date">
<details> <details>
<summary >📏 <span data-i18n="measurements-title">BODY MEASUREMENTS</span></summary> <summary>📏 <span data-i18n="measurements-title">BODY MEASUREMENTS</span></summary>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 10px;"> <div style="display:grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<input type="number" id="mWaist" data-i18n="placeholder-waist" placeholder="Waist" step="0.1" oninput="validate(this, 30, 180)"> <input data-i18n="placeholder-waist" id="mWaist" oninput="validate(this, 30, 180)" placeholder="Waist"
<input type="number" id="mChest" data-i18n="placeholder-chest" placeholder="Chest" step="0.1" oninput="validate(this, 30, 180)"> step="0.1" type="number">
<input type="number" id="mHips" data-i18n="placeholder-hips" placeholder="Hips" step="0.1" oninput="validate(this, 30, 180)"> <input data-i18n="placeholder-chest" id="mChest" oninput="validate(this, 30, 180)" placeholder="Chest"
<input type="number" id="mBicep" data-i18n="placeholder-bicep" placeholder="Bicep" step="0.1" oninput="validate(this, 10, 80)"> step="0.1" type="number">
<input data-i18n="placeholder-hips" id="mHips" oninput="validate(this, 30, 180)" placeholder="Hips"
step="0.1" type="number">
<input data-i18n="placeholder-bicep" id="mBicep" oninput="validate(this, 10, 80)" placeholder="Bicep"
step="0.1" type="number">
</div> </div>
</details> </details>
<button onclick="handleSaveClick()" class="btn-main" data-i18n="btn-save" id="saveBtn">SAVE</button> <button class="btn-main" data-i18n="btn-save" id="saveBtn" onclick="handleSaveClick()">SAVE
</button>
</div> </div>
<div class="card"> <div class="card">
<h2 data-i18n="chart-weight-title">Weight Dynamics</h2> <h2 data-i18n="chart-weight-title">Weight Dynamics</h2>
<div class="chart-container-fixed"> <div class="chart-container-fixed">
<canvas id="weightAxis" class="y-axis-fixed"></canvas> <canvas class="y-axis-fixed" id="weightAxis"></canvas>
<div id="wScroll" class="scroll-container"> <div class="scroll-container" id="wScroll">
<div id="wWrapper"> <div id="wWrapper">
<canvas id="weightChart"></canvas> <canvas id="weightChart"></canvas>
</div> </div>
@ -183,14 +192,18 @@ body.loaded {
<div class="card"> <div class="card">
<h2><span data-i18n="chart-measures-title">Measurements</span> <h2><span data-i18n="chart-measures-title">Measurements</span>
<select id="measureType" style="height:26px; font-size:10px; width:auto; border-radius:8px; margin:0; border:none; background:transparent; color:var(--primary);" onchange="renderUI()"> <select id="measureType"
<option value="waist" data-i18n="placeholder-waist">Waist</option> onchange="renderUI()"
<option value="chest" data-i18n="placeholder-chest">Chest</option> style="height:26px; font-size:10px; width:auto; border-radius:8px; margin:0; border:none; background:transparent; color:var(--primary);">
<option value="hips" data-i18n="placeholder-hips">Hips</option> <option data-i18n="placeholder-waist" value="waist">Waist</option>
<option value="bicep" data-i18n="placeholder-bicep">Bicep</option> <option data-i18n="placeholder-chest" value="chest">Chest</option>
<option data-i18n="placeholder-hips" value="hips">Hips</option>
<option data-i18n="placeholder-bicep" value="bicep">Bicep</option>
</select> </select>
</h2> </h2>
<div class="chart-wrapper"><canvas id="measureChart"></canvas></div> <div class="chart-wrapper">
<canvas id="measureChart"></canvas>
</div>
</div> </div>
<div class="card" id="histBox" style="display:none"> <div class="card" id="histBox" style="display:none">
@ -200,41 +213,57 @@ body.loaded {
<div class="card" style="margin-top: 20px;"> <div class="card" style="margin-top: 20px;">
<h3 style="margin-top:0; margin-bottom: 15px; font-size: 16px; text-align: center;">Данные</h3> <h3 data-i18n="data"
style="margin-top:0; margin-bottom: 15px; font-size: 16px; text-align: center;">Data</h3>
<div style="display: flex; gap: 10px;"> <div style="display: flex; gap: 10px;">
<button onclick="exportToCSV()" class="data-btn">📤 Экспорт</button> <button class="data-btn" data-i18n="export" onclick="exportToCSV()">📤 Export</button>
<button onclick="showImportModal()" class="data-btn">📥 Импорт</button> <button class="data-btn" data-i18n="import" onclick="showImportModal()">📥 Import</button>
</div> </div>
</div> </div>
<div id="importModal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:10000; padding:20px;"> <div id="importModal"
style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:10000; padding:20px;">
<div class="card" style="height:100%; display:flex; flex-direction:column;"> <div class="card" style="height:100%; display:flex; flex-direction:column;">
<h3 style="margin-top:0">Вставьте содержимое CSV</h3> <h3 data-i18n="insert-csv-data" style="margin-top:0">Insert CSV data</h3>
<textarea id="csvPasteArea" style="flex:1; width:100%; background:var(--bg); color:var(--text); border:1px solid var(--sub); border-radius:12px; padding:10px; font-family:monospace; font-size:12px;"></textarea> <textarea id="csvPasteArea"
style="flex:1; width:100%; background:var(--bg); color:var(--text); border:1px solid var(--sub); border-radius:12px; padding:10px; font-family:monospace; font-size:12px;"></textarea>
<div style="display:flex; gap:10px; margin-top:15px;"> <div style="display:flex; gap:10px; margin-top:15px;">
<button onclick="processPastedCSV()" class="data-btn" style="background:var(--primary); color:#000;">Загрузить</button> <button class="data-btn" data-i18n="btn-load-data"
<button onclick="document.getElementById('importModal').style.display='none'" class="data-btn">Отмена</button> onclick="processPastedCSV()" style="background:var(--primary); color:#000;">Load
</button>
<button class="data-btn"
data-i18n="btn-cancel" onclick="document.getElementById('importModal').style.display='none'">Cancel
</button>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
let db = JSON.parse(localStorage.getItem('weight_tracker_data') || '{"entries":[], "config":{"goal":"keep", "target":0}}'); var db;
let wChart, mChart, wAxisChart; // Добавили wAxisChart // Безопасная загрузка базы данных
let confirmMode = false; try {
let translations = {}; // Будет заполнено через initLanguage var savedData = localStorage.getItem('weight_tracker_data');
db = savedData ? JSON.parse(savedData) : { "entries": [], "config": { "goal": "keep", "target": 0 } };
} catch (e) {
console.error("Ошибка доступа к LocalStorage:", e);
// Резервный пустой объект, чтобы приложение не "упало"
db = { "entries": [], "config": { "goal": "keep", "target": 0 } };
}
var wChart, mChart, wAxisChart; // Добавили wAxisChart
var confirmMode = false;
var translations = {}; // Будет заполнено через initLanguage
async function initLanguage() { async function initLanguage() {
let lang = 'ru'; var lang = 'ru';
if (window.Android && window.Android.getLanguage) { if (window.Android && window.Android.getLanguage) {
const fullLang = window.Android.getLanguage(); var fullLang = window.Android.getLanguage();
lang = fullLang.startsWith('ru') ? 'ru' : 'en'; lang = fullLang.startsWith('ru') ? 'ru' : 'en';
} }
try { try {
if (window.Android && window.Android.getTranslations) { if (window.Android && window.Android.getTranslations) {
const jsonString = window.Android.getTranslations(lang); var jsonString = window.Android.getTranslations(lang);
translations = JSON.parse(jsonString); translations = JSON.parse(jsonString);
document.querySelectorAll('[data-i18n]').forEach(el => { document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n'); var key = el.getAttribute('data-i18n');
if (translations[key]) { if (translations[key]) {
if (el.tagName === 'INPUT') el.placeholder = translations[key]; if (el.tagName === 'INPUT') el.placeholder = translations[key];
else el.textContent = translations[key]; else el.textContent = translations[key];
@ -252,15 +281,16 @@ body.loaded {
document.querySelectorAll('.goal-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.goal-btn').forEach(b => b.classList.remove('active'));
if(document.getElementById('g-' + db.config.goal)) document.getElementById('g-' + db.config.goal).classList.add('active'); if(document.getElementById('g-' + db.config.goal)) document.getElementById('g-' + db.config.goal).classList.add('active');
document.getElementById('targetWeight').value = db.config.target || ''; document.getElementById('targetWeight').value = db.config.target || '';
var unitKg = translations['unit_kg'] || 'kg';
const log = document.getElementById('log'); var log = document.getElementById('log');
if (db.entries.length > 0) { if (db.entries.length > 0) {
document.getElementById('histBox').style.display = 'block'; document.getElementById('histBox').style.display = 'block';
log.innerHTML = db.entries.slice().reverse().map(e => ` log.innerHTML = db.entries.slice().reverse().map(e => `
<div class="hist-item"> <div class="hist-item">
<div> <div>
<div style="font-size:11px; font-weight:bold; color:var(--sub)">${e.date.split('-').reverse().join('.')}</div> <div style="font-size:11px; font-weight:bold; color:var(--sub)">${e.date.split('-').reverse().join('.')}</div>
<div class="hist-val">${e.weight || '?'} кг</div>
<div class="hist-val">${e.weight || '?'} ${unitKg}</div>
<div style="margin-top:4px"> <div style="margin-top:4px">
${['waist','chest','hips','bicep'].map(f => e[f] ? `<span class="detail-tag">${translations[f] || f}: ${e[f]}</span>` : '').join('')} ${['waist','chest','hips','bicep'].map(f => e[f] ? `<span class="detail-tag">${translations[f] || f}: ${e[f]}</span>` : '').join('')}
</div> </div>
@ -273,41 +303,41 @@ body.loaded {
document.body.classList.add('loaded'); document.body.classList.add('loaded');
} }
function drawCharts() { function drawCharts() {
const weights = db.entries var weights = db.entries
.filter(e => e.weight) .filter(e => e.weight)
.sort((a, b) => new Date(a.date) - new Date(b.date)); .sort((a, b) => new Date(a.date) - new Date(b.date));
if (weights.length === 0) return; if (weights.length === 0) return;
const scrollContainer = document.getElementById('wScroll'); var scrollContainer = document.getElementById('wScroll');
const wWrapper = document.getElementById('wWrapper'); var wWrapper = document.getElementById('wWrapper');
const axisCanvas = document.getElementById('weightAxis'); var axisCanvas = document.getElementById('weightAxis');
const chartCanvas = document.getElementById('weightChart'); var chartCanvas = document.getElementById('weightChart');
const style = getComputedStyle(document.body); var style = getComputedStyle(document.body);
const primaryColor = style.getPropertyValue('--primary').trim(); var primaryColor = style.getPropertyValue('--primary').trim();
const textColor = style.getPropertyValue('--text').trim(); var textColor = style.getPropertyValue('--text').trim();
const subColor = style.getPropertyValue('--sub').trim(); var subColor = style.getPropertyValue('--sub').trim();
// Цвета из твоей рабочей логики // Цвета из твоей рабочей логики
const trendDown = '#4ade80'; // Зеленый (хорошо) var trendDown = '#4ade80'; // Зеленый (хорошо)
const trendUp = '#f87171'; // Красный (плохо) var trendUp = '#f87171'; // Красный (плохо)
const mintColor = primaryColor; var mintColor = primaryColor;
// --- ТВОЯ РАБОЧАЯ ЛОГИКА ЦВЕТОВ --- // --- ТВОЯ РАБОЧАЯ ЛОГИКА ЦВЕТОВ ---
const pointColors = weights.map((e, idx) => { var pointColors = weights.map((e, idx) => {
const target = db.config.target || 0; var target = db.config.target || 0;
// 1. Режим УДЕРЖАНИЕ (keep) // 1. Режим УДЕРЖАНИЕ (keep)
if (db.config.goal === 'keep' && target > 0) { if (db.config.goal === 'keep' && target > 0) {
const diff = Math.abs(e.weight - target); var diff = Math.abs(e.weight - target);
return diff > 0.4 ? trendUp : trendDown; return diff > 0.4 ? trendUp : trendDown;
} }
// Для режимов Снижение и Набор сравниваем с предыдущим днем // Для режимов Снижение и Набор сравниваем с предыдущим днем
if (idx === 0) return trendDown; if (idx === 0) return trendDown;
const dailyDiff = e.weight - weights[idx - 1].weight; var dailyDiff = e.weight - weights[idx - 1].weight;
// 2. Режим СНИЖЕНИЕ (lose) // 2. Режим СНИЖЕНИЕ (lose)
if (db.config.goal === 'lose') { if (db.config.goal === 'lose') {
@ -323,13 +353,13 @@ function drawCharts() {
}); });
// Расчет границ шкалы (строго целые числа) // Расчет границ шкалы (строго целые числа)
const targetVal = parseFloat(db.config.target) || 0; var targetVal = parseFloat(db.config.target) || 0;
const allValues = weights.map(e => e.weight); var allValues = weights.map(e => e.weight);
if (targetVal > 0) allValues.push(targetVal); if (targetVal > 0) allValues.push(targetVal);
const minW = Math.floor(Math.min(...allValues) - 1); var minW = Math.floor(Math.min(...allValues) - 1);
const maxW = Math.ceil(Math.max(...allValues) + 1); var maxW = Math.ceil(Math.max(...allValues) + 1);
const commonYAxis = { var commonYAxis = {
min: minW, max: maxW, min: minW, max: maxW,
grid: { display: false, drawBorder: false }, grid: { display: false, drawBorder: false },
ticks: { color: subColor, font: { size: 11, weight: 'bold' }, stepSize: 1, precision: 0, padding: 5 } ticks: { color: subColor, font: { size: 11, weight: 'bold' }, stepSize: 1, precision: 0, padding: 5 }
@ -356,7 +386,7 @@ function drawCharts() {
}); });
// Основной график // Основной график
const chartWidth = Math.max(weights.length * 60, scrollContainer.clientWidth); var chartWidth = Math.max(weights.length * 60, scrollContainer.clientWidth);
wWrapper.style.width = chartWidth + 'px'; wWrapper.style.width = chartWidth + 'px';
chartCanvas.height = 240; chartCanvas.height = 240;
@ -388,18 +418,18 @@ function drawCharts() {
}, },
plugins: [{ plugins: [{
afterDatasetsDraw: (chart) => { afterDatasetsDraw: (chart) => {
const { ctx, chartArea: { right }, scales: { y } } = chart; var { ctx, chartArea: { right }, scales: { y } } = chart;
ctx.save(); ctx.save();
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.font = 'bold 11px sans-serif'; ctx.font = 'bold 11px sans-serif';
ctx.fillStyle = textColor; ctx.fillStyle = textColor;
chart.getDatasetMeta(0).data.forEach((point, index) => { chart.getDatasetMeta(0).data.forEach((point, index) => {
const val = chart.data.datasets[0].data[index]; var val = chart.data.datasets[0].data[index];
if (val) ctx.fillText(val, point.x, point.y - 12); if (val) ctx.fillText(val, point.x, point.y - 12);
}); });
if (targetVal > 0) { if (targetVal > 0) {
const yPos = y.getPixelForValue(targetVal); var yPos = y.getPixelForValue(targetVal);
ctx.beginPath(); ctx.beginPath();
ctx.setLineDash([5, 5]); ctx.setLineDash([5, 5]);
ctx.strokeStyle = primaryColor + '66'; ctx.strokeStyle = primaryColor + '66';
@ -417,11 +447,11 @@ function drawCharts() {
// Отрисовка замеров (если функция есть) // Отрисовка замеров (если функция есть)
if (typeof renderMeasuresChart === 'function') renderMeasuresChart(); if (typeof renderMeasuresChart === 'function') renderMeasuresChart();
} }
function renderMeasuresChart() { function renderMeasuresChart() {
const mType = document.getElementById('measureType').value; var mType = document.getElementById('measureType').value;
const measures = db.entries.filter(e => e[mType]); var measures = db.entries.filter(e => e[mType]);
const mCtx = document.getElementById('measureChart').getContext('2d'); var mCtx = document.getElementById('measureChart').getContext('2d');
if (mChart) mChart.destroy(); if (mChart) mChart.destroy();
if (measures.length > 0) { if (measures.length > 0) {
mChart = new Chart(mCtx, { mChart = new Chart(mCtx, {
@ -437,12 +467,12 @@ function drawCharts() {
// Вспомогательные функции // Вспомогательные функции
function updateTrendDisplay() { function updateTrendDisplay() {
const entries = db.entries.filter(e => e.weight); var entries = db.entries.filter(e => e.weight);
const el = document.getElementById('trendDisplay'); var el = document.getElementById('trendDisplay');
if (entries.length < 2) { el.innerText = translations['trend-analysis'] || "..."; return; } if (entries.length < 2) { el.innerText = translations['trend-analysis'] || "..."; return; }
const last = entries[entries.length - 1]; var last = entries[entries.length - 1];
const avg = entries.slice(-8, -1).reduce((s, e) => s + e.weight, 0) / Math.max(1, entries.slice(-8, -1).length); var avg = entries.slice(-8, -1).reduce((s, e) => s + e.weight, 0) / Math.max(1, entries.slice(-8, -1).length);
const diff = last.weight - avg; var diff = last.weight - avg;
if (Math.abs(diff) < 0.1) { el.innerText = translations['trend-stable']; el.style.color = "var(--sub)"; } if (Math.abs(diff) < 0.1) { el.innerText = translations['trend-stable']; el.style.color = "var(--sub)"; }
else if (diff > 0) { el.innerText = translations['trend-up']; el.style.color = "#f87171"; } else if (diff > 0) { el.innerText = translations['trend-up']; el.style.color = "#f87171"; }
else { el.innerText = translations['trend-down']; el.style.color = "#4ade80"; } else { el.innerText = translations['trend-down']; el.style.color = "#4ade80"; }
@ -453,13 +483,13 @@ function drawCharts() {
function setGoal(g) { db.config.goal = g; saveDB(); renderUI(); } function setGoal(g) { db.config.goal = g; saveDB(); renderUI(); }
function handleSaveClick() { function handleSaveClick() {
const date = document.getElementById('wDate').value; var date = document.getElementById('wDate').value;
if (!date) return; if (!date) return;
db.config.target = parseFloat(document.getElementById('targetWeight').value) || 0; db.config.target = parseFloat(document.getElementById('targetWeight').value) || 0;
let entry = db.entries.find(e => e.date === date) || { date }; var entry = db.entries.find(e => e.date === date) || { date };
if (document.getElementById('wVal').value) entry.weight = parseFloat(document.getElementById('wVal').value); if (document.getElementById('wVal').value) entry.weight = parseFloat(document.getElementById('wVal').value);
['waist', 'chest', 'hips', 'bicep'].forEach(f => { ['waist', 'chest', 'hips', 'bicep'].forEach(f => {
let v = document.getElementById('m' + f.charAt(0).toUpperCase() + f.slice(1)).value; var v = document.getElementById('m' + f.charAt(0).toUpperCase() + f.slice(1)).value;
if (v) entry[f] = parseFloat(v); if (v) entry[f] = parseFloat(v);
}); });
if (!db.entries.find(e => e.date === date)) db.entries.push(entry); if (!db.entries.find(e => e.date === date)) db.entries.push(entry);
@ -467,10 +497,10 @@ function drawCharts() {
saveDB(); renderUI(); saveDB(); renderUI();
} }
function exportToCSV() { function exportToCSV() {
if (!db.entries || db.entries.length === 0) return; if (!db.entries || db.entries.length === 0) return;
let csvContent = "date,weight,waist,chest,hips,bicep\n"; var csvContent = "date,weight,waist,chest,hips,bicep\n";
db.entries.forEach(e => { db.entries.forEach(e => {
csvContent += `${e.date},${e.weight || ''},${e.waist || ''},${e.chest || ''},${e.hips || ''},${e.bicep || ''}\n`; csvContent += `${e.date},${e.weight || ''},${e.waist || ''},${e.chest || ''},${e.hips || ''},${e.bicep || ''}\n`;
}); });
@ -480,36 +510,36 @@ function exportToCSV() {
window.Android.exportCSV(csvContent, `weight_data_${new Date().toISOString().split('T')[0]}.csv`); window.Android.exportCSV(csvContent, `weight_data_${new Date().toISOString().split('T')[0]}.csv`);
} else { } else {
// Обычный браузерный способ (для тестов) // Обычный браузерный способ (для тестов)
const blob = new Blob([csvContent], { type: 'text/csv' }); var blob = new Blob([csvContent], { type: 'text/csv' });
const url = URL.createObjectURL(blob); var url = URL.createObjectURL(blob);
const a = document.createElement('a'); var a = document.createElement('a');
a.href = url; a.href = url;
a.download = 'data.csv'; a.download = 'data.csv';
a.click(); a.click();
} }
} }
// ИМПОРТ // ИМПОРТ
function showImportModal() { function showImportModal() {
document.getElementById('importModal').style.display = 'block'; document.getElementById('importModal').style.display = 'block';
document.getElementById('csvPasteArea').value = ''; document.getElementById('csvPasteArea').value = '';
} }
function processPastedCSV() { function processPastedCSV() {
const text = document.getElementById('csvPasteArea').value; var text = document.getElementById('csvPasteArea').value;
if (!text.trim()) return; if (!text.trim()) return;
try { try {
const lines = text.split(/\r?\n/); var lines = text.split(/\r?\n/);
const dataLines = lines.slice(1); // Пропускаем заголовок (date,weight...) var dataLines = lines.slice(1); // Пропускаем заголовок (date,weight...)
let count = 0; var count = 0;
dataLines.forEach(line => { dataLines.forEach(line => {
if (!line.trim()) return; if (!line.trim()) return;
const [date, weight, waist, chest, hips, bicep] = line.split(','); var [date, weight, waist, chest, hips, bicep] = line.split(',');
if (!date || date.length < 8) return; // Проверка на корректность даты if (!date || date.length < 8) return; // Проверка на корректность даты
let entry = db.entries.find(item => item.date === date); var entry = db.entries.find(item => item.date === date);
if (!entry) { if (!entry) {
entry = { date }; entry = { date };
db.entries.push(entry); db.entries.push(entry);
@ -533,7 +563,7 @@ function processPastedCSV() {
alert("Ошибка формата: проверьте, что вы скопировали CSV целиком"); alert("Ошибка формата: проверьте, что вы скопировали CSV целиком");
console.error(err); console.error(err);
} }
} }
window.onload = initLanguage; window.onload = initLanguage;
</script> </script>

View File

@ -25,5 +25,12 @@
"waist": "Waist", "waist": "Waist",
"chest": "Chest", "chest": "Chest",
"hips": "Hips", "hips": "Hips",
"bicep": "Bicep"а "bicep": "Bicep",
"data": "Data",
"export": "Export",
"import": "Import",
"btn-load-data": "Load",
"btn-cancel": "Cancel",
"insert-csv-data": "Insert CSV data",
"unit_kg":"kg"
} }

View File

@ -25,5 +25,12 @@
"waist": "Талия", "waist": "Талия",
"chest": "Грудь", "chest": "Грудь",
"hips": "Бедра", "hips": "Бедра",
"bicep": "Бицепс" "bicep": "Бицепс",
"data": "Данные",
"export": "Экспорт",
"import": "Импорт",
"btn-load-data": "Загрузить",
"btn-cancel": "Отмена",
"insert-csv-data": "Вставьте содержимое CSV",
"unit_kg":"кг"
} }

View File

@ -46,9 +46,9 @@ public class MainActivity extends AppCompatActivity {
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
// Поддержка темной темы на уровне WebView // Поддержка темной темы на уровне WebView
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { // if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
settings.setForceDark(WebSettings.FORCE_DARK_AUTO); // settings.setForceDark(WebSettings.FORCE_DARK_AUTO);
} // }
webView.setWebViewClient(new WebViewClient()); webView.setWebViewClient(new WebViewClient());

View File

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">Body Fit</string> <string name="app_name">Body Tune</string>
</resources> </resources>