Skip to content

Commit

Permalink
PDF Viewing Feature (#3353)
Browse files Browse the repository at this point in the history
* android PDF file viewer and openLink function working
* desktop file viewer
* ios pdf viewing
* form rich text opening pdfs enabled
  • Loading branch information
VitorVieiraZ authored May 23, 2024
1 parent e952599 commit 38fe87a
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 8 deletions.
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>

1 comment on commit 38fe87a

@inputapp-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iOS - version 24.5.623311 just submitted!

Please sign in to comment.