Commit 8d598530 authored by Karel Slaný's avatar Karel Slaný

Merge branch 'icloud-upload-file' into 'develop'

Export files to iCloud or local storage

Closes #112 and #137

See merge request !114
parents fd92e4ee 14dd067e
......@@ -88,5 +88,17 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.cz.nic.mobile-datovka</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
<key>NSUbiquitousContainerName</key>
<string>Datovka</string>
</dict>
</dict>
</dict>
</plist>
......@@ -9,15 +9,19 @@ LIBS = \
HEADERS += \
ios/src/app_delegate.h \
ios/src/doc_picker_controller.h \
ios/src/doc_view_controller.h \
ios/src/qt_app_delegate.h \
ios/src/icloud_io.h \
ios/src/ios_file_opener.h \
ios/src/send_email_controller.h \
ios/src/url_opener.h
OBJECTIVE_SOURCES += \
ios/src/app_delegate.mm \
ios/src/doc_picker_controller.mm \
ios/src/doc_view_controller.mm \
ios/src/icloud_io.mm \
ios/src/ios_file_opener.mm \
ios/src/send_email_controller.mm \
ios/src/url_opener.mm
......
......@@ -24,7 +24,8 @@
#pragma once
#import <UIKit/UIKit.h>
#import <qt_app_delegate.h>
#include <ios/src/qt_app_delegate.h>
@interface QtAppDelegate : UIResponder <UIApplicationDelegate>
+(QtAppDelegate *)sharedQtAppDelegate;
......
......@@ -22,9 +22,10 @@
*/
#include <QtCore>
#include "src/qml_interaction/interaction_zfo_file.h"
#import "app_delegate.h"
#include "ios/src/app_delegate.h"
#include "ios/src/qt_app_delegate.h"
#include "src/qml_interaction/interaction_zfo_file.h"
@implementation QtAppDelegate
......
/*
* Copyright (C) 2014-2019 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.
*/
#pragma once
#import <UIKit/UIKit.h>
@interface DocumentPickerController : UIViewController
- (void)openImportDocumentPicker;
- (void)openExportDocumentPicker:(NSArray<NSURL*>*)exportUrls;
@end
/*
* Copyright (C) 2014-2019 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.
*/
#include <QList>
#include <QUrl>
#include "ios/src/doc_picker_controller.h"
#include "src/auxiliaries/ios_helper.h"
#include "src/global.h"
@interface DocumentPickerController () <UIDocumentPickerDelegate>
@end
@implementation DocumentPickerController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)openExportDocumentPicker:(NSArray<NSURL *> *)exportUrls {
//NSLog(@"EXPORT FILE URLs: %@", exportUrls);
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithURLs:exportUrls inMode:UIDocumentPickerModeExportToService];
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:documentPicker animated:YES completion:nil];
}
- (void)openImportDocumentPicker {
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.data"] inMode:UIDocumentPickerModeImport];
documentPicker.delegate = self;
// Next property does not work with iOS < 11.
// Apple bug: Selected files are not picked if multiple select is on.
documentPicker.allowsMultipleSelection = YES;
documentPicker.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:documentPicker animated:YES completion:nil];
}
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
if (controller.documentPickerMode == UIDocumentPickerModeImport) {
NSLog(@"SELECTED FILE URLs: %@", urls);
QList<QUrl> qUrls;
// Convert NSURL on QUrl for all selected files
for (NSURL *url in urls) {
QUrl tmpUrl = QUrl::fromNSURL(url);
qUrls.append(tmpUrl);
}
if (Q_NULLPTR != GlobInstcs::iOSHelperPtr) {
GlobInstcs::iOSHelperPtr->importFilesToAppInbox(qUrls);
}
} else if (controller.documentPickerMode == UIDocumentPickerModeExportToService) {
NSLog(@"STORAGE FILE URLs: %@", urls);
}
}
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
Q_UNUSED(controller);
}
@end
......@@ -21,10 +21,10 @@
* the two.
*/
#import "doc_view_controller.h"
#include <QtCore>
#include "ios/src/doc_view_controller.h"
@interface DocViewController ()
@end
......
/*
* Copyright (C) 2014-2019 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.
*/
#pragma once
#include <QString>
#include <QStringList>
#include <QUrl>
/*!
* @brief Provides objective-C IO methods for interaction with iCLoud.
*/
class ICloudIo {
public:
/*!
* @brief Return state describing what happened.
*/
enum ICloudResult {
ICLOUD_NOT_ON = 0, /*!< iCloud is not avalilable/turn on. */
ICLOUD_FILE_UPLOAD_SUCCESS, /*!< File upload was successful. */
ICLOUD_FILE_EXISTS, /*!< File exists on iCloud. */
ICLOUD_FILE_UPLOAD_ERROR, /*!< File upload failed. */
ICLOUD_TARGET_SAVE_DIR_ERROR /*!< Target dir is missing or not created. */
};
private:
/*!
* @brief Constructor.
*/
ICloudIo(void);
public:
/*!
* @brief Test if iCloud is on.
*
* @return Return true if iCloud is on.
*/
static
bool isCloudOn(void);
/*!
* @brief Upload single file into iCloud.
*
* @param[in] srcFilePath Source file path.
* @param[in] destFilePath iCloud target path.
* @return Return operation error/success code.
*/
static
ICloudResult moveFileToCloud(const QString &srcFilePath,
const QString &destFilePath);
/*!
* @brief Create and open document picker controller.
*
* @param[in] exportFilesPath File paths for export (can be empty).
* @return True if document picker controller is created and opened.
*/
static
bool openDocumentPickerController(const QStringList &exportFilesPath);
/*!
* @brief Move file from app temporary inbox to local app sandbox.
*
* @param[in] sourceFileUrl Source file url from inbox.
* @param[in] newFilePath Target path to local app sandbox.
* @return Full path where file was moved.
*/
static
QUrl moveFile(const QUrl &sourceFileUrl, const QString &newFilePath);
};
/*
* Copyright (C) 2014-2019 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.
*/
#include "ios/src/doc_picker_controller.h"
#include "ios/src/icloud_io.h"
static
NSURL *getCloudBaseUrl(void)
{
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}
static
NSURL *getCloudDocumentsUrl(NSURL *baseURL)
{
return [baseURL URLByAppendingPathComponent:@"Documents"];
}
bool ICloudIo::isCloudOn(void)
{
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}
ICloudIo::ICloudResult ICloudIo::moveFileToCloud(
const QString &srcFilePath, const QString &destFilePath)
{
NSURL *baseURL = getCloudBaseUrl();
if (!baseURL) {
return ICLOUD_NOT_ON;
}
// Create iCloud target path
NSURL *messageURL = [getCloudDocumentsUrl(baseURL) URLByAppendingPathComponent:destFilePath.toNSString()];
// Create subdirectorues on iCloud
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:messageURL
withIntermediateDirectories:YES attributes:nil error:&error]) {
return ICLOUD_TARGET_SAVE_DIR_ERROR;
}
// Create target upload path for iCloud
NSURL *sourceFileUrl = [NSURL fileURLWithPath:srcFilePath.toNSString()];
NSString *fileName = [srcFilePath.toNSString() lastPathComponent];
NSURL *fileURL = [messageURL URLByAppendingPathComponent:fileName];
// Upload file to target directory on iCloud
if ([[NSFileManager defaultManager] setUbiquitous:YES
itemAtURL:sourceFileUrl destinationURL:fileURL error:&error]) {
return ICLOUD_FILE_UPLOAD_SUCCESS;
} else {
// Code 516 = file exists in the iCloud. See NSFileManager error codes.
if (error.code == NSFileWriteFileExistsError) {
return ICLOUD_FILE_EXISTS;
} else {
NSLog(@"iCloud: Error code: %zd", error.code);
NSLog(@"iCloud: %@", error);
return ICLOUD_FILE_UPLOAD_ERROR;
}
}
}
bool ICloudIo::openDocumentPickerController(const QStringList &exportFilesPath)
{
static DocumentPickerController *dpc = nil;
if (dpc != nil) {
[dpc removeFromParentViewController];
[dpc release];
}
UIViewController *rootv = [[[[UIApplication sharedApplication]windows] firstObject]rootViewController];
if (rootv != nil) {
dpc = [[DocumentPickerController alloc] init];
[rootv addChildViewController:dpc];
if (exportFilesPath.isEmpty()) {
// exportFilesPath is empty so import Document Picker will open
[dpc openImportDocumentPicker];
} else {
// exportFilesPath is not empty so export Document Picker will open
NSMutableArray<NSURL*> *exportUrls = [NSMutableArray array];
// covert export file paths to array of nsurl
for (int i = 0; i < exportFilesPath.count(); ++i) {
QUrl url(QUrl::fromLocalFile(exportFilesPath.at(i)));
if (url.isValid()) {
NSURL *fileUrl = url.toNSURL();
[exportUrls addObject:fileUrl];
//NSLog(@"ADD FILE URL to list: %@", fileUrl);
} else {
NSLog(@"ERROR FILE URL: %@", url.toNSURL());
}
}
if ([exportUrls count] > 0) {
[dpc openExportDocumentPicker:exportUrls];
}
}
return true;
}
return false;
}
QUrl ICloudIo::moveFile(const QUrl &sourceFileUrl,
const QString &newFilePath)
{
// Convert string path to URL
NSURL *ofp = sourceFileUrl.toNSURL();
NSURL *np = [NSURL fileURLWithPath:newFilePath.toNSString()];
NSString *fileName = [ofp lastPathComponent];
// Create subdirectorues in the sandbox local storage
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtURL:np
withIntermediateDirectories:YES attributes:nil error:&error]) {
NSLog(@"Local storage: Create message subdirectories error: %@", error);
return QUrl();
}
NSURL *nfp = [np URLByAppendingPathComponent:fileName];
//NSLog(@"TMP url: %@", ofp);
//NSLog(@"NP url: %@", np);
//NSLog(@"SEND url: %@", nfp);
// Remove file from sandbox local storage if exists
[[NSFileManager defaultManager] removeItemAtURL:nfp error:&error];
if ([[NSFileManager defaultManager] moveItemAtURL:ofp toURL:nfp error:&error]) {
NSLog(@"Local storage: File has moved to target path.");
return QUrl::fromNSURL(nfp);
} else {
if (error.code == NSFileWriteFileExistsError) {
NSLog(@"Local storage: File with the same name already exists in the target path.");
} else {
NSLog(@"Local storage: Error code: %zd %@", error.code, error);
}
return QUrl();
}
}
......@@ -23,9 +23,6 @@
#pragma once
#include <QtCore/QtGlobal>
#ifdef Q_OS_IOS
#import <UIKit/UIKit.h>
@interface iOSFileOpener : NSObject <UIDocumentInteractionControllerDelegate>
......@@ -35,5 +32,3 @@
- (void)openFile:(NSString *)path fromViewController:(UIViewController *)viewController;
@end
#endif /* Q_OS_IOS */
......@@ -21,10 +21,10 @@
* the two.
*/
#import "send_email_controller.h"
#include <QtCore>
#include "ios/src/send_email_controller.h"
@implementation SimpleEmailSendController
- (void) showCantSendMailAlert
......
......@@ -23,7 +23,6 @@
#pragma once
#include <QObject>
#include <QString>
/*
......@@ -34,22 +33,20 @@
/*!
* @brief Used for opening URLs and create email with attachments on iOS.
*/
class UrlOpener : public QObject {
Q_OBJECT
public:
class UrlOpener {
private:
/*!
* @brief Constructor.
*
* @param[in] parent Parent object.
*/
UrlOpener(QObject *parent = 0);
UrlOpener(void);
public:
/*!
* @brief Open file on iOS.
*
* @param[in] filePath Path to file.
*/
static
void openFile(const QString &filePath);
/*!
......@@ -60,6 +57,7 @@ public:
* @param[in] subject Email subject.
* @param[in] filePaths Paths to attachment files.
*/
static
void createEmail(const QString &bodyText, const QString &to,
const QString &subject, const QStringList &filePaths);
};
......@@ -35,11 +35,6 @@
#include "ios/src/url_opener.h"
#endif /* Q_OS_IOS */
UrlOpener::UrlOpener(QObject *parent)
: QObject(parent)
{
}
void UrlOpener::openFile(const QString &filePath)
{
#ifndef Q_OS_IOS
......@@ -48,8 +43,8 @@ void UrlOpener::openFile(const QString &filePath)
#else /* Q_OS_IOS */
NSString* url = filePath.toNSString();
NSURL* fileURL = [NSURL fileURLWithPath:url];
NSString *url = filePath.toNSString();
NSURL *fileURL = [NSURL fileURLWithPath:url];
static DocViewController* mtv = nil;
if (mtv != nil) {
[mtv removeFromParentViewController];
......@@ -94,7 +89,7 @@ void UrlOpener::createEmail(const QString &bodyText, const QString &to,
}
NSArray *filePath = [NSArray arrayWithArray:tmp];
static SimpleEmailSendController* email = nil;
static SimpleEmailSendController *email = nil;
if (email != nil) {
[email removeFromParentViewController];
[email release];
......
......@@ -90,6 +90,7 @@ TRANSLATIONS_FILES += \
SOURCES += \
src/accounts.cpp \
src/auxiliaries/email_helper.cpp \
src/auxiliaries/ios_helper.cpp \
src/datovka_shared/gov_services/helper.cpp \
src/datovka_shared/gov_services/service/gov_mv_crr_vbh.cpp \
src/datovka_shared/gov_services/service/gov_mv_ir_vp.cpp \
......@@ -211,6 +212,7 @@ SOURCES += \
HEADERS += \
src/accounts.h \
src/auxiliaries/email_helper.h \
src/auxiliaries/ios_helper.h \
src/common.h \
src/datovka_shared/gov_services/helper.h \
src/datovka_shared/gov_services/service/gov_mv_crr_vbh.h \
......
......@@ -207,7 +207,7 @@ Dialog {
showFiles: root.showFiles
showDirsFirst: true
nameFilters: ["*.*"]
folder: standardLocationUrl(InteractionFilesystem.DESKTOP_LOCATION)
folder: standardLocationUrl(InteractionFilesystem.DOCUMENTS_LOCATION)
onFolderChanged: {
selectedFileIndex = -1
pathField.text = stripUrlPrefix(folder)
......
......@@ -54,6 +54,8 @@ Item {
property string text_COLOR_RED: "#ff0000"
property bool iOS: false
/* This property holds total attachment size in bytes */
property int totalAttachmentSizeBytes: 0
......@@ -163,7 +165,35 @@ Item {
}
}
/* Append one file into send model */
function appendFileToSendModel(filePath) {
var isInFiletList = false
for (var i = 0; i < sendMsgAttachmentModel.rowCount(); i++) {
if (sendMsgAttachmentModel.filePathFromRow(i) === filePath) {
isInFiletList = true
break
}
}
if (!isInFiletList) {
var fileName = getFileNameFromPath(filePath)
var fileSizeBytes = files.getAttachmentSizeInBytes(filePath)
sendMsgAttachmentModel.appendFileFromPath(FileIdType.NO_FILE_ID,
fileName, filePath, fileSizeBytes)
totalAttachmentSizeBytes = sendMsgAttachmentModel.dataSizeSum()
}
}
/* Append file list into send model */
function appendFilesToSendModel(pathListModel) {
var listLength = pathListModel.count
for (var j = 0; j < listLength; ++j) {
appendFileToSendModel(pathListModel.get(j).path)
}
pathListModel.clear()
}
Component.onCompleted: {
iOS = iOSHelper.isIos()
actionButton.enabled = false
initPDZ.visible = false
initPDZ.checked = false
......@@ -187,6 +217,9 @@ Item {
}
Component.onDestruction: {
if (iOS) {
iOSHelper.clearSendAndTmpDirs()
}
statusBar.visible = false
}
......@@ -194,26 +227,7 @@ Item {
FileDialogue {
id: fileDialogue
multiSelect: true
onFinished: {
var listLength = pathListModel.count
for (var j = 0; j < listLength; ++j) {
var isInFiletList = false
for (var i = 0; i < sendMsgAttachmentModel.rowCount(); i++) {
if (sendMsgAttachmentModel.filePathFromRow(i) === pathListModel.get(j).path) {
isInFiletList = true
break
}
}
if (!isInFiletList) {
var fileName = getFileNameFromPath(pathListModel.get(j).path)
var fileSizeBytes = files.getAttachmentSizeInBytes(pathListModel.get(j).path)
sendMsgAttachmentModel.appendFileFromPath(FileIdType.NO_FILE_ID,
fileName, pathListModel.get(j).path, fileSizeBytes)
totalAttachmentSizeBytes = sendMsgAttachmentModel.dataSizeSum()
}
}
pathListModel.clear()
}
onFinished: appendFilesToSendModel(pathListModel)
}
/* Holds send message recipent list model */
......@@ -488,6 +502,14 @@ Item {
//----ATTACHMENT SECTION------------
Item {
id: tabAttachments
Connections {
target: iOSHelper
onFileSelectedSig: {
if (filePath !== "") {
appendFileToSendModel(filePath)
}
}
}
AccessibleButton {
id: addFile
height: inputItemHeight
......@@ -495,7 +517,7 @@ Item {
font.pointSize: defaultTextFont.font.pointSize
text: qsTr("Add file")
onClicked: {
fileDialogue.raise(qsTr("Select files"), ["*.*"], true, "")
iOS ? iOSHelper.openDocumentPickerController() : fileDialogue.raise(qsTr("Select files"), ["*.*"], true, "")
}
}
Component {
......@@ -570,7 +592,11 @@ Item {
font.bold: true
}
Text {
text: (rFilePath != "") ? rFilePath : qsTr("Local database")
text: if (rFilePath != "") {
iOS ? iOSHelper.getShortSendFilePath(rFilePath) : rFilePath
} else {
qsTr("Local database")
}
color: datovkaPalette.mid
font.pointSize: textFontSizeSmall
}
......@@ -645,9 +671,7 @@ Item {
}
ScrollableListView {
id: attachmentListSend
delegateHeight: listItemHeight
anchors.top: attachmentsSizeLabel.bottom
anchors.bottom: parent.bottom
clip: true
......
......@@ -25,6 +25,7 @@
#include <QDesktopServices>
#include <QMimeDatabase>
#include <QMimeType>
#include <QObject>
#include <QUrl>
#include "ios/src/url_opener.h"
......@@ -127,8 +128,7 @@ void sendEmail(const QString &emailMessage, const QStringList &fileList,
#if defined Q_OS_IOS
UrlOpener urlOpener;
urlOpener.createEmail(body, to, subject, fileList);
UrlOpener::createEmail(body, to, subject, fileList);
#elif defined Q_OS_ANDROID
......
/*
* Copyright (C) 2014-2019 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.
*/
#include <QDir>
#include <QFileInfo>
#include <QMessageBox>
#include "src/auxiliaries/ios_helper.h"
#include "src/io/filesystem.h"
#include "src/datovka_shared/log/log.h"
#ifdef Q_OS_IOS
#include "ios/src/icloud_io.h"
#define ICLOUD_DATOVKA_CONTAINER_NAME "Datovka"