pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Services.UPower import qs.Common Singleton { id: root property bool suppressSound: true property bool previousPluggedState: false property int refCount: 0 function addRef() { refCount++; } function removeRef() { refCount = Math.max(0, refCount - 1); } // FreeBSD native ACPI fallback. UPower exists in ports, but on some FreeBSD // installations it exposes no usable laptop battery. Keep UPower as the // preferred path when it works and fall back to acpiconf(8)/sysctl(8). readonly property bool isBSD: Qt.platform.os === "unix" property bool freebsdBatteryAvailable: false property bool freebsdBatteryProbeComplete: false property real freebsdBatteryLevel: 0 property bool freebsdIsCharging: false property bool freebsdPluggedIn: true property real freebsdChangeRate: 0 property real freebsdFullCapacityWh: 0 property real freebsdDesignCapacityWh: 0 property string freebsdBatteryState: "" function parseFreeBsdCapacity(value, unit) { const n = parseFloat(value || "0"); if (!isFinite(n) || n <= 0) return 0; return unit === "mWh" ? n / 1000 : 0; } function applyFreeBsdBatteryState(output) { const text = output || ""; const blocks = text.split(/__DMS_BATTERY_\d+__\n?/).filter(b => /Remaining capacity:/i.test(b)); let weightedPct = 0; let weight = 0; let pctSum = 0; let pctCount = 0; let totalRateW = 0; let fullWh = 0; let designWh = 0; let charging = false; let state = ""; for (const block of blocks) { const pctMatch = block.match(/Remaining capacity:\s*(\d+(?:\.\d+)?)%/i); const stateMatch = block.match(/State:\s*([^\n\r]+)/i); const rateMatch = block.match(/Present rate:\s*[^\n\r]*?\((\d+(?:\.\d+)?)\s*mW\)/i) || block.match(/Present rate:\s*(\d+(?:\.\d+)?)\s*mW/i); const fullMatch = block.match(/Last full capacity:\s*(\d+(?:\.\d+)?)\s*(mWh|mAh)/i); const designMatch = block.match(/Design capacity:\s*(\d+(?:\.\d+)?)\s*(mWh|mAh)/i); const pct = pctMatch ? parseFloat(pctMatch[1]) : NaN; const full = fullMatch ? root.parseFreeBsdCapacity(fullMatch[1], fullMatch[2]) : 0; if (isFinite(pct)) { pctSum += pct; pctCount++; if (full > 0) { weightedPct += pct * full; weight += full; } } if (rateMatch) totalRateW += parseFloat(rateMatch[1]) / 1000; fullWh += full; if (designMatch) designWh += root.parseFreeBsdCapacity(designMatch[1], designMatch[2]); if (stateMatch) { const thisState = stateMatch[1].trim().toLowerCase(); if (!state) state = thisState; if (thisState === "charging" || thisState.startsWith("charging")) charging = true; } } const acMatch = text.match(/__DMS_AC__:(\d+)/); root.freebsdBatteryAvailable = blocks.length > 0; root.freebsdBatteryLevel = blocks.length > 0 ? Math.max(0, Math.min(100, weight > 0 ? weightedPct / weight : (pctCount > 0 ? pctSum / pctCount : 0))) : 0; root.freebsdIsCharging = charging; root.freebsdPluggedIn = acMatch ? acMatch[1] === "1" : charging; root.freebsdChangeRate = totalRateW; root.freebsdFullCapacityWh = fullWh; root.freebsdDesignCapacityWh = designWh; root.freebsdBatteryState = state; } function refreshFreeBsdBattery() { if (!root.isBSD || root.batteries.length > 0) return; const script = 'units=$(sysctl -n hw.acpi.battery.units 2>/dev/null || echo 0); i=0; while [ "$i" -lt "$units" ]; do echo "__DMS_BATTERY_${i}__"; acpiconf -i "$i" 2>/dev/null; i=$((i+1)); done; printf "__DMS_AC__:"; sysctl -n hw.acpi.acline 2>/dev/null || echo 1'; Proc.runCommand("battery-freebsd-acpi", ["nice", "-n", "10", "sh", "-c", script], (output, exitCode) => { root.freebsdBatteryProbeComplete = true; if (exitCode === 0) root.applyFreeBsdBatteryState(output); else root.freebsdBatteryAvailable = false; }, 0); } function updateFreeBsdBatteryFallback() { if (!root.isBSD) return; if (root.batteries.length > 0) { root.freebsdBatteryAvailable = false; root.freebsdBatteryProbeComplete = false; return; } if (!root.freebsdBatteryProbeComplete) root.refreshFreeBsdBattery(); } onBatteriesChanged: root.updateFreeBsdBatteryFallback() onRefCountChanged: { if (root.refCount === 1) root.refreshFreeBsdBattery(); } Timer { interval: 15000 repeat: true running: root.isBSD && root.batteries.length === 0 && root.freebsdBatteryAvailable && root.refCount > 0 onTriggered: root.refreshFreeBsdBattery() } Timer { id: startupTimer interval: 500 repeat: false running: true onTriggered: { root.suppressSound = false; root.applyPowerProfile(); } } Connections { target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null function onHasPerformanceProfileChanged() { root.applyPowerProfile(); } } function applyPowerProfile() { if (!batteryAvailable) return; const profileValue = isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName; if (profileValue === "") return; const targetProfile = parseInt(profileValue); if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile) return; PowerProfileWatcher.applyProfile(targetProfile); } readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY") // List of laptop batteries readonly property var batteries: UPower.devices.values.filter(dev => dev.isLaptopBattery) readonly property var readyBatteries: batteries.filter(b => b.ready) readonly property var stateKnownBatteries: batteries.filter(b => b.ready && b.state !== UPowerDeviceState.Unknown) readonly property var chargeBatteries: readyBatteries.filter(b => root._hasUsableCharge(b)) readonly property var energyBatteries: readyBatteries.filter(b => b.energyCapacity > 0) property real _lastBatteryLevel: 0 property bool _lastIsCharging: false property real _lastChangeRate: 0 property real _lastBatteryEnergy: 0 property real _lastBatteryCapacity: 0 // UPower: 0 means unset. State can be Unknown with a valid Percentage, or Unknown with 0. function _hasUsableCharge(dev) { if (!dev || !dev.ready) return false; if (dev.percentage > 0) return true; return dev.energy > 0 && dev.energyCapacity > 0; } function _chargePercent(dev) { if (!dev) return 0; if (dev.percentage > 0) return Math.min(100, Math.round(dev.percentage * 100)); if (dev.energy > 0 && dev.energyCapacity > 0) return Math.min(100, Math.round((dev.energy * 100) / dev.energyCapacity)); return 0; } function _liveChargePercent() { if (usePreferred && preferredDevice) { if (_hasUsableCharge(preferredDevice)) return _chargePercent(preferredDevice); return 0; } if (batteries.length > 1 && _hasUsableCharge(UPower.displayDevice)) return _chargePercent(UPower.displayDevice); if (chargeBatteries.length === 1) return _chargePercent(chargeBatteries[0]); if (chargeBatteries.length > 1) { const energyPacks = chargeBatteries.filter(b => b.energy > 0 && b.energyCapacity > 0); if (energyPacks.length === chargeBatteries.length) { const energy = energyPacks.reduce((sum, b) => sum + b.energy, 0); const cap = energyPacks.reduce((sum, b) => sum + b.energyCapacity, 0); if (cap > 0) return Math.min(100, Math.round((energy * 100) / cap)); } return Math.min(100, Math.round(chargeBatteries.reduce((sum, b) => sum + _chargePercent(b), 0) / chargeBatteries.length)); } if (_hasUsableCharge(UPower.displayDevice)) return _chargePercent(UPower.displayDevice); return 0; } function _liveEnergy() { if (usePreferred && preferredDevice) { if (preferredDeviceReady && preferredDevice.energy > 0) return preferredDevice.energy; return 0; } const packs = energyBatteries.filter(b => b.energy > 0); if (packs.length === 0) return 0; return packs.reduce((sum, b) => sum + b.energy, 0); } function _liveCapacity() { if (usePreferred && preferredDevice) { if (preferredDeviceReady && preferredDevice.energyCapacity > 0) return preferredDevice.energyCapacity; return 0; } if (energyBatteries.length === 0) return 0; return energyBatteries.reduce((sum, b) => sum + b.energyCapacity, 0); } readonly property bool usePreferred: preferredBatteryOverride && preferredBatteryOverride.length > 0 readonly property UPowerDevice preferredDevice: { if (!usePreferred) return null; const override = preferredBatteryOverride.toLowerCase(); return batteries.find(dev => dev.nativePath.toLowerCase().includes(override)) || null; } readonly property bool preferredDeviceReady: preferredDevice && preferredDevice.ready readonly property bool preferredDeviceKnown: preferredDeviceReady && preferredDevice.state !== UPowerDeviceState.Unknown readonly property bool _hasKnownChargingState: { if (!batteryAvailable) return false; if (usePreferred) return preferredDeviceKnown; return stateKnownBatteries.length > 0; } readonly property bool _currentIsCharging: { if (!batteryAvailable) return false; if (usePreferred && preferredDeviceKnown) return preferredDevice.state === UPowerDeviceState.Charging; if (usePreferred) return false; return stateKnownBatteries.some(b => b.state === UPowerDeviceState.Charging); } // Main battery (for backward compatibility) readonly property UPowerDevice device: { if (usePreferred) { if (preferredDeviceKnown) return preferredDevice; return stateKnownBatteries[0] || null; } return stateKnownBatteries[0] || readyBatteries[0] || batteries[0] || null; } // Whether at least one battery is available readonly property bool batteryAvailable: (isBSD && freebsdBatteryAvailable) || batteries.length > 0 readonly property real batteryLevel: { if (!batteryAvailable) return 0; if (isBSD && freebsdBatteryAvailable) return freebsdBatteryLevel; const live = _liveChargePercent(); return live > 0 ? live : _lastBatteryLevel; } readonly property bool isCharging: (isBSD && freebsdBatteryAvailable) ? freebsdIsCharging : (_hasKnownChargingState ? _currentIsCharging : _lastIsCharging) // Is the system plugged in (Is not running on battery) readonly property bool isPluggedIn: (isBSD && freebsdBatteryAvailable) ? freebsdPluggedIn : !UPower.onBattery readonly property bool hasBatteryReading: batteryAvailable && batteryLevel > 0 readonly property bool isLowBattery: hasBatteryReading && batteryLevel <= SettingsData.batteryLowThreshold readonly property bool isCriticalBattery: hasBatteryReading && batteryLevel <= SettingsData.batteryCriticalThreshold readonly property color levelCautionColor: "#FFC107" readonly property color levelCriticalColor: "#F44336" readonly property color levelColor: { if (isCharging) return Theme.success; if (isCriticalBattery) return levelCriticalColor; if (isLowBattery) return Theme.warning; if (batteryLevel <= SettingsData.batteryLowThreshold * 2) return levelCautionColor; return Theme.success; } property bool _hasNotifiedLowBattery: false property bool _hasNotifiedCriticalBattery: false property bool _hasNotifiedChargeLimit: false function _syncLastIsCharging() { if (_hasKnownChargingState) _lastIsCharging = _currentIsCharging; } on_HasKnownChargingStateChanged: _syncLastIsCharging() on_CurrentIsChargingChanged: _syncLastIsCharging() Component.onCompleted: { Qt.callLater(root.updateFreeBsdBatteryFallback); _syncLastIsCharging(); if (batteryLevel > 0) _lastBatteryLevel = batteryLevel; if (batteryEnergy > 0) _lastBatteryEnergy = batteryEnergy; if (batteryCapacity > 0) _lastBatteryCapacity = batteryCapacity; } // urgency: "critical" (red), "warning" (orange/important), or "info" function sendAlert(title, message, urgency, icon, notificationType) { if (notificationType === 1) { const dbusUrgency = urgency === "critical" ? "critical" : (urgency === "warning" ? "normal" : "low"); Quickshell.execDetached(["notify-send", "-u", dbusUrgency, "-a", "DMS", "-i", icon, title, message]); } else if (urgency === "critical") { ToastService.showError(title, message, "", icon); } else if (urgency === "warning") { ToastService.showWarning(title, message, "", icon); } else { ToastService.showInfo(title, message, "", icon); } } onBatteryLevelChanged: { if (batteryLevel > 0) _lastBatteryLevel = batteryLevel; if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) { if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) { _hasNotifiedChargeLimit = true; sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%", "charge limit notification body, %1 is the limit percentage").arg(SettingsData.batteryChargeLimit), "info", "material:battery_profile", SettingsData.batteryChargeLimitNotificationType); } } else if (!isCharging || batteryLevel < SettingsData.batteryChargeLimit - 2) { _hasNotifiedChargeLimit = false; } if (isCharging) { _hasNotifiedLowBattery = false; _hasNotifiedCriticalBattery = false; return; } // Critical battery check (higher priority) if (isCriticalBattery) { if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) { _hasNotifiedCriticalBattery = true; sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!", "critical battery notification body, %1 is the battery percentage").arg(batteryLevel), "critical", "material:battery_alert", SettingsData.batteryCriticalNotificationType); } return; } if (batteryLevel > SettingsData.batteryCriticalThreshold) _hasNotifiedCriticalBattery = false; // Low battery check if (isLowBattery) { if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) { _hasNotifiedLowBattery = true; sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon", "low battery notification body, %1 is the battery percentage").arg(batteryLevel), "warning", "material:battery_0_bar", SettingsData.batteryLowNotificationType); } if (SettingsData.batteryAutoPowerSaver && PowerProfileWatcher.available) { if (PowerProfileWatcher.currentProfile !== PowerProfile.PowerSaver) PowerProfileWatcher.applyProfile(PowerProfile.PowerSaver); } } if (batteryLevel > SettingsData.batteryLowThreshold) _hasNotifiedLowBattery = false; } onIsChargingChanged: { // Reset average when switching states _smoothedChangeRate = (_hasKnownChargingState && changeRate > 0) ? changeRate : 0; _lastRateSampleTime = _smoothedChangeRate > 0 ? Date.now() : 0; if (isCharging) { _hasNotifiedLowBattery = false; _hasNotifiedCriticalBattery = false; } else { _hasNotifiedChargeLimit = false; } } onIsPluggedInChanged: { if (suppressSound || !batteryAvailable) { previousPluggedState = isPluggedIn; return; } if (SettingsData.soundsEnabled && SettingsData.soundPluggedIn) { if (isPluggedIn && !previousPluggedState) { AudioService.playPowerPlugSound(); } else if (!isPluggedIn && previousPluggedState) { AudioService.playPowerUnplugSound(); } } applyPowerProfile(); if (isPluggedIn) { const dismissLow = SettingsData.batteryLowNotificationType === 1 && SettingsData.notificationTimeoutNormal === 0; const dismissCritical = SettingsData.batteryCriticalNotificationType === 1 && SettingsData.notificationTimeoutCritical === 0; if (dismissLow || dismissCritical) { const lowSummary = I18n.tr("Low Battery"); const criticalSummary = I18n.tr("Critical Battery"); for (const w of NotificationService.visibleNotifications) { if (!w || !w.notification) continue; const summary = w.notification.summary; if ((dismissLow && summary === lowSummary) || (dismissCritical && summary === criticalSummary)) { NotificationService.dismissNotification(w); } } } } previousPluggedState = isPluggedIn; } // Aggregated charge/discharge rate readonly property real changeRate: { if (!batteryAvailable) return 0; if (isBSD && freebsdBatteryAvailable) { _lastChangeRate = freebsdChangeRate; return _lastChangeRate; } if (usePreferred && preferredDeviceKnown) { _lastChangeRate = preferredDevice.changeRate; return _lastChangeRate; } if (usePreferred && preferredDevice) return _lastChangeRate; if (stateKnownBatteries.length === 0) return _lastChangeRate; const val = stateKnownBatteries.reduce((sum, b) => sum + b.changeRate, 0); _lastChangeRate = val; return val; } // Aggregated charge/discharge rate, signed: positive while charging, // negative while draining the battery, zero when idle/fully charged. readonly property real signedChangeRate: { if (!batteryAvailable) return 0; const rate = Math.abs(changeRate); if (!isFinite(rate) || rate < 0.05) return 0; return isCharging ? rate : -rate; } // Compact signed wattage for bar widgets, e.g. "+45W" / "-8.4W". // Returns an empty string when there is nothing meaningful to show. // compact drops the decimal entirely (used by vertical bars). function formatPowerRate(compact) { const rate = signedChangeRate; if (rate === 0) return ""; const magnitude = Math.abs(rate); const value = (compact || magnitude >= 10) ? Math.round(magnitude).toString() : magnitude.toFixed(1); return `${rate > 0 ? "+" : "-"}${value}W`; } // A time-weighted exponential moving average based on the aggregated charge/discharge rate property real _smoothedChangeRate: 0 property real _lastRateSampleTime: 0 readonly property real _rateSmoothingHalfLife: 90 // in seconds function _updateSmoothedRate() { if (!_hasKnownChargingState || changeRate <= 0) return; const now = Date.now(); if (_smoothedChangeRate <= 0 || _lastRateSampleTime <= 0) { _smoothedChangeRate = changeRate; _lastRateSampleTime = now; return; } const dt = (now - _lastRateSampleTime) / 1000; _lastRateSampleTime = now; if (dt <= 0) return; const tau = _rateSmoothingHalfLife / Math.LN2; const alpha = 1 - Math.exp(-dt / tau); _smoothedChangeRate += alpha * (changeRate - _smoothedChangeRate); } onChangeRateChanged: _updateSmoothedRate() onBatteryAvailableChanged: { if (!batteryAvailable) { _smoothedChangeRate = 0; _lastRateSampleTime = 0; } } // Aggregated battery health readonly property string batteryHealth: { if (!batteryAvailable) return "N/A"; if (isBSD && freebsdBatteryAvailable) { if (freebsdDesignCapacityWh > 0 && freebsdFullCapacityWh > 0) return `${Math.round(Math.min(100, freebsdFullCapacityWh * 100 / freebsdDesignCapacityWh))}%`; return "N/A"; } if (usePreferred && preferredDeviceReady && preferredDevice.healthSupported) return `${Math.round(preferredDevice.healthPercentage)}%`; const validBatteries = readyBatteries.filter(b => b.healthSupported && b.healthPercentage > 0); if (validBatteries.length === 0) return "N/A"; const avgHealth = validBatteries.reduce((sum, b) => sum + b.healthPercentage, 0) / validBatteries.length; return `${Math.round(avgHealth)}%`; } readonly property real batteryEnergy: { if (!batteryAvailable) return 0; if (isBSD && freebsdBatteryAvailable && freebsdFullCapacityWh > 0) return freebsdFullCapacityWh * freebsdBatteryLevel / 100; const live = _liveEnergy(); return live > 0 ? live : _lastBatteryEnergy; } onBatteryEnergyChanged: { if (batteryEnergy > 0) _lastBatteryEnergy = batteryEnergy; } readonly property real batteryCapacity: { if (!batteryAvailable) return 0; if (isBSD && freebsdBatteryAvailable) return freebsdFullCapacityWh; const live = _liveCapacity(); return live > 0 ? live : _lastBatteryCapacity; } onBatteryCapacityChanged: { if (batteryCapacity > 0) _lastBatteryCapacity = batteryCapacity; } function translateBatteryState(state) { switch (state) { case UPowerDeviceState.Charging: return I18n.tr("Charging", "battery status"); case UPowerDeviceState.Discharging: return I18n.tr("Discharging", "battery status"); case UPowerDeviceState.Empty: return I18n.tr("Empty", "battery status"); case UPowerDeviceState.FullyCharged: return I18n.tr("Fully Charged", "battery status"); case UPowerDeviceState.PendingCharge: return I18n.tr("Pending Charge", "battery status"); case UPowerDeviceState.PendingDischarge: return I18n.tr("Pending Discharge", "battery status"); default: return I18n.tr("Unknown", "battery status"); } } readonly property string batteryStatus: { if (!batteryAvailable) return I18n.tr("No battery", "battery status"); if (isBSD && freebsdBatteryAvailable) { if (isCharging) return I18n.tr("Charging", "battery status"); return isPluggedIn ? I18n.tr("Plugged in", "battery status") : I18n.tr("Discharging", "battery status"); } if (stateKnownBatteries.length === 0) { if (isCharging) return I18n.tr("Charging", "battery status"); return isPluggedIn ? I18n.tr("Plugged in", "battery status") : I18n.tr("Discharging", "battery status"); } if (isCharging && !stateKnownBatteries.some(b => b.changeRate > 0)) return I18n.tr("Plugged in", "battery status"); const states = stateKnownBatteries.map(b => b.state); if (states.every(s => s === states[0])) return translateBatteryState(states[0]); return isCharging ? I18n.tr("Charging", "battery status") : (isPluggedIn ? I18n.tr("Plugged in", "battery status") : I18n.tr("Discharging", "battery status")); } readonly property bool suggestPowerSaver: false readonly property var peripheralDevices: refCount === 0 ? [] : UPower.devices.values.filter(dev => dev && dev.ready && !dev.isLaptopBattery && peripheralIcon(dev.type) !== "").map(dev => ({ "name": dev.model || UPowerDeviceType.toString(dev.type), "percentage": Math.round(dev.percentage * 100), "type": dev.type, "icon": peripheralIcon(dev.type), "charging": dev.state === UPowerDeviceState.Charging })) readonly property var bluetoothDevices: { if (refCount === 0) return []; const bluetoothTypes = [UPowerDeviceType.BluetoothGeneric, UPowerDeviceType.Headphones, UPowerDeviceType.Headset, UPowerDeviceType.Keyboard, UPowerDeviceType.Mouse, UPowerDeviceType.Speakers]; return peripheralDevices.filter(dev => bluetoothTypes.includes(dev.type)); } function peripheralIcon(type) { switch (type) { case UPowerDeviceType.BluetoothGeneric: return "bluetooth"; case UPowerDeviceType.Headphones: return "headphones"; case UPowerDeviceType.Headset: return "headset_mic"; case UPowerDeviceType.Speakers: return "speaker"; case UPowerDeviceType.Keyboard: return "keyboard"; case UPowerDeviceType.Mouse: return "mouse"; case UPowerDeviceType.Touchpad: return "touchpad_mouse"; case UPowerDeviceType.Pen: return "stylus"; case UPowerDeviceType.GamingInput: return "sports_esports"; case UPowerDeviceType.Phone: return "smartphone"; case UPowerDeviceType.Tablet: return "tablet"; case UPowerDeviceType.MediaPlayer: return "media_output"; case UPowerDeviceType.Wearable: return "watch"; default: return ""; } } function estimatedSeconds() { if (!batteryAvailable) return 0; const rate = _smoothedChangeRate > 0 ? _smoothedChangeRate : changeRate; const totalTime = isCharging ? ((batteryCapacity - batteryEnergy) / rate) : (batteryEnergy / rate); const seconds = Math.abs(totalTime * 3600); if (!seconds || seconds <= 0 || seconds > 86400) return 0; return seconds; } function formatDuration(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return hours > 0 ? I18n.tr("%1h %2m", "battery time remaining").arg(hours).arg(minutes) : I18n.tr("%1m", "battery time remaining").arg(minutes); } function formatTimeRemaining() { const seconds = estimatedSeconds(); if (!seconds) return "Unknown"; return formatDuration(seconds); } function formatEstimatedTime() { const seconds = estimatedSeconds(); if (!seconds) return ""; const target = new Date(Date.now() + seconds * 1000); const use24Hour = SettingsData.use24HourClock !== false; return target.toLocaleTimeString(Qt.locale(), use24Hour ? "HH:mm" : "h:mm AP"); } function getBatteryIcon() { if (!batteryAvailable) return "power"; if (isCharging) { if (batteryLevel >= 90) return "battery_charging_full"; if (batteryLevel >= 80) return "battery_charging_90"; if (batteryLevel >= 60) return "battery_charging_80"; if (batteryLevel >= 50) return "battery_charging_60"; if (batteryLevel >= 30) return "battery_charging_50"; if (batteryLevel >= 20) return "battery_charging_30"; return "battery_charging_20"; } if (isPluggedIn) { if (batteryLevel >= 90) return "battery_charging_full"; if (batteryLevel >= 80) return "battery_charging_90"; if (batteryLevel >= 60) return "battery_charging_80"; if (batteryLevel >= 50) return "battery_charging_60"; if (batteryLevel >= 30) return "battery_charging_50"; if (batteryLevel >= 20) return "battery_charging_30"; return "battery_charging_20"; } if (batteryLevel >= 95) return "battery_full"; if (batteryLevel >= 85) return "battery_6_bar"; if (batteryLevel >= 70) return "battery_5_bar"; if (batteryLevel >= 55) return "battery_4_bar"; if (batteryLevel >= 40) return "battery_3_bar"; if (batteryLevel >= 25) return "battery_2_bar"; return "battery_1_bar"; } }