From 53c82434be69a68b5edf397d2dcdbc6769660f4b Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:14:08 -0700 Subject: [PATCH] Mobile: Add share button to log screen (#8364) Co-authored-by: Laurent Cozic --- .eslintignore | 2 + .gitignore | 2 + .../components/screens/LogScreen.tsx | 195 ++++++++++++++++++ packages/app-mobile/components/screens/log.js | 135 ------------ packages/app-mobile/root.tsx | 2 +- packages/app-mobile/utils/ShareUtils.test.ts | 10 + packages/app-mobile/utils/ShareUtils.ts | 21 +- readme/debugging.md | 5 +- 8 files changed, 232 insertions(+), 140 deletions(-) create mode 100644 packages/app-mobile/components/screens/LogScreen.tsx delete mode 100644 packages/app-mobile/components/screens/log.js create mode 100644 packages/app-mobile/utils/ShareUtils.test.ts diff --git a/.eslintignore b/.eslintignore index 9d378907f..c2862b719 100644 --- a/.eslintignore +++ b/.eslintignore @@ -462,6 +462,7 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExport packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.js packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/exportAllFolders.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js +packages/app-mobile/components/screens/LogScreen.js packages/app-mobile/components/screens/Note.js packages/app-mobile/components/screens/Notes.js packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js @@ -480,6 +481,7 @@ packages/app-mobile/services/voiceTyping/vosk.ios.js packages/app-mobile/setupQuickActions.js packages/app-mobile/tools/buildInjectedJs.js packages/app-mobile/utils/ShareExtension.js +packages/app-mobile/utils/ShareUtils.test.js packages/app-mobile/utils/ShareUtils.js packages/app-mobile/utils/TlsUtils.js packages/app-mobile/utils/autodetectTheme.js diff --git a/.gitignore b/.gitignore index b46767877..f983f2af5 100644 --- a/.gitignore +++ b/.gitignore @@ -448,6 +448,7 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExport packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.js packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/exportAllFolders.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js +packages/app-mobile/components/screens/LogScreen.js packages/app-mobile/components/screens/Note.js packages/app-mobile/components/screens/Notes.js packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js @@ -466,6 +467,7 @@ packages/app-mobile/services/voiceTyping/vosk.ios.js packages/app-mobile/setupQuickActions.js packages/app-mobile/tools/buildInjectedJs.js packages/app-mobile/utils/ShareExtension.js +packages/app-mobile/utils/ShareUtils.test.js packages/app-mobile/utils/ShareUtils.js packages/app-mobile/utils/TlsUtils.js packages/app-mobile/utils/autodetectTheme.js diff --git a/packages/app-mobile/components/screens/LogScreen.tsx b/packages/app-mobile/components/screens/LogScreen.tsx new file mode 100644 index 000000000..b48e7ace4 --- /dev/null +++ b/packages/app-mobile/components/screens/LogScreen.tsx @@ -0,0 +1,195 @@ +import * as React from 'react'; + +import { FlatList, View, Text, Button, StyleSheet, Platform, Alert } from 'react-native'; +import { connect } from 'react-redux'; +import { reg } from '@joplin/lib/registry.js'; +import { ScreenHeader } from '../ScreenHeader'; +import time from '@joplin/lib/time'; +const { themeStyle } = require('../global-style.js'); +import Logger from '@joplin/utils/Logger'; +const { BaseScreenComponent } = require('../base-screen.js'); +import { _ } from '@joplin/lib/locale'; +import { MenuOptionType } from '../ScreenHeader'; +import { AppState } from '../../utils/types'; +import Share from 'react-native-share'; +import { writeTextToCacheFile } from '../../utils/ShareUtils'; +import shim from '@joplin/lib/shim'; + +const logger = Logger.create('LogScreen'); + +class LogScreenComponent extends BaseScreenComponent { + private readonly menuOptions: MenuOptionType[]; + + public static navigationOptions(): any { + return { header: null }; + } + + public constructor() { + super(); + + this.state = { + logEntries: [], + showErrorsOnly: false, + }; + this.styles_ = {}; + + this.menuOptions = [ + { + title: _('Share'), + onPress: () => { + void this.onSharePress(); + }, + }, + ]; + } + + private async onSharePress() { + const limit: number|null = null; // no limit + const levels = this.getLogLevels(this.state.showErrorsOnly); + const allEntries: any[] = await reg.logger().lastEntries(limit, { levels }); + const logData = allEntries.map(entry => this.formatLogEntry(entry)).join('\n'); + + let fileToShare; + try { + // Using a .txt file extension causes a "No valid provider found from URL" error + // and blank share sheet on iOS for larger log files (around 200 KiB). + fileToShare = await writeTextToCacheFile(logData, 'mobile-log.log'); + + await Share.open({ + type: 'text/plain', + filename: 'log.txt', + url: `file://${fileToShare}`, + failOnCancel: false, + }); + } catch (e) { + logger.error('Unable to share log data:', e); + + // Display a message to the user (e.g. in the case where the user is out of disk space). + Alert.alert(_('Error'), _('Unable to share log data. Reason: %s', e.toString())); + } finally { + if (fileToShare) { + await shim.fsDriver().remove(fileToShare); + } + } + } + + public styles() { + const theme = themeStyle(this.props.themeId); + + if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId]; + this.styles_ = {}; + + const styles: any = { + row: { + flexDirection: 'row', + paddingLeft: 1, + paddingRight: 1, + paddingTop: 0, + paddingBottom: 0, + }, + rowText: { + fontSize: 10, + color: theme.color, + }, + }; + + if (Platform.OS !== 'ios') { + // Crashes on iOS with error "Unrecognized font family 'monospace'" + styles.rowText.fontFamily = 'monospace'; + } + + styles.rowTextError = { ...styles.rowText }; + styles.rowTextError.color = theme.colorError; + + styles.rowTextWarn = { ...styles.rowText }; + styles.rowTextWarn.color = theme.colorWarn; + + this.styles_[this.props.themeId] = StyleSheet.create(styles); + return this.styles_[this.props.themeId]; + } + + public UNSAFE_componentWillMount() { + void this.resfreshLogEntries(); + } + + private getLogLevels(showErrorsOnly: boolean) { + let levels = [Logger.LEVEL_DEBUG, Logger.LEVEL_INFO, Logger.LEVEL_WARN, Logger.LEVEL_ERROR]; + if (showErrorsOnly) levels = [Logger.LEVEL_WARN, Logger.LEVEL_ERROR]; + + return levels; + } + + private async resfreshLogEntries(showErrorsOnly: boolean = null) { + if (showErrorsOnly === null) showErrorsOnly = this.state.showErrorsOnly; + + const levels = this.getLogLevels(showErrorsOnly); + + this.setState({ + logEntries: await reg.logger().lastEntries(1000, { levels: levels }), + showErrorsOnly: showErrorsOnly, + }); + } + + private toggleErrorsOnly() { + void this.resfreshLogEntries(!this.state.showErrorsOnly); + } + + private formatLogEntry(item: any) { + return `${time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss')}: ${item.message}`; + } + + public render() { + const renderRow = ({ item }: any) => { + let textStyle = this.styles().rowText; + if (item.level === Logger.LEVEL_WARN) textStyle = this.styles().rowTextWarn; + if (item.level === Logger.LEVEL_ERROR) textStyle = this.styles().rowTextError; + + return ( + + {this.formatLogEntry(item)} + + ); + }; + + // `enableEmptySections` is to fix this warning: https://github.com/FaridSafi/react-native-gifted-listview/issues/39 + + return ( + + + { return `${item.id}`; }} + /> + + +