Skip to content

Commit

Permalink
Add battery percentage message
Browse files Browse the repository at this point in the history
Fixes #42
  • Loading branch information
evertonstz committed Apr 25, 2024
1 parent 01b0ef6 commit 71a85c7
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 7 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/web-bluetooth": "^0.0.20",
"@uidotdev/usehooks": "^2.4.1",
"class-variance-authority": "^0.7.0",
Expand Down
56 changes: 56 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions src/components/BatteryIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { usePaxContext } from '@/state/hooks';
import {
Battery,
BatteryFull,
BatteryLow,
BatteryMedium,
Unplug,
} from 'lucide-react';

import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';

export function BatteryIndicator() {
const { state } = usePaxContext();
const tooltipContent = state.batteryPercentage
? `${state.batteryPercentage}%`
: 'Disconnected';

const getIcon = () => {
if (!state.batteryPercentage) {
return <Unplug />;
}
if (state.batteryPercentage < 25) {
return <Battery />;
}
if (state.batteryPercentage < 50) {
return <BatteryLow />;
}
if (state.batteryPercentage < 75) {
return <BatteryMedium />;
}
if (state.batteryPercentage >= 75) {
return <BatteryFull />;
}
};

return (
<Tooltip>
<TooltipTrigger>{getIcon()}</TooltipTrigger>
<TooltipContent>{tooltipContent}</TooltipContent>
</Tooltip>
);
}
8 changes: 7 additions & 1 deletion src/components/MainContent/SelectedDevice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { usePaxBluetoothServices } from '@/hooks';
import { BaseBluetoothException } from '@/hooks/usePaxBluetoothServices/useBluetooth/exceptions';
import { Pax } from '@/pax';
import { post } from '@/pax/containers/api';
import { ColorThemeMessage } from '@/pax/core/messages';
import {
BatteryPercentageMessage,
ColorThemeMessage,
} from '@/pax/core/messages';
import { ColorTheme } from '@/pax/shared/types';
import { usePaxContext } from '@/state/hooks';
import { isEqual } from 'lodash';
Expand Down Expand Up @@ -47,6 +50,9 @@ export const SelectedDevice = ({ currentDevice }: SelectedDeviceProps) => {
if (message instanceof Pax.lib.messages.ColorThemeMessage) {
actions.setColorTheme(message.theme);
}
if (message instanceof BatteryPercentageMessage) {
actions.setBatteryPercentage(message.percentage);
}
})
.catch(e => {
if (e instanceof BaseBluetoothException) {
Expand Down
20 changes: 15 additions & 5 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useDevicesLocalStorage } from '@/hooks';
import { useDevicesLocalStorage, useIsMobile } from '@/hooks';
import { Cloudy } from 'lucide-react';
import { useState } from 'react';

import { ThemeDropdownButton } from '.';
import { BatteryIndicator } from './BatteryIndicator';
import DevicesModal from './DevicesModal';
import { Button } from './ui/button';

const Navbar = () => {
const isMobile = useIsMobile();
const [isDeviceModalOpen, openDevicesModal] = useState(false);
const deviceStore = useDevicesLocalStorage();

Expand All @@ -28,10 +30,18 @@ const Navbar = () => {
</div>
<div className="flex-grow bg-white"></div>
<div className="flex gap-3">
<Button onClick={openDevicesModal as () => void}>
{deviceStore.currentDevice
? deviceStore.currentDevice.serial
: 'Devices'}
<Button
variant={deviceStore.currentDevice ? 'secondary' : 'default'}
onClick={openDevicesModal as () => void}
>
{deviceStore.currentDevice ? (
<div className="flex flex-row place-items-center gap-3">
<BatteryIndicator />
{isMobile ? null : deviceStore.currentDevice.serial}
</div>
) : (
'Devices'
)}
</Button>
<ThemeDropdownButton />
</div>
Expand Down
33 changes: 33 additions & 0 deletions src/components/ui/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { cn } from '@/lib/utils';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import * as React from 'react';

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
`z-50 overflow-hidden rounded-md border border-neutral-200 bg-white px-3 py-1.5
text-sm text-neutral-950 shadow-md animate-in fade-in-0 zoom-in-95
data-[state=closed]:animate-out data-[state=closed]:fade-out-0
data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2
data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800
dark:bg-neutral-950 dark:text-neutral-50`,
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
5 changes: 4 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './App.tsx';
import { TooltipProvider } from './components/ui/tooltip.tsx';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ThemeProvider>
<PaxProvider>
<App />
<TooltipProvider>
<App />
</TooltipProvider>
</PaxProvider>
</ThemeProvider>
</React.StrictMode>,
Expand Down
3 changes: 3 additions & 0 deletions src/pax/containers/api/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ColorThemeMessage } from '@/pax/core/messages/ColorThemeMessage';

import {
ActualTemperatureMessage,
BatteryPercentageMessage,
HeaterSetPointMessage,
HeatingStateMessage,
MessageAbs,
Expand Down Expand Up @@ -33,6 +34,8 @@ export const decodeDecryptedPacket = (
return new HeatingStateMessage(packet);
case Messages.ATTRIBUTE_COLOR_THEME:
return ColorThemeMessage.createWithPacket(packet);
case Messages.ATTRIBUTE_BATTERY:
return new BatteryPercentageMessage(packet);
default:
return new UnknownMessage(messageType, packet);
}
Expand Down
16 changes: 16 additions & 0 deletions src/pax/core/messages/BatteryPercentageMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Messages } from '../../shared/enums/Messages';
import { PaxDecryptedPacket } from '../../shared/models/Packet';
import { MessageAbs } from './MessageAbs';

export class BatteryPercentageMessage implements MessageAbs {
readonly percentage: number;
readonly messageType: Messages;
readonly packet: PaxDecryptedPacket;

constructor(packet: PaxDecryptedPacket) {
const temperature = packet.getUint8(1);
this.percentage = temperature;
this.messageType = Messages.ATTRIBUTE_BATTERY;
this.packet = packet;
}
}
1 change: 1 addition & 0 deletions src/pax/core/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './MessageAbs';
export * from './ReadAndWriteMessageAbs';
export * from './HeatingStateMessage';
export * from './ColorThemeMessage';
export * from './BatteryPercentageMessage';
11 changes: 11 additions & 0 deletions src/state/paxState/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ export type PaxActions =
| { type: 'SET_HEATER_SETPOINT_TEMPERATURE'; payload: number }
| { type: 'SET_HEATING_STATE'; payload: Pax.lib.HeatingStates }
| { type: 'SET_COLOR_THEME'; payload: Pax.lib.ColorTheme }
| { type: 'SET_BATTERY_PERCENTAGE'; payload: number }
| { type: 'RESET_PAX_STATE' };

export interface BuiltPaxActions {
setActualTemperature: (temperature: number) => void;
setHeaterSetPointTemperature: (temperature: number) => void;
setHeatingState: (heatingSate: Pax.lib.HeatingStates) => void;
setColorTheme: (theme: Pax.lib.ColorTheme) => void;
setBatteryPercentage: (percentage: number) => void;
resetPaxState: () => void;
}

Expand Down Expand Up @@ -47,6 +49,13 @@ const setColorTheme = (
dispatch({ type: 'SET_COLOR_THEME', payload: theme });
};

const setBatteryPercentage = (
dispatch: React.Dispatch<PaxActions>,
percentage: number,
) => {
dispatch({ type: 'SET_BATTERY_PERCENTAGE', payload: percentage });
};

export const buildActions = (
dispatch: React.Dispatch<PaxActions>,
): BuiltPaxActions => {
Expand All @@ -59,6 +68,8 @@ export const buildActions = (
setHeatingState(dispatch, heatingSate),
setColorTheme: (theme: Pax.lib.ColorTheme) =>
setColorTheme(dispatch, theme),
setBatteryPercentage: (percentage: number) =>
setBatteryPercentage(dispatch, percentage),
resetPaxState: () => resetPaxState(dispatch),
};
};
2 changes: 2 additions & 0 deletions src/state/paxState/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const reducer = (state: PaxState, action: PaxActions): PaxState => {
return { ...state, heatingSate: action.payload };
case 'SET_COLOR_THEME':
return { ...state, colorTheme: action.payload };
case 'SET_BATTERY_PERCENTAGE':
return { ...state, batteryPercentage: action.payload };
case 'RESET_PAX_STATE':
return initialPaxState;
default:
Expand Down
1 change: 1 addition & 0 deletions src/state/paxState/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export interface PaxState {
heaterSetPointTemperature: number;
heatingSate?: Pax.lib.HeatingStates;
colorTheme?: Pax.lib.ColorTheme;
batteryPercentage?: number;
}

0 comments on commit 71a85c7

Please sign in to comment.