From 42051ba924e78a0381ebff5a9ba33f9ab2052ca0 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 27 Dec 2021 16:29:47 -0600 Subject: [PATCH] status-bar: add basic battery indicator This adds a basic battery indicator that just shows green for OK and red for low battery warning. This is all we can do for now since that is the only information that the hub sends currently. Fixes: https://github.com/pybricks/support/issues/559 --- CHANGELOG.md | 2 ++ src/ble/reducers.test.ts | 32 ++++++++++++++++++++ src/ble/reducers.ts | 27 +++++++++++++++++ src/status-bar/StatusBar.tsx | 53 ++++++++++++++++++++++++++++++---- src/status-bar/i18n.en.json | 4 +++ src/status-bar/i18n.ts | 2 ++ src/status-bar/status-bar.scss | 39 +++++++++++++++++++++++++ 7 files changed, 153 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 180aac0b..594f72c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Added - Status bar indicator for connected hub. +- Basic battery OK/low indicator ([support#559]). ### Changed - Saving file now uses proper save dialog in Chromium browser ([support#84]). @@ -17,6 +18,7 @@ [support#84]: https://github.com/pybricks/support/issues/84 [support#300]: https://github.com/pybricks/support/issues/300 [support#369]: https://github.com/pybricks/support/issues/369 +[support#559]: https://github.com/pybricks/support/issues/559 ## [1.1.0] - 2021-12-16 diff --git a/src/ble/reducers.test.ts b/src/ble/reducers.test.ts index 8427caff..06a0662a 100644 --- a/src/ble/reducers.test.ts +++ b/src/ble/reducers.test.ts @@ -8,6 +8,8 @@ import { } from '../ble-device-info-service/actions'; import { PnpIdVendorIdSource } from '../ble-device-info-service/protocol'; import { HubType, LegoCompanyId } from '../ble-lwp3-service/protocol'; +import { didReceiveStatusReport } from '../ble-pybricks-service/actions'; +import { Status, statusToFlag } from '../ble-pybricks-service/protocol'; import { BleDeviceDidFailToConnectReason, connect, @@ -25,7 +27,9 @@ test('initial state', () => { expect(reducers(undefined, {} as Action)).toMatchInlineSnapshot(` Object { "connection": "ble.connection.state.disconnected", + "deviceBatteryCharging": false, "deviceFirmwareVersion": "", + "deviceLowBatteryWarning": false, "deviceName": "", "deviceType": "", } @@ -113,3 +117,31 @@ test('deviceFirmwareVersion', () => { .deviceFirmwareVersion, ).toBe(''); }); + +test('deviceLowBatteryWarning', () => { + expect( + reducers( + { deviceLowBatteryWarning: false } as State, + didReceiveStatusReport(statusToFlag(Status.BatteryLowVoltageWarning)), + ).deviceLowBatteryWarning, + ).toBeTruthy(); + + expect( + reducers( + { deviceLowBatteryWarning: true } as State, + didReceiveStatusReport(~statusToFlag(Status.BatteryLowVoltageWarning)), + ).deviceLowBatteryWarning, + ).toBeFalsy(); + + expect( + reducers({ deviceLowBatteryWarning: true } as State, didDisconnect()) + .deviceLowBatteryWarning, + ).toBeFalsy(); +}); + +test('deviceBatteryCharging', () => { + expect( + reducers({ deviceBatteryCharging: true } as State, didDisconnect()) + .deviceBatteryCharging, + ).toBeFalsy(); +}); diff --git a/src/ble/reducers.ts b/src/ble/reducers.ts index 449fe567..c14e089b 100644 --- a/src/ble/reducers.ts +++ b/src/ble/reducers.ts @@ -8,6 +8,8 @@ import { Reducer, combineReducers } from 'redux'; import { Action } from '../actions'; import { BleDIServiceActionType } from '../ble-device-info-service/actions'; import { getHubTypeName } from '../ble-device-info-service/protocol'; +import { BlePybricksServiceEventActionType } from '../ble-pybricks-service/actions'; +import { Status, statusToFlag } from '../ble-pybricks-service/protocol'; import { BleDeviceActionType } from './actions'; /** @@ -85,9 +87,34 @@ const deviceFirmwareVersion: Reducer = (state = '', action) => { } }; +const deviceLowBatteryWarning: Reducer = (state = false, action) => { + switch (action.type) { + case BleDeviceActionType.DidDisconnect: + return false; + case BlePybricksServiceEventActionType.DidReceiveStatusReport: + return Boolean( + action.statusFlags & statusToFlag(Status.BatteryLowVoltageWarning), + ); + default: + return state; + } +}; + +const deviceBatteryCharging: Reducer = (state = false, action) => { + switch (action.type) { + case BleDeviceActionType.DidDisconnect: + return false; + // TODO: hub does not currently have a status flag for this + default: + return state; + } +}; + export default combineReducers({ connection, deviceName, deviceType, deviceFirmwareVersion, + deviceLowBatteryWarning, + deviceBatteryCharging, }); diff --git a/src/status-bar/StatusBar.tsx b/src/status-bar/StatusBar.tsx index 08a4830a..017133c1 100644 --- a/src/status-bar/StatusBar.tsx +++ b/src/status-bar/StatusBar.tsx @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2021 The Pybricks Authors -import { Button } from '@blueprintjs/core'; -import { Classes as Classes2, Popover2 } from '@blueprintjs/popover2'; +import { Button, Intent, ProgressBar } from '@blueprintjs/core'; +import { Classes as Classes2, Popover2, Popover2Props } from '@blueprintjs/popover2'; import { useI18n } from '@shopify/react-i18n'; import React from 'react'; import { useSelector } from 'react-redux'; @@ -13,6 +13,11 @@ import en from './i18n.en.json'; import './status-bar.scss'; +const commonPopoverProps: Partial = { + popoverClassName: Classes2.POPOVER2_CONTENT_SIZING, + placement: 'top', +}; + const HubInfoButton: React.VFC = (_props) => { const deviceName = useSelector((state: RootState) => state.ble.deviceName); const deviceType = useSelector((state: RootState) => state.ble.deviceType); @@ -24,9 +29,7 @@ const HubInfoButton: React.VFC = (_props) => { return ( @@ -65,6 +68,39 @@ const HubInfoButton: React.VFC = (_props) => { ); }; +const BatteryIndicator: React.VFC = (_props) => { + const charging = useSelector((state: RootState) => state.ble.deviceBatteryCharging); + const lowBatteryWarning = useSelector( + (state: RootState) => state.ble.deviceLowBatteryWarning, + ); + + const [i18n] = useI18n({ id: 'statusBar', translations: { en }, fallback: en }); + + return ( + + {i18n.translate( + lowBatteryWarning ? MessageId.BatteryLow : MessageId.BatteryOk, + )} + + } + > +
+
+ +
+
+
+ + ); +}; + const StatusBar: React.VFC = (_props) => { const connection = useSelector((state: RootState) => state.ble.connection); @@ -75,7 +111,12 @@ const StatusBar: React.VFC = (_props) => { aria-live="off" onContextMenu={(e): void => e.preventDefault()} > - {connection === BleConnectionState.Connected && } + {connection === BleConnectionState.Connected && ( + <> + + + + )}
); }; diff --git a/src/status-bar/i18n.en.json b/src/status-bar/i18n.en.json index 1222b2ff..61d9161a 100644 --- a/src/status-bar/i18n.en.json +++ b/src/status-bar/i18n.en.json @@ -3,5 +3,9 @@ "connectedTo": "Connected to:", "hubType": "Hub type:", "firmware": "Firmware:" + }, + "battery": { + "low": "Battery is low. Hub will turn off soon.", + "ok": "Battery level is OK." } } diff --git a/src/status-bar/i18n.ts b/src/status-bar/i18n.ts index 5e128865..48076c76 100644 --- a/src/status-bar/i18n.ts +++ b/src/status-bar/i18n.ts @@ -4,6 +4,8 @@ // Status bar translation keys. export enum MessageId { + BatteryLow = 'battery.low', + BatteryOk = 'battery.ok', HubInfoConnectedTo = 'hubInfo.connectedTo', HubInfoHubType = 'hubInfo.hubType', HubInfoFirmware = 'hubInfo.firmware', diff --git a/src/status-bar/status-bar.scss b/src/status-bar/status-bar.scss index 52be658d..e823749a 100644 --- a/src/status-bar/status-bar.scss +++ b/src/status-bar/status-bar.scss @@ -11,6 +11,7 @@ background-color: $pb-pybricks-blue; height: $pb-status-bar-height; width: 100vw; + padding-inline: 5px; display: flex; align-items: center; } @@ -19,3 +20,41 @@ .pb-status-bar :first-child { margin-left: auto; } + +.pb-battery-indicator { + display: flex; + flex-direction: row; + align-items: center; + margin: 5px; +} + +.pb-battery-indicator-body { + border-radius: 3px; + border: 2px solid $pt-text-color; + background-color: $pt-text-color; + width: 30px; +} + +.pb-battery-indicator-body .#{$ns}-progress-bar { + height: 12px; +} + +.pb-battery-indicator-body .#{$ns}-progress-bar, +.pb-battery-indicator-body .#{$ns}-progress-meter { + border-radius: 3px; +} + +.pb-battery-indicator-tip { + border-top-right-radius: 1px; + border-bottom-right-radius: 1px; + border: 1px solid $pt-text-color; + background-color: $pt-text-color; + width: 3px; + height: 8px; +} + +.#{$ns}-dark .pb-battery-indicator-body, +.#{$ns}-dark .pb-battery-indicator-tip { + border-color: $pt-dark-text-color; + background-color: $pt-dark-text-color; +}