Skip to content

Commit d5c0590

Browse files
Added expo-cellular hooks, utils, component demo implementation.
1 parent 50cdd58 commit d5c0590

File tree

3 files changed

+187
-44
lines changed

3 files changed

+187
-44
lines changed

components/CellularDemo.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React from "react";
2+
import { ScrollView, Text, StyleSheet, View, Button } from "react-native";
3+
import { useCeullar } from "@/hooks/useCellular";
4+
import * as Cellular from "expo-cellular";
5+
6+
const CellularDemo = () => {
7+
const { cellularInfo, refreshCellularInfo, requestPermissionsAsync } =
8+
useCeullar();
9+
10+
const handleRequestPermissions = async () => {
11+
try {
12+
await requestPermissionsAsync();
13+
refreshCellularInfo(); // Refresh state to show updated permissions
14+
} catch (error) {
15+
console.error("Permission request failed:", error);
16+
}
17+
};
18+
return (
19+
<ScrollView contentContainerStyle={styles.container}>
20+
<Text style={styles.header}>Cellular Info Demo</Text>
21+
<Text style={styles.text}>
22+
Allows VoIP:{" "}
23+
{cellularInfo.allowsVoip !== null
24+
? String(cellularInfo.allowsVoip)
25+
: "N/A"}
26+
</Text>
27+
<Text style={styles.text}>Carrier: {cellularInfo.carrier || "N/A"}</Text>
28+
<Text style={styles.text}>
29+
Cellular Generation:{" "}
30+
{Cellular.CellularGeneration[cellularInfo.cellularGeneration] ||
31+
"UNKNOWN"}
32+
</Text>
33+
<Text style={styles.text}>
34+
ISO Country Code: {cellularInfo.isoCountryCode || "N/A"}
35+
</Text>
36+
<Text style={styles.text}>
37+
Mobile Country Code: {cellularInfo.mobileCountryCode || "N/A"}
38+
</Text>
39+
<Text style={styles.text}>
40+
Mobile Network Code: {cellularInfo.mobileNetworkCode || "N/A"}
41+
</Text>
42+
<Text style={styles.text}>
43+
Permission Status:{" "}
44+
{cellularInfo.permission ? cellularInfo.permission.status : "N/A"}
45+
</Text>
46+
<View style={styles.buttonContainer}>
47+
<Button
48+
title="Refresh Info"
49+
onPress={refreshCellularInfo}
50+
color="#61dafb"
51+
/>
52+
</View>
53+
<View style={styles.buttonContainer}>
54+
<Button
55+
title="Request Permissions"
56+
onPress={handleRequestPermissions}
57+
color="#61dafb"
58+
/>
59+
</View>
60+
</ScrollView>
61+
);
62+
};
63+
64+
export default CellularDemo;
65+
66+
const styles = StyleSheet.create({
67+
container: {
68+
flexGrow: 1,
69+
padding: 20,
70+
backgroundColor: "#121212",
71+
alignItems: "flex-start",
72+
},
73+
header: {
74+
fontSize: 24,
75+
marginBottom: 20,
76+
color: "#ffffff",
77+
},
78+
text: {
79+
fontSize: 16,
80+
marginBottom: 10,
81+
color: "#ffffff",
82+
},
83+
buttonContainer: {
84+
marginVertical: 10,
85+
width: "100%",
86+
},
87+
});

hooks/useClipboard.ts

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect, useState, useRef } from "react";
22
import { AppState, Platform } from "react-native";
33
import { clipboard } from "../utils/clipboard";
44

55
export const useClipboard = () => {
66
const [clipboardContent, setClipboardContent] = useState<string>("");
77
const [hasCopiedText, setHasCopiedText] = useState(false);
88
const [history, setHistory] = useState<string[]>([]);
9-
const [previousContent, setPreviousContent] = useState<string>("");
9+
const previousContentRef = useRef<string>("");
10+
const isMounted = useRef(true);
1011

1112
const checkClipboard = async () => {
1213
try {
1314
const content = await clipboard.getString();
14-
if (content !== previousContent) {
15-
setPreviousContent(content);
15+
if (content !== previousContentRef.current) {
16+
previousContentRef.current = content;
1617
setClipboardContent(content);
1718
setHasCopiedText(content.length > 0);
1819
addToHistory(content);
@@ -24,66 +25,60 @@ export const useClipboard = () => {
2425

2526
const addToHistory = (content: string) => {
2627
if (content.trim() && content !== history[0]) {
27-
setHistory((prev) => [content, ...prev.slice(0, 49)]); // Keep last 50 items
28+
setHistory((prev) => [content, ...prev.slice(0, 49)]);
2829
}
2930
};
3031

31-
// useEffect(() => {
32-
// const initialLoad = async () => {
33-
// const content = await clipboard.getString();
34-
// setClipboardContent(content);
35-
// setHasCopiedText(content.length > 0);
36-
// addToHistory(content);
37-
// };
38-
// initialLoad();
39-
40-
// const subscription = clipboard.addListener((content) => {
41-
// setClipboardContent(content);
42-
// setHasCopiedText(content.length > 0);
43-
// addToHistory(content);
44-
// });
45-
46-
// return () => {
47-
// clipboard.removeListener(subscription);
48-
// };
49-
// }, []);
50-
5132
useEffect(() => {
52-
let intervalId: NodeJS.Timeout;
53-
const subscription = AppState.addEventListener("change", (nextAppState) => {
54-
if (nextAppState === "active") {
33+
isMounted.current = true;
34+
35+
const handleAppStateChange = (nextAppState: string) => {
36+
if (nextAppState === "active" && isMounted.current) {
5537
checkClipboard();
5638
}
57-
});
39+
};
40+
41+
const subscription = AppState.addEventListener(
42+
"change",
43+
handleAppStateChange
44+
);
5845

5946
// Initial check
6047
checkClipboard();
6148

49+
let intervalId: NodeJS.Timeout | null = null;
50+
let listener: any;
6251
if (Platform.OS === "ios") {
63-
// Poll clipboard every 1.5 seconds for iOS
6452
intervalId = setInterval(checkClipboard, 1500);
6553
} else {
66-
// Android listener
67-
const listenerSubscription = clipboard.addListener((content) => {
68-
setClipboardContent(content);
69-
setHasCopiedText(content.length > 0);
70-
addToHistory(content);
54+
listener = clipboard.addListener((content: string) => {
55+
if (isMounted.current) {
56+
previousContentRef.current = content;
57+
setClipboardContent(content);
58+
setHasCopiedText(content.length > 0);
59+
addToHistory(content);
60+
}
7161
});
72-
73-
return () => {
74-
clipboard.removeListener(listenerSubscription);
75-
};
7662
}
7763

7864
return () => {
79-
clearInterval(intervalId);
65+
isMounted.current = false;
8066
subscription.remove();
67+
if (intervalId) clearInterval(intervalId);
68+
if (Platform.OS === "android") {
69+
clipboard.removeListener(listener);
70+
}
8171
};
82-
}, [previousContent]);
72+
}, []);
8373

8474
const copyToClipboard = async (text: string) => {
8575
await clipboard.copy(text);
86-
addToHistory(text);
76+
if (isMounted.current) {
77+
previousContentRef.current = text;
78+
setClipboardContent(text);
79+
setHasCopiedText(true);
80+
addToHistory(text);
81+
}
8782
};
8883

8984
const getLatestClipboard = async () => {
@@ -92,8 +87,11 @@ export const useClipboard = () => {
9287

9388
const clearClipboard = async () => {
9489
await clipboard.clear();
95-
setClipboardContent("");
96-
setHasCopiedText(false);
90+
if (isMounted.current) {
91+
previousContentRef.current = "";
92+
setClipboardContent("");
93+
setHasCopiedText(false);
94+
}
9795
};
9896

9997
return {

utils/cellular.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as Cellular from "expo-cellular";
2+
3+
/**
4+
* Returns whether the carrier allows VoIP calls.
5+
*/
6+
export const allowsVoipAsync = async (): Promise<boolean | null> => {
7+
return await Cellular.allowsVoipAsync();
8+
};
9+
10+
/**
11+
* Returns the carrier name.
12+
*/
13+
export const getCarrierNameAsync = async (): Promise<string | null> => {
14+
return await Cellular.getCarrierNameAsync();
15+
};
16+
17+
/**
18+
* Returns the current cellular generation (e.g. CELLULAR_3G, CELLULAR_4G, etc.).
19+
*/
20+
export const getCellularGenerationAsync =
21+
async (): Promise<Cellular.CellularGeneration> => {
22+
return await Cellular.getCellularGenerationAsync();
23+
};
24+
25+
/**
26+
* Returns the ISO country code of the carrier.
27+
*/
28+
export const getIsoCountryCodeAsync = async (): Promise<string | null> => {
29+
return await Cellular.getIsoCountryCodeAsync();
30+
};
31+
32+
/**
33+
* Returns the mobile country code (MCC).
34+
*/
35+
export const getMobileCountryCodeAsync = async (): Promise<string | null> => {
36+
return await Cellular.getMobileCountryCodeAsync();
37+
};
38+
39+
/**
40+
* Returns the mobile network code (MNC).
41+
*/
42+
export const getMobileNetworkCodeAsync = async (): Promise<string | null> => {
43+
return await Cellular.getMobileNetworkCodeAsync();
44+
};
45+
46+
/**
47+
* Gets the current permission response for accessing cellular information.
48+
*/
49+
export const getPermissionsAsync = async (): Promise<any> => {
50+
return await Cellular.getPermissionsAsync();
51+
};
52+
53+
/**
54+
* Requests permission for accessing cellular information.
55+
*/
56+
export const requestPermissionsAsync = async (): Promise<any> => {
57+
return await Cellular.requestPermissionsAsync();
58+
};

0 commit comments

Comments
 (0)