Compare commits

..

No commits in common. "master" and "BodyTune_ver_1.fix1" have entirely different histories.

3 changed files with 283 additions and 292 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-11T19:42:19.363392546Z"> <DropdownSelection timestamp="2026-02-11T16:50:15.295594688Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/lina/.android/avd/Medium_Phone.avd" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=M7GEJFYHBA554L7T" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -238,32 +238,23 @@ body.loaded {
</div> </div>
</div> </div>
<script> <script>
var db; let db = JSON.parse(localStorage.getItem('weight_tracker_data') || '{"entries":[], "config":{"goal":"keep", "target":0}}');
// Безопасная загрузка базы данных let wChart, mChart, wAxisChart; // Добавили wAxisChart
try { let confirmMode = false;
var savedData = localStorage.getItem('weight_tracker_data'); let translations = {}; // Будет заполнено через initLanguage
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() {
var lang = 'ru'; let lang = 'ru';
if (window.Android && window.Android.getLanguage) { if (window.Android && window.Android.getLanguage) {
var fullLang = window.Android.getLanguage(); const 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) {
var jsonString = window.Android.getTranslations(lang); const 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 => {
var key = el.getAttribute('data-i18n'); const 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];
@ -281,8 +272,8 @@ 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 unitKg = translations['unit_kg'] || 'kg';
var log = document.getElementById('log'); const 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 => `
@ -303,41 +294,41 @@ body.loaded {
document.body.classList.add('loaded'); document.body.classList.add('loaded');
} }
function drawCharts() { function drawCharts() {
var weights = db.entries const 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;
var scrollContainer = document.getElementById('wScroll'); const scrollContainer = document.getElementById('wScroll');
var wWrapper = document.getElementById('wWrapper'); const wWrapper = document.getElementById('wWrapper');
var axisCanvas = document.getElementById('weightAxis'); const axisCanvas = document.getElementById('weightAxis');
var chartCanvas = document.getElementById('weightChart'); const chartCanvas = document.getElementById('weightChart');
var style = getComputedStyle(document.body); const style = getComputedStyle(document.body);
var primaryColor = style.getPropertyValue('--primary').trim(); const primaryColor = style.getPropertyValue('--primary').trim();
var textColor = style.getPropertyValue('--text').trim(); const textColor = style.getPropertyValue('--text').trim();
var subColor = style.getPropertyValue('--sub').trim(); const subColor = style.getPropertyValue('--sub').trim();
// Цвета из твоей рабочей логики // Цвета из твоей рабочей логики
var trendDown = '#4ade80'; // Зеленый (хорошо) const trendDown = '#4ade80'; // Зеленый (хорошо)
var trendUp = '#f87171'; // Красный (плохо) const trendUp = '#f87171'; // Красный (плохо)
var mintColor = primaryColor; const mintColor = primaryColor;
// --- ТВОЯ РАБОЧАЯ ЛОГИКА ЦВЕТОВ --- // --- ТВОЯ РАБОЧАЯ ЛОГИКА ЦВЕТОВ ---
var pointColors = weights.map((e, idx) => { const pointColors = weights.map((e, idx) => {
var target = db.config.target || 0; const target = db.config.target || 0;
// 1. Режим УДЕРЖАНИЕ (keep) // 1. Режим УДЕРЖАНИЕ (keep)
if (db.config.goal === 'keep' && target > 0) { if (db.config.goal === 'keep' && target > 0) {
var diff = Math.abs(e.weight - target); const 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;
var dailyDiff = e.weight - weights[idx - 1].weight; const dailyDiff = e.weight - weights[idx - 1].weight;
// 2. Режим СНИЖЕНИЕ (lose) // 2. Режим СНИЖЕНИЕ (lose)
if (db.config.goal === 'lose') { if (db.config.goal === 'lose') {
@ -353,13 +344,13 @@ body.loaded {
}); });
// Расчет границ шкалы (строго целые числа) // Расчет границ шкалы (строго целые числа)
var targetVal = parseFloat(db.config.target) || 0; const targetVal = parseFloat(db.config.target) || 0;
var allValues = weights.map(e => e.weight); const allValues = weights.map(e => e.weight);
if (targetVal > 0) allValues.push(targetVal); if (targetVal > 0) allValues.push(targetVal);
var minW = Math.floor(Math.min(...allValues) - 1); const minW = Math.floor(Math.min(...allValues) - 1);
var maxW = Math.ceil(Math.max(...allValues) + 1); const maxW = Math.ceil(Math.max(...allValues) + 1);
var commonYAxis = { const 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 }
@ -386,7 +377,7 @@ body.loaded {
}); });
// Основной график // Основной график
var chartWidth = Math.max(weights.length * 60, scrollContainer.clientWidth); const chartWidth = Math.max(weights.length * 60, scrollContainer.clientWidth);
wWrapper.style.width = chartWidth + 'px'; wWrapper.style.width = chartWidth + 'px';
chartCanvas.height = 240; chartCanvas.height = 240;
@ -418,18 +409,18 @@ body.loaded {
}, },
plugins: [{ plugins: [{
afterDatasetsDraw: (chart) => { afterDatasetsDraw: (chart) => {
var { ctx, chartArea: { right }, scales: { y } } = chart; const { 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) => {
var val = chart.data.datasets[0].data[index]; const 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) {
var yPos = y.getPixelForValue(targetVal); const yPos = y.getPixelForValue(targetVal);
ctx.beginPath(); ctx.beginPath();
ctx.setLineDash([5, 5]); ctx.setLineDash([5, 5]);
ctx.strokeStyle = primaryColor + '66'; ctx.strokeStyle = primaryColor + '66';
@ -447,11 +438,11 @@ body.loaded {
// Отрисовка замеров (если функция есть) // Отрисовка замеров (если функция есть)
if (typeof renderMeasuresChart === 'function') renderMeasuresChart(); if (typeof renderMeasuresChart === 'function') renderMeasuresChart();
} }
function renderMeasuresChart() { function renderMeasuresChart() {
var mType = document.getElementById('measureType').value; const mType = document.getElementById('measureType').value;
var measures = db.entries.filter(e => e[mType]); const measures = db.entries.filter(e => e[mType]);
var mCtx = document.getElementById('measureChart').getContext('2d'); const 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, {
@ -467,12 +458,12 @@ body.loaded {
// Вспомогательные функции // Вспомогательные функции
function updateTrendDisplay() { function updateTrendDisplay() {
var entries = db.entries.filter(e => e.weight); const entries = db.entries.filter(e => e.weight);
var el = document.getElementById('trendDisplay'); const el = document.getElementById('trendDisplay');
if (entries.length < 2) { el.innerText = translations['trend-analysis'] || "..."; return; } if (entries.length < 2) { el.innerText = translations['trend-analysis'] || "..."; return; }
var last = entries[entries.length - 1]; const last = entries[entries.length - 1];
var avg = entries.slice(-8, -1).reduce((s, e) => s + e.weight, 0) / Math.max(1, entries.slice(-8, -1).length); const avg = entries.slice(-8, -1).reduce((s, e) => s + e.weight, 0) / Math.max(1, entries.slice(-8, -1).length);
var diff = last.weight - avg; const 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"; }
@ -483,13 +474,13 @@ body.loaded {
function setGoal(g) { db.config.goal = g; saveDB(); renderUI(); } function setGoal(g) { db.config.goal = g; saveDB(); renderUI(); }
function handleSaveClick() { function handleSaveClick() {
var date = document.getElementById('wDate').value; const 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;
var entry = db.entries.find(e => e.date === date) || { date }; let 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 => {
var v = document.getElementById('m' + f.charAt(0).toUpperCase() + f.slice(1)).value; let 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);
@ -497,10 +488,10 @@ body.loaded {
saveDB(); renderUI(); saveDB(); renderUI();
} }
function exportToCSV() { function exportToCSV() {
if (!db.entries || db.entries.length === 0) return; if (!db.entries || db.entries.length === 0) return;
var csvContent = "date,weight,waist,chest,hips,bicep\n"; let 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`;
}); });
@ -510,36 +501,36 @@ body.loaded {
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 {
// Обычный браузерный способ (для тестов) // Обычный браузерный способ (для тестов)
var blob = new Blob([csvContent], { type: 'text/csv' }); const blob = new Blob([csvContent], { type: 'text/csv' });
var url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
var a = document.createElement('a'); const 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() {
var text = document.getElementById('csvPasteArea').value; const text = document.getElementById('csvPasteArea').value;
if (!text.trim()) return; if (!text.trim()) return;
try { try {
var lines = text.split(/\r?\n/); const lines = text.split(/\r?\n/);
var dataLines = lines.slice(1); // Пропускаем заголовок (date,weight...) const dataLines = lines.slice(1); // Пропускаем заголовок (date,weight...)
var count = 0; let count = 0;
dataLines.forEach(line => { dataLines.forEach(line => {
if (!line.trim()) return; if (!line.trim()) return;
var [date, weight, waist, chest, hips, bicep] = line.split(','); const [date, weight, waist, chest, hips, bicep] = line.split(',');
if (!date || date.length < 8) return; // Проверка на корректность даты if (!date || date.length < 8) return; // Проверка на корректность даты
var entry = db.entries.find(item => item.date === date); let entry = db.entries.find(item => item.date === date);
if (!entry) { if (!entry) {
entry = { date }; entry = { date };
db.entries.push(entry); db.entries.push(entry);
@ -563,7 +554,7 @@ body.loaded {
alert("Ошибка формата: проверьте, что вы скопировали CSV целиком"); alert("Ошибка формата: проверьте, что вы скопировали CSV целиком");
console.error(err); console.error(err);
} }
} }
window.onload = initLanguage; window.onload = initLanguage;
</script> </script>

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());