import React, {createRef, useEffect, useState} from 'react'; import { ActivityIndicator, Clipboard, Dimensions, StatusBar, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import Share from 'react-native-share'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import {useTracked} from '../../provider'; import {Actions} from '../../provider/Actions'; import {eSendEvent, openVault, sendNoteEditedEvent, ToastEvent} from '../../services/EventManager'; import { eOnNoteEdited, eOpenLoginDialog, eOpenMoveNoteDialog, refreshNotesPage, } from '../../utils/Events'; import {PremiumTag} from '../Premium/PremiumTag'; import {PressableButton} from '../PressableButton'; import {Toast} from '../Toast'; import {hexToRGBA, RGB_Linear_Shade} from "../../utils/ColorUtils"; import {timeConverter} from "../../utils/TimeUtils"; import { ACCENT, COLOR_SCHEME, COLOR_SCHEME_DARK, COLOR_SCHEME_LIGHT, COLORS_NOTE, setColorScheme } from "../../utils/Colors"; import {opacity, ph, pv, SIZE, WEIGHT} from "../../utils/SizeUtils"; import {db} from "../../utils/DB"; import {DDS} from "../../services/DeviceDetection"; import {MMKV} from "../../utils/MMKV"; import id from "notes-core/utils/id"; const w = Dimensions.get('window').width; const h = Dimensions.get('window').height; const tagsInputRef = createRef(); export const ActionSheetComponent = ({ close = () => {}, item, hasColors = false, hasTags = false, rowItems = [], columnItems = [], }) => { const [state, dispatch] = useTracked(); const {colors, tags, premiumUser, user} = state; const [focused, setFocused] = useState(false); const [refreshing, setRefreshing] = useState(false); const [note, setNote] = useState( item ? item : { colors: [], tags: [], pinned: false, favorite: false, locked: false, content: { text: null, delta: null, }, dateCreated: null, }, ); function changeColorScheme(colors = COLOR_SCHEME, accent = ACCENT) { let newColors = setColorScheme(colors, accent); StatusBar.setBarStyle(colors.night ? 'light-content' : 'dark-content'); dispatch({type: Actions.THEME, colors: newColors}); } useEffect(() => { if (item.dateCreated !== null) { setNote({...item}); } }, [item]); let tagToAdd = null; let backPressCount = 0; const _onSubmit = async () => { if (!tagToAdd || tagToAdd === '' || tagToAdd.trimStart().length == 0) { ToastEvent.show('Empty Tag', 'success'); return; } let tag = tagToAdd; tag = tag.trim(); if (tag.includes(' ')) { tag = tag.replace(' ', '_'); } if (tag.includes(',')) { tag = tag.replace(',', ''); } await db.notes.note(note.id).tag(tag); setNote({...db.notes.note(note.id).data}); dispatch({type: Actions.TAGS}); tagsInputRef.current?.setNativeProps({ text: '', }); tagToAdd = ''; }; const _onKeyPress = async (event) => { if (event.nativeEvent.key === 'Backspace') { if (backPressCount === 0 && !tagToAdd) { backPressCount = 1; return; } if (backPressCount === 1 && !tagToAdd) { backPressCount = 0; let tagInputValue = note.tags[note.tags.length - 1]; let oldProps = {...note}; if (oldProps.tags.length === 0) return; await db.notes .note(note.id) .untag(oldProps.tags[oldProps.tags.length - 1]); setNote({...db.notes.note(note.id).data}); tagsInputRef.current?.setNativeProps({ text: tagInputValue, }); } } else if (event.nativeEvent.key === ' ') { await _onSubmit(); tagsInputRef.current?.setNativeProps({ text: '', }); } else if (event.nativeEvent.key === ',') { await _onSubmit(); tagsInputRef.current?.setNativeProps({ text: '', }); } }; const localRefresh = (type, nodispatch = false) => { if (!note || !note.id) return; let toAdd; switch (type) { case 'note': { toAdd = db.notes.note(note.id); if (toAdd) { toAdd = toAdd.data; } else { setTimeout(() => { toAdd = db.notes.note(note.id); if (toAdd) { toAdd = toAdd.data; } }, 500); } break; } case 'notebook': { toAdd = db.notebooks.notebook(note.id); if (toAdd) { toAdd = toAdd.data; } else { setTimeout(() => { toAdd = db.notebooks.notebook(note.id); if (toAdd) { toAdd = toAdd.data; } }, 500); } break; } case 'topic': { toAdd = db.notebooks.notebook(note.notebookId).topics.topic(note.title); break; } } if (!toAdd || !toAdd.id) return; if (!nodispatch) { dispatch({type: type}); dispatch({type: Actions.FAVORITES}); } setNote({...toAdd}); }; const rowItemsData = [ { name: 'Add to', icon: 'book-outline', func: () => { dispatch({type: Actions.MODAL_NAVIGATOR, enabled: true}); dispatch({type: Actions.SELECTED_ITEMS, item: note}); close(); setTimeout(() => { eSendEvent(eOpenMoveNoteDialog); }, 400); }, }, { name: 'Share', icon: 'share-variant', func: () => { if (note.locked) { openVault(item, false, true, false, false, true); } else { close(); let m = `${note.title}\n \n ${note.content.text}`; Share.open({ title: 'Share note to', failOnCancel: false, message: m, }); } }, }, { name: 'Export', icon: 'export', func: () => { close('export'); }, }, { name: 'Delete', icon: 'delete', func: () => close('delete'), }, { name: 'Edit Notebook', icon: 'square-edit-outline', func: () => { close('notebook'); }, }, { name: 'Edit Topic', icon: 'square-edit-outline', func: () => { close('topic'); }, }, { name: 'Copy', icon: 'content-copy', func: async () => { let text = await db.notes.note(note.id).text(); Clipboard.setString(text); ToastEvent.show('Note copied to clipboard', 'success', 'local'); }, }, { name: 'Restore', icon: 'delete-restore', func: async () => { dispatch({type: Actions.TRASH}); localRefresh(note.type); ToastEvent.show( item.type === 'note' ? 'Note restored' : 'Notebook restored', 'success', ); close(); }, }, { name: 'Remove', icon: 'delete', func: () => { close('permanant_delete'); }, }, ]; const columnItemsData = [ { name: 'Dark Mode', icon: 'theme-light-dark', func: () => { if (!colors.night) { MMKV.setStringAsync('theme', JSON.stringify({night: true})); changeColorScheme(COLOR_SCHEME_DARK); } else { MMKV.setStringAsync('theme', JSON.stringify({night: false})); changeColorScheme(COLOR_SCHEME_LIGHT); } }, switch: true, on: colors.night ? true : false, close: false, nopremium: true, }, { name: 'Pin', icon: 'tag-outline', func: async () => { if (!premiumUser) { close('premium'); return; } if (!note.id) return; if (note.type === 'note') { await db.notes.note(note.id).pin(); } else { await db.notebooks.notebook(note.id).pin(); } localRefresh(item.type); }, close: false, check: true, on: note.pinned, }, { name: 'Favorite', icon: 'star', func: async () => { if (!premiumUser) { close('premium'); return; } if (!note.id) return; if (note.type === 'note') { await db.notes.note(note.id).favorite(); } else { await db.notebooks.notebook(note.id).favorite(); } dispatch({type: Actions.FAVORITES}); sendNoteEditedEvent(note.id, false,true) localRefresh(item.type,true); }, close: false, check: true, on: note.favorite, }, { name: 'Add to Vault', icon: 'shield', func: () => { if (!premiumUser) { close('premium'); return; } if (!note.id) return; if (note.locked) { close('unlock'); } else { db.vault .add(note.id) .then(() => { sendNoteEditedEvent(note.id, false,true) close(); }) .catch(async (e) => { switch (e.message) { case db.vault.ERRORS.noVault: close('novault'); break; case db.vault.ERRORS.vaultLocked: close('locked'); break; case db.vault.ERRORS.wrongPassword: close(); break; } }); } }, close: true, check: true, on: note.locked, }, ]; const _renderTag = (tag) => ( { let oldProps = {...note}; try { await db.notes .note(note.id) .untag(oldProps.tags[oldProps.tags.indexOf(tag)]); sendNoteEditedEvent(note.id, false,true) dispatch({type: Actions.TAGS}); } catch (e) { sendNoteEditedEvent(note.id, false,true) } }} style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', margin: 1, paddingHorizontal: 5, paddingVertical: 2.5, }}> # {tag} ); const _renderColor = (c) => { const color = { name: c, value: COLORS_NOTE[c], }; return ( { let noteColors = note.colors; if (noteColors.includes(color.name)) { await db.notes.note(note.id).uncolor(color.name); } else { await db.notes.note(note.id).color(color.name); } dispatch({type: Actions.COLORS}); sendNoteEditedEvent(note.id, false,true) localRefresh(note.type,true); }} customStyle={{ width: DDS.isTab ? 400 / 10 : w / 10, height: DDS.isTab ? 400 / 10 : w / 10, borderRadius: 100, justifyContent: 'center', alignItems: 'center', }}> {note && note.colors && note.colors.includes(color.name) ? ( ) : null} ); }; const _renderRowItem = (rowItem) => rowItems.includes(rowItem.name) ? ( {rowItem.name} ) : null; const _renderColumnItem = (item) => (note.id && columnItems.includes(item.name)) || (item.name === 'Dark Mode' && columnItems.includes(item.name)) ? ( { item.func(); }} style={{ width: '100%', alignSelf: 'center', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-end', paddingHorizontal: 12, paddingVertical: pv, }}> {item.name} {item.switch ? ( ) : undefined} {item.nopremium ? null : } {item.check ? ( ) : null} ) : null; const onPressSync = async () => { if (!user || !user.Id) { ToastEvent.show( 'You must login to sync', 'error', 'local', 5000, () => { close(); setTimeout(() => { eSendEvent(eOpenLoginDialog); }, 500); }, 'Login', ); return; } if (user?.lastSynced < note?.dateEdited) { setRefreshing(true); try { let user = await db.user.get(); dispatch({type: Actions.USER, user: user}); await db.sync(); localRefresh(); ToastEvent.show('Note synced', 'success', 'local'); } catch (e) { ToastEvent.show(e.message, 'error', 'local'); } finally { setRefreshing(false); } dispatch({type: Actions.ALL}); } }; return ( { if (!item.dateDeleted) { localRefresh(item.type, true); } }} style={{ paddingBottom: 30, backgroundColor: colors.bg, paddingHorizontal: 0, }}> {!note.id && !note.dateCreated ? ( Please start writing to save your note. ) : ( {note.title.replace('\n', '')} {note.type === 'notebook' && note.description ? note.description : null} {note.type === 'note' ? note.headline[item.headline.length - 1] === '\n' ? note.headline.slice(0, note.headline.length - 1) : note.headline : null} {note.type === 'note' ? 'Last edited on ' + timeConverter(note.dateEdited) : null} {note.type !== 'note' && !note.dateDeleted ? 'Created on ' + timeConverter(note.dateCreated) : null} {note.dateDeleted ? 'Deleted on ' + timeConverter(note.dateDeleted) : null} {note.type !== 'notebook' ? null : ( {note && note.topics ? note.topics.slice(1, 4).map((topic) => ( {topic.title} )) : null} )} {note.type !== 'note' || refreshing ? null : ( {user && user.lastSynced > note.dateEdited ? 'Synced' : 'Sync Now'} )} {refreshing ? ( ) : null} )} {note.id || note.dateCreated ? ( {rowItemsData.map(_renderRowItem)} ) : null} {hasColors && note.id ? ( {Object.keys(COLORS_NOTE).map(_renderColor)} ) : null} {hasTags && (note.id || note.dateCreated) ? ( {tags.filter( (o) => o.count > 1 && !note.tags.find((t) => t === o.title), ).length === 0 ? '' : 'Suggestions '} {tags .filter( (o) => o.count > 1 && !note.tags.find((t) => t === o.title), ) .map((tag) => ( { tagToAdd = tag.title; _onSubmit(); }} style={{ flexDirection: 'row', justifyContent: 'flex-start', alignItems: 'center', margin: 1, marginRight: 5, paddingHorizontal: 5, paddingVertical: 1, borderRadius: 2.5, borderBottomWidth: 1, borderBottomColor: colors.nav, }}> # {tag.title}{' '} {tag.count} ))} { if (!premiumUser) { close('premium'); return; } tagsInputRef.current?.focus(); }} activeOpacity={1} style={{ position: 'absolute', width: '100%', height: '100%', justifyContent: 'flex-start', alignItems: 'flex-end', zIndex:10 }}> {!premiumUser ? ( PRO ) : null} {note && note.tags ? note.tags.map(_renderTag) : null} { setFocused(true); }} selectionColor={colors.accent} onBlur={() => { setFocused(false); }} placeholder="#hashtag" onChangeText={(value) => { tagToAdd = value; if (tagToAdd.length > 0) backPressCount = 0; }} onSubmitEditing={_onSubmit} onKeyPress={_onKeyPress} /> ) : null} {columnItems.length > 0 ? ( {columnItemsData.map(_renderColumnItem)} ) : null} {DDS.isTab ? ( ) : null} ); };