Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDF Viewing Feature #3353

Merged
merged 27 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
dab98ac
Android PDF file viewer and openLink function working (#3268)
VitorVieiraZ Apr 8, 2024
44d5876
Desktop file viewer - part one (#3281)
VitorVieiraZ Apr 8, 2024
bd7a490
code cleaning
VitorVieiraZ Apr 8, 2024
1417757
git layout adjusts
VitorVieiraZ Apr 8, 2024
235ba85
last adjustments
VitorVieiraZ Apr 8, 2024
9c3962a
ios pdf viewing
VitorVieiraZ Apr 23, 2024
8f41edf
form rich text opening pdfs enabled
VitorVieiraZ Apr 23, 2024
f42a624
new code adjustments
VitorVieiraZ Apr 23, 2024
0a5f114
code cleanup
VitorVieiraZ Apr 23, 2024
6702130
dealing with no-existent file
VitorVieiraZ Apr 23, 2024
2c67b6b
adjustments
VitorVieiraZ Apr 24, 2024
6872f3c
first part of post review changes
VitorVieiraZ Apr 25, 2024
21023ec
second part of post review changes
VitorVieiraZ Apr 25, 2024
af25a75
third part of post review changes
VitorVieiraZ Apr 25, 2024
61c01f6
java fix
VitorVieiraZ May 2, 2024
901f502
fixing windows build
VitorVieiraZ May 3, 2024
8ca2f99
layout fix
VitorVieiraZ May 3, 2024
c438009
local_file_prefix to header file
VitorVieiraZ May 7, 2024
bf1bcb3
adjustments for newer android versions
VitorVieiraZ May 15, 2024
a356ed6
inputactivity code layout adjust
VitorVieiraZ May 15, 2024
7d5fef6
post rebase adjusts
VitorVieiraZ May 15, 2024
e116979
android manifest update and code layout fix
VitorVieiraZ May 15, 2024
8524155
iosutils.mm fix
VitorVieiraZ May 15, 2024
bfb8e44
start activity in openFile method
VitorVieiraZ May 15, 2024
8eaff6f
manifest update
VitorVieiraZ May 20, 2024
d3293db
manifest update
VitorVieiraZ May 20, 2024
e909344
manifest adjustment
VitorVieiraZ May 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/android/res/xml/file_paths.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="input_camera" path="./" />
<external-path
name="app_files"
path="Android/data/uk.co.lutraconsulting/" />
</paths>
45 changes: 45 additions & 0 deletions app/android/src/uk/co/lutraconsulting/InputActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
import android.graphics.Insets;
import android.graphics.Color;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.content.ActivityNotFoundException;
import java.io.File;
import androidx.core.content.FileProvider;
import android.widget.Toast;

import androidx.core.view.WindowCompat;
import androidx.core.splashscreen.SplashScreen;

Expand Down Expand Up @@ -123,6 +131,43 @@ public void hideSplashScreen()
keepSplashScreenVisible = false;
}

public boolean openFile( String filePath ) {
File file = new File( filePath );

if ( !file.exists() )
{
return false;
}

Intent showFileIntent = new Intent( Intent.ACTION_VIEW );

try
{
Uri fileUri = FileProvider.getUriForFile( this, "uk.co.lutraconsulting.fileprovider", file );

showFileIntent.setData( fileUri );

// FLAG_GRANT_READ_URI_PERMISSION grants temporary read permission to the content URI.
// FLAG_ACTIVITY_NEW_TASK is used when starting an Activity from a non-Activity context.
showFileIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION );
}
catch ( IllegalArgumentException e )
{
return false;
}

if ( showFileIntent.resolveActivity( getPackageManager() ) != null )
{
startActivity( showFileIntent );
}
else
{
return false;
}

return true;
}

public void quitGracefully()
{
String man = android.os.Build.MANUFACTURER.toUpperCase();
Expand Down
11 changes: 11 additions & 0 deletions app/androidutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,17 @@ void AndroidUtils::hideSplashScreen()
#endif
}

bool AndroidUtils::openFile( const QString &filePath )
{
bool result = false;
#ifdef ANDROID
auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() );
QJniObject jFilePath = QJniObject::fromString( filePath );
result = activity.callMethod<jboolean>( "openFile", "(Ljava/lang/String;)Z", jFilePath.object<jstring>() );
#endif
return result;
}

bool AndroidUtils::requestStoragePermission()
{
#ifdef ANDROID
Expand Down
1 change: 1 addition & 0 deletions app/androidutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class AndroidUtils: public QObject
*/
Q_INVOKABLE void callImagePicker( const QString &code = "" );
Q_INVOKABLE void callCamera( const QString &targetPath, const QString &code = "" );
Q_INVOKABLE bool openFile( const QString &filePath );

#ifdef ANDROID
const static int MEDIA_CODE = 101;
Expand Down
41 changes: 40 additions & 1 deletion app/inpututils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@
#include <Qt>
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QFileInfo>
#include <QRegularExpression>
#include <QDesktopServices>
#include <QUrl>
#include <algorithm>
#include <limits>
#include <math.h>
Expand Down Expand Up @@ -2175,3 +2177,40 @@ QString InputUtils::getDeviceModel()
#endif
return QStringLiteral( "N/A" );
}

bool InputUtils::openLink( const QString &homePath, const QString &link )
{
if ( link.startsWith( LOCAL_FILE_PREFIX ) )
{
QString relativePath = link.mid( QString( LOCAL_FILE_PREFIX ).length() );
QString absoluteLinkPath = homePath + QDir::separator() + relativePath;
if ( !fileExists( absoluteLinkPath ) )
{
return false;
}
#ifdef Q_OS_ANDROID
if ( !mAndroidUtils->openFile( absoluteLinkPath ) )
{
return false;
}
#elif defined(Q_OS_IOS)
if ( ! IosUtils::openFile( absoluteLinkPath ) )
{
return false;
}
#else
// Desktop environments
QUrl fileUrl = QUrl::fromLocalFile( absoluteLinkPath );
if ( !QDesktopServices::openUrl( fileUrl ) )
{
return false;
}
#endif
}
else
{
QDesktopServices::openUrl( QUrl( link ) );
}

return true;
}
9 changes: 9 additions & 0 deletions app/inpututils.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ class InputUtils: public QObject
*/
Q_INVOKABLE static QString bytesToHumanSize( double bytes );

/**
* Opens the specified link in an appropriate application. For "project://" links, it converts them to
* absolute paths and opens with default file handlers. Other links are opened in the default web browser.
* @param link The link to open, either a "project://" link or a standard URL.
*/
Q_INVOKABLE bool openLink( const QString &homePath, const QString &link );

Q_INVOKABLE bool acquireCameraPermission();

Q_INVOKABLE bool isBluetoothTurnedOn();
Expand Down Expand Up @@ -603,6 +610,8 @@ class InputUtils: public QObject
static QUrl iconFromGeometry( const Qgis::GeometryType &geometry );

AndroidUtils *mAndroidUtils = nullptr; // not owned

const QString LOCAL_FILE_PREFIX = QStringLiteral( "project://" );
};

#endif // INPUTUTILS_H
9 changes: 9 additions & 0 deletions app/ios/iosutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,12 @@ QString IosUtils::getDeviceModel()
#endif
return "";
}

bool IosUtils::openFile( const QString &filePath )
{
#ifdef Q_OS_IOS
return openFileImpl( filePath );
#else
return false;
#endif
}
5 changes: 4 additions & 1 deletion app/ios/iosutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class IosUtils: public QObject

Q_INVOKABLE QVector<int> getSafeArea();

Q_INVOKABLE static bool openFile( const QString &filePath );

static Q_INVOKABLE QString getManufacturer();
static Q_INVOKABLE QString getDeviceModel();

Expand All @@ -64,9 +66,10 @@ class IosUtils: public QObject
void setIdleTimerDisabled();

QVector<int> getSafeAreaImpl();

static QString getManufacturerImpl();
static QString getDeviceModelImpl();

static bool openFileImpl( const QString &filePath );
};

#endif // IOSUTILS_H
38 changes: 38 additions & 0 deletions app/ios/iosutils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

#include <UIKit/UIKit.h>
#include <sys/utsname.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include <QString>
#include "iosutils.h"

void IosUtils::setIdleTimerDisabled()
Expand Down Expand Up @@ -54,3 +57,38 @@
QString deviceModel = QString::fromUtf8( systemInfo.machine );
return deviceModel.toUpper();
}

@interface FileOpener : UIViewController <UIDocumentInteractionControllerDelegate>
@end

@implementation FileOpener

- ( UIViewController * )documentInteractionControllerViewControllerForPreview:( UIDocumentInteractionController * )ctrl
{
return self;
}

@end

bool IosUtils::openFileImpl( const QString &filePath )
{
static FileOpener *viewer = nil;
NSURL *resourceURL = [NSURL fileURLWithPath:filePath.toNSString()];

UIDocumentInteractionController *interactionCtrl = [UIDocumentInteractionController interactionControllerWithURL:resourceURL];
UIViewController *rootViewController = [[[[UIApplication sharedApplication] windows] firstObject] rootViewController];

viewer = [[FileOpener alloc] init];
[rootViewController addChildViewController: viewer];
interactionCtrl.delegate = ( id<UIDocumentInteractionControllerDelegate> )viewer;

if ( ![interactionCtrl presentPreviewAnimated:NO] )
{
if ( ![interactionCtrl presentOptionsMenuFromRect:CGRectZero inView:viewer.view animated:NO] )
{
return false;
}
}

return true;
}
8 changes: 6 additions & 2 deletions app/qml/form/editors/MMFormRichTextViewer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ MMPrivateComponents.MMBaseInput {
property bool _fieldShouldShowTitle: parent.fieldShouldShowTitle

property string _fieldTitle: parent.fieldTitle
property string _fieldHomePath: parent.fieldHomePath

title: _fieldShouldShowTitle ? _fieldTitle : ""

Expand Down Expand Up @@ -47,8 +48,11 @@ MMPrivateComponents.MMBaseInput {
leftPadding: __style.margin20
rightPadding: __style.margin20

onLinkActivated: function( link ) {
Qt.openUrlExternally( link )
onLinkActivated: function ( link ) {
if ( !__inputUtils.openLink( root._fieldHomePath, link.toString( ) ) )
{
__notificationModel.addError( "Could not open the file. It may not exist, could be invalid, or there might be no application available to open it." )
}
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion app/qml/form/editors/MMFormTextMultilineEditor.qml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ MMPrivateComponents.MMBaseInput {
property string _fieldTitle: parent.fieldTitle
property string _fieldErrorMessage: parent.fieldErrorMessage
property string _fieldWarningMessage: parent.fieldWarningMessage
property string _fieldHomePath: parent.fieldHomePath

property bool _fieldRememberValueSupported: parent.fieldRememberValueSupported
property bool _fieldRememberValueState: parent.fieldRememberValueState
Expand Down Expand Up @@ -117,7 +118,13 @@ MMPrivateComponents.MMBaseInput {
radius: __style.radius12
}

onLinkActivated: ( link ) => Qt.openUrlExternally( link )
onLinkActivated: function ( link ) {
if ( !__inputUtils.openLink( root._fieldHomePath, link.toString( ) ) )
{
__notificationModel.addError( "Could not open the file. It may not exist, could be invalid, or there might be no application available to open it." )
}
}

onTextChanged: root.editorValueChanged( textArea.text, textArea.text === "" )
}

Expand Down
11 changes: 8 additions & 3 deletions cmake_templates/AndroidManifest.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,15 @@
<service android:process=":trackingThread" android:name=".PositionTrackingService" android:foregroundServiceType="location" android:stopWithTask="true"/>
</application>

<!-- Explicitly mention that we need to use external app for capturing an image -->
<!-- Explicitly mention that we need to use external app for capturing an image and open file-->
<queries>
<intent>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>
</manifest>
Loading