diff --git a/Account.js b/Account.js index 0d507bf..0490756 100644 --- a/Account.js +++ b/Account.js @@ -15,19 +15,22 @@ import totp from "totp-generator"; class Account { - constructor(description, secretCode, onUpdate) { + constructor(description, secretCode, onUpdate, icon) { this.title = description; this.secretCode = secretCode; this.countdowns = 30; this.timer = setInterval(this.updateCountdown.bind(this), 1000); this.token = ""; this.onUpdate = onUpdate; + this.isEditing = false; + this.icon = icon ? icon : null; } generateToken() { if (this.secretCode !== null && this.secretCode !== undefined && this.secretCode !== "") { const token = totp(this.secretCode); - return token; + const tokenWithSpace = token.slice(0, 3) + " " + token.slice(3); + return tokenWithSpace; } } @@ -44,6 +47,25 @@ class Account { } this.onUpdate(); } + getTitle() { + return this.title; + } + setTitle(title) { + this.title = title; + this.setEditingStatus(false); + } + setEditingStatus(status) { + this.isEditing = status; + this.onUpdate(); + } + getEditStatus() { + return this.isEditing; + } + + deleteAccount() { + clearInterval(this.timer); + this.onUpdate(); + } } export default Account; diff --git a/EditAccountDetails.js b/EditAccountDetails.js new file mode 100644 index 0000000..5d96cf7 --- /dev/null +++ b/EditAccountDetails.js @@ -0,0 +1,61 @@ +// Copyright 2023 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React, {useState} from "react"; +import {Text, TextInput, View} from "react-native"; +import {Button, IconButton} from "react-native-paper"; +import PropTypes from "prop-types"; + +export default function EnterAccountDetails({onClose, onEdit, placeholder}) { + EnterAccountDetails.propTypes = { + onClose: PropTypes.func.isRequired, + onEdit: PropTypes.func.isRequired, + placeholder: PropTypes.string.isRequired, + }; + + const [description, setDescription] = useState(""); + + const handleConfirm = () => { + onEdit(description); + }; + return ( + + Enter new description + + + setDescription(text)} + style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}} + /> + + + + + ); +} diff --git a/Header.js b/Header.js index 757475c..defab9e 100644 --- a/Header.js +++ b/Header.js @@ -16,9 +16,9 @@ import * as React from "react"; import {Appbar, Avatar, Text} from "react-native-paper"; const Header = () => ( - + - + Admin ); diff --git a/HomePage.js b/HomePage.js index 787537c..7cfb20a 100644 --- a/HomePage.js +++ b/HomePage.js @@ -14,12 +14,14 @@ import * as React from "react"; import {Dimensions, FlatList, Text, TouchableOpacity, View} from "react-native"; -import {IconButton, List, Modal, Portal} from "react-native-paper"; +import {Avatar, Divider, IconButton, List, Modal, Portal} from "react-native-paper"; import SearchBar from "./SearchBar"; +import {GestureHandlerRootView, Swipeable} from "react-native-gesture-handler"; import EnterAccountDetails from "./EnterAccountDetails"; import Account from "./Account"; import ScanQRCode from "./ScanQRCode"; +import EditAccountDetails from "./EditAccountDetails"; export default function HomePage() { const [isPlusButton, setIsPlusButton] = React.useState(true); @@ -29,7 +31,12 @@ export default function HomePage() { const [searchQuery, setSearchQuery] = React.useState(""); const [filteredData, setFilteredData] = React.useState(accountList); const [showScanner, setShowScanner] = React.useState(false); - + const [showEditAccountModal, setShowEditAccountModal] = React.useState(false); + const swipeableRef = React.useRef(null); + const [placeholder, setPlaceholder] = React.useState(""); + const closeEditAccountModal = () => { + setShowEditAccountModal(false); + }; const handleScanPress = () => { setShowScanner(true); setIsPlusButton(true); @@ -60,11 +67,11 @@ export default function HomePage() { setShowEnterAccountModal(false); }; + const onUpdate = () => { + setAccountList(prevList => [...prevList]); + }; const handleAddAccount = (accountData) => { - const onUpdate = () => { - setAccountList(prevList => [...prevList]); - }; - const newAccount = new Account(accountData.description, accountData.secretCode, onUpdate); + const newAccount = new Account(accountData.description, accountData.secretCode, onUpdate, accountData.icon); const token = newAccount.generateToken(); newAccount.token = token; @@ -72,6 +79,41 @@ export default function HomePage() { closeEnterAccountModal(); }; + const handleDeleteAccount = (accountDescp) => { + const accountToDelete = accountList.find(account => { + return account.getTitle() === accountDescp; + }); + if (accountToDelete) { + accountToDelete.deleteAccount(); + } + setAccountList(prevList => prevList.filter(account => account.getTitle() !== accountDescp)); + }; + const handleEditAccount = (accountDescp) => { + closeSwipeableMenu(); + setPlaceholder(accountDescp); + setShowEditAccountModal(true); + const accountToEdit = accountList.find(account => account.getTitle() === accountDescp); + + if (accountToEdit) { + accountToEdit.setEditingStatus(true); + } + }; + + const onAccountEdit = (accountDescp) => { + const accountToEdit = accountList.find(account => account.getEditStatus() === true); + if (accountToEdit) { + accountToEdit.setTitle(accountDescp); + } + setPlaceholder(""); + closeEditAccountModal(); + }; + + const closeSwipeableMenu = () => { + if (swipeableRef.current) { + swipeableRef.current.close(); + } + }; + const handleSearch = (query) => { setSearchQuery(query); @@ -94,25 +136,50 @@ export default function HomePage() { index.toString()} renderItem={({item}) => ( - - {item.title} + + ( - {item.token} - {item.countdowns}s + + Edit + + + Delete + - - } - left={(props) => ( - - )} - /> + )} + > + + {item.title} + + {item.token} + {item.countdowns}s + + + } + left={(props) => ( + item.icon ? + + : + )} + /> + + )} + ItemSeparatorComponent={() => } /> @@ -166,6 +233,25 @@ export default function HomePage() { + + + + + {showScanner && ( )} diff --git a/NavigationBar.js b/NavigationBar.js index ef78d75..561a885 100644 --- a/NavigationBar.js +++ b/NavigationBar.js @@ -31,6 +31,7 @@ export default function NavigationBar() { }} tabBar={({navigation, state, descriptors, insets}) => ( { diff --git a/ScanQRCode.js b/ScanQRCode.js index a21d7b7..b4e3a05 100644 --- a/ScanQRCode.js +++ b/ScanQRCode.js @@ -42,12 +42,11 @@ const ScanQRCode = ({onClose, showScanner, onAdd}) => { // type org.iso.QRCode // data otpauth://totp/casdoor:built-in/admin?algorithm=SHA1&digits=6&issuer=casdoor&period=30&secret=DL5XI33M772GSGU73GJPCOIBNJE7TG3J // console.log(`Bar code with type ${type} and data ${data} has been scanned!`); - - const description = data.match(/casdoor:([^?]+)/); // description casdoor:built-in/admin + const description = data.match(/otpauth:\/\/totp\/([^?]+)/); // description casdoor:built-in/admin const secretCode = data.match(/secret=([^&]+)/); // secretCode II5UO7HIA3SPVXAB6KPAIXZ33AQP7C3R - + const icon = data.match(/issuer=([^&]+)/); if (description && secretCode) { - onAdd({description: description[1], secretCode: secretCode[1]}); + onAdd({description: description[1], secretCode: secretCode[1], icon: `https://cdn.casbin.org/img/social_${icon && icon[1].toLowerCase()}.png`}); } closeOptions(); diff --git a/SearchBar.js b/SearchBar.js index 1f5ac33..6fb3a2d 100644 --- a/SearchBar.js +++ b/SearchBar.js @@ -27,6 +27,8 @@ const SearchBar = ({onSearch}) => { placeholder="Search" onChangeText={onChangeSearch} value={searchQuery} + style={{height: 48, backgroundColor: "#E6DFF3"}} + inputStyle={{textAlignVertical: "center", justifyContent: "center", alignItems: "center"}} /> ); }; diff --git a/package-lock.json b/package-lock.json index 5826f73..5c0ebc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.72.4", + "react-native-gesture-handler": "^2.12.1", "react-native-paper": "^5.10.3", "react-native-web": "~0.19.6", "totp-generator": "^0.0.14" @@ -2085,6 +2086,17 @@ "node": ">=0.10.0" } }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -6467,6 +6479,11 @@ "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.41", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz", + "integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==" + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -17041,6 +17058,22 @@ "react": "18.2.0" } }, + "node_modules/react-native-gesture-handler": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.1.tgz", + "integrity": "sha512-deqh36bw82CFUV9EC4tTo2PP1i9HfCOORGS3Zmv71UYhEZEHkzZv18IZNPB+2Awzj45vLIidZxGYGFxHlDSQ5A==", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", + "lodash": "^4.17.21", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-paper": { "version": "5.10.3", "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.10.3.tgz", diff --git a/package.json b/package.json index ad57056..87ab40d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react": "18.2.0", "react-dom": "18.2.0", "react-native": "0.72.4", + "react-native-gesture-handler": "^2.12.1", "react-native-paper": "^5.10.3", "react-native-web": "~0.19.6", "totp-generator": "^0.0.14"