Skip to content

Instantly share code, notes, and snippets.

@firstChairCoder
Last active June 26, 2024 14:59
Show Gist options
  • Save firstChairCoder/24a99c6a169f2a34c6d48e5ad592e74d to your computer and use it in GitHub Desktop.
Save firstChairCoder/24a99c6a169f2a34c6d48e5ad592e74d to your computer and use it in GitHub Desktop.
Hook files (please place each in a hooks folder under src)
import { useMemo } from "react";
import { View } from "react-native";
import Battery0Icon from "../../assets/images/battery-0-icon.svg";
import Battery25Icon from "../../assets/images/battery-25-icon.svg";
import Battery50Icon from "../../assets/images/battery-50-icon.svg";
import Battery75Icon from "../../assets/images/battery-75-icon.svg";
import Battery100Icon from "../../assets/images/battery-100-icon.svg";
import { COLORS } from "../constants";
/**
* @param {object} params
* @param {number} params.value A number between 0 and 100
* @param {string} params.color
*/
const BatteryIcon = ({ value, color }) => {
const level = useMemo(() => {
if (value < 12.5) {
return "empty";
} else if (value < 37.5) {
return "low";
} else if (value < 62.5) {
return "medium";
} else if (value < 87.5) {
return "high";
}
return "full";
}, [value]);
const buttonColor = value > 20 ? color : COLORS.alert;
return (
<View>
{level === "empty" && (
<Battery0Icon width={36} height={32} fill={buttonColor} />
)}
{level === "low" && (
<Battery25Icon width={36} height={32} fill={buttonColor} />
)}
{level === "medium" && (
<Battery50Icon width={36} height={32} fill={buttonColor} />
)}
{level === "high" && (
<Battery75Icon width={36} height={32} fill={buttonColor} />
)}
{level === "full" && (
<Battery100Icon width={36} height={32} fill={buttonColor} />
)}
</View>
);
};
export default BatteryIcon;
/**
* Colors
*/
// Color palette inspired in TailwindCSS colors:
// https://tailwindcss.com/docs/customizing-colors
export const PALETTE = {
black: "#000000",
white: "#ffffff",
slate300: "#cbd5e1",
red300: "#fca5a5",
red500: "#ef4444",
green300: "#86efac",
sky300: "#7dd3fc"
};
// Semantic colors
export const COLORS = {
base: PALETTE.slate300,
alert: PALETTE.red500
};
/**
* Fonts
*/
// Available fonts
export const APP_FONTS = [
"ShareTechRegular",
"PlayBold",
"FrederickaRegular",
"SpecialRegular",
"NanumRegular"
];
export const APP_COLORS = [
PALETTE.slate300,
PALETTE.sky300,
PALETTE.green300,
PALETTE.red300
];
// System font
export const FONT_SIZE = 18;
export const FONT_COLOR = PALETTE.slate300;
/**
* Metrics (spaces, borders, etc).
*/
export const GAP = 8;
export const BORDER_RADIUS = 10;
/**
* Languages
*/
export const SUPPORTED_LANGUAGES = ["en", "es"];
export const FALLBACK_LANGUAGE = "en";
/**
* Date and times
*/
export const AM_PM = "am_pm";
export const H24 = "24h";
/**
* Config
*/
export const DEFAULT_CONFIG = {
timeFont: "shareTechMono400Regular",
timeFormat: H24,
timeColor: PALETTE.slate300,
showSeconds: false,
showDate: true,
showBattery: true
};
import { useEffect, useState } from "react";
import Text from "./text";
/**
* @param {object} params
* @param {dayjs.Dayjs} params.value
* @param {string} params.color
* @param {object} [params.style]
*/
const DateDisplay = ({ value, color, style = {} }) => {
const [text, setText] = useState("");
useEffect(() => {
setText(value.format("ll"));
}, [value]);
return (
<Text fontSize={20} style={{ ...style, color }}>
{text}
</Text>
);
};
export default DateDisplay;
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
dayjs.extend(localizedFormat);
export default dayjs;
import { Pressable, StyleSheet } from "react-native";
import { GAP } from "../constants";
const styles = StyleSheet.create({
root: {
padding: 1.5 * GAP
}
});
const IconButton = ({ icon, onPress }) => {
return (
<Pressable
style={({ pressed }) => ({
...styles.root,
opacity: pressed ? 0.7 : 1
})}
onPress={onPress}
>
{icon}
</Pressable>
);
};
export default IconButton;
export * from "./useBatteryLevel";
export * from "./useOrientation";
export * from "./store-hooks";
import { Pressable, StyleSheet } from "react-native";
import { GAP, PALETTE } from "../constants";
import Text from "./text";
/**
* @param {object} params
* @param {string} params.label
* @param {() => void} params.onPress
*/
const styles = StyleSheet.create({
text: {
color: PALETTE.black,
paddingHorizontal: 6 * GAP,
paddingVertical: 2.5 * GAP
}
});
const MenuItem = ({ label, onPress }) => {
return (
<Pressable
onPress={onPress}
style={({ pressed }) => ({ opacity: pressed ? 0.3 : 1 })}
>
<Text style={styles.text}>{label}</Text>
</Pressable>
);
};
export default MenuItem;
/**
* @param {object} params
* @param {boolean} params.open
* @param {() => void} params.onClose
* @param {React.ReactNode} children
*/
import { Modal, Pressable, StyleSheet, View } from "react-native";
import { Children } from "react";
import { BORDER_RADIUS, GAP, PALETTE } from "../constants";
const styles = StyleSheet.create({
root: {
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.7)",
flex: 1,
justifyContent: "center"
},
contents: {
backgroundColor: PALETTE.white,
borderRadius: BORDER_RADIUS,
paddingVertical: 0.5 * GAP
},
item: {
borderBottomColor: PALETTE.black,
borderBottomWidth: 1
}
});
const ModalMenu = ({ open, onClose, children }) => {
const childItems = Children.toArray(children);
return (
<Modal visible={open} animationType="fade" transparent statusBarTranslucent>
<Pressable style={styles.root} onPress={onClose}>
<View style={styles.contents}>
{childItems.map((child, i) => (
<View
key={i}
style={{
...styles.item,
borderBottomWidth: i < childItems.length - 1 ? 1 : 0
}}
>
{child}
</View>
))}
</View>
</Pressable>
</Modal>
);
};
export default ModalMenu;
import { useDispatch, useSelector } from "react-redux";
export const useTimeFont = (_) => {
const dispatch = useDispatch();
const timeFont = useSelector((state) => state.timeFont);
const setTimeFont = (payload) => dispatch({ type: "SET_TIME_FONT", payload });
return { timeFont, setTimeFont };
};
export const useTimeFormat = (_) => {
const dispatch = useDispatch();
const timeFormat = useSelector((state) => state.timeFormat);
const setTimeFormat = (payload) =>
dispatch({ type: "SET_TIME_FORMAT", payload });
return { timeFormat, setTimeFormat };
};
export const useTimeColor = (_) => {
const dispatch = useDispatch();
const timeColor = useSelector((state) => state.timeColor);
const setTimeColor = (payload) =>
dispatch({ type: "SET_TIME_COLOR", payload });
return { timeColor, setTimeColor };
};
export const useShowSeconds = (_) => {
const dispatch = useDispatch();
const showSeconds = useSelector((state) => state.showSeconds);
const setShowSeconds = (payload) =>
dispatch({ type: "SET_SHOW_SECONDS", payload });
return { showSeconds, setShowSeconds };
};
export const useShowDate = (_) => {
const dispatch = useDispatch();
const showDate = useSelector((state) => state.showDate);
const setShowDate = (payload) => dispatch({ type: "SET_SHOW_DATE", payload });
return { showDate, setShowDate };
};
export const useShowBattery = (_) => {
const dispatch = useDispatch();
const showBattery = useSelector((state) => state.showBattery);
const setShowBattery = (payload) =>
dispatch({ type: "SET_SHOW_BATTERY", payload });
return { showBattery, setShowBattery };
};
import { useEffect, useMemo, useState } from "react";
import { AM_PM } from "../constants";
import { useOrientation } from "../hooks/use-orientation";
import Text from "./text";
/**
* @param {object} params
* @param {dayjs.Dayjs} params.value
* @param {string} params.color
* @param {'am_pm' | '24h'} params.format
* @param {boolean} params.showSeconds
* @param {string} [params.font]
* @param {object} [params.style]
*/
const TimeDisplay = ({
value,
color,
format,
showSeconds = false,
font = "ShareTechRegular",
style = {}
}) => {
const [text, setText] = useState("");
const orientation = useOrientation();
const textWidth = useMemo(() => {
return orientation === "portrait" ? 65 : 75;
}, [orientation]);
// console.log("width: ", textWidth);
const timeFormat = useMemo(() => {
if (format === AM_PM) {
return showSeconds ? "h:mm:ss a" : "h:mm a";
}
return showSeconds ? "HH:mm:ss" : "HH:mm";
}, [format, showSeconds]);
useEffect(() => {
setText(value.format(timeFormat));
}, [value, timeFormat]);
return (
<Text fontSize={textWidth} style={{ ...style, color, fontFamily: font }}>
{text}
</Text>
);
};
export default TimeDisplay;
import { useEffect, useState } from "react";
import * as Battery from "expo-battery";
/**
* Gets the battery level of the device as a number between 0 and 1, inclusive.
* If the device does not support retrieving the battery level, this method
* returns -1.
*/
export const useBatteryLevel = () => {
const [level, setLevel] = useState(-1);
useEffect(() => {
// checks the battery level every minute
const interval = setInterval(() => {
Battery.getBatteryLevelAsync().then(setLevel);
}, 60000);
Battery.getBatteryLevelAsync().then(setLevel);
return () => clearInterval(interval);
}, []);
return level;
};
import { useWindowDimensions } from "react-native";
export const useOrientation = () => {
const { width, height } = useWindowDimensions();
return width < height ? "portrait" : "landscape";
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment