diff --git a/apps/mobile/src/components/launcher/index.js b/apps/mobile/src/components/launcher/index.js
index 035724597..e519970ae 100644
--- a/apps/mobile/src/components/launcher/index.js
+++ b/apps/mobile/src/components/launcher/index.js
@@ -17,7 +17,7 @@ import { useNoteStore } from '../../stores/use-notes-store';
import { useSettingStore } from '../../stores/use-setting-store';
import { useThemeStore } from '../../stores/use-theme-store';
import { useUserStore } from '../../stores/use-user-store';
-import { db, loadDatabase } from '../../utils/database';
+import { DatabaseLogger, db, loadDatabase } from '../../utils/database';
import { MMKV } from '../../utils/database/mmkv';
import { eCloseProgressDialog, eOpenAnnouncementDialog } from '../../utils/events';
import { tabBarRef } from '../../utils/global-refs';
@@ -85,6 +85,7 @@ const Launcher = React.memo(
await loadDatabase();
db?.eventManager?.subscribe(EVENTS.databaseMigrating, onDatabaseMigratingProgess);
db?.eventManager?.subscribe(EVENTS.databaseMigrated, onMigrationCompleted);
+ DatabaseLogger.info('Initializing database');
await db.init();
dbInitCompleted.current = true;
}
@@ -139,6 +140,7 @@ const Launcher = React.memo(
};
const checkAppUpdateAvailable = async () => {
+ if (__DEV__) return;
try {
const version = await checkVersion();
if (!version.needsUpdate) return false;
diff --git a/apps/mobile/src/navigation/index.js b/apps/mobile/src/navigation/index.js
index a77ee0619..79ec2332c 100644
--- a/apps/mobile/src/navigation/index.js
+++ b/apps/mobile/src/navigation/index.js
@@ -6,18 +6,28 @@ import { Header } from '../components/header';
import { Toast } from '../components/toast';
import { useNoteStore } from '../stores/use-notes-store';
import { useSettingStore } from '../stores/use-setting-store';
+import { useThemeStore } from '../stores/use-theme-store';
import { TabsHolder } from './tabs-holder';
export const ApplicationHolder = React.memo(
() => {
const loading = useNoteStore(state => state.loading);
const introCompleted = useSettingStore(state => state.settings.introCompleted);
+ const colors = useThemeStore(state => state.colors);
+
return (
<>
{loading && introCompleted ? (
<>
-
-
+
+
+
+
>
) : (
<>
diff --git a/apps/mobile/src/screens/editor/index.tsx b/apps/mobile/src/screens/editor/index.tsx
index 4ccb8091f..aaccdb739 100755
--- a/apps/mobile/src/screens/editor/index.tsx
+++ b/apps/mobile/src/screens/editor/index.tsx
@@ -139,7 +139,7 @@ const Editor = React.memo(
allowUniversalAccessFromFileURLs={true}
originWhitelist={['*']}
source={{
- uri: __DEV__ ? 'http://localhost:3000/index.html' : EDITOR_URI
+ uri: __DEV__ ? 'http://192.168.10.6:3000/index.html' : EDITOR_URI
}}
style={style}
autoManageStatusBarEnabled={false}
diff --git a/apps/mobile/src/screens/settings/debug.tsx b/apps/mobile/src/screens/settings/debug.tsx
index 6f096804d..4c0812cf2 100644
--- a/apps/mobile/src/screens/settings/debug.tsx
+++ b/apps/mobile/src/screens/settings/debug.tsx
@@ -1,5 +1,5 @@
import Clipboard from '@react-native-clipboard/clipboard';
-import React from 'react';
+import React, { useState } from 'react';
import { FlatList, Platform, TouchableOpacity, View } from 'react-native';
import * as ScopedStorage from 'react-native-scoped-storage';
import RNFetchBlob from 'rn-fetch-blob';
@@ -12,28 +12,71 @@ import { useThemeStore } from '../../stores/use-theme-store';
import { hexToRGBA } from '../../utils/color-scheme/utils';
import Storage from '../../utils/database/storage';
import { sanitizeFilename } from '../../utils/sanitizer';
+import { DatabaseLogger } from '../../utils/database/index';
+import { useEffect } from 'react';
+import useTimer from '../../utils/hooks/use-timer';
+import { LogLevel } from '@streetwriters/notesnook-core/logger';
+
+function getLevelString(level: number) {
+ switch (level) {
+ case LogLevel.Debug:
+ return 'DEBUG';
+ case LogLevel.Info:
+ return 'INFO';
+ case LogLevel.Log:
+ return 'LOG';
+ case LogLevel.Error:
+ return 'ERROR';
+ case LogLevel.Warn:
+ return 'WARN';
+ case LogLevel.Fatal:
+ return 'FATAL';
+ }
+}
export default function DebugLogs() {
const colors = useThemeStore(state => state.colors);
+ const { seconds, start } = useTimer('debug_logs_timer');
+ const [logs, setLogs] = useState<
+ {
+ key: string;
+ logs: any[];
+ }[]
+ >([]);
+ const [currentLog, setCurrentLog] = useState<{
+ key: string;
+ logs: any[];
+ }>();
+
+ useEffect(() => {
+ (async () => {
+ if (seconds === 0) {
+ start(5, 'debug_logs_timer');
+ let logs = await DatabaseLogger.get();
+ if (logs.length > 0 && !currentLog) {
+ setCurrentLog(logs[0]);
+ } else {
+ setCurrentLog(logs[logs.findIndex(l => l.key === currentLog?.key)]);
+ }
+ setLogs(logs);
+ }
+ })();
+ }, [seconds]);
const renderItem = ({ item, index }) => {
const background =
- item.type === 'error'
+ item.level === LogLevel.Error || item.level === LogLevel.Fatal
? hexToRGBA(colors.red, 0.2)
- : item.type === 'success'
- ? hexToRGBA(colors.green, 0.2)
- : item.type === 'warn'
+ : item.level === LogLevel.Warn
? hexToRGBA(colors.orange, 0.2)
: 'transparent';
const color =
- item.type === 'error'
+ item.level === LogLevel.Error || item.level === LogLevel.Fatal
? colors.red
- : item.type === 'success'
- ? colors.green
- : item.type === 'warn'
+ : item.level === LogLevel.Warn
? colors.orange
- : 'transparent';
+ : colors.pri;
return (
- {item.error}
+ [{getLevelString(item.level)}] {item.message}
);
};
return (
-
+
-
-
- {
- Clipboard.setString('All logs copied');
- ToastEvent.show({
- heading: 'Debug log copied!',
- context: 'global',
- type: 'success'
- });
- }}
- size={20}
- name="clipboard"
- color={colors.gray}
- />
- {
- try {
- let path = null;
- const fileName = sanitizeFilename(`notesnook_logs_${Date.now()}`);
- if (Platform.OS === 'android') {
- let file = await ScopedStorage.createDocument(
- fileName,
- 'text/plain',
- 'save data',
- 'utf8'
- );
- if (!file) return;
- path = file.uri;
- } else {
- path = await Storage.checkAndCreateDir('/');
- await RNFetchBlob.fs.writeFile(path + fileName, 'save data', 'utf8');
- path = path + fileName;
- }
-
- if (path) {
- ToastEvent.show({
- heading: 'Debug logs downloaded',
- context: 'global',
- type: 'success'
- });
- }
- } catch (e) {
- console.log(e);
- }
- }}
- size={20}
- name="download"
- color={colors.gray}
- />
-
- {
- presentDialog({
- title: 'Clear all logs',
- paragraph: 'Are you sure you want to delete alll logs?',
- negativeText: 'Cancel',
- positiveText: 'Clear',
- positivePress: () => {}
- });
- }}
- size={20}
- name="delete"
- color={colors.gray}
- />
-
-
+
+ {currentLog.key}
+
+ {
+ let index = logs.findIndex(l => l.key === currentLog.key);
+ if (index === 0) return;
+ setCurrentLog(logs[index - 1]);
+ }}
+ size={20}
+ name="chevron-left"
+ color={colors.icon}
+ />
+
+ {
+ let index = logs.findIndex(l => l.key === currentLog.key);
+ if (index === logs.length - 1) return;
+ setCurrentLog(logs[index + 1]);
+ }}
+ size={20}
+ name="chevron-right"
+ color={colors.icon}
+ />
+
+
+
+ {
+ Clipboard.setString('All logs copied');
+ ToastEvent.show({
+ heading: 'Debug log copied!',
+ context: 'global',
+ type: 'success'
+ });
+ }}
+ size={20}
+ customStyle={{
+ width: 30,
+ height: 30,
+ marginRight: 5
+ }}
+ name="content-copy"
+ color={colors.gray}
+ />
+ {
+ try {
+ let path = null;
+ const fileName = sanitizeFilename(`notesnook_logs_${Date.now()}`);
+ const data = currentLog?.logs
+ .map(
+ log =>
+ `${new Date(log.timestamp).toUTCString()}: [${getLevelString(
+ log.level
+ )}] ${log.message || log.error?.message}${
+ log.error?.stack ? '\n' + log.error?.stack : ''
+ }`
+ )
+ .join(`\n`);
+ if (!data) return;
+ if (Platform.OS === 'android') {
+ let file = await ScopedStorage.createDocument(
+ fileName + '.txt',
+ 'text/plain',
+ data,
+ 'utf8'
+ );
+ if (!file) return;
+ path = file.uri;
+ } else {
+ path = await Storage.checkAndCreateDir('/');
+ await RNFetchBlob.fs.writeFile(path + fileName + '.txt', data, 'utf8');
+ path = path + fileName;
+ }
+
+ if (path) {
+ ToastEvent.show({
+ heading: 'Debug logs downloaded',
+ context: 'global',
+ type: 'success'
+ });
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ }}
+ customStyle={{
+ width: 30,
+ height: 30,
+ marginRight: 5
+ }}
+ size={20}
+ name="download"
+ color={colors.gray}
+ />
+
+
}
- ]}
- renderItem={renderItem}
- />
+ style={{
+ flex: 1,
+ width: '100%'
+ }}
+ stickyHeaderIndices={[0]}
+ ListFooterComponent={
+
+ }
+ data={currentLog.logs}
+ renderItem={renderItem}
+ />
+ )}
);
}
diff --git a/apps/mobile/src/screens/settings/settingsdata.tsx b/apps/mobile/src/screens/settings/settingsdata.tsx
index 8472e3a6e..34fab3af7 100644
--- a/apps/mobile/src/screens/settings/settingsdata.tsx
+++ b/apps/mobile/src/screens/settings/settingsdata.tsx
@@ -749,14 +749,14 @@ export const settingsGroups: SettingSection[] = [
name: 'Debug mode',
description: 'Show debug options on items',
property: 'devMode'
+ },
+ {
+ id: 'debug-logs',
+ type: 'screen',
+ name: 'Debug logs',
+ description: 'View debug logs from the app',
+ component: 'debug-logs'
}
- // {
- // id: 'debug-logs',
- // type: 'screen',
- // name: 'Debug logs',
- // description: 'View debug logs from the app',
- // component: 'debug-logs'
- // }
]
},
{
diff --git a/apps/mobile/src/utils/database/Storage.js b/apps/mobile/src/utils/database/Storage.js
index 8e1054017..a73d5b1a6 100644
--- a/apps/mobile/src/utils/database/Storage.js
+++ b/apps/mobile/src/utils/database/Storage.js
@@ -12,53 +12,71 @@ import {
} from './encryption';
import { MMKV } from './mmkv';
-async function read(key) {
- if (!key) return null;
- let data = MMKV.getString(key);
- if (!data) return null;
- try {
- let parse = JSON.parse(data);
- return parse;
- } catch (e) {
- return data;
+export class KV {
+ storage = null;
+ constructor(storage) {
+ this.storage = storage;
+ }
+ async read(key) {
+ if (!key) return null;
+ let data = this.storage.getString(key);
+ if (!data) return null;
+ try {
+ let parse = JSON.parse(data);
+ return parse;
+ } catch (e) {
+ return data;
+ }
+ }
+
+ async write(key, data) {
+ this.storage.setString(key, typeof data === 'string' ? data : JSON.stringify(data));
+ return true;
+ }
+
+ async readMulti(keys) {
+ if (keys.length <= 0) {
+ return [];
+ } else {
+ let data = await this.storage.getMultipleItemsAsync(keys.slice());
+
+ return data.map(([key, value]) => {
+ let obj;
+ try {
+ obj = JSON.parse(value);
+ } catch (e) {
+ obj = value;
+ }
+
+ return [key, obj];
+ });
+ }
+ }
+
+ async remove(key) {
+ return await this.storage.removeItem(key);
+ }
+
+ async clear() {
+ return await this.storage.clearStore();
+ }
+
+ async getAllKeys() {
+ let keys = (await this.storage.indexer.getKeys()) || [];
+ keys = keys.filter(
+ k =>
+ k !== 'stringIndex' &&
+ k !== 'boolIndex' &&
+ k !== 'mapIndex' &&
+ k !== 'arrayIndex' &&
+ k !== 'numberIndex' &&
+ k !== this.storage.instanceID
+ );
+ return keys;
}
}
-async function write(key, data) {
- MMKV.setString(key, typeof data === 'string' ? data : JSON.stringify(data));
- return true;
-}
-
-async function readMulti(keys) {
- if (keys.length <= 0) {
- return [];
- } else {
- let data = await MMKV.getMultipleItemsAsync(keys.slice());
-
- return data.map(([key, value]) => {
- let obj;
- try {
- obj = JSON.parse(value);
- } catch (e) {
- obj = value;
- }
-
- return [key, obj];
- });
- }
-}
-
-async function remove(key) {
- return await MMKV.removeItem(key);
-}
-
-async function clear() {
- return await MMKV.clearStore();
-}
-
-async function getAllKeys() {
- return await MMKV.indexer.getKeys();
-}
+const DefaultStorage = new KV(MMKV);
async function requestPermission() {
if (Platform.OS === 'ios') return true;
@@ -84,14 +102,14 @@ async function checkAndCreateDir(path) {
}
export default {
- read,
- write,
- readMulti,
- remove,
- clear,
+ read: key => DefaultStorage.read(key),
+ write: (key, value) => DefaultStorage.write(key, value),
+ readMulti: keys => DefaultStorage.readMulti(keys),
+ remove: key => DefaultStorage.remove(key),
+ clear: () => DefaultStorage.clear(),
+ getAllKeys: () => DefaultStorage.getAllKeys(),
encrypt,
decrypt,
- getAllKeys,
getRandomBytes,
checkAndCreateDir,
requestPermission,
diff --git a/apps/mobile/src/utils/database/index.js b/apps/mobile/src/utils/database/index.js
index 902290c41..ae5b2d127 100644
--- a/apps/mobile/src/utils/database/index.js
+++ b/apps/mobile/src/utils/database/index.js
@@ -1,9 +1,14 @@
+import Database from '@streetwriters/notesnook-core/api/index';
+import { initalize, logger as dbLogger } from '@streetwriters/notesnook-core/logger';
import { Platform } from 'react-native';
+import { MMKVLoader } from 'react-native-mmkv-storage';
import filesystem from '../filesystem';
import EventSource from '../sse/even-source-ios';
import AndroidEventSource from '../sse/event-source';
-import Storage from './storage';
-import Database from '@streetwriters/notesnook-core/api/index';
+import Storage, { KV } from './storage';
+const LoggerStorage = new MMKVLoader().withInstanceID('notesnook_logs').initialize();
+initalize(new KV(LoggerStorage));
+export const DatabaseLogger = dbLogger;
/**
* @type {import("@streetwriters/notesnook-core/api/index").default}