Skip to content

Commit

Permalink
feat: use "drizzle-orm/expo-sqlite" as DB (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
IZUMI-Zu authored Aug 22, 2024
1 parent e245cfa commit 7f87a3b
Show file tree
Hide file tree
Showing 18 changed files with 1,705 additions and 397 deletions.
61 changes: 46 additions & 15 deletions App.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,59 @@
// limitations under the License.

import * as React from "react";
import {PaperProvider} from "react-native-paper";
import {NavigationContainer} from "@react-navigation/native";
import {BulletList} from "react-content-loader/native";
import {SQLiteProvider} from "expo-sqlite";
import {PaperProvider} from "react-native-paper";
import {SafeAreaView, Text} from "react-native";
import ContentLoader, {Circle, Rect} from "react-content-loader/native";
import Toast from "react-native-toast-message";
import {useMigrations} from "drizzle-orm/expo-sqlite/migrator";

import Header from "./Header";
import NavigationBar from "./NavigationBar";
import {migrateDb} from "./TotpDatabase";
import {db} from "./db/client";
import migrations from "./drizzle/migrations";

const App = () => {
const {success, error} = useMigrations(db, migrations);

if (error) {
return (
<SafeAreaView style={{flex: 1}}>
<Text>Migration error: {error.message}</Text>
</SafeAreaView>
);
}

if (!success) {
return (
<ContentLoader
speed={2}
width={400}
height={150}
viewBox="0 0 400 150"
backgroundColor="#f3f3f3"
foregroundColor="#ecebeb"
>
<Circle cx="10" cy="20" r="8" />
<Rect x="25" y="15" rx="5" ry="5" width="220" height="10" />
<Circle cx="10" cy="50" r="8" />
<Rect x="25" y="45" rx="5" ry="5" width="220" height="10" />
<Circle cx="10" cy="80" r="8" />
<Rect x="25" y="75" rx="5" ry="5" width="220" height="10" />
<Circle cx="10" cy="110" r="8" />
<Rect x="25" y="105" rx="5" ry="5" width="220" height="10" />
</ContentLoader>
);
}

return (
<React.Suspense fallback={<BulletList />}>
<SQLiteProvider databaseName="totp.db" onInit={migrateDb} options={{enableChangeListener: true}}>
<NavigationContainer>
<PaperProvider>
<Header />
<NavigationBar />
</PaperProvider>
</NavigationContainer>
<Toast />
</SQLiteProvider>
</React.Suspense>
<NavigationContainer>
<PaperProvider>
<Header />
<NavigationBar />
</PaperProvider>
<Toast />
</NavigationContainer>
);
};
export default App;
5 changes: 3 additions & 2 deletions Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import {Appbar, Avatar, Menu, Text, TouchableRipple} from "react-native-paper";
import Toast from "react-native-toast-message";
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
import useStore from "./useStorage";
import useSyncStore from "./useSyncStore";
import {useAccountSync} from "./useAccountStore";

const {width} = Dimensions.get("window");

const Header = () => {
const {userInfo, clearAll} = useStore();
const syncError = useSyncStore(state => state.syncError);
const {syncError, clearSyncError} = useAccountSync();
const [showLoginPage, setShowLoginPage] = React.useState(false);
const [menuVisible, setMenuVisible] = React.useState(false);

Expand All @@ -42,6 +42,7 @@ const Header = () => {
const handleCasdoorLogout = () => {
CasdoorLogout();
clearAll();
clearSyncError();
};

const handleSyncErrorPress = () => {
Expand Down
61 changes: 23 additions & 38 deletions HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,19 @@ import {CountdownCircleTimer} from "react-native-countdown-circle-timer";
import {useNetInfo} from "@react-native-community/netinfo";
import {FlashList} from "@shopify/flash-list";
import Toast from "react-native-toast-message";
import * as SQLite from "expo-sqlite/next";
import {useLiveQuery} from "drizzle-orm/expo-sqlite";
import {isNull} from "drizzle-orm";

import SearchBar from "./SearchBar";
import EnterAccountDetails from "./EnterAccountDetails";
import ScanQRCode from "./ScanQRCode";
import EditAccountDetails from "./EditAccountDetails";
import AvatarWithFallback from "./AvatarWithFallback";
import * as TotpDatabase from "./TotpDatabase";
import useStore from "./useStorage";
import useSyncStore from "./useSyncStore";
import * as schema from "./db/schema";
import {db} from "./db/client";
import {calculateCountdown, validateSecret} from "./totpUtil";
import {useAccountSync, useEditAccount, useUpdateAccountToken} from "./useAccountStore";

const {width, height} = Dimensions.get("window");
const REFRESH_INTERVAL = 10000;
Expand All @@ -41,7 +44,7 @@ export default function HomePage() {
const [showOptions, setShowOptions] = useState(false);
const [showEnterAccountModal, setShowEnterAccountModal] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [accounts, setAccounts] = useState([]);
const {data: accounts} = useLiveQuery(db.select().from(schema.accounts).where(isNull(schema.accounts.deletedAt)));
const [filteredData, setFilteredData] = useState(accounts);
const [showScanner, setShowScanner] = useState(false);
const [showEditAccountModal, setShowEditAccountModal] = useState(false);
Expand All @@ -53,17 +56,10 @@ export default function HomePage() {
const [key, setKey] = useState(0);

const swipeableRef = useRef(null);
const db = SQLite.useSQLiteContext();
const {userInfo, serverUrl, token} = useStore();
const {startSync} = useSyncStore();
const syncError = useSyncStore(state => state.syncError);

useEffect(() => {
if (db) {
const subscription = SQLite.addDatabaseChangeListener((event) => {loadAccounts();});
return () => {if (subscription) {subscription.remove();}};
}
}, [db]);
const {startSync} = useAccountSync();
const {updateToken} = useUpdateAccountToken();
const {setAccount, updateAccount, insertAccount, deleteAccount} = useEditAccount();

useEffect(() => {
setCanSync(Boolean(isConnected && userInfo && serverUrl));
Expand All @@ -73,27 +69,17 @@ export default function HomePage() {
setFilteredData(accounts);
}, [accounts]);

useEffect(() => {
loadAccounts();
}, []);

useEffect(() => {
const timer = setInterval(() => {
if (canSync) {startSync(db, userInfo, serverUrl, token);}
if (canSync) {startSync(userInfo, serverUrl, token);}
}, REFRESH_INTERVAL);
return () => clearInterval(timer);
}, [startSync]);

const loadAccounts = async() => {
const loadedAccounts = await TotpDatabase.getAllAccounts(db);
setAccounts(loadedAccounts);
setFilteredData(loadedAccounts);
};

const onRefresh = async() => {
setRefreshing(true);
if (canSync) {
await startSync(db, userInfo, serverUrl, token);
const syncError = await startSync(userInfo, serverUrl, token);
if (syncError) {
Toast.show({
type: "error",
Expand All @@ -110,19 +96,17 @@ export default function HomePage() {
});
}
}
setKey(prevKey => prevKey + 1);
setRefreshing(false);
};

const handleAddAccount = async(accountData) => {
setKey(prevKey => prevKey + 1);
await TotpDatabase.insertAccount(db, accountData);
setAccount(accountData);
insertAccount();
closeEnterAccountModal();
};

const handleDeleteAccount = async(id) => {
await TotpDatabase.deleteAccount(db, id);
};

const handleEditAccount = (account) => {
closeSwipeableMenu();
setEditingAccount(account);
Expand All @@ -132,7 +116,8 @@ export default function HomePage() {

const onAccountEdit = async(newAccountName) => {
if (editingAccount) {
await TotpDatabase.updateAccountName(db, editingAccount.id, newAccountName);
setAccount({...editingAccount, accountName: newAccountName, oldAccountName: editingAccount.accountName});
updateAccount();
setPlaceholder("");
setEditingAccount(null);
closeEditAccountModal();
Expand Down Expand Up @@ -176,7 +161,7 @@ export default function HomePage() {
const handleSearch = (query) => {
setSearchQuery(query);
setFilteredData(query.trim() !== ""
? accounts.filter(item => item.accountName.toLowerCase().includes(query.toLowerCase()))
? accounts && accounts.filter(item => item.accountName.toLowerCase().includes(query.toLowerCase()))
: accounts
);
};
Expand Down Expand Up @@ -205,7 +190,7 @@ export default function HomePage() {
</TouchableOpacity>
<TouchableOpacity
style={{height: 70, width: 80, backgroundColor: "#FFC0CB", alignItems: "center", justifyContent: "center"}}
onPress={() => handleDeleteAccount(item.id)}
onPress={() => deleteAccount(item.id)}
>
<Text>Delete</Text>
</TouchableOpacity>
Expand Down Expand Up @@ -242,16 +227,16 @@ export default function HomePage() {
key={key}
isPlaying={true}
duration={30}
initialRemainingTime={TotpDatabase.calculateCountdown()}
initialRemainingTime={calculateCountdown()}
colors={["#004777", "#0072A0", "#0099CC", "#FF6600", "#CC3300", "#A30000"]}
colorsTime={[30, 24, 18, 12, 6, 0]}
size={60}
onComplete={() => {
TotpDatabase.updateToken(db, item.id);
updateToken(item.id);
return {
shouldRepeat: true,
delay: 0,
newInitialRemainingTime: TotpDatabase.calculateCountdown(),
newInitialRemainingTime: calculateCountdown(),
};
}}
strokeWidth={5}
Expand Down Expand Up @@ -318,7 +303,7 @@ export default function HomePage() {
transform: [{translateX: -OFFSET_X}, {translateY: -OFFSET_Y}],
}}
>
<EnterAccountDetails onClose={closeEnterAccountModal} onAdd={handleAddAccount} validateSecret={TotpDatabase.validateSecret} />
<EnterAccountDetails onClose={closeEnterAccountModal} onAdd={handleAddAccount} validateSecret={validateSecret} />
</Modal>
</Portal>

Expand Down
Loading

0 comments on commit 7f87a3b

Please sign in to comment.