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"