Fix for old devices (android less>9)
This commit is contained in:
parent
2db678cd17
commit
24c89045b0
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@ -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-11T16:50:15.295594688Z">
|
<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>
|
||||||
|
|||||||
@ -238,325 +238,334 @@ body.loaded {
|
|||||||
</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];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
renderUI();
|
renderUI();
|
||||||
}
|
}
|
||||||
} catch (e) { console.error("Language Error:", e); }
|
} catch (e) { console.error("Language Error:", e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveDB() { localStorage.setItem('weight_tracker_data', JSON.stringify(db)); }
|
function saveDB() { localStorage.setItem('weight_tracker_data', JSON.stringify(db)); }
|
||||||
|
|
||||||
function renderUI() {
|
function renderUI() {
|
||||||
if (!document.getElementById('wDate').value) document.getElementById('wDate').value = new Date().toISOString().split('T')[0];
|
if (!document.getElementById('wDate').value) document.getElementById('wDate').value = new Date().toISOString().split('T')[0];
|
||||||
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 || '';
|
||||||
const unitKg = translations['unit_kg'] || 'kg';
|
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 || '?'} ${unitKg}</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>
|
||||||
</div>
|
</div>
|
||||||
<button class="del-btn" onclick="manualDelete('${e.date}')">×</button>
|
<button class="del-btn" onclick="manualDelete('${e.date}')">×</button>
|
||||||
</div>`).join('');
|
</div>`).join('');
|
||||||
} else { document.getElementById('histBox').style.display = 'none'; }
|
} else { document.getElementById('histBox').style.display = 'none'; }
|
||||||
updateTrendDisplay();
|
updateTrendDisplay();
|
||||||
drawCharts();
|
drawCharts();
|
||||||
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') {
|
||||||
return dailyDiff <= 0 ? trendDown : trendUp;
|
return dailyDiff <= 0 ? trendDown : trendUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Режим НАБОР (gain)
|
// 3. Режим НАБОР (gain)
|
||||||
if (db.config.goal === 'gain') {
|
if (db.config.goal === 'gain') {
|
||||||
return dailyDiff >= 0 ? trendDown : trendUp;
|
return dailyDiff >= 0 ? trendDown : trendUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mintColor;
|
return mintColor;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Расчет границ шкалы (строго целые числа)
|
// Расчет границ шкалы (строго целые числа)
|
||||||
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 }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (wAxisChart) wAxisChart.destroy();
|
if (wAxisChart) wAxisChart.destroy();
|
||||||
if (wChart) wChart.destroy();
|
if (wChart) wChart.destroy();
|
||||||
|
|
||||||
// Левая шкала (вертикальная)
|
// Левая шкала (вертикальная)
|
||||||
axisCanvas.width = 40;
|
axisCanvas.width = 40;
|
||||||
axisCanvas.height = 240;
|
axisCanvas.height = 240;
|
||||||
wAxisChart = new Chart(axisCanvas.getContext('2d'), {
|
wAxisChart = new Chart(axisCanvas.getContext('2d'), {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: { labels: weights.map(e => e.date), datasets: [{ data: weights.map(e => e.weight), showLine: false, pointRadius: 0 }] },
|
data: { labels: weights.map(e => e.date), datasets: [{ data: weights.map(e => e.weight), showLine: false, pointRadius: 0 }] },
|
||||||
options: {
|
options: {
|
||||||
responsive: false, maintainAspectRatio: false,
|
responsive: false, maintainAspectRatio: false,
|
||||||
layout: { padding: { top: 40, bottom: 0 } },
|
layout: { padding: { top: 40, bottom: 0 } },
|
||||||
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
plugins: { legend: { display: false }, tooltip: { enabled: false } },
|
||||||
scales: {
|
scales: {
|
||||||
y: { ...commonYAxis, display: true, position: 'left', afterFit: (axis) => { axis.width = 40; } },
|
y: { ...commonYAxis, display: true, position: 'left', afterFit: (axis) => { axis.width = 40; } },
|
||||||
x: { display: true, ticks: { display: false }, grid: { display: false }, afterFit: (axis) => { axis.height = 30; } }
|
x: { display: true, ticks: { display: false }, grid: { display: false }, afterFit: (axis) => { axis.height = 30; } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Основной график
|
// Основной график
|
||||||
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;
|
||||||
|
|
||||||
wChart = new Chart(chartCanvas.getContext('2d'), {
|
wChart = new Chart(chartCanvas.getContext('2d'), {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: weights.map(e => e.date.split('-').reverse().join('.').substring(0, 5)),
|
labels: weights.map(e => e.date.split('-').reverse().join('.').substring(0, 5)),
|
||||||
datasets: [{
|
datasets: [{
|
||||||
data: weights.map(e => e.weight),
|
data: weights.map(e => e.weight),
|
||||||
borderColor: primaryColor,
|
borderColor: primaryColor,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
fill: true,
|
fill: true,
|
||||||
backgroundColor: primaryColor + '45',
|
backgroundColor: primaryColor + '45',
|
||||||
pointRadius: 6,
|
pointRadius: 6,
|
||||||
pointBackgroundColor: pointColors, // Применяем рассчитанные цвета
|
pointBackgroundColor: pointColors, // Применяем рассчитанные цвета
|
||||||
pointBorderColor: primaryColor,
|
pointBorderColor: primaryColor,
|
||||||
pointBorderWidth: 2
|
pointBorderWidth: 2
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true, maintainAspectRatio: false,
|
responsive: true, maintainAspectRatio: false,
|
||||||
layout: { padding: { top: 40, bottom: 0, right: 20 } },
|
layout: { padding: { top: 40, bottom: 0, right: 20 } },
|
||||||
plugins: { legend: { display: false } },
|
plugins: { legend: { display: false } },
|
||||||
scales: {
|
scales: {
|
||||||
y: { ...commonYAxis, ticks: { display: false }, afterFit: (axis) => { axis.width = 0; } },
|
y: { ...commonYAxis, ticks: { display: false }, afterFit: (axis) => { axis.width = 0; } },
|
||||||
x: { grid: { display: false }, ticks: { color: subColor, font: { size: 10 }, maxRotation: 0 }, afterFit: (axis) => { axis.height = 30; } }
|
x: { grid: { display: false }, ticks: { color: subColor, font: { size: 10 }, maxRotation: 0 }, afterFit: (axis) => { axis.height = 30; } }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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';
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.moveTo(0, yPos); ctx.lineTo(right, yPos);
|
ctx.moveTo(0, yPos); ctx.lineTo(right, yPos);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => { scrollContainer.scrollLeft = 99999; }, 50);
|
setTimeout(() => { scrollContainer.scrollLeft = 99999; }, 50);
|
||||||
|
|
||||||
|
|
||||||
// Отрисовка замеров (если функция есть)
|
// Отрисовка замеров (если функция есть)
|
||||||
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, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: measures.map(e => e.date.split('-').reverse().join('.').substring(0,5)),
|
labels: measures.map(e => e.date.split('-').reverse().join('.').substring(0,5)),
|
||||||
datasets: [{ data: measures.map(e => e[mType]), borderColor: '#A0F6B1', tension: 0.4, pointRadius: 4 }]
|
datasets: [{ data: measures.map(e => e[mType]), borderColor: '#A0F6B1', tension: 0.4, pointRadius: 4 }]
|
||||||
},
|
},
|
||||||
options: { responsive: true, maintainAspectRatio: false, animation: false, plugins: { legend: { display: false } } }
|
options: { responsive: true, maintainAspectRatio: false, animation: false, plugins: { legend: { display: false } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вспомогательные функции
|
// Вспомогательные функции
|
||||||
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"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
window.manualDelete = function(date) { db.entries = db.entries.filter(e => e.date !== date); saveDB(); renderUI(); };
|
window.manualDelete = function(date) { db.entries = db.entries.filter(e => e.date !== date); saveDB(); renderUI(); };
|
||||||
|
|
||||||
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);
|
||||||
db.entries.sort((a, b) => new Date(a.date) - new Date(b.date));
|
db.entries.sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||||
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`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Проверяем, запущено ли в Android приложении
|
// Проверяем, запущено ли в Android приложении
|
||||||
if (window.Android && window.Android.exportCSV) {
|
if (window.Android && window.Android.exportCSV) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weight && !isNaN(weight)) entry.weight = parseFloat(weight);
|
if (weight && !isNaN(weight)) entry.weight = parseFloat(weight);
|
||||||
if (waist && !isNaN(waist)) entry.waist = parseFloat(waist);
|
if (waist && !isNaN(waist)) entry.waist = parseFloat(waist);
|
||||||
if (chest && !isNaN(chest)) entry.chest = parseFloat(chest);
|
if (chest && !isNaN(chest)) entry.chest = parseFloat(chest);
|
||||||
if (hips && !isNaN(hips)) entry.hips = parseFloat(hips);
|
if (hips && !isNaN(hips)) entry.hips = parseFloat(hips);
|
||||||
if (bicep && !isNaN(bicep)) entry.bicep = parseFloat(bicep);
|
if (bicep && !isNaN(bicep)) entry.bicep = parseFloat(bicep);
|
||||||
count++;
|
count++;
|
||||||
});
|
});
|
||||||
|
|
||||||
db.entries.sort((a, b) => new Date(a.date) - new Date(b.date));
|
db.entries.sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||||
saveDB();
|
saveDB();
|
||||||
renderUI();
|
renderUI();
|
||||||
|
|
||||||
document.getElementById('importModal').style.display = 'none';
|
document.getElementById('importModal').style.display = 'none';
|
||||||
alert("Успешно загружено записей: " + count);
|
alert("Успешно загружено записей: " + count);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert("Ошибка формата: проверьте, что вы скопировали CSV целиком");
|
alert("Ошибка формата: проверьте, что вы скопировали CSV целиком");
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = initLanguage;
|
window.onload = initLanguage;
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -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());
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user