From f7d86420ea102b9b5f42bf29577a91e9448fb242 Mon Sep 17 00:00:00 2001 From: Ammar Ahmed Date: Sat, 24 Jan 2026 12:32:12 +0500 Subject: [PATCH] mobile: add inbox api settings --- apps/mobile/app/common/database/storage.ts | 28 +- .../components/sheets/add-api-key/index.tsx | 169 +++++++ .../mobile/app/components/ui/notice/index.tsx | 4 +- apps/mobile/app/components/ui/sheet/index.jsx | 8 - .../app/screens/settings/components.tsx | 5 +- .../screens/settings/manage-inbox-keys.tsx | 474 ++++++++++++++++++ .../app/screens/settings/section-item.tsx | 59 +-- .../app/screens/settings/settings-data.tsx | 58 +++ apps/mobile/app/stores/use-setting-store.ts | 9 +- apps/mobile/fonts/MaterialCommunityIcons.ttf | Bin 26152 -> 26260 bytes apps/mobile/globals.js | 3 + apps/mobile/package-lock.json | 31 +- apps/mobile/package.json | 1 + apps/mobile/scripts/optimize-fonts.mjs | 3 +- packages/intl/locale/en.po | 209 ++++++++ packages/intl/locale/pseudo-LOCALE.po | 209 ++++++++ packages/intl/src/strings.ts | 63 ++- 17 files changed, 1284 insertions(+), 49 deletions(-) create mode 100644 apps/mobile/app/components/sheets/add-api-key/index.tsx create mode 100644 apps/mobile/app/screens/settings/manage-inbox-keys.tsx diff --git a/apps/mobile/app/common/database/storage.ts b/apps/mobile/app/common/database/storage.ts index c0241db25..1e263dde0 100644 --- a/apps/mobile/app/common/database/storage.ts +++ b/apps/mobile/app/common/database/storage.ts @@ -31,6 +31,7 @@ import { generateCryptoKeyFallback } from "./encryption"; import { MMKV } from "./mmkv"; +import OpenPGP from "react-native-fast-openpgp"; export class KV { storage: MMKVInstance; @@ -136,11 +137,30 @@ export const Storage: IStorage = { clear(): Promise { return DefaultStorage.clear(); }, - generateCryptoKeyPair() { - throw new Error("Not implemented"); + async generatePGPKeyPair() { + const keys = await OpenPGP.generate({ + name: "NN", + email: "NN@NN.NN" + }); + return { publicKey: keys.publicKey, privateKey: keys.privateKey }; }, - decryptAsymmetric() { - throw new Error("Not implemented"); + async validatePGPKeyPair(keys) { + try { + const dummyData = JSON.stringify({ + favorite: true, + title: "Hello world" + }); + const encrypted = await OpenPGP.encrypt(dummyData, keys.publicKey); + const decrypted = await OpenPGP.decrypt(encrypted, keys.privateKey, ""); + + return decrypted === dummyData; + } catch (e) { + console.error("PGP key pair validation error:", e); + return false; + } + }, + async decryptPGPMessage(privateKeyArmored, encryptedMessage) { + return await OpenPGP.decrypt(encryptedMessage, privateKeyArmored, ""); }, getAllKeys(): Promise { return DefaultStorage.getAllKeys(); diff --git a/apps/mobile/app/components/sheets/add-api-key/index.tsx b/apps/mobile/app/components/sheets/add-api-key/index.tsx new file mode 100644 index 000000000..ea7a00f78 --- /dev/null +++ b/apps/mobile/app/components/sheets/add-api-key/index.tsx @@ -0,0 +1,169 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useRef, useState } from "react"; +import { View, ScrollView } from "react-native"; +import { strings } from "@notesnook/intl"; +import { db } from "../../../common/database"; +import { Button } from "../../ui/button"; +import Input from "../../ui/input"; +import Paragraph from "../../ui/typography/paragraph"; +import Heading from "../../ui/typography/heading"; +import { DefaultAppStyles } from "../../../utils/styles"; +import { AppFontSize } from "../../../utils/size"; +import { ToastManager, presentSheet } from "../../../services/event-manager"; +import { useThemeColors } from "@notesnook/theme"; +import { Pressable } from "../../ui/pressable"; + +const getExpiryOptions = () => [ + { label: strings.expiryOneDay(), value: 24 * 60 * 60 * 1000 }, + { label: strings.expiryOneWeek(), value: 7 * 24 * 60 * 60 * 1000 }, + { label: strings.expiryOneMonth(), value: 30 * 24 * 60 * 60 * 1000 }, + { label: strings.expiryOneYear(), value: 365 * 24 * 60 * 60 * 1000 }, + { label: strings.never(), value: -1 } +]; + +type AddApiKeySheetProps = { + close?: (ctx?: string | undefined) => void; + onAdd: () => void; +}; + +export default function AddApiKeySheet({ close, onAdd }: AddApiKeySheetProps) { + const { colors } = useThemeColors(); + const keyNameRef = useRef(""); + const [selectedExpiry, setSelectedExpiry] = useState( + getExpiryOptions()[2].value + ); + const [isCreating, setIsCreating] = useState(false); + + const handleCreate = async () => { + try { + if (!keyNameRef.current || !keyNameRef.current.trim()) { + ToastManager.show({ + message: strings.enterKeyName(), + type: "error" + }); + return; + } + + setIsCreating(true); + await db.inboxApiKeys.create(keyNameRef.current, selectedExpiry); + ToastManager.show({ + message: strings.apiKeyCreatedSuccessfully(), + type: "success" + }); + onAdd(); + close?.(); + } catch (error) { + const message = error instanceof Error ? error.message : ""; + ToastManager.show({ + message: strings.failedToCreateApiKey(message), + type: "error" + }); + } finally { + setIsCreating(false); + } + }; + + return ( + + + {strings.createApiKey()} + + + + {strings.keyName()} + { + keyNameRef.current = text; + }} + /> + + + + {strings.expiresIn()} + + {getExpiryOptions().map((option) => ( + setSelectedExpiry(option.value)} + type={ + selectedExpiry === option.value ? "selected" : "transparent" + } + style={{ + paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL, + paddingHorizontal: DefaultAppStyles.GAP + }} + > + + {option.label} + + + ))} + + + +