From d49bafe3d5b3f8c7d2fd7815c5c9a3bc1bf45bad Mon Sep 17 00:00:00 2001 From: seo Date: Tue, 25 Aug 2026 00:11:57 +0900 Subject: [PATCH] =?UTF-8?q?findmydevice=20=EC=9B=8C=EC=B9=98=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EB=B9=88=20?= =?UTF-8?q?=EC=84=B9=EC=85=98=20=EC=88=A8=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/main.js | 146 ++++++++++++++++++++++++++++++---------------------- php/api.php | 46 ++--------------- 2 files changed, 88 insertions(+), 104 deletions(-) diff --git a/js/main.js b/js/main.js index 6654d5f..ac0914e 100644 --- a/js/main.js +++ b/js/main.js @@ -39,20 +39,6 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts battery: "배터리", batteryState: "배터리 상태", batteryLevel: "배터리 잔량", - watchbatteryState: "워치 배터리 상태", - watchbatteryLevel: "워치 배터리 잔량", - watch: "워치", - watchBatteryState: "워치 배터리 상태", - watchBatteryLevel: "워치 배터리 잔량", - watchChargerType: "워치 충전 방식", - watchDeviceState: "워치 위치 상태", - watchInteractive: "워치 화면 활성", - watchPowerSave: "워치 절전 모드", - watchDozeMode: "워치 Doze 모드", - watchBedtimeMode: "워치 취침 모드", - watchTheaterMode: "워치 극장 모드", - watchWetMode: "워치 방수 모드", - watchOnBodySensor: "워치 착용 감지", isCharging: "충전 여부", chargerType: "충전 방식", batteryHealth: "배터리 건강", @@ -275,20 +261,6 @@ const deviceLang = (navigator.language || navigator.userLanguage || "en").starts battery: "Battery", batteryState: "Battery State", batteryLevel: "Battery Level", - watchbatteryState: "Watch Battery State", - watchbatteryLevel: "Watch Battery Level", - watch: "Watch", - watchBatteryState: "Watch Battery State", - watchBatteryLevel: "Watch Battery Level", - watchChargerType: "Watch Charger Type", - watchDeviceState: "Watch Location State", - watchInteractive: "Watch Interactive", - watchPowerSave: "Watch Power Save", - watchDozeMode: "Watch Doze Mode", - watchBedtimeMode: "Watch Bedtime Mode", - watchTheaterMode: "Watch Theater Mode", - watchWetMode: "Watch Water Lock", - watchOnBodySensor: "Watch On Body", isCharging: "Charging", chargerType: "Charger Type", batteryHealth: "Battery Health", @@ -837,9 +809,7 @@ function ensureFindMyDeviceShell() {
-
-
@@ -863,6 +833,7 @@ function ensureFindMyDeviceShell() { ensureFindMyDeviceShell(); applyLanguage(langCode); +setLiveStatusSectionsVisible(false); document.querySelectorAll('.locateRow').forEach(row => { row.setAttribute('role', 'button'); @@ -1237,8 +1208,6 @@ function applyLanguage(langCode) { labelForceUpdate: t.forceUpdate, labelBatteryState: t.batteryState, labelBatteryLevel: t.batteryLevel, - labelWatchBatteryState: t.watchbatteryState, - labelWatchBatteryLevel: t.watchbatteryLevel, labelCurrentActivity: t.currentActivity, labelActivityAccuracy: t.activityAccuracy, labelDistance: t.distance, @@ -1316,10 +1285,14 @@ function setCurrentPositionText(value) { function updateCurrentAddress(data) { if (!hasValidCoords(data.latitude, data.longitude)) { setCurrentPositionText(isUsefulAddress(data.address) ? displayAddress(data.address) : '-'); + setLiveValueRowVisible('currentPosition', data.address); + updateLiveSectionVisibility(); return; } if (isUsefulAddress(data.address)) { setCurrentPositionText(displayAddress(data.address)); + setLiveValueRowVisible('currentPosition', data.address); + updateLiveSectionVisibility(); return; } @@ -1327,6 +1300,8 @@ function updateCurrentAddress(data) { const lng = Number(data.longitude); const fallback = formatLatLngAddress(lat, lng); setCurrentPositionText(fallback); + setLiveValueRowVisible('currentPosition', fallback); + updateLiveSectionVisibility(); if (!naver.maps.Service?.reverseGeocode) return; const key = `${lat.toFixed(5)},${lng.toFixed(5)}`; @@ -1350,46 +1325,92 @@ function updateCurrentAddress(data) { reverseGeocodeCache.set(key, resolved); if (Math.abs(Number(lastLat) - lat) < 0.00001 && Math.abs(Number(lastLng) - lng) < 0.00001) { setCurrentPositionText(resolved); + setLiveValueRowVisible('currentPosition', resolved); + updateLiveSectionVisibility(); } }); } +function formatPercentValue(value) { + const displayValue = normalizeDisplayValue(value); + if (!displayValue) return ''; + + const n = Number(displayValue); + return Number.isFinite(n) ? `${formatNumber(n)}%` : ''; +} + +function formatPositiveMetric(value, suffix) { + const displayValue = normalizeDisplayValue(value); + if (!displayValue) return ''; + + const n = Number(displayValue); + return Number.isFinite(n) && n > 0 ? `${formatNumber(n)}${suffix}` : ''; +} + +function setLiveValueRowVisible(valueId, value) { + const el = document.getElementById(valueId); + const row = el?.closest('.settingRow'); + if (!row) return; + row.hidden = !normalizeDisplayValue(value); +} + +function updateLiveSectionVisibility() { + [ + { titleId: 'titleLocation', valueIds: ['currentPosition', 'accuracyValue'] }, + { titleId: 'titleUpdate', valueIds: ['lastUpdateTime', 'updateTrigger'] }, + { titleId: 'titleBattery', valueIds: ['batteryStatus', 'Battery'] }, + { titleId: 'titleActivity', valueIds: ['nowActivity', 'todayDistance', 'todaySteps', 'todayAscended', 'todayDescended', 'nowactivityAccuracy'] }, + ].forEach(({ titleId, valueIds }) => { + const title = document.getElementById(titleId)?.closest('.settingTitle'); + const section = title?.nextElementSibling; + if (!title || !section?.classList?.contains('settingSection')) return; + + const hasVisibleValue = valueIds.some(id => { + const row = document.getElementById(id)?.closest('.settingRow'); + return row && !row.hidden && row.style.display !== 'none'; + }); + const hasSensorValue = !!section.querySelector('[data-sensor-row="1"]'); + const visible = hasVisibleValue || hasSensorValue; + + title.hidden = !visible; + section.hidden = !visible; + }); +} + function updateDOM(data) { - setLiveStatusSectionsVisible(true); highAccuracyModeActive = !!data.highAccuracyMode; syncUpdateBtn(); const textMap = { currentPosition: isUsefulAddress(data.address) ? displayAddress(data.address) - : (hasValidCoords(data.latitude, data.longitude) ? formatLatLngAddress(data.latitude, data.longitude) : '-'), - lastUpdateTime: data.updated ? getRelativeTime(data.updated) : "-", + : (hasValidCoords(data.latitude, data.longitude) ? formatLatLngAddress(data.latitude, data.longitude) : ''), + lastUpdateTime: data.updated ? getRelativeTime(data.updated) : "", nowActivity: data.activity, connectionType: data.connection, updateTrigger: data.trigger, batteryStatus: data.state, - Battery: data.level > 0 ? `${data.level}%` : "-", - WbatteryStatus: data.wstate, - WBattery: data.wlevel > 0 ? `${data.wlevel}%` : "-", - todaySteps: data.steps > 0 ? `${formatNumber(data.steps)}${langCode === "ko" ? "걸음" : "steps"}` : "-", - todayAscended: data.ascended > 0 ? `${formatNumber(data.ascended)}${langCode === "ko" ? "층" : "floors"}` : "-", - todayDescended: data.descended > 0 ? `${formatNumber(data.descended)}${langCode === "ko" ? "층" : "floors"}` : "-", - todayDistance: data.distance > 0 ? `${formatNumber(data.distance)}m` : "-", - accuracyValue: data.accuracy > 0 ? `${formatNumber(data.accuracy)}m` : "-" + Battery: formatPercentValue(data.level), + todaySteps: formatPositiveMetric(data.steps, langCode === "ko" ? "걸음" : "steps"), + todayAscended: formatPositiveMetric(data.ascended, langCode === "ko" ? "층" : "floors"), + todayDescended: formatPositiveMetric(data.descended, langCode === "ko" ? "층" : "floors"), + todayDistance: formatPositiveMetric(data.distance, "m"), + accuracyValue: formatPositiveMetric(data.accuracy, "m") }; for (const [id, value] of Object.entries(textMap)) { const el = document.getElementById(id); if (el) { - el.textContent = value || "-"; - el.title = value || ""; + const displayValue = normalizeDisplayValue(value); + el.textContent = displayValue || "-"; + el.title = displayValue || ""; + setLiveValueRowVisible(id, displayValue); } } - toggleOptionalRow('WbatteryStatus', data.wstate); - toggleOptionalRow('WBattery', data.wlevel); updateSensorSections(data.sensors || {}); cleanupLegacyConnectionRow(); + updateLiveSectionVisibility(); } function cleanupLegacyConnectionRow() { @@ -1407,11 +1428,6 @@ function cleanupLegacyConnectionRow() { function sensorLabel(t, key) { if (t[key]) return t[key]; - if (key.startsWith('watch') && key.length > 5) { - const baseKey = key.charAt(5).toLowerCase() + key.slice(6); - const baseLabel = t[baseKey] || baseKey; - return (t.watch || 'Watch') + ' ' + baseLabel; - } return key; } @@ -1504,18 +1520,18 @@ function upsertOptionalRow(section, id, label, value) { row.children[1].textContent = displayValue; } -function toggleOptionalRow(valueId, value) { - const el = document.getElementById(valueId); - const row = el?.closest('.settingRow'); - if (!row) return; - row.style.display = normalizeDisplayValue(value) ? '' : 'none'; -} - function normalizeDisplayValue(value) { - if (value === null || typeof value === 'undefined' || value === '' || value === '-' || value === 'unknown' || value === 'unavailable') { + if (value === null || typeof value === 'undefined') { return ''; } - return String(value); + + const text = String(value).trim(); + const lower = text.toLowerCase(); + if (!text || text === '-' || lower === 'unknown' || lower === 'unavailable' || lower === 'none' || lower === 'null' || lower === 'nan' || lower === 'n/a') { + return ''; + } + + return text; } function updateActivitySection(data) { @@ -3090,12 +3106,20 @@ function updateDOMWithData(data) { } // ── 라이브 모드 기존 로직 ── + if (!hasValidCoords(data.latitude, data.longitude)) { + updateDOM(data); + updateActivitySection(data); + updateLiveSectionVisibility(); + return; + } + const latlng = new naver.maps.LatLng(data.latitude, data.longitude); updateConfiguredHome(data) || updateHomeCenter(data.latitude, data.longitude); // DOM 업데이트 updateDOM(data); updateActivitySection(data); + updateLiveSectionVisibility(); // 지도 초기화 if (!mapState.map) { diff --git a/php/api.php b/php/api.php index dda1c3f..effe416 100644 --- a/php/api.php +++ b/php/api.php @@ -530,38 +530,6 @@ function findmydevice_starts_with(string $value, string $prefix): bool { return strncmp($value, $prefix, strlen($prefix)) === 0; } -function findmydevice_watch_pair_key(string $key): ?string -{ - if (!findmydevice_starts_with($key, 'watch') || strlen($key) <= 5) { - return null; - } - - return lcfirst(substr($key, 5)); -} - -function findmydevice_pair_watch_sensor_rows(array $rows): array -{ - $ordered = []; - foreach ($rows as $key => $value) { - if (findmydevice_watch_pair_key((string)$key) !== null) { - continue; - } - - $ordered[$key] = $value; - $watchKey = 'watch' . ucfirst((string)$key); - if (array_key_exists($watchKey, $rows)) { - $ordered[$watchKey] = $rows[$watchKey]; - } - } - - foreach ($rows as $key => $value) { - if (!array_key_exists($key, $ordered)) { - $ordered[$key] = $value; - } - } - - return $ordered; -} function readableTriggerCode(string $value, string $lang): string @@ -723,8 +691,6 @@ $deviceData = getData($deviceUrl); $geoData = getData($geoUrl); $batteryStateData = getData($batteryStateUrl); $batteryLevelData = getData($batteryLevelUrl); -$watchBatteryStateData = getEntityData($entities, 'watch_battery_state'); -$watchBatteryLevelData = getEntityData($entities, 'watch_battery_level'); $activityData = getData($activityUrl); $connectionData = getData($connectionUrl); $ascendedData = getData($fascendedUrl); @@ -1294,9 +1260,6 @@ $activityState = $activityData['state'] ?? null; $aaccuracyState = $activityData['attributes']['Confidence'] ?? $activityData['attributes']['confidence'] ?? null; $connectionState = $connectionData['state'] ?? null; $batteryState = $batteryStateData['state'] ?? null; -$watchBatteryState = stateValue($watchBatteryStateData); -$watchBatteryLevel = stateValue($watchBatteryLevelData); -$watchBatteryText = $batteryStateMap[$lang][$watchBatteryState] ?? $watchBatteryState ?? null; $highAccuracyMode = strtolower((string)($highAccuracyModeData['state'] ?? '')) === 'on'; $address = null; @@ -1321,6 +1284,9 @@ $flatSensors = []; foreach (($config['findmydevice_sensor_sections'] ?? []) as $sectionKey => $definitions) { $sensorSections[$sectionKey] = []; foreach ($definitions as $sensorKey => $definition) { + if (!is_array($definition)) { + continue; + } if (!empty($definition['requires']) && empty($flatSensors[$definition['requires']])) { continue; } @@ -1334,8 +1300,6 @@ foreach (($config['findmydevice_sensor_sections'] ?? []) as $sectionKey => $defi } if (!$sensorSections[$sectionKey]) { unset($sensorSections[$sectionKey]); - } else { - $sensorSections[$sectionKey] = findmydevice_pair_watch_sensor_rows($sensorSections[$sectionKey]); } } @@ -1350,8 +1314,6 @@ if (isset($_GET['hashonly']) && $_GET['hashonly'] == '1') { 'address' => $address, 'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null, 'level' => $batteryLevelData['state'] ?? null, - 'wstate' => $watchBatteryText, - 'wlevel' => $watchBatteryLevel, 'ascended' => $ascendedData['state'] ?? null, 'descended' => $descendedData['state'] ?? null, 'steps' => $stepsData['state'] ?? null, @@ -1378,8 +1340,6 @@ echo json_encode([ 'address' => $address, 'state' => $batteryStateMap[$lang][$batteryState] ?? $batteryState ?? null, 'level' => $batteryLevelData['state'] ?? null, - 'wstate' => $watchBatteryText, - 'wlevel' => $watchBatteryLevel, 'ascended' => $ascendedData['state'] ?? null, 'descended' => $descendedData['state'] ?? null, 'steps' => $stepsData['state'] ?? null,