Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions src/app/(tabs)/(password-manager)/password-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import PasswordFormCard, {
import { usePasswordRepository } from "@/contexts/RepositoryContext";
import { useToast } from "@/contexts/ToastContext";

import { getScreenShotSecureScreen } from "@/libs/screenshot_prevention";
import { formatDate } from "@passcodes/passalgo";

import FontAwesome6 from "@react-native-vector-icons/fontawesome6";
Expand All @@ -28,8 +27,6 @@ export default function PasswordDetailsScreen() {

const { showToast } = useToast();

getScreenShotSecureScreen();

/**
* Remove focus from whichever TextInput is currently focused
* and dismiss the keyboard.
Expand Down
4 changes: 1 addition & 3 deletions src/app/(tabs)/(password-manager)/save-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import FormTextField from "@/components/FormTextField";
import SecureTextField from "@/components/SecureTextField";
import { usePasswordRepository } from "@/contexts/RepositoryContext";
import { useToast } from "@/contexts/ToastContext";
import { getScreenShotSecureScreen } from "@/libs/screenshot_prevention";
import FontAwesome6 from "@react-native-vector-icons/fontawesome6";
import { router, useLocalSearchParams } from "expo-router";
import { useState } from "react";
Expand Down Expand Up @@ -59,8 +58,6 @@ export default function SavePasswordScreen() {

const { showToast } = useToast();

getScreenShotSecureScreen();

return (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView
Expand Down Expand Up @@ -93,6 +90,7 @@ export default function SavePasswordScreen() {
onChangeText={setPassword}
placeholder="************"
placeholderTextColor={"#9e9e9e"}
screenCaptureKey="save-password"
/>

{lockCategory !== "1" && (
Expand Down
8 changes: 4 additions & 4 deletions src/app/(tabs)/(settings)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import LinkButton from "@/components/LinkButton";
import { LinkIconButton } from "@/components/LinkIconButton";
import Text from "@/components/Text";
import Config from "@/config";
import {
isScreenshotPreventionEnabled,
toggleScreenshotPreventionFeature,
} from "@/features/screenshot-prevention";
import {
isBiometricsAuthEnabled,
toggleBiometricsFeature,
Expand All @@ -11,10 +15,6 @@ import {
useHomeLayout,
type HomeLayout,
} from "@/libs/home_layout";
import {
isScreenshotPreventionEnabled,
toggleScreenshotPreventionFeature,
} from "@/libs/screenshot_prevention";
import { FontAwesome6 } from "@react-native-vector-icons/fontawesome6";
import { LinearGradient } from "expo-linear-gradient";
import { router } from "expo-router";
Expand Down
1 change: 1 addition & 0 deletions src/components/PasswordFormCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default function PasswordFormCard({
value={value.password}
onChangeText={(text) => onChange("password", text)}
editable={editable}
screenCaptureKey="password-form"
/>

<CategoryField
Expand Down
29 changes: 25 additions & 4 deletions src/components/SecureTextField.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { useSecretScreenController } from "@/features/screenshot-prevention";
import FontAwesome6 from "@react-native-vector-icons/fontawesome6";
import { useState } from "react";
import { TextInput, TextInputProps } from "react-native-paper";
import FormTextField from "./FormTextField";

type SecureTextFieldProps = Omit<TextInputProps, "secureTextEntry" | "right">;
type StrippedTextFieldProps = Omit<TextInputProps, "secureTextEntry" | "right">;
type SecureTextFieldProps = StrippedTextFieldProps & {
/* generally, you can set to name of screen route, we need it for screen shot prevention */
screenCaptureKey: string;
};

// isPasswordVisible ? "eye-off" : "eye"
export default function SecureTextField({ ...props }: SecureTextFieldProps) {
export default function SecureTextField({
screenCaptureKey,
...props
}: SecureTextFieldProps) {
let { preventScreenShots, allowScreenShots } =
useSecretScreenController(screenCaptureKey);
const [isPasswordVisible, setIsPasswordVisible] = useState(false);

return (
Expand All @@ -17,7 +26,19 @@ export default function SecureTextField({ ...props }: SecureTextFieldProps) {
secureTextEntry={!isPasswordVisible}
right={
<TextInput.Icon
onPress={() => setIsPasswordVisible(!isPasswordVisible)}
onPress={() => {
setIsPasswordVisible((prev) => {
// Note: this checks are hard to understand,
// because we using previous state, read it twice!!
if (!prev) {
preventScreenShots();
} else {
allowScreenShots();
}

return !prev;
});
}}
icon={({ size, color }) => (
<FontAwesome6
name={isPasswordVisible ? "eye-slash" : "eye"}
Expand Down
5 changes: 5 additions & 0 deletions src/features/screenshot-prevention/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
isScreenshotPreventionEnabled,
toggleScreenshotPreventionFeature,
} from "./store";
export { default as useSecretScreenController } from "./useSecretScreenController";
12 changes: 12 additions & 0 deletions src/features/screenshot-prevention/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as ScreenCapture from "expo-screen-capture";
import { isScreenshotPreventionEnabled } from "./store";

export async function preventScreenShots(key?: string) {
isScreenshotPreventionEnabled() &&
(await ScreenCapture.preventScreenCaptureAsync(key));
}

export async function allowScreenShots(key?: string) {
isScreenshotPreventionEnabled() &&
(await ScreenCapture.allowScreenCaptureAsync(key));
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import AsyncStorage from "expo-sqlite/kv-store";

import * as ScreenCapture from "expo-screen-capture";
const IS_SCREENSHOT_PREVENTION_ENABLED = "feat_screenshot_prevention_isEnabled";
enum SCREENSHOT_PREVENTION_KV {
ENABLED = "true",
DISABLED = "false",
}

export function getScreenShotSecureScreen() {
if (isScreenshotPreventionEnabled()) {
ScreenCapture.preventScreenCaptureAsync();
return true;
} else {
ScreenCapture.allowScreenCaptureAsync();
return false;
}
}

export function toggleScreenshotPreventionFeature() {
let isEnabled = isScreenshotPreventionEnabled();

Expand Down
16 changes: 16 additions & 0 deletions src/features/screenshot-prevention/useSecretScreenController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect } from "react";
import { allowScreenShots, preventScreenShots } from "./runtime";

export default function useSecretScreenController(key: string) {
// This for clean up, whenever screen goes it reenables screen shots.
useEffect(() => {
return () => {
allowScreenShots(key);
};
}, []);

return {
preventScreenShots: () => preventScreenShots(key),
allowScreenShots: () => allowScreenShots(key),
};
}
Loading