diff --git a/App.tsx b/App.tsx index 8925c5324..5511129bd 100644 --- a/App.tsx +++ b/App.tsx @@ -214,6 +214,7 @@ export default class App extends React.PureComponent { InventoryStore={Stores.inventoryStore} ModalStore={Stores.modalStore} NotesStore={Stores.notesStore} + ContactStore={Stores.contactStore} SyncStore={Stores.syncStore} LSPStore={Stores.lspStore} LightningAddressStore={Stores.lightningAddressStore} diff --git a/stores/ContactStore.ts b/stores/ContactStore.ts new file mode 100644 index 000000000..8fa70f46c --- /dev/null +++ b/stores/ContactStore.ts @@ -0,0 +1,151 @@ +import { action, observable } from 'mobx'; +import { v4 as uuidv4 } from 'uuid'; +import Contact from '../models/Contact'; +import EncryptedStorage from 'react-native-encrypted-storage'; + +export default class ContactStore { + @observable public loading: boolean = true; + @observable public contacts: any = []; + @observable public prefillContact: any = {}; + + @action + public loadContacts = async () => { + try { + this.loading = true; + console.log('LOADING CONTACTS.....'); + const contactsString = await EncryptedStorage.getItem( + 'zeus-contacts' + ); + if (contactsString) { + const allContacts: Contact[] = JSON.parse(contactsString); + this.contacts = allContacts; + this.loading = false; + } else { + this.loading = false; + } + } catch (error) { + console.log('Error loading contacts:', error); + this.loading = false; + } + }; + + @action + public saveContact = async ( + contactDetails: any, + isEdit: boolean, + isNostrContact: boolean, + navigation: any + ) => { + try { + const contactsString = await EncryptedStorage.getItem( + 'zeus-contacts' + ); + const existingContacts: Contact[] = contactsString + ? JSON.parse(contactsString) + : []; + + if (isEdit && this.prefillContact && !isNostrContact) { + const updatedContacts = existingContacts.map((contact) => + contact.contactId === this.prefillContact.contactId + ? { ...contact, ...contactDetails } + : contact + ); + + // Sort the updated contacts alphabetically + updatedContacts.sort((a, b) => a.name.localeCompare(b.name)); + + // Save the updated contacts to encrypted storage + await EncryptedStorage.setItem( + 'zeus-contacts', + JSON.stringify(updatedContacts) + ); + + console.log('Contact updated successfully!', updatedContacts); + + this.loadContacts(); + navigation.popTo('Contacts'); + } else { + // Creating a new contact + const contactId = uuidv4(); + + const newContact: Contact = { contactId, ...contactDetails }; + + const updatedContacts = [...existingContacts, newContact].sort( + (a, b) => a.name.localeCompare(b.name) + ); + + // Save the updated contacts to encrypted storage + await EncryptedStorage.setItem( + 'zeus-contacts', + JSON.stringify(updatedContacts) + ); + + console.log('Contact saved successfully!'); + + this.loadContacts(); + navigation.popTo('Contacts'); + } + // Clear the prefillContact after saving + this.clearPrefillContact(); + } catch (error) { + console.log('Error saving contacts:', error); + } + }; + + @action + public deleteContact = async (navigation: any) => { + if (this.prefillContact) { + try { + const contactsString = await EncryptedStorage.getItem( + 'zeus-contacts' + ); + const existingContacts: Contact[] = contactsString + ? JSON.parse(contactsString) + : []; + + const updatedContacts = existingContacts.filter( + (contact) => + contact.contactId !== this.prefillContact.contactId + ); + + await EncryptedStorage.setItem( + 'zeus-contacts', + JSON.stringify(updatedContacts) + ); + + console.log('Contact deleted successfully!'); + + this.loadContacts(); + navigation.popTo('Contacts'); + } catch (error) { + console.log('Error deleting contact:', error); + } + } + }; + + @action + public setPrefillContact = (prefillContact: Contact | null) => { + if (prefillContact) { + this.prefillContact = { + lnAddress: prefillContact.lnAddress, + bolt12Address: prefillContact.bolt12Address, + onchainAddress: prefillContact.onchainAddress, + nip05: prefillContact.nip05, + nostrNpub: prefillContact.nostrNpub, + pubkey: prefillContact.pubkey, + name: prefillContact.name, + description: prefillContact.description, + photo: prefillContact.photo, + isFavourite: prefillContact.isFavourite, + contactId: prefillContact?.contactId + }; + } else { + this.prefillContact = null; + } + }; + + @action + public clearPrefillContact = () => { + this.prefillContact = null; + }; +} diff --git a/stores/Stores.ts b/stores/Stores.ts index 8ca702269..cd64e0f69 100644 --- a/stores/Stores.ts +++ b/stores/Stores.ts @@ -16,6 +16,7 @@ import ActivityStore from './ActivityStore'; import PosStore from './PosStore'; import ModalStore from './ModalStore'; import NotesStore from './NotesStore'; +import ContactStore from './ContactStore'; import SyncStore from './SyncStore'; import LSPStore from './LSPStore'; import LightningAddressStore from './LightningAddressStore'; @@ -42,6 +43,7 @@ class Stores { public posStore: PosStore; public modalStore: ModalStore; public notesStore: NotesStore; + public contactStore: ContactStore; public syncStore: SyncStore; public lspStore: LSPStore; public lightningAddressStore: LightningAddressStore; @@ -101,6 +103,7 @@ class Stores { this.utxosStore = new UTXOsStore(this.settingsStore); this.messageSignStore = new MessageSignStore(); this.notesStore = new NotesStore(); + this.contactStore = new ContactStore(); this.syncStore = new SyncStore(this.settingsStore); this.activityStore = new ActivityStore( this.settingsStore, diff --git a/views/ContactDetails.tsx b/views/ContactDetails.tsx index bcc93ff80..33e92c3eb 100644 --- a/views/ContactDetails.tsx +++ b/views/ContactDetails.tsx @@ -7,6 +7,7 @@ import { TouchableOpacity, ScrollView } from 'react-native'; +import { inject, observer } from 'mobx-react'; import EncryptedStorage from 'react-native-encrypted-storage'; import { Route } from '@react-navigation/native'; import { StackNavigationProp } from '@react-navigation/stack'; @@ -17,6 +18,8 @@ import LoadingIndicator from '../components/LoadingIndicator'; import Header from '../components/Header'; import { Row } from '../components/layout/Row'; +import ContactStore from '../stores/ContactStore'; + import LightningBolt from '../assets/images/SVG/Lightning Bolt.svg'; import BitcoinIcon from '../assets/images/SVG/BitcoinIcon.svg'; import KeySecurity from '../assets/images/SVG/Key Security.svg'; @@ -41,6 +44,7 @@ interface ContactDetailsProps { nostrContact: any; } >; + ContactStore: ContactStore; } interface ContactDetailsState { @@ -48,6 +52,9 @@ interface ContactDetailsState { isLoading: boolean; isNostrContact: boolean; } + +@inject('ContactStore') +@observer export default class ContactDetails extends React.Component< ContactDetailsProps, ContactDetailsState @@ -136,6 +143,7 @@ export default class ContactDetails extends React.Component< }; saveUpdatedContact = async (updatedContact: Contact) => { + const { ContactStore } = this.props; try { const contactsString = await EncryptedStorage.getItem( 'zeus-contacts' @@ -160,6 +168,7 @@ export default class ContactDetails extends React.Component< ); console.log('Contact updated successfully!'); + ContactStore?.loadContacts(); } } } catch (error) { @@ -212,7 +221,8 @@ export default class ContactDetails extends React.Component< render() { const { isLoading, isNostrContact } = this.state; - const { navigation } = this.props; + const { navigation, ContactStore } = this.props; + const { setPrefillContact } = ContactStore; const contact = new Contact(this.state.contact); const nostrContact = this.props.route.params?.nostrContact; @@ -229,12 +239,12 @@ export default class ContactDetails extends React.Component< const EditContactButton = () => ( + onPress={() => { + setPrefillContact(contact); navigation.navigate('AddContact', { - prefillContact: contact, isEdit: true - }) - } + }); + }} > { navigation.goBack(); + setPrefillContact(nostrContact); navigation.navigate('AddContact', { - prefillContact: nostrContact, isEdit: true, isNostrContact }); diff --git a/views/NostrContacts.tsx b/views/NostrContacts.tsx index 961393068..03c0fe4f9 100644 --- a/views/NostrContacts.tsx +++ b/views/NostrContacts.tsx @@ -9,6 +9,7 @@ import { Animated, Easing } from 'react-native'; +import { inject, observer } from 'mobx-react'; import { CheckBox, Icon } from 'react-native-elements'; import EncryptedStorage from 'react-native-encrypted-storage'; import { relayInit, nip05, nip19 } from 'nostr-tools'; @@ -28,12 +29,14 @@ import { localeString } from '../utils/LocaleUtils'; import { themeColor } from '../utils/ThemeUtils'; import { DEFAULT_NOSTR_RELAYS } from '../stores/SettingsStore'; +import ContactStore from '../stores/ContactStore'; import SelectOff from '../assets/images/SVG/Select Off.svg'; import SelectOn from '../assets/images/SVG/Select On.svg'; interface NostrContactsProps { navigation: StackNavigationProp; + ContactStore: ContactStore; } interface NostrContactsState { @@ -49,6 +52,8 @@ interface NostrContactsState { error: string; } +@inject('ContactStore') +@observer export default class NostrContacts extends React.Component< NostrContactsProps, NostrContactsState @@ -367,6 +372,7 @@ export default class NostrContacts extends React.Component< }; importContacts = async () => { + const { ContactStore } = this.props; this.setState({ loading: true }); @@ -420,6 +426,8 @@ export default class NostrContacts extends React.Component< ); console.log('Contacts imported successfully!'); + + ContactStore?.loadContacts(); this.setState({ loading: false }); diff --git a/views/Settings/AddContact.tsx b/views/Settings/AddContact.tsx index 97c0b2809..6394bb01e 100644 --- a/views/Settings/AddContact.tsx +++ b/views/Settings/AddContact.tsx @@ -11,8 +11,7 @@ import { Text, TextInput } from 'react-native'; -import { v4 as uuidv4 } from 'uuid'; -import EncryptedStorage from 'react-native-encrypted-storage'; +import { inject, observer } from 'mobx-react'; import { Icon, Divider } from 'react-native-elements'; import { launchImageLibrary } from 'react-native-image-picker'; import RNFS from 'react-native-fs'; @@ -29,6 +28,8 @@ import AddressUtils from '../../utils/AddressUtils'; import { getPhoto } from '../../utils/PhotoUtils'; import { themeColor } from '../../utils/ThemeUtils'; +import ContactStore from '../../stores/ContactStore'; + import LightningBolt from '../../assets/images/SVG/Lightning Bolt.svg'; import BitcoinIcon from '../../assets/images/SVG/BitcoinIcon.svg'; import KeySecurity from '../../assets/images/SVG/Key Security.svg'; @@ -39,10 +40,8 @@ import Star from '../../assets/images/SVG/Star.svg'; interface AddContactProps { navigation: StackNavigationProp; - route: Route< - 'AddContact', - { isEdit: boolean; prefillContact: Contact; isNostrContact: boolean } - >; + route: Route<'AddContact', { isEdit: boolean; isNostrContact: boolean }>; + ContactStore: ContactStore; } interface Contact { @@ -84,6 +83,8 @@ interface AddContactState { isValidPubkey: boolean; } +@inject('ContactStore') +@observer export default class AddContact extends React.Component< AddContactProps, AddContactState @@ -128,144 +129,21 @@ export default class AddContact extends React.Component< }; saveContact = async () => { - const { navigation, route } = this.props; - const { - lnAddress, - bolt12Address, - bolt12Offer, - onchainAddress, - nip05, - nostrNpub, - pubkey, - name, - description, - photo, - isFavourite - } = this.state; - - const { isEdit, prefillContact, isNostrContact } = route.params ?? {}; - - try { - // Retrieve existing contacts from storage - const contactsString = await EncryptedStorage.getItem( - 'zeus-contacts' - ); - const existingContacts: Contact[] = contactsString - ? JSON.parse(contactsString) - : []; - - if (isEdit && prefillContact && !isNostrContact) { - // Editing an existing contact - const updatedContacts = existingContacts.map((contact) => - contact.contactId === prefillContact.contactId - ? { - ...contact, - lnAddress, - bolt12Address, - bolt12Offer, - onchainAddress, - nip05, - nostrNpub, - pubkey, - name, - description, - photo, - isFavourite - } - : contact - ); - - // Sort the updated contacts alphabetically - updatedContacts.sort((a, b) => a.name.localeCompare(b.name)); - - // Save the updated contacts to encrypted storage - await EncryptedStorage.setItem( - 'zeus-contacts', - JSON.stringify(updatedContacts) - ); - - console.log('Contact updated successfully!'); - navigation.popTo('Contacts'); - } else { - // Creating a new contact - const contactId = uuidv4(); - - const newContact: Contact = { - contactId, - lnAddress, - bolt12Address, - bolt12Offer, - onchainAddress, - nip05, - nostrNpub, - pubkey, - name, - description, - photo, - isFavourite - }; - - const updatedContacts = [...existingContacts, newContact].sort( - (a, b) => a.name.localeCompare(b.name) - ); - - // Save the updated contacts to encrypted storage - await EncryptedStorage.setItem( - 'zeus-contacts', - JSON.stringify(updatedContacts) - ); - - console.log('Contact saved successfully!'); - navigation.popTo('Contacts'); - - // Reset the input fields after saving the contact - this.setState({ - contacts: updatedContacts, - lnAddress: [], - bolt12Address: [], - bolt12Offer: [], - onchainAddress: [], - nip05: [], - nostrNpub: [], - pubkey: [], - name: '', - description: '', - photo: null - }); - } - } catch (error) { - console.log('Error saving contacts:', error); - } + const { navigation, route, ContactStore } = this.props; + const { isEdit, isNostrContact } = route.params ?? {}; + const contactDetails = { ...this.state }; + await ContactStore.saveContact( + contactDetails, + isEdit, + isNostrContact, + navigation + ); }; deleteContact = async () => { - const { navigation, route } = this.props; - const prefillContact = route.params?.prefillContact; + const { navigation, ContactStore } = this.props; - if (prefillContact) { - try { - const contactsString = await EncryptedStorage.getItem( - 'zeus-contacts' - ); - const existingContacts: Contact[] = contactsString - ? JSON.parse(contactsString) - : []; - - const updatedContacts = existingContacts.filter( - (contact) => contact.contactId !== prefillContact.contactId - ); - - await EncryptedStorage.setItem( - 'zeus-contacts', - JSON.stringify(updatedContacts) - ); - - console.log('Contact deleted successfully!'); - navigation.popTo('Contacts'); - } catch (error) { - console.log('Error deleting contact:', error); - } - } + await ContactStore?.deleteContact(navigation); }; selectPhoto = () => { @@ -373,37 +251,25 @@ export default class AddContact extends React.Component< } componentDidUpdate(prevProps: AddContactProps) { - const prefillContact = this.props.route.params?.prefillContact; - const prevPrefillContact = prevProps.route.params?.prefillContact; - - // Check if the prefillContact prop has changed - if (prefillContact !== prevPrefillContact) { + const { ContactStore } = this.props; + if ( + ContactStore.prefillContact !== + prevProps.ContactStore.prefillContact + ) { this.handlePrefillContact(); } } - handlePrefillContact() { - const prefillContact = this.props.route.params?.prefillContact; + handlePrefillContact = () => { + const { ContactStore } = this.props; - if (prefillContact) { - this.setState({ - lnAddress: prefillContact.lnAddress, - bolt12Address: prefillContact.bolt12Address, - bolt12Offer: prefillContact.bolt12Offer, - onchainAddress: prefillContact.onchainAddress, - nip05: prefillContact.nip05, - nostrNpub: prefillContact.nostrNpub, - pubkey: prefillContact.pubkey, - name: prefillContact.name, - description: prefillContact.description, - photo: prefillContact.photo, - isFavourite: prefillContact.isFavourite - }); + if (ContactStore.prefillContact) { + this.setState({ ...ContactStore.prefillContact }); } - } + }; render() { - const { navigation } = this.props; + const { navigation, ContactStore } = this.props; const { lnAddress, bolt12Address, @@ -481,7 +347,7 @@ export default class AddContact extends React.Component< /> ); - const { isEdit, prefillContact } = this.props.route.params ?? {}; + const { isEdit } = this.props.route.params ?? {}; const ScanBadge = ({ navigation @@ -521,6 +387,9 @@ export default class AddContact extends React.Component< } + onBack={() => { + ContactStore?.clearPrefillContact(); + }} containerStyle={{ borderBottomWidth: 0 }} @@ -1555,7 +1424,7 @@ export default class AddContact extends React.Component< } /> - {isEdit && prefillContact && ( + {isEdit && ContactStore?.prefillContact && (