Fixes: https://github.com/lesspass/lesspass/issues/677tags/mobile-v9.7.3
@@ -147,8 +147,8 @@ android { | |||
applicationId "com.lesspass.android" | |||
minSdkVersion rootProject.ext.minSdkVersion | |||
targetSdkVersion rootProject.ext.targetSdkVersion | |||
versionCode 9007002 | |||
versionName "9.7.2" | |||
versionCode 9007003 | |||
versionName "9.7.3" | |||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() | |||
if (isNewArchitectureEnabled()) { | |||
@@ -257,9 +257,8 @@ android { | |||
def abi = output.getFilter(OutputFile.ABI) | |||
if (abi != null) { // null for the universal-debug, universal-release variants | |||
output.versionCodeOverride = | |||
defaultConfig.versionCode * 1000 + versionCodes.get(abi) | |||
defaultConfig.versionCode * 100 + versionCodes.get(abi) | |||
} | |||
} | |||
} | |||
} | |||
@@ -1,7 +1,7 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<resources> | |||
<color name="statusBarColor">#2E2E2E</color> | |||
<color name="colorPrimary">#F3F3F3</color> | |||
<color name="backgroundColor">#4D4D4D</color> | |||
<color name="navigationBarColor">#2E2E2E</color> | |||
<color name="statusBarColor">#1b1b1f</color> | |||
<color name="colorPrimary">#bfc2ff</color> | |||
<color name="backgroundColor">#46464f</color> | |||
<color name="navigationBarColor">#1b1b1f</color> | |||
</resources> |
@@ -1,7 +1,7 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<resources> | |||
<color name="statusBarColor">#FFFFFF</color> | |||
<color name="colorPrimary">#2E2E2E</color> | |||
<color name="backgroundColor">#F3F3F3</color> | |||
<color name="navigationBarColor">#FFFFFF</color> | |||
<color name="statusBarColor">#fffbff</color> | |||
<color name="colorPrimary">#4951c3</color> | |||
<color name="backgroundColor">#e4e1ec</color> | |||
<color name="navigationBarColor">#fffbff</color> | |||
</resources> |
@@ -529,7 +529,7 @@ | |||
"$(inherited)", | |||
"@executable_path/Frameworks", | |||
); | |||
MARKETING_VERSION = 9.7.2; | |||
MARKETING_VERSION = 9.7.3; | |||
OTHER_LDFLAGS = ( | |||
"$(inherited)", | |||
"-ObjC", | |||
@@ -557,7 +557,7 @@ | |||
"$(inherited)", | |||
"@executable_path/Frameworks", | |||
); | |||
MARKETING_VERSION = 9.7.2; | |||
MARKETING_VERSION = 9.7.3; | |||
OTHER_LDFLAGS = ( | |||
"$(inherited)", | |||
"-ObjC", | |||
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "lesspass-mobile", | |||
"version": "9.7.2", | |||
"version": "9.7.3", | |||
"description": "LessPass mobile application", | |||
"license": "(MPL-2.0 OR GPL-3.0)", | |||
"author": "Guillaume Vincent <guillaume@oslab.fr>", | |||
@@ -21,47 +21,47 @@ | |||
}, | |||
"dependencies": { | |||
"@react-native-community/async-storage": "^1.12.1", | |||
"@react-navigation/bottom-tabs": "^6.4.0", | |||
"@react-navigation/native": "^6.0.13", | |||
"@react-navigation/native-stack": "^6.9.1", | |||
"@react-navigation/stack": "^6.3.2", | |||
"axios": "^0.27.2", | |||
"fuzzysort": "^2.0.1", | |||
"@react-navigation/bottom-tabs": "^6.5.2", | |||
"@react-navigation/native": "^6.1.1", | |||
"@react-navigation/native-stack": "^6.9.7", | |||
"@react-navigation/stack": "^6.3.10", | |||
"axios": "^1.2.1", | |||
"fuzzysort": "^2.0.4", | |||
"lesspass-fingerprint": "^9.1.9", | |||
"lesspass-render-password": "^9.1.9", | |||
"lodash": "^4.17.21", | |||
"react": "18.2.0", | |||
"react-native": "0.70.6", | |||
"react-native-gesture-handler": "^2.7.1", | |||
"react-native-gesture-handler": "^2.8.0", | |||
"react-native-keychain": "^8.1.1", | |||
"react-native-paper": "^4.12.5", | |||
"react-native-paper": "^5.1.0", | |||
"react-native-safe-area-context": "^4.4.1", | |||
"react-native-screens": "^3.18.2", | |||
"react-native-touch-id": "^4.4.1", | |||
"react-native-touch-id": "https://github.com/lesspass/react-native-touch-id", | |||
"react-native-vector-icons": "^9.2.0", | |||
"react-redux": "^8.0.4", | |||
"react-redux": "^8.0.5", | |||
"redux": "^4.2.0", | |||
"redux-persist": "^6.0.0", | |||
"redux-thunk": "^2.4.1" | |||
"redux-thunk": "^2.4.2" | |||
}, | |||
"devDependencies": { | |||
"@babel/core": "^7.19.6", | |||
"@babel/runtime": "^7.12.5", | |||
"@react-native-community/eslint-config": "^3.1.0", | |||
"@tsconfig/react-native": "^2.0.2", | |||
"@types/jest": "^29.2.0", | |||
"@types/react-native": "^0.70.6", | |||
"@babel/core": "^7.20.7", | |||
"@babel/runtime": "^7.20.7", | |||
"@react-native-community/eslint-config": "^3.2.0", | |||
"@tsconfig/react-native": "^2.0.3", | |||
"@types/jest": "^29.2.4", | |||
"@types/react-native": "^0.70.8", | |||
"@types/react-test-renderer": "^18.0.0", | |||
"@typescript-eslint/eslint-plugin": "^5.40.1", | |||
"@typescript-eslint/parser": "^5.40.1", | |||
"babel-jest": "^29.2.1", | |||
"eslint": "^8.25.0", | |||
"jest": "^29.2.1", | |||
"metro-react-native-babel-preset": "0.73.2", | |||
"prettier": "^2.7.1", | |||
"@typescript-eslint/eslint-plugin": "^5.47.0", | |||
"@typescript-eslint/parser": "^5.47.0", | |||
"babel-jest": "^29.3.1", | |||
"eslint": "^8.30.0", | |||
"jest": "^29.3.1", | |||
"metro-react-native-babel-preset": "0.73.6", | |||
"prettier": "^2.8.1", | |||
"react-native-version": "^4.0.0", | |||
"react-test-renderer": "18.2.0", | |||
"typescript": "^4.8.3" | |||
"typescript": "^4.9.4" | |||
}, | |||
"jest": { | |||
"preset": "react-native", | |||
@@ -12,18 +12,20 @@ import routes from "./routes"; | |||
import { getPasswordProfiles } from "./password/profilesActions"; | |||
import { refreshTokens, signOut } from "./auth/authActions"; | |||
import ProfilesScreen from "./profiles/ProfilesScreen"; | |||
import { SafeAreaView, useColorScheme } from "react-native"; | |||
import { SafeAreaView } from "react-native"; | |||
import Errors from "./errors/Errors"; | |||
import Header from "./header/Header"; | |||
import Styles from "./ui/Styles"; | |||
import { useTheme } from "react-native-paper"; | |||
import { getReactNavigationTheme } from "./ui/Theme"; | |||
const Tab = createBottomTabNavigator(); | |||
export default function App() { | |||
const colorScheme = useColorScheme(); | |||
const { isAuthenticated } = useSelector((state) => state.auth); | |||
const dispatch = useDispatch(); | |||
const theme = useTheme(); | |||
useEffect(() => { | |||
if (isAuthenticated) { | |||
dispatch(refreshTokens()) | |||
@@ -37,12 +39,12 @@ export default function App() { | |||
}); | |||
} | |||
}, [isAuthenticated, dispatch]); | |||
const reactNavigationTheme = getReactNavigationTheme(colorScheme); | |||
const reactNavigationTheme = getReactNavigationTheme(theme); | |||
return ( | |||
<SafeAreaView | |||
style={{ | |||
...Styles.container, | |||
backgroundColor: reactNavigationTheme.colors.card, | |||
backgroundColor: theme.colors.background, | |||
}} | |||
> | |||
<Header /> | |||
@@ -11,8 +11,14 @@ export default function DeleteMyAccountModal({ onDeleteConfirmed }) { | |||
return ( | |||
<View> | |||
<Portal> | |||
<Dialog onDismiss={() => setShowModal(false)} visible={showModal}> | |||
<Dialog.Title style={{ color: theme.colors.red }}> | |||
<Dialog | |||
onDismiss={() => { | |||
setMasterPassword(""); | |||
setShowModal(false); | |||
}} | |||
visible={showModal} | |||
> | |||
<Dialog.Title style={{ color: theme.colors.error }}> | |||
Delete my account | |||
</Dialog.Title> | |||
<Dialog.ScrollArea style={{ maxHeight: 170, paddingHorizontal: 0 }}> | |||
@@ -22,38 +28,50 @@ export default function DeleteMyAccountModal({ onDeleteConfirmed }) { | |||
masterPassword={masterPassword} | |||
onChangeText={setMasterPassword} | |||
/> | |||
<Text style={{ color: theme.colors.red }}> | |||
<Text style={{ color: theme.colors.error }}> | |||
Enter your master password to delete your account. Warning! this | |||
action is irreversible. | |||
</Text> | |||
</View> | |||
</Dialog.ScrollArea> | |||
<Dialog.Actions> | |||
<Button primary onPress={() => setShowModal(false)}> | |||
<Button | |||
primary | |||
onPress={() => { | |||
setMasterPassword(""); | |||
setShowModal(false); | |||
}} | |||
> | |||
Cancel | |||
</Button> | |||
<Button | |||
disabled={!masterPassword} | |||
primary | |||
disabled={!masterPassword} | |||
mode="contained" | |||
style={{ | |||
borderColor: theme.colors.error, | |||
}} | |||
buttonColor={theme.colors.error} | |||
onPress={() => { | |||
setShowModal(false); | |||
onDeleteConfirmed(masterPassword); | |||
}} | |||
> | |||
<Text style={{ color: theme.colors.red }}>Delete my account</Text> | |||
Delete my account | |||
</Button> | |||
</Dialog.Actions> | |||
</Dialog> | |||
</Portal> | |||
<Button | |||
compact | |||
mode="outlined" | |||
style={{ marginTop: 10, color: theme.colors.red }} | |||
mode="contained" | |||
style={{ | |||
borderColor: theme.colors.error, | |||
}} | |||
buttonColor={theme.colors.error} | |||
onPress={() => { | |||
setShowModal(true); | |||
}} | |||
> | |||
<Text style={{ color: theme.colors.red }}>Delete my account</Text> | |||
Delete my account | |||
</Button> | |||
</View> | |||
); | |||
@@ -8,7 +8,7 @@ import { | |||
TouchableWithoutFeedback, | |||
Keyboard, | |||
} from "react-native"; | |||
import { Text, Button, Title } from "react-native-paper"; | |||
import { Text, Button, Title, useTheme } from "react-native-paper"; | |||
import MasterPassword from "../password/MasterPassword"; | |||
import TextInput from "../ui/TextInput"; | |||
import Styles from "../ui/Styles"; | |||
@@ -23,9 +23,10 @@ export default function SignInScreen() { | |||
const [isLoading, setIsLoading] = useState(false); | |||
const navigation = useNavigation(); | |||
const dispatch = useDispatch(); | |||
const settings = useSelector((state) => state.settings); | |||
const { encryptMasterPassword } = settings; | |||
const theme = useTheme(); | |||
const encryptMasterPassword = useSelector( | |||
(state) => state.settings.encryptMasterPassword | |||
); | |||
return ( | |||
<KeyboardAvoidingView | |||
behavior={Platform.OS === "ios" ? "padding" : "height"} | |||
@@ -33,7 +34,7 @@ export default function SignInScreen() { | |||
> | |||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> | |||
<ScrollView contentContainerStyle={Styles.innerContainer}> | |||
<Title>Connect to Lesspass Database</Title> | |||
<Title style={Styles.title}>Connect to Lesspass Database</Title> | |||
<TextInput | |||
mode="outlined" | |||
label="Email" | |||
@@ -47,10 +48,12 @@ export default function SignInScreen() { | |||
onChangeText={(password) => setPassword(password)} | |||
/> | |||
<Button | |||
compact | |||
icon={"account-circle"} | |||
mode="contained" | |||
style={Styles.loginSignInButton} | |||
style={{ | |||
marginTop: 10, | |||
marginBottom: 30, | |||
}} | |||
disabled={isEmpty(email) || isEmpty(password) || isLoading} | |||
onPress={() => { | |||
setIsLoading(true); | |||
@@ -78,15 +81,11 @@ export default function SignInScreen() { | |||
> | |||
Sign In | |||
</Button> | |||
<Text>Don't have an account?</Text> | |||
<Button | |||
compact | |||
icon="account-circle" | |||
mode="outlined" | |||
style={Styles.loginSignUpButton} | |||
mode="text" | |||
onPress={() => navigation.navigate(routes.SIGN_UP)} | |||
> | |||
Sign Up | |||
Don't have an account? Sign Up | |||
</Button> | |||
</ScrollView> | |||
</TouchableWithoutFeedback> | |||
@@ -1,5 +1,5 @@ | |||
import React, { useEffect, useState } from "react"; | |||
import { ScrollView, Text } from "react-native"; | |||
import { ScrollView } from "react-native"; | |||
import { useDispatch } from "react-redux"; | |||
import { deleteMyAccount, getCurrentUser, signOut } from "./authActions"; | |||
import Styles from "../ui/Styles"; | |||
@@ -24,10 +24,11 @@ const SignOutScreen = ({ navigation }) => { | |||
<Title style={{ marginBottom: 10 }}>My Account</Title> | |||
<Paragraph>Email: {email}</Paragraph> | |||
<Button | |||
compact | |||
icon="account-circle" | |||
mode="outlined" | |||
style={Styles.loginSignUpButton} | |||
mode="contained" | |||
style={{ | |||
marginTop: 10 | |||
}} | |||
onPress={() => { | |||
dispatch(signOut()); | |||
navigation.navigate(routes.PASSWORD_GENERATOR); | |||
@@ -36,7 +37,7 @@ const SignOutScreen = ({ navigation }) => { | |||
Sign out | |||
</Button> | |||
<Title | |||
style={{ marginTop: 50, marginBottom: 10, color: theme.colors.red }} | |||
style={{ marginTop: 60, marginBottom: 10, color: theme.colors.error }} | |||
> | |||
Danger zone | |||
</Title> | |||
@@ -1,5 +1,4 @@ | |||
import React, { Component } from "react"; | |||
import { connect } from "react-redux"; | |||
import React, { useState } from "react"; | |||
import { | |||
KeyboardAvoidingView, | |||
ScrollView, | |||
@@ -7,7 +6,7 @@ import { | |||
TouchableWithoutFeedback, | |||
Keyboard, | |||
} from "react-native"; | |||
import { Text, Button, Title } from "react-native-paper"; | |||
import { Button, Title, useTheme } from "react-native-paper"; | |||
import MasterPassword from "../password/MasterPassword"; | |||
import TextInput from "../ui/TextInput"; | |||
import Styles from "../ui/Styles"; | |||
@@ -15,97 +14,82 @@ import { addError } from "../errors/errorsActions"; | |||
import { signUp } from "./authActions"; | |||
import { isEmpty } from "lodash"; | |||
import routes from "../routes"; | |||
import { useNavigation } from "@react-navigation/native"; | |||
import { useDispatch, useSelector } from "react-redux"; | |||
export class SignUpScreen extends Component { | |||
constructor(props) { | |||
super(props); | |||
this.state = { | |||
email: "", | |||
password: "", | |||
isLoading: false, | |||
}; | |||
} | |||
export default function SignUpScreen() { | |||
const [email, setEmail] = useState(""); | |||
const [password, setPassword] = useState(""); | |||
const [isLoading, setIsLoading] = useState(false); | |||
const navigation = useNavigation(); | |||
const dispatch = useDispatch(); | |||
const theme = useTheme(); | |||
const encryptMasterPassword = useSelector( | |||
(state) => state.settings.encryptMasterPassword | |||
); | |||
render() { | |||
const { email, password, isLoading } = this.state; | |||
const { navigation, settings, addError, signUp } = this.props; | |||
const { encryptMasterPassword } = settings; | |||
return ( | |||
<KeyboardAvoidingView | |||
behavior={Platform.OS === "ios" ? "padding" : "height"} | |||
style={Styles.container} | |||
> | |||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> | |||
<ScrollView contentContainerStyle={Styles.innerContainer}> | |||
<Title style={Styles.title}>Create an account</Title> | |||
<TextInput | |||
mode="outlined" | |||
label="Email" | |||
value={email} | |||
onChangeText={(text) => this.setState({ email: text.trim() })} | |||
/> | |||
<MasterPassword | |||
label={encryptMasterPassword ? "Master Password" : "Password"} | |||
masterPassword={password} | |||
hideFingerprint={!encryptMasterPassword} | |||
onChangeText={(password) => this.setState({ password })} | |||
/> | |||
<Button | |||
compact | |||
icon="account-circle" | |||
mode="contained" | |||
style={Styles.loginSignInButton} | |||
disabled={isEmpty(email) || isEmpty(password) || isLoading} | |||
onPress={() => { | |||
this.setState({ isLoading: true }); | |||
return ( | |||
<KeyboardAvoidingView | |||
behavior={Platform.OS === "ios" ? "padding" : "height"} | |||
style={Styles.container} | |||
> | |||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> | |||
<ScrollView contentContainerStyle={Styles.innerContainer}> | |||
<Title style={Styles.title}>Create an account</Title> | |||
<TextInput | |||
mode="outlined" | |||
label="Email" | |||
value={email} | |||
onChangeText={setEmail} | |||
/> | |||
<MasterPassword | |||
label={encryptMasterPassword ? "Master Password" : "Password"} | |||
masterPassword={password} | |||
hideFingerprint={!encryptMasterPassword} | |||
onChangeText={setPassword} | |||
/> | |||
<Button | |||
icon="account-circle" | |||
mode="contained" | |||
style={{ | |||
marginTop: 10, | |||
marginBottom: 30, | |||
}} | |||
disabled={isEmpty(email) || isEmpty(password) || isLoading} | |||
onPress={() => { | |||
setIsLoading(true); | |||
dispatch( | |||
signUp( | |||
{ | |||
email, | |||
email: email.trim(), | |||
password, | |||
}, | |||
encryptMasterPassword | |||
) | |||
.then(() => navigation.navigate(routes.PASSWORD_GENERATOR)) | |||
.catch(() => { | |||
this.setState({ isLoading: false }); | |||
) | |||
.then(() => navigation.navigate(routes.PASSWORD_GENERATOR)) | |||
.catch(() => { | |||
dispatch( | |||
addError( | |||
"Unable to sign up. Try in a few minutes or contact an administrator." | |||
); | |||
}); | |||
}} | |||
> | |||
Sign Up | |||
</Button> | |||
<Text>Already have an account?</Text> | |||
<Button | |||
compact | |||
icon="account-circle" | |||
mode="outlined" | |||
style={Styles.loginSignUpButton} | |||
onPress={() => navigation.navigate(routes.SIGN_IN)} | |||
> | |||
Sign In | |||
</Button> | |||
</ScrollView> | |||
</TouchableWithoutFeedback> | |||
</KeyboardAvoidingView> | |||
); | |||
} | |||
} | |||
function mapStateToProps(state) { | |||
return { | |||
settings: state.settings, | |||
}; | |||
) | |||
); | |||
}) | |||
.finally(() => { | |||
setIsLoading(false); | |||
}); | |||
}} | |||
> | |||
Sign Up | |||
</Button> | |||
<Button | |||
mode="text" | |||
onPress={() => navigation.navigate(routes.SIGN_IN)} | |||
> | |||
Already have an account? Sign In | |||
</Button> | |||
</ScrollView> | |||
</TouchableWithoutFeedback> | |||
</KeyboardAvoidingView> | |||
); | |||
} | |||
function mapDispatchToProps(dispatch) { | |||
return { | |||
addError: (message) => dispatch(addError(message)), | |||
signUp: (credentials, encryptMasterPassword) => | |||
dispatch(signUp(credentials, encryptMasterPassword)), | |||
}; | |||
} | |||
export default connect(mapStateToProps, mapDispatchToProps)(SignUpScreen); |
@@ -79,7 +79,8 @@ export function refreshTokens() { | |||
.post(`${settings.baseURL}/auth/jwt/refresh/`, { | |||
refresh: auth.refreshToken, | |||
}) | |||
.then((response) => dispatch(setJWT(response.data))); | |||
.then((response) => dispatch(setJWT(response.data))) | |||
.catch(console.log); | |||
}; | |||
} | |||
@@ -13,7 +13,7 @@ export default function Errors() { | |||
<Snackbar | |||
key={error.id} | |||
visible={true} | |||
style={{ backgroundColor: theme.colors.red }} | |||
style={{ backgroundColor: theme.colors.error }} | |||
onDismiss={() => dispatch(deleteError(error))} | |||
action={{ | |||
label: "x", | |||
@@ -1,9 +1,10 @@ | |||
import React from "react"; | |||
import { ScrollView, Image, Linking, View } from "react-native"; | |||
import { Title, Subheading, Paragraph, Button } from "react-native-paper"; | |||
import { Title, Subheading, Paragraph, Button, useTheme } from "react-native-paper"; | |||
import Styles from "../ui/Styles"; | |||
export default function HelpScreen() { | |||
const theme = useTheme() | |||
return ( | |||
<ScrollView contentContainerStyle={Styles.innerContainer}> | |||
<Title style={Styles.title}>Help</Title> | |||
@@ -82,7 +83,7 @@ export default function HelpScreen() { | |||
marginTop: 10, | |||
}} | |||
> | |||
send us an email | |||
Send us an email | |||
</Button> | |||
</ScrollView> | |||
); | |||
@@ -19,8 +19,8 @@ export default function Counter({ | |||
setIsValid(false); | |||
} | |||
}, [value]); | |||
const color = isValid ? theme.colors.placeholder : theme.colors.red; | |||
const colorAccent = isValid ? theme.colors.accent : theme.colors.red; | |||
const color = isValid ? theme.colors.primary : theme.colors.error; | |||
const borderColor = isValid ? theme.colors.outline : theme.colors.error; | |||
return ( | |||
<View {...props}> | |||
<Text | |||
@@ -37,10 +37,6 @@ export default function Counter({ | |||
justifyContent: "center", | |||
alignItems: "center", | |||
height: 26, | |||
borderWidth: 1, | |||
borderColor: colorAccent, | |||
borderRadius: theme.roundness, | |||
backgroundColor: colorAccent, | |||
}} | |||
> | |||
<TouchableOpacity | |||
@@ -50,6 +46,11 @@ export default function Counter({ | |||
alignSelf: "stretch", | |||
paddingVertical: 6, | |||
paddingHorizontal: 16, | |||
backgroundColor: isValid | |||
? theme.colors.primary | |||
: theme.colors.error, | |||
borderTopLeftRadius: 5 * theme.roundness, | |||
borderBottomLeftRadius: 5 * theme.roundness, | |||
}} | |||
onPress={() => setValue(value - 1)} | |||
> | |||
@@ -57,7 +58,7 @@ export default function Counter({ | |||
size={12} | |||
name="minus" | |||
style={{ | |||
color: theme.colors.background, | |||
color: isValid ? theme.colors.onPrimary : theme.colors.onError, | |||
}} | |||
/> | |||
</TouchableOpacity> | |||
@@ -65,7 +66,11 @@ export default function Counter({ | |||
style={{ | |||
justifyContent: "center", | |||
alignItems: "center", | |||
backgroundColor: theme.colors.background, | |||
borderWidth: 0, | |||
borderTopWidth: 1, | |||
borderTopColor: borderColor, | |||
borderBottomWidth: 1, | |||
borderBottomColor: borderColor, | |||
}} | |||
> | |||
<TextInput | |||
@@ -104,6 +109,11 @@ export default function Counter({ | |||
alignSelf: "stretch", | |||
paddingVertical: 6, | |||
paddingHorizontal: 16, | |||
backgroundColor: isValid | |||
? theme.colors.primary | |||
: theme.colors.error, | |||
borderTopRightRadius: 5 * theme.roundness, | |||
borderBottomRightRadius: 5 * theme.roundness, | |||
}} | |||
onPress={() => setValue(value + 1)} | |||
> | |||
@@ -111,7 +121,7 @@ export default function Counter({ | |||
size={12} | |||
name="plus" | |||
style={{ | |||
color: theme.colors.background, | |||
color: isValid ? theme.colors.onPrimary : theme.colors.onError, | |||
}} | |||
/> | |||
</TouchableOpacity> | |||
@@ -6,7 +6,7 @@ import { areOptionsValid } from "./validations"; | |||
export default function Options({ options, onOptionsChange, style }) { | |||
const [isValid, setIsvalid] = useState(true); | |||
const theme = useTheme(); | |||
const color = isValid ? theme.colors.placeholder : theme.colors.red; | |||
const color = isValid ? theme.colors.secondary : theme.colors.error; | |||
useEffect(() => { | |||
if (areOptionsValid(options)) { | |||
@@ -96,13 +96,11 @@ export default function PasswordGeneratorScreen() { | |||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}> | |||
<ScrollView contentContainerStyle={Styles.innerContainer}> | |||
<TextInput | |||
mode="outlined" | |||
label="Site" | |||
value={state.site} | |||
onChangeText={(site) => setState((state) => ({ ...state, site }))} | |||
/> | |||
<TextInput | |||
mode="outlined" | |||
label="Login" | |||
value={state.login} | |||
onChangeText={(login) => setState((state) => ({ ...state, login }))} | |||
@@ -199,7 +197,7 @@ export default function PasswordGeneratorScreen() { | |||
style={{ marginRight: 10, marginBottom: 10 }} | |||
icon="refresh" | |||
> | |||
clear | |||
CLEAR | |||
</Button> | |||
{state.password && state.copyPasswordAfterGeneration === false && ( | |||
<Button | |||
@@ -223,7 +221,7 @@ export default function PasswordGeneratorScreen() { | |||
icon="eye" | |||
style={{ marginRight: 10, marginBottom: 10 }} | |||
> | |||
{seePassword ? "hide" : "show"} | |||
{seePassword ? "HIDE" : "SHOW"} | |||
</Button> | |||
)} | |||
{state.password && auth.isAuthenticated ? ( | |||
@@ -242,7 +240,7 @@ export default function PasswordGeneratorScreen() { | |||
icon="content-save" | |||
style={{ marginRight: 10, marginBottom: 10 }} | |||
> | |||
Save | |||
SAVE | |||
</Button> | |||
) : ( | |||
<Button | |||
@@ -259,7 +257,7 @@ export default function PasswordGeneratorScreen() { | |||
icon="content-save" | |||
style={{ marginRight: 10, marginBottom: 10 }} | |||
> | |||
Update | |||
UPDATE | |||
</Button> | |||
) | |||
) : null} | |||
@@ -1,44 +1,38 @@ | |||
import React, { Component } from "react"; | |||
import { connect } from "react-redux"; | |||
import React from "react"; | |||
import { useSelector } from "react-redux"; | |||
import TouchID from "react-native-touch-id"; | |||
import { View } from "react-native"; | |||
import { IconButton } from "react-native-paper"; | |||
import { IconButton, useTheme } from "react-native-paper"; | |||
import Styles from "../ui/Styles"; | |||
import { getGenericPassword } from "react-native-keychain"; | |||
export class TouchId extends Component { | |||
getMasterPasswordSavedLocally = () => { | |||
const { onChangeText } = this.props; | |||
TouchID.authenticate() | |||
.then(() => { | |||
return getGenericPassword().then((credentials) => { | |||
if (credentials) { | |||
onChangeText(credentials.password); | |||
} | |||
}); | |||
}) | |||
.catch(console.log); | |||
}; | |||
render() { | |||
const { settings } = this.props; | |||
const { keepMasterPasswordLocally = false } = settings; | |||
if (!keepMasterPasswordLocally) return null; | |||
return ( | |||
<View style={Styles.fingerprint}> | |||
<IconButton | |||
icon="fingerprint" | |||
onPress={() => this.getMasterPasswordSavedLocally()} | |||
/> | |||
</View> | |||
); | |||
} | |||
} | |||
function mapStateToProps(state) { | |||
return { | |||
settings: state.settings, | |||
}; | |||
export default function TouchId({ onChangeText }) { | |||
const theme = useTheme(); | |||
const keepMasterPasswordLocally = useSelector( | |||
(state) => state.settings.keepMasterPasswordLocally | |||
); | |||
if (!keepMasterPasswordLocally) return null; | |||
return ( | |||
<View style={Styles.fingerprint}> | |||
<IconButton | |||
icon="fingerprint" | |||
onPress={() => { | |||
TouchID.authenticate("Get master password saved locally", { | |||
imageColor: theme.colors.primary, | |||
imageErrorColor: theme.colors.error, | |||
cancelTextColor: theme.colors.onPrimary, | |||
cancelButtonColor: theme.colors.primary | |||
}) | |||
.then(() => | |||
getGenericPassword().then((credentials) => { | |||
if (credentials) { | |||
onChangeText(credentials.password); | |||
} | |||
}) | |||
) | |||
.catch(console.log); | |||
}} | |||
/> | |||
</View> | |||
); | |||
} | |||
export default connect(mapStateToProps)(TouchId); |
@@ -109,8 +109,6 @@ export default function ProfilesScreen() { | |||
description={ | |||
"There is no password profile that matches this search." | |||
} | |||
titleStyle={{ color: theme.colors.primary }} | |||
descriptionStyle={{ color: theme.colors.primary }} | |||
/> | |||
) : ( | |||
sortByNewestFirst(results).map((profile) => ( | |||
@@ -123,8 +121,6 @@ export default function ProfilesScreen() { | |||
setQuery(""); | |||
navigation.navigate(routes.PASSWORD_GENERATOR); | |||
}} | |||
titleStyle={{ color: theme.colors.primary }} | |||
descriptionStyle={{ color: theme.colors.primary }} | |||
right={(props) => ( | |||
<IconButton | |||
{...props} | |||
@@ -175,7 +175,6 @@ export class SettingsScreen extends Component { | |||
/> | |||
<Divider /> | |||
<List.Item title={`LessPass version: ${version}`} /> | |||
<Divider /> | |||
</List.Section> | |||
</ScrollView> | |||
); | |||
@@ -17,11 +17,9 @@ export default StyleSheet.create({ | |||
}, | |||
fingerprint: { | |||
position: "absolute", | |||
right: 3, | |||
top: 0, | |||
right: 0, | |||
top: 6, | |||
bottom: 0, | |||
justifyContent: "center", | |||
alignItems: "center", | |||
zIndex: 4, | |||
}, | |||
switch: { | |||
@@ -31,11 +29,4 @@ export default StyleSheet.create({ | |||
paddingVertical: 8, | |||
paddingHorizontal: 10, | |||
}, | |||
loginSignInButton: { | |||
marginTop: 10, | |||
marginBottom: 30, | |||
}, | |||
loginSignUpButton: { | |||
marginTop: 10, | |||
}, | |||
}); |
@@ -1,21 +1,19 @@ | |||
import React, { Component } from "react"; | |||
import React from "react"; | |||
import { View } from "react-native"; | |||
import { TextInput } from "react-native-paper"; | |||
import styles from "./Styles"; | |||
import { TextInput, useTheme } from "react-native-paper"; | |||
export default class Input extends Component { | |||
render() { | |||
const { showError = false, errorText, ...props } = this.props; | |||
return ( | |||
<View> | |||
<TextInput | |||
autoCapitalize="none" | |||
autoCorrect={false} | |||
style={styles.input} | |||
mode="outlined" | |||
{...props} | |||
/> | |||
</View> | |||
); | |||
} | |||
export default function Input({ showError = false, errorText, ...props }) { | |||
const theme = useTheme(); | |||
return ( | |||
<TextInput | |||
autoCapitalize="none" | |||
autoCorrect={false} | |||
outlineStyle={{ | |||
borderRadius: 5 * theme.roundness, | |||
}} | |||
style={{marginBottom:5}} | |||
mode="outlined" | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,62 +0,0 @@ | |||
import { DefaultTheme } from "react-native-paper"; | |||
import { | |||
DefaultTheme as DefaultThemeReactNavigation, | |||
DarkTheme as DarkThemeReactNavigation, | |||
} from "@react-navigation/native"; | |||
export function getReactNavigationTheme(colorScheme) { | |||
const isDarkTheme = colorScheme === "dark"; | |||
if (isDarkTheme) { | |||
return { | |||
...DarkThemeReactNavigation, | |||
dark: isDarkTheme, | |||
colors: { | |||
...DarkThemeReactNavigation.colors, | |||
card: "#2E2E2E", | |||
primary: "#F3F3F3", | |||
text: "#F3F3F3", | |||
border: "#2E2E2E", | |||
background: "#4D4D4D", | |||
}, | |||
}; | |||
} | |||
return { | |||
...DefaultThemeReactNavigation, | |||
dark: isDarkTheme, | |||
colors: { | |||
...DefaultThemeReactNavigation.colors, | |||
card: "#FFFFFF", | |||
text: "#4D4D4D", | |||
primary: "#2E2E2E", | |||
}, | |||
}; | |||
} | |||
export function getTheme(colorScheme) { | |||
const isDarkTheme = colorScheme === "dark"; | |||
if (isDarkTheme) { | |||
return { | |||
...DefaultTheme, | |||
dark: isDarkTheme, | |||
colors: { | |||
...DefaultTheme.colors, | |||
primary: "#F3F3F3", | |||
accent: "#DEDEDE", | |||
text: "#F3F3F3", | |||
placeholder: "#DEDEDE", | |||
background: "#4D4D4D", | |||
red: "#f32c1e", | |||
}, | |||
}; | |||
} | |||
return { | |||
...DefaultTheme, | |||
dark: isDarkTheme, | |||
colors: { | |||
...DefaultTheme.colors, | |||
primary: "#2E2E2E", | |||
accent: "#2E2E2E", | |||
red: "#f32c1e", | |||
}, | |||
}; | |||
} |
@@ -0,0 +1,126 @@ | |||
import { DefaultTheme, MD3Theme } from "react-native-paper"; | |||
import { | |||
DefaultTheme as DefaultThemeReactNavigation, | |||
DarkTheme as DarkThemeReactNavigation, | |||
Theme, | |||
} from "@react-navigation/native"; | |||
export function getReactNavigationTheme(theme: MD3Theme): Theme { | |||
const reactNativeTheme = theme.dark | |||
? DarkThemeReactNavigation | |||
: DefaultThemeReactNavigation; | |||
return { | |||
...reactNativeTheme, | |||
dark: theme.dark, | |||
colors: { | |||
...reactNativeTheme.colors, | |||
text: theme.colors.secondary, | |||
primary: theme.colors.primary, | |||
card: theme.colors.background, | |||
}, | |||
}; | |||
} | |||
export function getTheme(colorScheme: string | undefined | null): MD3Theme { | |||
const isDarkTheme = typeof colorScheme === "string" && colorScheme === "dark"; | |||
if (isDarkTheme) { | |||
return { | |||
...DefaultTheme, | |||
dark: isDarkTheme, | |||
mode: "adaptive", | |||
roundness: 1, | |||
colors: { | |||
...DefaultTheme.colors, | |||
primary: "#bfc2ff", | |||
onPrimary: "#141994", | |||
primaryContainer: "#3037aa", | |||
onPrimaryContainer: "#e0e0ff", | |||
secondary: "#c5c4dd", | |||
onSecondary: "#2e2f42", | |||
secondaryContainer: "#444559", | |||
onSecondaryContainer: "#e1e0f9", | |||
tertiary: "#e8b9d5", | |||
onTertiary: "#46263b", | |||
tertiaryContainer: "#5e3c52", | |||
onTertiaryContainer: "#ffd8ee", | |||
error: "#ffb4ab", | |||
onError: "#690005", | |||
errorContainer: "#93000a", | |||
onErrorContainer: "#ffb4ab", | |||
background: "#1b1b1f", | |||
onBackground: "#e5e1e6", | |||
surface: "#1b1b1f", | |||
onSurface: "#e5e1e6", | |||
surfaceVariant: "#46464f", | |||
onSurfaceVariant: "#c7c5d0", | |||
outline: "#918f9a", | |||
outlineVariant: "#46464f", | |||
shadow: "#000000", | |||
scrim: "#000000", | |||
inverseSurface: "#e5e1e6", | |||
inverseOnSurface: "#303034", | |||
inversePrimary: "#4951c3", | |||
elevation: { | |||
level0: "transparent", | |||
level1: "#23232a", | |||
level2: "#282831", | |||
level3: "#2d2d38", | |||
level4: "#2f2f3a", | |||
level5: "#32323e", | |||
}, | |||
surfaceDisabled: "#e5e1e6ff", | |||
onSurfaceDisabled: "#e5e1e6ff", | |||
backdrop: "#303038ff", | |||
}, | |||
}; | |||
} | |||
return { | |||
...DefaultTheme, | |||
dark: isDarkTheme, | |||
mode: "adaptive", | |||
roundness: 1, | |||
colors: { | |||
...DefaultTheme.colors, | |||
primary: "#4951c3", | |||
onPrimary: "#ffffff", | |||
primaryContainer: "#e0e0ff", | |||
onPrimaryContainer: "#00006e", | |||
secondary: "#5c5d72", | |||
onSecondary: "#ffffff", | |||
secondaryContainer: "#e1e0f9", | |||
onSecondaryContainer: "#191a2c", | |||
tertiary: "#78536b", | |||
onTertiary: "#ffffff", | |||
tertiaryContainer: "#ffd8ee", | |||
onTertiaryContainer: "#2e1126", | |||
error: "#ba1a1a", | |||
onError: "#ffffff", | |||
errorContainer: "#ffdad6", | |||
onErrorContainer: "#410002", | |||
background: "#fffbff", | |||
onBackground: "#1b1b1f", | |||
surface: "#fffbff", | |||
onSurface: "#1b1b1f", | |||
surfaceVariant: "#e4e1ec", | |||
onSurfaceVariant: "#46464f", | |||
outline: "#777680", | |||
outlineVariant: "#c7c5d0", | |||
shadow: "#000000", | |||
scrim: "#000000", | |||
inverseSurface: "#303034", | |||
inverseOnSurface: "#f3eff4", | |||
inversePrimary: "#bfc2ff", | |||
elevation: { | |||
level0: "transparent", | |||
level1: "#f6f3fc", | |||
level2: "#f0edfa", | |||
level3: "#ebe8f8", | |||
level4: "#e9e7f8", | |||
level5: "#e6e3f7", | |||
}, | |||
surfaceDisabled: "#1b1b1f1e", | |||
onSurfaceDisabled: "#1b1b1f60", | |||
backdrop: "#30303866", | |||
}, | |||
}; | |||
} |
@@ -1,3 +1,3 @@ | |||
{ | |||
"version": "9.7.2" | |||
"version": "9.7.3" | |||
} |