diff --git a/ios/Info.tmp b/ios/Info.tmp index 49e9920044f1796c3c9dbca7810e89d9e0093655..cd941368da3ec07640296f166d2095febf28da8a 100644 --- a/ios/Info.tmp +++ b/ios/Info.tmp @@ -88,5 +88,17 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSUbiquitousContainers + + iCloud.cz.nic.mobile-datovka + + NSUbiquitousContainerIsDocumentScopePublic + + NSUbiquitousContainerSupportedFolderLevels + Any + NSUbiquitousContainerName + Datovka + + diff --git a/ios/ios.pri b/ios/ios.pri index 20ef0c8ad8013f4efefb716aca979db7c8771e8d..f208c5f37178dd03a3a002d51543bc0cef7f3971 100644 --- a/ios/ios.pri +++ b/ios/ios.pri @@ -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 diff --git a/ios/src/app_delegate.h b/ios/src/app_delegate.h index 53e95826ef996cc03a3478368529f833518efc88..cf60419e3568d449e4139c38dd5d7d605be38548 100644 --- a/ios/src/app_delegate.h +++ b/ios/src/app_delegate.h @@ -24,7 +24,8 @@ #pragma once #import -#import + +#include @interface QtAppDelegate : UIResponder +(QtAppDelegate *)sharedQtAppDelegate; diff --git a/ios/src/app_delegate.mm b/ios/src/app_delegate.mm index adf3a2fdce45b0a0b7fc9810096b1dc1e45684f6..b7a77c2aac126359730b29d4f953bcfcc1865d9f 100644 --- a/ios/src/app_delegate.mm +++ b/ios/src/app_delegate.mm @@ -22,9 +22,10 @@ */ #include -#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 diff --git a/ios/src/doc_picker_controller.h b/ios/src/doc_picker_controller.h new file mode 100644 index 0000000000000000000000000000000000000000..19b860fdbf6cad7be6a2f26f11981d4b3d517fea --- /dev/null +++ b/ios/src/doc_picker_controller.h @@ -0,0 +1,33 @@ +/* + * 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 . + * + * 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 + +@interface DocumentPickerController : UIViewController + +- (void)openImportDocumentPicker; +- (void)openExportDocumentPicker:(NSArray*)exportUrls; + +@end diff --git a/ios/src/doc_picker_controller.mm b/ios/src/doc_picker_controller.mm new file mode 100644 index 0000000000000000000000000000000000000000..90a1f3599884ccb1361820a874e922d61ad850a9 --- /dev/null +++ b/ios/src/doc_picker_controller.mm @@ -0,0 +1,85 @@ +/* + * 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 . + * + * 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 +#include + +#include "ios/src/doc_picker_controller.h" +#include "src/auxiliaries/ios_helper.h" +#include "src/global.h" + +@interface DocumentPickerController () + +@end + +@implementation DocumentPickerController + +- (void)viewDidLoad { + [super viewDidLoad]; +} + +- (void)openExportDocumentPicker:(NSArray *)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 *)urls { + + if (controller.documentPickerMode == UIDocumentPickerModeImport) { + + NSLog(@"SELECTED FILE URLs: %@", urls); + QList 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 diff --git a/ios/src/doc_view_controller.mm b/ios/src/doc_view_controller.mm index 9f9babe8e50425cdb7f6e3f024a3a385a806df3e..8a945b1bc347aca86041667b2e6fdd70cd62283a 100644 --- a/ios/src/doc_view_controller.mm +++ b/ios/src/doc_view_controller.mm @@ -21,10 +21,10 @@ * the two. */ -#import "doc_view_controller.h" - #include +#include "ios/src/doc_view_controller.h" + @interface DocViewController () @end diff --git a/ios/src/icloud_io.h b/ios/src/icloud_io.h new file mode 100644 index 0000000000000000000000000000000000000000..10c5aa8ce7508c6ed6f994795befd460d3e7d449 --- /dev/null +++ b/ios/src/icloud_io.h @@ -0,0 +1,90 @@ +/* + * 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 . + * + * 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 +#include +#include + +/*! + * @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); +}; diff --git a/ios/src/icloud_io.mm b/ios/src/icloud_io.mm new file mode 100644 index 0000000000000000000000000000000000000000..d5e2bb3e4f6ff061cf8d58c541350252553a9d73 --- /dev/null +++ b/ios/src/icloud_io.mm @@ -0,0 +1,158 @@ +/* + * 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 . + * + * 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 *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(); + } +} diff --git a/ios/src/ios_file_opener.h b/ios/src/ios_file_opener.h index 04aab37c18a84363827ed4520e8515b833b8d513..f9db3ee6cd550b52acc9c1bfdb848286351e12bc 100644 --- a/ios/src/ios_file_opener.h +++ b/ios/src/ios_file_opener.h @@ -23,9 +23,6 @@ #pragma once -#include - -#ifdef Q_OS_IOS #import @interface iOSFileOpener : NSObject @@ -35,5 +32,3 @@ - (void)openFile:(NSString *)path fromViewController:(UIViewController *)viewController; @end - -#endif /* Q_OS_IOS */ diff --git a/ios/src/send_email_controller.mm b/ios/src/send_email_controller.mm index e4a5d0254577c7ed69ffc0dadf20231f7c177dd3..65742562974244705ca5a199a24c92b1a0d625d0 100644 --- a/ios/src/send_email_controller.mm +++ b/ios/src/send_email_controller.mm @@ -21,10 +21,10 @@ * the two. */ -#import "send_email_controller.h" - #include +#include "ios/src/send_email_controller.h" + @implementation SimpleEmailSendController - (void) showCantSendMailAlert diff --git a/ios/src/url_opener.h b/ios/src/url_opener.h index 420ab4b44be1589886b95e95e03289e05e05e5aa..5a74955ab5c653cb9beaff934124d4f420f51040 100644 --- a/ios/src/url_opener.h +++ b/ios/src/url_opener.h @@ -23,7 +23,6 @@ #pragma once -#include #include /* @@ -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); }; diff --git a/ios/src/url_opener.mm b/ios/src/url_opener.mm index f332158848980c1229dbbe665788ecca7d3a2f44..bebf64cb9f95466c7c174521362586a4a2024503 100644 --- a/ios/src/url_opener.mm +++ b/ios/src/url_opener.mm @@ -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]; diff --git a/mobile-datovka.pro b/mobile-datovka.pro index aa9561916f72c5170e150e18ff2f1f0a9272fe67..49e77d72acb62b719158b2845d01915076dbb2bd 100644 --- a/mobile-datovka.pro +++ b/mobile-datovka.pro @@ -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 \ diff --git a/qml/dialogues/FileDialogue.qml b/qml/dialogues/FileDialogue.qml index 4e8a39bb2f32ddee29e784a20c3a26bc6c62ad9c..3f5bafe1baf921658f3bc7e3b3b19dbb55cd86a3 100644 --- a/qml/dialogues/FileDialogue.qml +++ b/qml/dialogues/FileDialogue.qml @@ -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) diff --git a/qml/pages/PageSendMessage.qml b/qml/pages/PageSendMessage.qml index 71a0e7eff01cac1f6a9e30c2a8abc06d417fc590..29d6156a7830dd87b72e7da210852f61ce1cc13f 100644 --- a/qml/pages/PageSendMessage.qml +++ b/qml/pages/PageSendMessage.qml @@ -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 diff --git a/src/auxiliaries/email_helper.cpp b/src/auxiliaries/email_helper.cpp index 64bfbd124dc0eb51fdeec2553129ac60b8e02a95..1ea00dbb40dcab86db8e057ed501e961108831ef 100644 --- a/src/auxiliaries/email_helper.cpp +++ b/src/auxiliaries/email_helper.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #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 diff --git a/src/auxiliaries/ios_helper.cpp b/src/auxiliaries/ios_helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..738e82378ae6f3a90af812f3680f04570eee35a2 --- /dev/null +++ b/src/auxiliaries/ios_helper.cpp @@ -0,0 +1,210 @@ +/* + * 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 . + * + * 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 +#include +#include + +#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" +#define SEND_FILE_PATH_PREFIX "Documents/Datovka" +#endif /* Q_OS_IOS */ + +IosHelper::IosHelper(QObject *parent) + : QObject(parent) +{ +} + +void IosHelper::storeFilesToCloud(const QStringList &srcFilePaths, + const QString &targetPath) +{ + debugFuncCall(); + +#ifdef Q_OS_IOS + + if (!ICloudIo::isCloudOn()) { + QMessageBox::critical(Q_NULLPTR, tr("iCloud error"), + tr("Unable to access iCloud account. Open the settings and check your iCloud settings."), + QMessageBox::Ok); + return; + } + + /* Upload files to iCloud */ + bool success = true; + QStringList errorUploads; + QString filePath; + + foreach (filePath, srcFilePaths) { + + ICloudIo::ICloudResult retCode = ICloudIo::moveFileToCloud(filePath, targetPath); + + QFileInfo fi(filePath); + + switch (retCode) { + case ICloudIo::ICLOUD_NOT_ON: + success = false; + errorUploads.append(tr("Unable to access iCloud!")); + goto finish; + break; + case ICloudIo::ICLOUD_TARGET_SAVE_DIR_ERROR: + success = false; + errorUploads.append(tr("Cannot create subdirectory '%1' in iCloud.").arg(targetPath)); + goto finish; + break; + case ICloudIo::ICLOUD_FILE_EXISTS: + success = false; + errorUploads.append(tr("File '%1' already exists in iCloud.").arg(fi.fileName())); + break; + case ICloudIo::ICLOUD_FILE_UPLOAD_ERROR: + success = false; + errorUploads.append(tr("File '%1' upload failed.").arg(fi.fileName())); + break; + case ICloudIo::ICLOUD_FILE_UPLOAD_SUCCESS: + errorUploads.append(tr("File '%1' has been stored into iCloud.").arg(fi.fileName())); + break; + default: + break; + } + } + +finish: + /* Delete files and source directory. */ + QFileInfo fi(filePath); + QDir dir(fi.absolutePath()); + dir.removeRecursively(); + + /* Show final notification */ + if (success) { + QMessageBox::information(Q_NULLPTR, tr("Saved to iCloud"), + tr("Files have been stored into iCloud.") + "\n\n" + + tr("Path: '%1'").arg(QString(ICLOUD_DATOVKA_CONTAINER_NAME) + + "/" + targetPath), + QMessageBox::Ok); + } else { + QString txt; + foreach (const QString &error, errorUploads) { + txt += "\n" + error; + } + QMessageBox::warning(Q_NULLPTR, + tr("iCloud Problem"), + tr("Files have not been saved!") + "\n" + txt, + QMessageBox::Ok); + } + +#else /* !Q_OS_IOS */ + Q_UNUSED(srcFilePaths); + Q_UNUSED(targetPath); +#endif /* Q_OS_IOS */ +} + +QString IosHelper::getShortSendFilePath(const QString &sandBoxFilePath) +{ +#ifdef Q_OS_IOS + + // Get short local send file path + QString pattern(SEND_FILE_PATH_PREFIX); + int pos = sandBoxFilePath.indexOf(pattern) + pattern.length(); + return QUrl::fromPercentEncoding(sandBoxFilePath.mid(pos).toUtf8()); + +#else + Q_UNUSED(sandBoxFilePath); + return QString(); +#endif +} + +void IosHelper::clearSendAndTmpDirs(void) +{ + debugFuncCall(); + + QDir dir(appSendDirPath()); + dir.removeRecursively(); + dir.setPath(appTmpDirPath()); + dir.removeRecursively(); +} + +void IosHelper::openDocumentPickerController(void) +{ + debugFuncCall(); + +#ifdef Q_OS_IOS + + ICloudIo::openDocumentPickerController(QStringList()); + +#endif /* Q_OS_IOS */ +} + +void IosHelper::importFilesToAppInbox(const QList &selectedFileUrls) +{ + foreach (const QUrl &fileUrl, selectedFileUrls) { + moveFileToSendDir(fileUrl); + } +} + +void IosHelper::storeFilesToDeviceStorage(const QStringList &srcFilePaths) +{ + debugFuncCall(); + +#ifdef Q_OS_IOS + + if (Q_UNLIKELY(srcFilePaths.isEmpty())) { + return; + } + + ICloudIo::openDocumentPickerController(srcFilePaths); + +#else /* !Q_OS_IOS */ + Q_UNUSED(srcFilePaths); +#endif /* Q_OS_IOS */ +} + +bool IosHelper::isIos(void) +{ +#ifdef Q_OS_IOS + return true; +#else + return false; +#endif +} + +void IosHelper::moveFileToSendDir(const QUrl &sourceFileUrl) +{ +#ifdef Q_OS_IOS + if (Q_UNLIKELY(!sourceFileUrl.isValid())) { + return; + } + + QUrl targetFileUrl(ICloudIo::moveFile(sourceFileUrl, appSendDirPath())); + if (targetFileUrl.isValid()) { + targetFileUrl.setScheme(QString()); + emit fileSelectedSig(targetFileUrl.toString()); + } + +#else /* !Q_OS_IOS */ + Q_UNUSED(sourceFileUrl); +#endif +} diff --git a/src/auxiliaries/ios_helper.h b/src/auxiliaries/ios_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..84e8b56a5038ce615c24523eb72462db0e66df33 --- /dev/null +++ b/src/auxiliaries/ios_helper.h @@ -0,0 +1,114 @@ +/* + * 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 . + * + * 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 +#include +#include +#include + +/*! + * @brief Provides QT interface for some iOS objective-C based methods. + */ +class IosHelper : public QObject { + Q_OBJECT + +public: + /*! + * @brief Constructor. + * + * @param[in] parent Parent object. + */ + explicit IosHelper(QObject *parent = Q_NULLPTR); + + /*! + * @brief Store files to iCloud. + * + * @param[in] srcFilePaths List of file paths to be saved into iCloud. + * @param[in] targetPath Target iCloud path where files will store. + */ + static + void storeFilesToCloud(const QStringList &srcFilePaths, + const QString &targetPath); + + /*! + * @brief Retrun short local send file path (without full sandbox path). + * + * @param[in] sandBoxFilePath Send file path. + * @return Short path to app sandbox where send file was stored. + */ + Q_INVOKABLE static + QString getShortSendFilePath(const QString &sandBoxFilePath); + + /*! + * @brief Clear send and tmp folder. + */ + Q_INVOKABLE static + void clearSendAndTmpDirs(void); + + /*! + * @brief Create and open document picker controller. + */ + Q_INVOKABLE static + void openDocumentPickerController(void); + + /*! + * @brief Is activated when files have been selected with iOS file picker. + * + * @param[in] selectedFileUrls Path file list for QML. + */ + void importFilesToAppInbox(const QList &selectedFileUrls); + + /*! + * @brief Store files to device local storage. + * + * @param[in] srcFilePaths List of file paths to be saved into storage. + */ + static + void storeFilesToDeviceStorage(const QStringList &srcFilePaths); + + /*! + * @brief iOS check for QML. + * + * @return True if iOS. + */ + Q_INVOKABLE static + bool isIos(void); + +signals: + /*! + * @brief Is activated when a file has been chosen with document picker. + * + * @param[in] filePath Path file for QML. + */ + void fileSelectedSig(QString filePath); + +private: + /*! + * @brief Move file from app temporary inbox to send path. + * + * @param[in] sourceFileUrl Source file url from inbox. + */ + void moveFileToSendDir(const QUrl &sourceFileUrl); +}; diff --git a/src/files.cpp b/src/files.cpp index 2cf0f8f4be1fc608b179a749b4ef44be034731b3..9f44684b5baa8a1c5634cbdf70a04b3c67080e6c 100644 --- a/src/files.cpp +++ b/src/files.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,7 @@ #endif #include "ios/src/url_opener.h" #include "src/auxiliaries/email_helper.h" +#include "src/auxiliaries/ios_helper.h" #include "src/common.h" #include "src/crypto/crypto.h" #include "src/datovka_shared/log/log.h" @@ -262,8 +264,7 @@ void Files::openAttachmentFromPath(const QString &filePath) #ifdef Q_OS_IOS - UrlOpener urlOpener; - urlOpener.openFile(filePath); + UrlOpener::openFile(filePath); #elif defined (Q_OS_ANDROID) @@ -638,6 +639,26 @@ void Files::sendAttachmentEmailZfo(const QVariant &attachModelVariant, QString::number(msgId)); } +#ifdef Q_OS_IOS +static +void exportFilesiOS(const QStringList &destFilePaths, + const QString &targetDir) +{ + QMessageBox msgBox; + msgBox.setText(QObject::tr("You can export files into iCloud or into device local storage.")); + msgBox.setDetailedText(QObject::tr("Do you want to export files to Datovka iCloud container?")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::No); + int ret = msgBox.exec(); + + if (ret == QMessageBox::Yes) { + IosHelper::storeFilesToCloud(destFilePaths, targetDir); + } else if (ret == QMessageBox::No) { + IosHelper::storeFilesToDeviceStorage(destFilePaths); + } +} +#endif + void Files::saveMsgFilesToDisk(const QString &userName, const QString &msgIdStr, MsgAttachFlags attachFlags) { @@ -704,15 +725,29 @@ void Files::saveMsgFilesToDisk(const QString &userName, documents.append(document); } - QString filePath(appMsgAttachDirPath(msgIdStr)); + QString targetPath; + +#ifdef Q_OS_IOS + targetPath = appMsgAttachDirPathiOS(msgIdStr); +#else + targetPath = appMsgAttachDirPath(msgIdStr); +#endif - QString destPath; + QString destFilePath; + QStringList destFilePaths; foreach (const Isds::Document &document, documents) { - destPath = writeFile(filePath, document.fileDescr(), + destFilePath = writeFile(targetPath, document.fileDescr(), document.binaryContent()); + if (!destFilePath.isEmpty()) { + destFilePaths.append(destFilePath); + } } - attachmentSavingNotification(destPath); +#ifndef Q_OS_IOS + attachmentSavingNotification(destFilePath); +#else + exportFilesiOS(destFilePaths, joinDirs(userName, msgIdStr)); +#endif } void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant, @@ -728,18 +763,32 @@ void Files::saveAttachmentsToDiskZfo(const QVariant &attachModelVariant, return; } - QString targetPath(appMsgAttachDirPath(msgIdStr)); + QString targetPath; + +#ifdef Q_OS_IOS + targetPath = appMsgAttachDirPathiOS(msgIdStr); +#else + targetPath = appMsgAttachDirPath(msgIdStr); +#endif - QString destPath; + QString destFilePath; + QStringList destFilePaths; for (int row = 0; row < attachModel->rowCount(); ++row) { QModelIndex idx(attachModel->index(row)); - destPath = writeFile(targetPath, attachModel->data(idx, + destFilePath = writeFile(targetPath, attachModel->data(idx, FileListModel::ROLE_FILE_NAME).toString(), attachModel->data(idx, FileListModel::ROLE_BINARY_DATA).toByteArray()); + if (!destFilePath.isEmpty()) { + destFilePaths.append(destFilePath); + } } - attachmentSavingNotification(destPath); +#ifndef Q_OS_IOS + attachmentSavingNotification(destFilePath); +#else + exportFilesiOS(destFilePaths, msgIdStr); +#endif } void Files::deleteTmpFileFromStorage(const QString &filePath) diff --git a/src/global.cpp b/src/global.cpp index be6f6cf26b658745c913a75adb95963d47ddb2f8..5b38b7497260eafb9b4a6a58dc5ae46b042b8229 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -40,3 +40,5 @@ class RecordsManagementDb *GlobInstcs::recMgmtDbPtr = Q_NULLPTR; class AccountsMap *GlobInstcs::acntMapPtr = Q_NULLPTR; class ImageProvider *GlobInstcs::imgProvPtr = Q_NULLPTR; + +class IosHelper *GlobInstcs::iOSHelperPtr = Q_NULLPTR; diff --git a/src/global.h b/src/global.h index 87fbb1660d189aa64b042e096e8c20cbe0001ba5..1c0ba5872eb39f35ef90123f36f1d65658b8717f 100644 --- a/src/global.h +++ b/src/global.h @@ -43,6 +43,8 @@ class AccountsMap; class ImageProvider; +class IosHelper; + /*! * @brief The namespace holds pointers to all globally accessible structures. */ @@ -75,4 +77,6 @@ namespace GlobInstcs { extern class ImageProvider *imgProvPtr; /*!< Image provider. */ + extern + class IosHelper *iOSHelperPtr; /*!< iOS helper. */ } diff --git a/src/io/filesystem.cpp b/src/io/filesystem.cpp index 265d1e3fb1c169e7d271458e15a3047f7470b1e0..5a43269944fd8ca8434fbc92acce92fd19b8a6a6 100644 --- a/src/io/filesystem.cpp +++ b/src/io/filesystem.cpp @@ -40,10 +40,14 @@ */ #define ILL_FNAME_REP "_" -QString appAttachDirPath(void) +QString joinDirs(const QString &dirName1, const QString &dirName2) { - return dfltAttachSavingLoc() + QDir::separator() + - QLatin1String(DATOVKA_BASE_DIR_NAME); + return dirName1 + QDir::separator() + dirName2; +} + +QString appSendDirPath(void) +{ + return existingAppPath(dfltAttachSavingLoc(), DATOVKA_SEND_DIR_NAME); } QString appCertDirPath(void) @@ -59,7 +63,7 @@ QString appEmailDirPath(const QString &msgIdStr) QString appLogDirPath(void) { - return existingAppPath(dfltAttachSavingLoc(), DATOVKA_LOG_DIR_NAME); + return existingAppPath(dfltAttachSavingLoc(), DATOVKA_LOGS_DIR_NAME); } QString appMsgAttachDirPath(const QString &msgIdStr) @@ -67,6 +71,12 @@ QString appMsgAttachDirPath(const QString &msgIdStr) return existingAppPath(dfltAttachSavingLoc(), msgIdStr); } +QString appMsgAttachDirPathiOS(const QString &msgIdStr) +{ + return existingAppPath(dfltAttachSavingLoc(), + QStringLiteral(DATOVKA_TEMP_DIR_NAME) + QDir::separator() + msgIdStr); +} + QString appTmpDirPath(void) { return existingAppPath(dfltAttachSavingLoc(), DATOVKA_TEMP_DIR_NAME); diff --git a/src/io/filesystem.h b/src/io/filesystem.h index 77a1e6aa5ea5b55949f4ff71a1671ec1e5a522c7..52cf3fe665abd7fece8fe09df228535e439e3b18 100644 --- a/src/io/filesystem.h +++ b/src/io/filesystem.h @@ -30,8 +30,9 @@ * @brief Datovka file store directory names. */ #define DATOVKA_BASE_DIR_NAME "Datovka" -#define DATOVKA_CERT_DIR_NAME "Certs" -#define DATOVKA_LOG_DIR_NAME "Logs" +#define DATOVKA_SEND_DIR_NAME "Send" +#define DATOVKA_CERT_DIR_NAME "Cert" +#define DATOVKA_LOGS_DIR_NAME "Logs" #define DATOVKA_MAIL_DIR_NAME "Email" #define DATOVKA_TEMP_DIR_NAME "Temp" @@ -41,11 +42,19 @@ #define DATOVKA_MAX_LOG_FILES 5 /*! - * @brief Return path to location where the application stores the attachments. + * @brief Join two directoris to path. + * @param[in] dirName1 First directory name. + * @param[in] dirName2 Second irectory name. + * @return Directory path. + */ +QString joinDirs(const QString &dirName1, const QString &dirName2); + +/*! + * @brief Return path to location where the application stores send files. * * @return Full path. */ -QString appAttachDirPath(void); +QString appSendDirPath(void); /*! * @brief Return path to location where the application stores certificates. @@ -79,6 +88,15 @@ QString appLogDirPath(void); */ QString appMsgAttachDirPath(const QString &msgIdStr); +/*! + * @brief Return path to location where attachments of a particular message + * can be stored for iOS. + * + * @param[in] msgIdStr String containing message identifier. + * @return Full path containing the supplied identifier. + */ +QString appMsgAttachDirPathiOS(const QString &msgIdStr); + /*! * @brief Return path to location where temporary files can be stored. * diff --git a/src/main.cpp b/src/main.cpp index a0681d9f810adabd30ffde126075d9b8a6afd1c4..279a796d8bfd6534f4b5080a1a3b4336a0cf092c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,7 @@ #include #include "src/accounts.h" +#include "src/auxiliaries/ios_helper.h" #include "src/datovka_shared/crypto/crypto_trusted_certs.h" #include "src/datovka_shared/io/records_management_db.h" #include "src/datovka_shared/localisation/localisation.h" @@ -407,6 +408,12 @@ int main(int argc, char *argv[]) logErrorNL("%s", "Cannot create image provider."); return EXIT_FAILURE; } + + GlobInstcs::iOSHelperPtr = new (std::nothrow) IosHelper; + if (GlobInstcs::iOSHelperPtr == Q_NULLPTR) { + logErrorNL("%s", "Cannot create iOS helper."); + return EXIT_FAILURE; + } } QStringList cmdLineFileNames; @@ -447,6 +454,7 @@ int main(int argc, char *argv[]) Files files; Log log(&memLog); IsdsWrapper isds; + IosHelper iOSHelper; GovWrapper gov(&isds); GlobalSettingsQmlWrapper settings; InteractionZfoFile interactionZfoFile; @@ -457,7 +465,13 @@ int main(int argc, char *argv[]) /* Inicialize app delegate component for interaction with iOS * Reaction on the iOS action "Open in..." */ #if defined Q_OS_IOS + QtAppDelegateInitialize(&interactionZfoFile); + + GlobInstcs::iOSHelperPtr = &iOSHelper; + + /* Clear send and tmp dir (iOS only). */ + IosHelper::clearSendAndTmpDirs(); #endif /* @@ -506,6 +520,7 @@ int main(int argc, char *argv[]) ctx->setContextProperty("messages", &messages); ctx->setContextProperty("accounts", &accounts); ctx->setContextProperty("files", &files); + ctx->setContextProperty("iOSHelper", &iOSHelper); ctx->setContextProperty("gov", &gov); ctx->setContextProperty("settings", &settings); ctx->setContextProperty("strManipulation", &strManipulation); @@ -748,6 +763,8 @@ int main(int argc, char *argv[]) delete GlobInstcs::workPoolPtr; GlobInstcs::workPoolPtr = Q_NULLPTR; delete GlobInstcs::msgProcEmitterPtr; GlobInstcs::msgProcEmitterPtr = Q_NULLPTR; + + GlobInstcs::iOSHelperPtr = Q_NULLPTR; } /* Finally, destroy global log object. */