/* * MIT License * * Copyright (c) 2022 Robin E. R. Davies * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import React from 'react'; import IconButton from '@mui/material/IconButton'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; import DialogEx from './DialogEx'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import Switch from '@mui/material/Switch'; import FormControlLabel from '@mui/material/FormControlLabel'; import ResizeResponsiveComponent from './ResizeResponsiveComponent'; import WifiDirectConfigSettings from './WifiDirectConfigSettings'; import { Theme } from '@mui/material/styles'; import { WithStyles } from '@mui/styles'; import withStyles from '@mui/styles/withStyles'; import createStyles from '@mui/styles/createStyles'; import MenuItem from '@mui/material/MenuItem'; import WifiChannel from './WifiChannel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; const styles = (theme: Theme) => createStyles({ pgraph: { paddingBottom: 16 } }); declare let crypto: any; const NBSP = "\u00A0"; export interface WifiDirectConfigProps extends WithStyles { open: boolean, wifiDirectConfigSettings: WifiDirectConfigSettings, onOk: (wifiDirectConfigSettings: WifiDirectConfigSettings) => void, onClose: () => void } export interface WifiDirectConfigState { fullScreen: boolean; enabled: boolean; name: string; pin: string; nameError: boolean; nameErrorMessage: string; pinError: boolean; pinErrorMessage: string; countryCode: string; channel: string; controlWidth: number; landscapeLayout: boolean; controlSize: ("medium" | "small" | undefined); wifiChannels: WifiChannel[]; } const WifiDirectConfigDialog = withStyles(styles, { withTheme: true })( class extends ResizeResponsiveComponent { refName: React.RefObject; refPassword: React.RefObject; model: PiPedalModel; constructor(props: WifiDirectConfigProps) { super(props); this.model = PiPedalModelFactory.getInstance(); this.state = { fullScreen: this.useFullScreen(), enabled: this.props.wifiDirectConfigSettings.enable, name: this.props.wifiDirectConfigSettings.hotspotName, pin: this.props.wifiDirectConfigSettings.pin, nameError: false, nameErrorMessage: NBSP, pinError: false, pinErrorMessage: NBSP, countryCode: this.props.wifiDirectConfigSettings.countryCode, channel: this.props.wifiDirectConfigSettings.channel, controlWidth: 260, controlSize: this.windowSize.height < 600 ? "small" : "medium", landscapeLayout: this.useLandscapeLayout(), wifiChannels: [] }; this.model.getWifiChannels(this.props.wifiDirectConfigSettings.countryCode) .then((wifiChannels_) => { this.setState({ wifiChannels: wifiChannels_ }); }).catch((err) => { }); this.refName = React.createRef(); this.refPassword = React.createRef(); } useLandscapeLayout(): boolean { return (this.windowSize.height < 690 && this.windowSize.width > this.windowSize.height && this.windowSize.width > 690); } mounted: boolean = false; handleEnableChanged(e: any) { this.setState({ enabled: e.target.checked }); } onWindowSizeChanged(width: number, height: number): void { this.setState({ fullScreen: this.useFullScreen(), controlSize: height < 600 ? "small" : "medium", landscapeLayout: this.useLandscapeLayout() }) } useFullScreen(): boolean { return this.windowSize.height < 390; } componentDidMount() { super.componentDidMount(); this.mounted = true; } componentWillUnmount() { super.componentWillUnmount(); this.mounted = false; } componentDidUpdate(prevProps: WifiDirectConfigProps) { if (this.props.open && !prevProps.open) { this.setState({ enabled: this.props.wifiDirectConfigSettings.enable, name: this.props.wifiDirectConfigSettings.hotspotName, pin: this.props.wifiDirectConfigSettings.pin, countryCode: this.props.wifiDirectConfigSettings.countryCode, channel: this.props.wifiDirectConfigSettings.channel }); if (this.props.wifiDirectConfigSettings.countryCode !== this.state.countryCode) { this.model.getWifiChannels(this.props.wifiDirectConfigSettings.countryCode) .then((wifiChannels) => { this.setState({ wifiChannels: wifiChannels }); }) .catch((error) => { }); } } } utf8Encode: TextEncoder = new TextEncoder(); validateName(name: string): void { if (name.length === 0) { throw new RangeError("Required."); } let bytes = this.utf8Encode.encode("DIRECT-XX-" + name); if (bytes.length > 31) { throw new RangeError("Too long."); } } validatePin(pin: string) { if (pin.length === 0) throw new Error("Required."); if (pin.length !== 8) { throw new RangeError("Must be exactly 8 digits."); } for (let i = 0; i < pin.length; ++i) { let c = pin.charCodeAt(i); if (c < '0'.charCodeAt(0) || c > '9'.charCodeAt(0)) { throw new RangeError("Must be exactly 8 digits."); } } } handleOk() { let nameError = false; let nameErrorMessage = NBSP; let pinError = false; let pinErrorMessage = NBSP; if (this.state.enabled) { let name = this.state.name; try { this.validateName(name); } catch (e: any) { nameError = true; nameErrorMessage = "* " + e.message; } let pin = this.state.pin; try { this.validatePin(pin); } catch (e: any) { pinError = true; pinErrorMessage = "* " + e.message; } let error = pinError || nameError || this.state.countryCode === ""; if (error) { this.setState({ nameError: nameError, nameErrorMessage: nameErrorMessage, pinError: pinError, pinErrorMessage: pinErrorMessage }); } else { let wifiDirectConfigSettings = new WifiDirectConfigSettings(); wifiDirectConfigSettings.valid = true; wifiDirectConfigSettings.enable = this.state.enabled; wifiDirectConfigSettings.hotspotName = this.state.name; wifiDirectConfigSettings.countryCode = this.state.countryCode; wifiDirectConfigSettings.channel = this.state.channel; wifiDirectConfigSettings.pin = this.state.pin; this.props.onOk(wifiDirectConfigSettings); } } else { let wifiDirectConfigSettings = new WifiDirectConfigSettings(); wifiDirectConfigSettings.valid = true; wifiDirectConfigSettings.enable = false; this.props.onOk(wifiDirectConfigSettings); } } preventPasswordPrompt() { } handleChannelChange(e: any) { let value = e.target.value as string; this.setState({ channel: value }); } handleCountryChanged(e: any) { let value = e.target.value as string; this.setState({ countryCode: value }); this.model.getWifiChannels(value) .then(wifiChannels => { this.setState({ wifiChannels: wifiChannels }); }) .catch((error) => { }); } generateRandomPin(): string { // prefer crypto if it's available. let zero = "0".charCodeAt(0); try { let randomValue = new Int32Array(8); crypto.getRandomValues(randomValue); let s: string = ""; for (let i = 0; i < 8; ++i) { let k = randomValue[i]; if (k < 0) k = -k; k = k % 10; s += String.fromCharCode(zero + k); } return s; } catch (e) { } let zero2 = "a".charCodeAt(0); let s2 = ""; for (let i = 0; i < 8; ++i) { let k = Math.floor(Math.random() * 10); if (k < 0) k = -k; k = k % 10; s2 += String.fromCharCode(zero2 + k); } return s2; } onGenerateRandomPin() { let newPin = this.generateRandomPin(); this.setState({ pin: newPin, pinError: false, pinErrorMessage: NBSP }); if (this.refPassword.current) { this.refPassword.current.value = newPin; } } render() { let props = this.props; let { open, onClose } = props; const handleClose = () => { this.preventPasswordPrompt(); // let preventPasswordPrompt changes settle (it's HARD to prevent a password prompt) setTimeout(() => { onClose(); }, 100); }; return ( {this.state.landscapeLayout && (
{ this.props.onClose(); }} aria-label="back" size="large"> this.handleEnableChanged(e)} color="primary" /> )} label="Wi-Fi Direct Hotspot" />
this.setState({ name: e.target.value, nameError: false, nameErrorMessage: NBSP })} inputRef={this.refName} variant="filled" /> { this.setState({ pin: e.target.value, pinError: false, pinErrorMessage: NBSP }); }} label="Pin" inputProps={{ inputMode: 'numeric' }} fullWidth error={this.state.pinError} helperText={this.state.pinErrorMessage} defaultValue={this.state.pin} disabled={!this.state.enabled} variant="filled" />
{ this.handleCountryChanged(event); }} > {Object.entries(this.model.countryCodes).map(([key, value]) => { return ( {value} ); })} { this.handleChannelChange(event); }} > {this.state.wifiChannels.map((channel) => { let t = channel.channelId; if (t.startsWith("a")) t = t.substring(1); if (t.startsWith("g")) t = t.substring(1); let name = channel.channelName; if (name.startsWith("1 ") || name.startsWith("6 ") || name.startsWith("11 ")) { name += " *Recommended"; } return ( {name} ) })}
)} {(!this.state.landscapeLayout) && (
this.handleEnableChanged(e)} color="secondary" /> )} label="Wi-Fi Direct Hotspot" />
this.setState({ name: e.target.value, nameError: false, nameErrorMessage: NBSP })} inputRef={this.refName} variant="filled" />
{ this.setState({ pin: e.target.value, pinError: false, pinErrorMessage: NBSP }); }} label="Pin" inputProps={{ inputMode: 'numeric' }} fullWidth error={this.state.pinError} helperText={this.state.pinErrorMessage} defaultValue={this.state.pin} disabled={!this.state.enabled} variant="filled" />
{ this.handleCountryChanged(event); }} > {Object.entries(this.model.countryCodes).map(([key, value]) => { return ( {value} ); })}
{ this.handleChannelChange(event); }} > {this.state.wifiChannels.map((channel) => { let t = channel.channelId; if (t.startsWith("a")) t = t.substring(1); if (t.startsWith("g")) t = t.substring(1); let name = channel.channelName; if (name.startsWith("1 ") || name.startsWith("6 ") || name.startsWith("11 ")) { name += " *Recommended"; } return ( {name} ) })}
)}
); } }); export default WifiDirectConfigDialog;