├── .expo-shared
└── assets.json
├── .gitignore
├── App.js
├── App
├── Add.js
├── Browser.js
└── Devices.js
├── README.md
├── app.json
├── assets
├── android-icon-foreground.png
├── gplay-feature-graphic.png
├── icon.png
├── qr-code.svg
└── splash.png
├── babel.config.js
├── package-lock.json
├── package.json
└── screenshots
├── S10-Add.jpg
├── S10-Devices.jpg
├── S10-Lights.jpg
├── SP-S10-Add.png
├── SP-S10-Devices.png
├── SP-S10-Lights.png
├── SP-iPhone-Add.png
├── SP-iPhone-Devices.png
├── SP-iPhone-Lights.png
├── iPad-Add.png
├── iPad-Devices.png
├── iPad-Lights.png
├── iPhone-Add.JPG
├── iPhone-Devices.JPG
└── iPhone-Lights.JPG
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 |
12 | # macOS
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { StatusBar } from 'expo-status-bar'
3 | import { getStatusBarHeight } from 'react-native-status-bar-height';
4 | import { ThemeProvider } from 'react-native-elements'
5 | import { View, Platform, StyleSheet } from 'react-native';
6 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
7 | import { NavigationContainer } from '@react-navigation/native';
8 | import { SafeAreaProvider } from 'react-native-safe-area-context'
9 | import { MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
10 | import AsyncStorage from '@react-native-community/async-storage'
11 | import Browser from './App/Browser'
12 | import Devices from './App/Devices'
13 | import Add from './App/Add'
14 |
15 | global.theme = {
16 | colors: {
17 | primary: '#009688',
18 | secondary: '#333333',
19 | success: '#3CB479',
20 | error: '#F44336',
21 | warning: '#BAA441',
22 | info: '#949494',
23 | lightGray: '#d1d1d1',
24 | background: '#FAFAFA'
25 | }
26 | }
27 |
28 | global.styles = StyleSheet.create({
29 | button: {
30 | borderRadius: 4,
31 | paddingVertical: 6,
32 | paddingHorizontal: 16
33 | },
34 | text: {
35 | fontFamily: "Roboto",
36 | },
37 | })
38 |
39 | //This ensures that there is a single color status bar on both IOS and Android and that the content starts below it.
40 | const ColoredStatusBar = ({ backgroundColor, ...props }) => (
41 |
42 |
43 |
44 | );
45 |
46 | const Tab = createBottomTabNavigator();
47 |
48 | export default function App() {
49 | const [selectedDevice, setSelectedDevice] = useState(-1)
50 | const [devices, setDevices] = useState([])
51 |
52 | const saveData = async () => {
53 | try {
54 | await AsyncStorage.setItem('devices', JSON.stringify(devices))
55 | await AsyncStorage.setItem('selectedDevice', JSON.stringify(selectedDevice))
56 | } catch (e) {
57 | console.warn('Failed to save the data to the storage. Error: ' + e)
58 | }
59 | }
60 |
61 | const readData = async () => {
62 | try {
63 | const devicesRead = await AsyncStorage.getItem('devices')
64 | const selectedDeviceRead = await AsyncStorage.getItem('selectedDevice')
65 |
66 | if (devicesRead !== null) {
67 | setDevices(JSON.parse(devicesRead));
68 | }
69 | else {
70 | console.warn("Error reading devices")
71 | }
72 |
73 | if (selectedDeviceRead !== null) {
74 | setSelectedDevice(JSON.parse(selectedDeviceRead));
75 | }
76 | else {
77 | console.warn("Error reading selected device")
78 | }
79 | } catch (e) {
80 | console.warn('Failed to fetch the data from storage')
81 | }
82 | }
83 |
84 | useEffect(() => {
85 | readData();
86 | }, [])
87 |
88 | useEffect(() => {
89 | saveData();
90 | }, [devices, selectedDevice])
91 |
92 | return (
93 |
94 |
95 |
96 |
97 |
118 | (
123 |
124 | ),
125 | }}>
126 | {() => { return }}
127 |
128 | (
133 |
134 | ),
135 | }}>
136 | {() => { return }}
137 |
138 | (
143 |
144 | ),
145 | }}>
146 | {() => { return }}
147 |
148 |
149 |
150 |
151 |
152 | );
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/App/Add.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { View, Text, ScrollView } from 'react-native'
3 | import { Input, Button } from 'react-native-elements'
4 | import { SafeAreaView } from 'react-native-safe-area-context'
5 | import DropDownPicker from 'react-native-dropdown-picker';
6 | import produce from 'immer'
7 | import { useNavigation } from '@react-navigation/native'
8 | import { Platform } from 'react-native';
9 |
10 | export default function Add({ setDevices }) {
11 | const navigation = useNavigation();
12 | const [device, setDevice] = useState({
13 | name: "",
14 | protocol: "http",
15 | ip: "",
16 | port: "8888"
17 | })
18 |
19 | const addDevice = () => {
20 | console.log(device);
21 | if(device.name !== "" && device.ip !== "") {
22 | setDevices(devices => produce(devices, devicesDraft => { devicesDraft.push({ name: device.name, protocol: device.protocol, ip: device.ip, port: device.port }) }))
23 | setDevice({
24 | name: "",
25 | protocol: "http",
26 | ip: "",
27 | port: "8888"
28 | })
29 | navigation.navigate("Devices");
30 | }
31 | }
32 |
33 | return (
34 |
35 |
36 |
37 | Add Device:
38 |
39 | Name:
40 | setDevice({ ...device, name: value })} placeholder="Name" />
41 |
42 |
43 | IP Address:
44 | setDevice({ ...device, ip: value })} placeholder="192.168.1.67" />
45 |
46 |
47 | Port:
48 | setDevice({ ...device, port: value })} placeholder="8888" />
49 |
50 |
51 | Protocol:
52 |
53 | { setDevice({ ...device, protocol: protocol.value }) }}
68 | />
69 |
70 |
71 |
76 |
77 |
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/App/Browser.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext, useEffect } from 'react'
2 | import { View, Text, ActivityIndicator, StyleSheet } from 'react-native'
3 | import { SafeAreaView } from 'react-native-safe-area-context'
4 | import { WebView } from 'react-native-webview'
5 | import { useNavigation } from '@react-navigation/native'
6 | import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
7 | import { MaterialCommunityIcons } from '@expo/vector-icons';
8 | import { ThemeContext, Button } from 'react-native-elements'
9 | import { Platform } from 'react-native'
10 |
11 | export default function Browser({ devices, selectedDevice }) {
12 | const [webViewState, setWebViewState] = useState(1);
13 | // 0 means show page
14 | // 1 means show loading screen
15 | // -1 means show error screen
16 |
17 | const { theme } = useContext(ThemeContext)
18 | const navigation = useNavigation();
19 |
20 | useEffect(() => {
21 | if(devices.length === 0) {
22 | navigation.navigate("Add");
23 | }
24 | })
25 |
26 | useEffect(() => {
27 | if(selectedDevice !== -1 && devices.length !== 0) {
28 | setWebViewState(1);
29 | }
30 | else {
31 | setWebViewState(2);
32 | }
33 | }, [selectedDevice])
34 |
35 | let webViewRef;
36 |
37 | const displaySpinner = () => {
38 | return (
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | const displayNoDevice = () => {
46 | return (
47 |
48 |
49 | No device selected
50 |
51 |
53 | )
54 | }
55 |
56 | const displayError = () => {
57 | console.log(devices[selectedDevice])
58 | if(selectedDevice === -1 || devices.length === 0) {
59 | return displayNoDevice()
60 | }
61 | return (
62 |
63 |
64 |
65 | {"Failed to load the " }
66 | { devices[selectedDevice].name }
67 | { " device"}
68 |
69 |
70 | } title=" TRY AGAIN" onPress={() => { webViewRef.reload() }} />
71 |
72 | )
73 | }
74 |
75 | return (
76 |
77 | { webViewState === 1 ? displaySpinner() : null}
78 | { webViewState === -1 ? displayError() : null }
79 | {
80 | devices.length !== 0 && selectedDevice !== -1 ?
81 | (webViewRef = ref)}
83 | startInLoadingState={true}
84 | onError={() => { setWebViewState(-1) }}
85 | onLoad={() => { setWebViewState(0) }}
86 | onLoadStart={() => { setWebViewState(1) }}
87 | onNavigationStateChange={(navState) => { if (navState.url === "about:blank" && !navState.loading) setWebViewState(-1) }} //Necessary because sometimes it loads about:blank when a site doesn't render and says that the result was successful
88 | style={[ Platform.OS !== 'ios' ? { marginBottom: useBottomTabBarHeight() } : null, webViewState === 0 ? { display: "flex" } : { display: "none" }]}
89 | source={{ uri: devices[selectedDevice].protocol + "://" + devices[selectedDevice].ip + ":" + devices[selectedDevice].port + "/" }} />
90 | :
91 | displayNoDevice()
92 | }
93 |
94 | )
95 | }
96 |
97 | const styles = { ...global.styles, ...StyleSheet.create({
98 | text: {
99 | fontSize: 18,
100 | textAlign: 'center',
101 | paddingHorizontal: 55
102 | },
103 | view: {
104 | height: '100%',
105 | flexDirection: 'column',
106 | justifyContent: 'center',
107 | alignItems: 'center',
108 | }
109 | })}
--------------------------------------------------------------------------------
/App/Devices.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react'
2 | import { View, TouchableWithoutFeedback, StyleSheet, FlatList } from 'react-native'
3 | import { SafeAreaView } from 'react-native-safe-area-context'
4 | import produce from 'immer'
5 | import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
6 | import { MaterialCommunityIcons } from '@expo/vector-icons';
7 | import { ListItem, Icon } from 'react-native-elements'
8 | import hexToRgba from 'hex-to-rgba';
9 | import { useIsFocused, useNavigation } from '@react-navigation/native'
10 | import { Platform } from 'react-native'
11 |
12 | export default function Devices({ devices, setDevices, selectedDevice, setSelectedDevice }) {
13 | const [isDeleting, setIsDeleting] = useState(false);
14 |
15 | const isFocused = useIsFocused();
16 | const navigation = useNavigation();
17 |
18 | useEffect(() => {
19 | if(isFocused && devices.length === 0) {
20 | navigation.navigate("Add");
21 | }
22 | }, [isFocused])
23 |
24 | const deleteDevice = (deviceIdx) => {
25 | if(selectedDevice == deviceIdx) {
26 | setSelectedDevice(-1)
27 | }
28 | setDevices(devices => produce(devices, devicesDraft => { devicesDraft.splice(deviceIdx, 1) }))
29 | if(deviceIdx < selectedDevice && selectedDevice !== -1) {
30 | setSelectedDevice(selectedDevice - 1);
31 | }
32 | }
33 |
34 | const styles = {
35 | ...global.styles, ...StyleSheet.create({
36 | floating: {
37 | borderRadius: 100,
38 | padding: 10,
39 | backgroundColor: isDeleting ? global.theme.colors.success : global.theme.colors.error,
40 | position: 'absolute',
41 | bottom: 10 + (Platform.OS === 'ios' ? useBottomTabBarHeight() : 0),
42 | right: 10,
43 | zIndex: 2
44 | },
45 | })
46 | }
47 |
48 | const renderItem = ({ item: device, index: i }) => (
49 | setSelectedDevice(i)}>
50 | deleteDevice(i) } : undefined}
52 | bottomDivider
53 | containerStyle={{ backgroundColor: selectedDevice == i ? hexToRgba(global.theme.colors.success, 0.25) : global.theme.colors.background }}
54 | >
55 |
56 | {device.name}
57 | {device.ip}
58 |
59 | {isDeleting ? deleteDevice(i)} /> : undefined}
60 |
61 |
62 | );
63 |
64 | return (
65 |
66 | index.toString()}
68 | renderItem={renderItem}
69 | data={devices}
70 | />
71 | { setIsDeleting(!isDeleting) }}>
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LedFxMobile
2 |
3 |
4 |
5 | Control your [LedFx](https://github.com/LedFx/LedFx) sound reactive LEDs from your phone! See [Expo Installation](#expo-installation) for IOS installation instructions.
6 |
7 | ## Screenshots
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | More screenshots including iPad screenshots are available in `screenshots/`.
16 |
17 | ## Expo Installation
18 |
19 | Just download the Expo app on either the [Play store](https://play.google.com/store/apps/details?id=host.exp.exponent&hl=en_US&gl=US) or the [App store](https://apps.apple.com/us/app/expo-client/id982107779) and scan the QR code below.
20 |
21 |
22 |
23 | ## Todo
24 |
25 | - Device discovery (needs backend support)
26 | - Power on/off (needs backend support)
27 | - Brightness controls (needs backend support)
28 | - Speed up devices list rendering
29 | - Redesign Add device screen
30 |
31 | ## Contributing
32 |
33 | This app is built with [React Native](https://reactnative.dev/) using [Expo](https://expo.io/). You'll need to install the [expo-cli](https://docs.expo.io/get-started/installation/) and the [latest version of Node.JS](https://nodejs.org/en/).
34 |
35 | Be sure to use the expo versions of npm commands. For example use `expo install` rather than `npm install` (this one is very important since it could install the wrong versions otherwise).
36 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "LedFx",
4 | "slug": "LedFx",
5 | "version": "1.1.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "primaryColor": "#333333",
9 | "description": "Control your LedFx sound reactive LEDs from your phone!",
10 | "privacy": "public",
11 | "splash": {
12 | "image": "./assets/splash.png",
13 | "resizeMode": "contain",
14 | "backgroundColor": "#333333"
15 | },
16 | "updates": {
17 | "fallbackToCacheTimeout": 0
18 | },
19 | "assetBundlePatterns": [
20 | "**/*"
21 | ],
22 | "ios": {
23 | "supportsTablet": true,
24 | "buildNumber": "1.1.0",
25 | "bundleIdentifier": "com.shirom.ledfx"
26 | },
27 | "android": {
28 | "adaptiveIcon": {
29 | "foregroundImage": "./assets/android-icon-foreground.png",
30 | "backgroundColor": "#333333"
31 | },
32 | "versionCode": 3,
33 | "package": "com.shirom.ledfx"
34 | },
35 | "androidNavigationBar": {
36 | "barStyle": "light-content",
37 | "backgroundColor": "#333333"
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/assets/android-icon-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/assets/android-icon-foreground.png
--------------------------------------------------------------------------------
/assets/gplay-feature-graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/assets/gplay-feature-graphic.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/assets/icon.png
--------------------------------------------------------------------------------
/assets/qr-code.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject"
9 | },
10 | "dependencies": {
11 | "@fortawesome/fontawesome-svg-core": "^1.2.32",
12 | "@fortawesome/free-regular-svg-icons": "^5.15.1",
13 | "@fortawesome/free-solid-svg-icons": "^5.15.1",
14 | "@fortawesome/react-native-fontawesome": "^0.2.6",
15 | "@react-native-community/async-storage": "~1.12.0",
16 | "@react-navigation/bottom-tabs": "^5.11.1",
17 | "@react-navigation/native": "^5.8.9",
18 | "expo": "^40.0.0",
19 | "expo-status-bar": "~1.0.3",
20 | "hex-to-rgba": "^2.0.1",
21 | "immer": "^8.0.4",
22 | "react": "16.13.1",
23 | "react-dom": "16.13.1",
24 | "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz",
25 | "react-native-dropdown-picker": "^3.8.3",
26 | "react-native-elements": "^3.3.2",
27 | "react-native-paper": "^4.5.0",
28 | "react-native-safe-area-context": "3.1.9",
29 | "react-native-screens": "~2.15.2",
30 | "react-native-status-bar-height": "^2.6.0",
31 | "react-native-svg": "12.1.0",
32 | "react-native-web": "^0.13.18",
33 | "react-native-webview": "11.0.0"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "~7.9.0"
37 | },
38 | "private": true
39 | }
40 |
--------------------------------------------------------------------------------
/screenshots/S10-Add.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/S10-Add.jpg
--------------------------------------------------------------------------------
/screenshots/S10-Devices.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/S10-Devices.jpg
--------------------------------------------------------------------------------
/screenshots/S10-Lights.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/S10-Lights.jpg
--------------------------------------------------------------------------------
/screenshots/SP-S10-Add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/SP-S10-Add.png
--------------------------------------------------------------------------------
/screenshots/SP-S10-Devices.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/SP-S10-Devices.png
--------------------------------------------------------------------------------
/screenshots/SP-S10-Lights.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/SP-S10-Lights.png
--------------------------------------------------------------------------------
/screenshots/SP-iPhone-Add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/SP-iPhone-Add.png
--------------------------------------------------------------------------------
/screenshots/SP-iPhone-Devices.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/SP-iPhone-Devices.png
--------------------------------------------------------------------------------
/screenshots/SP-iPhone-Lights.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/SP-iPhone-Lights.png
--------------------------------------------------------------------------------
/screenshots/iPad-Add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/iPad-Add.png
--------------------------------------------------------------------------------
/screenshots/iPad-Devices.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/iPad-Devices.png
--------------------------------------------------------------------------------
/screenshots/iPad-Lights.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/iPad-Lights.png
--------------------------------------------------------------------------------
/screenshots/iPhone-Add.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/iPhone-Add.JPG
--------------------------------------------------------------------------------
/screenshots/iPhone-Devices.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/iPhone-Devices.JPG
--------------------------------------------------------------------------------
/screenshots/iPhone-Lights.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiromMakkad/LedFxMobile/4fef83f09d607bef7dae543520b7d318a086298b/screenshots/iPhone-Lights.JPG
--------------------------------------------------------------------------------