Commit 37694dbf authored by Karel Slaný's avatar Karel Slaný

Merge branch 'log-viewer' into 'develop'

QML log viewer

See merge request !127
parents 88cbef11 01a84974
......@@ -29,7 +29,7 @@
@interface SimpleEmailSendController:UIViewController<MFMailComposeViewControllerDelegate> {
}
- (void)createEmail:(NSString *)body sub:(NSString *)subject
files:(NSArray *)filePaths;
- (void)createEmail:(NSString *)body rec:(NSString *)recipient
sub:(NSString *)subject files:(NSArray *)filePaths;
@end
......@@ -49,8 +49,8 @@
return (__bridge NSString *)(MIMEType);
}
- (void)createEmail:(NSString *)body sub:(NSString *)subject
files:(NSArray *)filePaths
- (void)createEmail:(NSString *)body rec:(NSString *)recipient
sub:(NSString *)subject files:(NSArray *)filePaths
{
if (![MFMailComposeViewController canSendMail]) {
[self showCantSendMailAlert];
......@@ -60,6 +60,7 @@
MFMailComposeViewController *controller =
[[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = self;
[controller setToRecipients:@[recipient]];
[controller setSubject:subject];
[controller setMessageBody:body isHTML:NO];
......
......@@ -57,11 +57,12 @@ public:
* @brief Create email on iOS.
*
* @param[in] bodyText Email text.
* @param[in] to Recipient mail address.
* @param[in] subject Email subject.
* @param[in] filePaths Paths to attachment files.
*/
void createEmail(const QString &bodyText, const QString &subject,
const QStringList &filePaths);
void createEmail(const QString &bodyText, const QString &to,
const QString &subject, const QStringList &filePaths);
};
#endif /* _URL_OPENER_H_ */
......@@ -72,17 +72,19 @@ void UrlOpener::openFile(const QString &filePath)
#endif /* !Q_OS_IOS */
}
void UrlOpener::createEmail(const QString &bodyText, const QString &subject,
const QStringList &filePaths)
void UrlOpener::createEmail(const QString &bodyText, const QString &to,
const QString &subject, const QStringList &filePaths)
{
#ifndef Q_OS_IOS
Q_UNUSED(to);
Q_UNUSED(bodyText);
Q_UNUSED(subject);
Q_UNUSED(filePaths);
#else /* Q_OS_IOS */
NSString *recipient = to.toNSString();
NSString *body = bodyText.toNSString();
NSString *sbjct = subject.toNSString();
......@@ -103,7 +105,7 @@ void UrlOpener::createEmail(const QString &bodyText, const QString &subject,
if (rootv != nil) {
email = [[SimpleEmailSendController alloc] init];
[rootv addChildViewController:email];
[email createEmail:body sub:sbjct files:filePath];
[email createEmail:body rec:recipient sub:sbjct files:filePath];
}
#endif /* !Q_OS_IOS */
}
......@@ -133,6 +133,7 @@ SOURCES += \
src/isds/isds_conversion.cpp \
src/isds/isds_type_conversion.cpp \
src/locker.cpp \
src/log.cpp \
src/main.cpp \
src/messages.cpp \
src/models/accountmodel.cpp \
......@@ -241,6 +242,7 @@ HEADERS += \
src/isds/isds_conversion.h \
src/isds/isds_type_conversion.h \
src/locker.h \
src/log.h \
src/messages.h \
src/models/accountmodel.h \
src/models/databoxmodel.h \
......
/*
* Copyright (C) 2014-2018 CZ.NIC
*
* 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 <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations including
* the two.
*/
import QtQuick 2.7
import QtQuick.Controls 2.1
Rectangle {
id: root
visible: false
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.right: parent.right
height: parent.height * 0.3
color: datovkaPalette.text
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
color: datovkaPalette.base
width: imgHeightHeader
height: imgHeightHeader
z: 1
AccessibleImageButton {
id: closeButton
anchors.centerIn: parent
sourceSize.height: imgHeightHeader
source: "qrc:/ui/remove.svg"
accessibleName: qsTr("Close log bar")
onClicked: {
root.visible = false
}
}
} // Rectangle
Flickable {
anchors.fill: parent
contentHeight: flickContent.implicitHeight
contentY: contentHeight-height
clip: true
Pane {
id: flickContent
anchors.fill: parent
background: parent
TextEdit {
id: logBarText
color: datovkaPalette.base
anchors.fill: parent
readOnly: true
wrapMode: Text.WordWrap
font.pointSize: textFontSizeSmall
text: log.loadLogContent("")
Connections {
target: log
onAppendNewLogMessage: {
logBarText.text += newLog
}
}
} // TextEdit
} // Pane
ScrollIndicator.vertical: ScrollIndicator {}
} // Flickable
}
......@@ -69,7 +69,11 @@ Dialog {
return (root.showDirs && !root.showFiles);
}
function raise(title, filters, showFiles) {
function raise(title, filters, showFiles, targetLocation) {
if (targetLocation !== "") {
folderNavigation.visible = false
folderModel.folder = "file://" + targetLocation
}
root.title = title
folderModel.folder = folderModel.folder
root.showFiles = showFiles
......@@ -121,71 +125,74 @@ Dialog {
contentItem: ColumnLayout {
spacing: formItemVerticalSpacing
AccessibleComboBox {
anchors {
left: parent.left;
right: parent.right;
}
accessibleDescription: qsTr("Select location type")
model: ListModel {
ListElement { label: qsTr("Desktop"); key: "desktop" }
ListElement { label: qsTr("Documents"); key: "document" }
ListElement { label: qsTr("Downloads"); key: "download" }
ListElement { label: qsTr("Pictures"); key: "picture" }
ListElement { label: qsTr("Temp"); key: "temp" }
}
onCurrentIndexChanged: {
var location = InteractionFilesystem.DESKTOP_LOCATION
if (currentKey() === "document") {
location = InteractionFilesystem.DOCUMENTS_LOCATION
} else if (currentKey() === "download") {
location = InteractionFilesystem.DOWNLOAD_LOCATION
} else if (currentKey() === "picture") {
location = InteractionFilesystem.PICTURE_LOCATION
} else if (currentKey() === "temp") {
location = InteractionFilesystem.TEMP_LOCATION
ColumnLayout {
id: folderNavigation
AccessibleComboBox {
anchors {
left: parent.left;
right: parent.right;
}
folderModel.folder = standardLocationUrl(location)
}
}
RowLayout {
anchors {
left: parent.left;
right: parent.right;
}
spacing: formItemVerticalSpacing
AccessibleButton {
id: upButton
text: "<"
accessibleName: qsTr("Up") /* Needs to be specified as "<" is not read. */
onClicked: {
/* Navigate to parent folder. */
if (folderModel.parentFolder !== "") {
folderModel.folder = folderModel.parentFolder
accessibleDescription: qsTr("Select location type")
model: ListModel {
ListElement { label: qsTr("Desktop"); key: "desktop" }
ListElement { label: qsTr("Documents"); key: "document" }
ListElement { label: qsTr("Downloads"); key: "download" }
ListElement { label: qsTr("Pictures"); key: "picture" }
ListElement { label: qsTr("Temp"); key: "temp" }
}
onCurrentIndexChanged: {
var location = InteractionFilesystem.DESKTOP_LOCATION
if (currentKey() === "document") {
location = InteractionFilesystem.DOCUMENTS_LOCATION
} else if (currentKey() === "download") {
location = InteractionFilesystem.DOWNLOAD_LOCATION
} else if (currentKey() === "picture") {
location = InteractionFilesystem.PICTURE_LOCATION
} else if (currentKey() === "temp") {
location = InteractionFilesystem.TEMP_LOCATION
}
folderModel.folder = standardLocationUrl(location)
}
}
AccessibleTextField {
id: pathField
Layout.fillWidth: true
text: stripUrlPrefix(folderModel.folder)
onEditingFinished: {
/* Navigate to supplied location if location exists. */
var path = interactionFilesystem.absoluteDirPath(text)
if (path !== "") {
folderModel.folder = "file://" + path
} else {
path = interactionFilesystem.absolutePath(text)
RowLayout {
anchors {
left: parent.left;
right: parent.right;
}
spacing: formItemVerticalSpacing
AccessibleButton {
id: upButton
text: "<"
accessibleName: qsTr("Up") /* Needs to be specified as "<" is not read. */
onClicked: {
/* Navigate to parent folder. */
if (folderModel.parentFolder !== "") {
folderModel.folder = folderModel.parentFolder
}
}
}
AccessibleTextField {
id: pathField
Layout.fillWidth: true
text: stripUrlPrefix(folderModel.folder)
onEditingFinished: {
/* Navigate to supplied location if location exists. */
var path = interactionFilesystem.absoluteDirPath(text)
if (path !== "") {
folderModel.folder = "file://" + path
} else {
/* Restore original location. */
text = stripUrlPrefix(folderModel.folder)
path = interactionFilesystem.absolutePath(text)
if (path !== "") {
folderModel.folder = "file://" + path
} else {
/* Restore original location. */
text = stripUrlPrefix(folderModel.folder)
}
}
}
}
}
} // RowLayout
} // RowLayout
} // ColumnLayout
ScrollableListView {
id: fileList
......@@ -264,8 +271,13 @@ Dialog {
selectedFileIndex = index
path = stripUrlPrefix(folderModel.folder) + "/" + folderModel.get(selectedFileIndex, "fileName")
if (!multiSelect) {
// deselect all item in the file idalog
for (fileList.currentIndex = 0; fileList.currentIndex < fileList.count; ++fileList.currentIndex) {
fileList.currentItem.color = datovkaPalette.window
}
// only one file can be selected and append to list
pathListModel.clear()
fileRectangle.color = datovkaPalette.highlight
pathListModel.append({path: path})
} else {
// file can be append to list or removed and deselected
......
......@@ -8,7 +8,7 @@
*
* 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
* 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
......@@ -69,6 +69,7 @@ ApplicationWindow {
property Component pageDataboxDetail: PageDataboxDetail {}
property Component pageDataboxSearch: PageDataboxSearch {}
property Component pageImportMessage: PageImportMessage {}
property Component pageLog: PageLog {}
property Component pageMenuAccount: PageMenuAccount {}
property Component pageMenuDatovkaSettings: PageMenuDatovkaSettings {}
property Component pageMenuMessage: PageMenuMessage {}
......@@ -161,7 +162,10 @@ ApplicationWindow {
visible: true
StackView {
id: pageView
anchors.fill: parent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: (logBar.visible) ? logBar.top : parent.bottom
visible: true
initialItem: PageAccountList {}
......@@ -169,6 +173,9 @@ ApplicationWindow {
nestedStack = pageView
}
}
LogBar {
id: logBar
}
}
ProgressBar {
id: statusBar
......
......@@ -8,7 +8,7 @@
*
* 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
* 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
......@@ -135,7 +135,8 @@ Component {
pageView.push(pageMenuDatovkaSettings, {
"pageView": pageView,
"statusBar": statusBar,
"accountModel": accountModel
"accountModel": accountModel,
"logBar": logBar
}, StackView.Immediate)
}
}
......
......@@ -80,11 +80,11 @@ Component {
property var funcs: {
"zfoFile": function callZfoFile() {
clearInfo()
fileDialogue.raise(qsTr("Select ZFO files"), ["*.zfo"], true)
fileDialogue.raise(qsTr("Select ZFO files"), ["*.zfo"], true, "")
},
"zfoDir": function callZfoDir() {
clearInfo()
fileDialogue.raise(qsTr("Select import directory"), ["*.*"], false)
fileDialogue.raise(qsTr("Select import directory"), ["*.*"], false, "")
}
}
......
/*
* Copyright (C) 2014-2018 CZ.NIC
*
* 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 <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations including
* the two.
*/
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2
import cz.nic.mobileDatovka 1.0
Item {
id: pageLog
/* These properties must be set by caller. */
property var pageView
property var statusBar
property string logFileLocation: ""
property string logFilePath: ""
/* Return file name from file path */
function getFileNameFromPath(filePath) {
return filePath.replace(/^.*[\\\/]/, '')
}
Component.onCompleted: {
var logTxt = log.loadLogContent(logFilePath)
logContent.text = qsTr("Log from current run:") + "\n\n" + logTxt
actionButton.enabled = (logContent.text !== "")
logFileLocation = log.getLogFileLocation()
}
/* File dialog for choose of files from the storage */
FileDialogue {
id: fileDialogue
multiSelect: false
onFinished: {
if (pathListModel.count === 1) {
logContent.clear()
logFilePath = pathListModel.get(pathListModel.count-1).path
var logTxt = log.loadLogContent(logFilePath)
logContent.text = qsTr("Log file") + ": " + getFileNameFromPath(logFilePath) + "\n\n" + logTxt
actionButton.enabled = (logContent.text !== "")
}
}
}
PageHeader {
id: headerBar
title: qsTr("Log Viewer")
onBackClicked: {
pageView.pop(StackView.Immediate)
}
AccessibleOverlaidImageButton {
id: actionButton
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: defaultMargin
image.sourceSize.height: imgHeightHeader
image.source: "qrc:/ui/send-msg.svg"
accessibleName: qsTr("Send log to helpdesk")
onClicked: log.sendLogViaEmail(logContent.text)
}
}
Flickable {
id: flickable
z: 0
anchors.top: headerBar.bottom
anchors.right: parent.right
anchors.left: parent.left
contentHeight: flickContent.implicitHeight
height: parent.height * 0.8
Pane {
id: flickContent
anchors.fill: parent
TextEdit {
id: logContent
color: datovkaPalette.text
anchors.fill: parent
readOnly: true
wrapMode: Text.WordWrap
font.pointSize: textFontSizeSmall
}
} // Pane
ScrollIndicator.vertical: ScrollIndicator {}
} // Flickable
Rectangle {
anchors.top: flickable.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
color: datovkaPalette.window
AccessibleButton {
visible: true
text: qsTr("Choose log file")
height: inputItemHeight
font.pointSize: defaultTextFont.font.pointSize
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
fileDialogue.raise("Choose log file", ["*.log"], true, logFileLocation)
}
}
}
} // Item
......@@ -8,7 +8,7 @@
*
* 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
* 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
......@@ -34,6 +34,7 @@ Component {
property var pageView
property var statusBar
property var accountModel: null
property var logBar
Component.onCompleted: {
var index
......@@ -131,6 +132,16 @@ Component {
"accountModel": accountModel
}, StackView.Immediate)
},
"logViewer": function callLogViewer() {
pageView.replace(pageLog, {
"pageView": pageView,
"statusBar": statusBar
}, StackView.Immediate)
},
"showLogPanel": function callShowLogPanel() {
logBar.visible = true
pageView.pop(StackView.Immediate)
},
"userGuide": function callUserGuide() {
Qt.openUrlExternally("https://secure.nic.cz/files/datove_schranky/redirect/mobile-manual.html")
pageView.pop(StackView.Immediate)
......@@ -202,6 +213,20 @@ Component {
name: qsTr("Security and PIN")
funcName: "settSecPin"
}
ListElement {
image: "qrc:/ui/book-open.svg"
showEntry: true
showNext: true
name: qsTr("Log Viewer")
funcName: "logViewer"
}
ListElement {
image: "qrc:/ui/format-list-bulleted.svg"
showEntry: true
showNext: false
name: qsTr("Show log panel")
funcName: "showLogPanel"
}
ListElement {
image: "qrc:/ui/information.svg"
showEntry: true
......
......@@ -453,7 +453,7 @@ Item {
font.pointSize: defaultTextFont.font.pointSize
text: qsTr("Add file")
onClicked: {
fileDialogue.raise(qsTr("Select files"), ["*.*"], true)
fileDialogue.raise(qsTr("Select files"), ["*.*"], true, "")
}
}
Component {
......
......@@ -202,7 +202,7 @@ Item {
height: inputItemHeight
font.pointSize: defaultTextFont.font.pointSize
onClicked: {
fileDialogue.raise("Select certificate file", ["*.pem","*.p12","*.pfx"], true)
fileDialogue.raise("Select certificate file", ["*.pem","*.p12","*.pfx"], true, "")
}
}
AccessibleText {
......
......@@ -118,6 +118,7 @@
<file>../qml/components/DataboxList.qml</file>
<file>../qml/components/FilterBar.qml</file>
<file>../qml/components/InputLineMenu.qml</file>
<file>../qml/components/LogBar.qml</file>
<file>../qml/components/MessageList.qml</file>
<file>../qml/components/OverlaidImage.qml</file>
<file>../qml/components/PageHeader.qml</file>
......@@ -135,6 +136,7 @@
<file>../qml/pages/PageDataboxDetail.qml</file>
<file>../qml/pages/PageDataboxSearch.qml</file>
<file>../qml/pages/PageImportMessage.qml</file>
<file>../qml/pages/PageLog.qml</file>
<file>../qml/pages/PageMenuAccount.qml</file>
<file>../qml/pages/PageMenuDatovkaSettings.qml</file>
<file>../qml/pages/PageMenuMessage.qml</file>
......
/*
* Copyright (C) 2014-2017 CZ.NIC
* Copyright (C) 2014-2018 CZ.NIC
*
* 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
......@@ -8,7 +8,7 @@
*
* 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
* 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
......@@ -22,10 +22,14 @@
*/
#include <QDateTime>
#include <QDesktopServices>
#include <QMimeDatabase>
#include <QMimeType>
#include <QUrl>
#include "ios/src/url_opener.h"
#include "src/auxiliaries/email_helper.h"
#include "src/io/filesystem.h"
void addAttachmentToEmailMessage(QString &message, const QString &attachName,
const QByteArray &base64, const QString &boundary)
......@@ -54,13 +58,16 @@ void addAttachmentToEmailMessage(QString &message, const QString &attachName,
message += newLine;
}
QString createEmailMessage(const QString &body, const QString &subj,
const QString &boundary)
QString createEmailMessage(const QString &to, const QString &body,
const QString &subj, const QString &boundary)
{
const QString newLine("\n"); /* "\r\n" ? */
/* Rudimentary header. */
QString message("Subject: " + subj + newLine);
if (!to.isEmpty()) {
message += "To: " + to + newLine;
}
message += "MIME-Version: 1.0" + newLine;
message += "Content-Type: multipart/mixed;" + newLine +
" boundary=\"" + boundary + "\"" + newLine;
......@@ -106,3 +113,33 @@ QString generateEmailBodyText(qint64 dmId, const QString &from,
body += "\n";
return body;
}
void sendEmail(const QString &emailMessage, const QStringList &fileList,
const QString &to, const QString &subject, const QString &body,
const QString &msgId)
{
Q_UNUSED(to);
Q_UNUSED(subject);
Q_UNUSED(body);
Q_UNUSED(emailMessage);
Q_UNUSED(msgId);
Q_UNUSED(fileList);
#if defined Q_OS_IOS
UrlOpener urlOpener;
urlOpener.createEmail(body, to, subject, fileList);
#elif defined Q_OS_ANDROID
QDesktopServices::openUrl(QUrl("mailto:" + to + "?"
+ "subject=" + subject + "&body=" + body));
#else
QString tmpEmailFile = writeFile(appEmailDirPath(msgId),
msgId + "_mail.eml", emailMessage.toUtf8());
QDesktopServices::openUrl(QUrl::fromLocalFile(tmpEmailFile));
#endif
}
/*
* Copyright (C) 2014-2017 CZ.NIC
* Copyright (C) 2014-2018 CZ.NIC
*
* 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
......@@ -8,7 +8,7 @@
*
* 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
* 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
......@@ -21,10 +21,11 @@
* the two.
*/
#ifndef _EMAIL_HELPER_H_
#define _EMAIL_HELPER_H_
#pragma once
#include <QObject>
#include <QByteArray>
#include <QString>
#include <QStringList>
/*!
* @brief Add attachment into email.
......@@ -40,13 +41,14 @@ void addAttachmentToEmailMessage(QString &message, const QString &attachName,
/*!
* @brief Creates email header and message body.
*
* @param[in] to Recipient mail address.
* @param[in] body Email body.
* @param[in] subj Subject text.
* @param[in] boundary Boundary to be used.