From b74b1fe0c9fd264db04df4e2c240bd0337643c90 Mon Sep 17 00:00:00 2001 From: Vitor Vieira <155513369+VitorVieiraZ@users.noreply.github.com> Date: Mon, 8 Apr 2024 07:04:11 -0300 Subject: [PATCH] Android PDF file viewer and openLink function working (#3268) --- app/android/res/xml/file_paths.xml | 3 ++ .../uk/co/lutraconsulting/InputActivity.java | 43 +++++++++++++++++++ app/androidutils.cpp | 9 ++++ app/androidutils.h | 1 + app/inpututils.cpp | 35 ++++++++++++++- app/inpututils.h | 7 +++ .../editors/MMFormTextMultilineEditor.qml | 2 +- 7 files changed, 98 insertions(+), 2 deletions(-) diff --git a/app/android/res/xml/file_paths.xml b/app/android/res/xml/file_paths.xml index 7829eff2ee..b4950a8d7f 100644 --- a/app/android/res/xml/file_paths.xml +++ b/app/android/res/xml/file_paths.xml @@ -1,4 +1,7 @@ + diff --git a/app/android/src/uk/co/lutraconsulting/InputActivity.java b/app/android/src/uk/co/lutraconsulting/InputActivity.java index cbe0204c84..54a900cbde 100644 --- a/app/android/src/uk/co/lutraconsulting/InputActivity.java +++ b/app/android/src/uk/co/lutraconsulting/InputActivity.java @@ -28,6 +28,13 @@ import android.graphics.Insets; import android.graphics.Color; +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; @@ -115,6 +122,42 @@ public void hideSplashScreen() keepSplashScreenVisible = false; } + public void openFile( String filePath ) { + Log.d( TAG, "Expected file path: " + filePath ); + + File file = new File( filePath ); + + if ( !file.exists() ) { + Log.d( TAG, "File does not exist: " + filePath ); + runOnUiThread( () -> Toast.makeText( getApplicationContext(), "File not available", Toast.LENGTH_SHORT ).show() ); + return; + } else { + Log.d( TAG, "File exists: " + filePath ); + } + + Intent showFileIntent = new Intent( Intent.ACTION_VIEW ); + + try { + Uri fileUri = FileProvider.getUriForFile( this, "uk.co.lutraconsulting.fileprovider", file ); + Log.d( TAG, "File URI: " + fileUri.toString() ); + + 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 ) { + Log.d( TAG, "FileProvider URI issue", e ); + return; + } + + if ( showFileIntent.resolveActivity( getPackageManager() ) != null ) { + startActivity( showFileIntent ); + } else { + runOnUiThread( () -> Toast.makeText( getApplicationContext(), "No application for opening this file", Toast.LENGTH_SHORT ).show() ); + } + } + public void quitGracefully() { String man = android.os.Build.MANUFACTURER.toUpperCase(); diff --git a/app/androidutils.cpp b/app/androidutils.cpp index 60b100f8a8..5bef4db8f6 100644 --- a/app/androidutils.cpp +++ b/app/androidutils.cpp @@ -207,6 +207,15 @@ void AndroidUtils::hideSplashScreen() #endif } +void AndroidUtils::openFile( const QString &filePath ) +{ +#ifdef ANDROID + auto activity = QJniObject( QNativeInterface::QAndroidApplication::context() ); + QJniObject jFilePath = QJniObject::fromString( filePath ); + activity.callMethod( "openFile", "(Ljava/lang/String;)V", jFilePath.object() ); +#endif +} + bool AndroidUtils::requestStoragePermission() { #ifdef ANDROID diff --git a/app/androidutils.h b/app/androidutils.h index 1dd340f621..5c8eda416d 100644 --- a/app/androidutils.h +++ b/app/androidutils.h @@ -67,6 +67,7 @@ class AndroidUtils: public QObject */ Q_INVOKABLE void callImagePicker( const QString &code = "" ); Q_INVOKABLE void callCamera( const QString &targetPath, const QString &code = "" ); + Q_INVOKABLE void openFile( const QString &filePath ); #ifdef ANDROID const static int MEDIA_CODE = 101; diff --git a/app/inpututils.cpp b/app/inpututils.cpp index c6e26183fd..7438acf658 100644 --- a/app/inpututils.cpp +++ b/app/inpututils.cpp @@ -56,7 +56,7 @@ #include #include #include -#include +#include #include #include #include @@ -2153,3 +2153,36 @@ QList InputUtils::parsePositionUpdates( const QString &data ) return parsedUpdates; } + +void InputUtils::openLink( const QString &homePath, const QString &link ) +{ + qDebug() << "LINK" << link; + qDebug() << "HOMEPATH" << homePath; + + QString cleanedLink = link.trimmed(); + static QRegularExpression re( "^\\?|\\?$" ); + cleanedLink.remove( re ); + + qDebug() << "cleanedLink" << cleanedLink; + + if ( cleanedLink.startsWith( "project://" ) ) + { + QString relativePath = cleanedLink.mid( QString( "project://" ).length() ); + QString absoluteLinkPath = homePath + QDir::separator() + relativePath; + + qDebug() << "relativePath" << relativePath; + qDebug() << "absoluteLinkPath" << absoluteLinkPath; + +#ifdef Q_OS_ANDROID + qDebug() << "openLink android"; + mAndroidUtils->openFile( absoluteLinkPath ); +#elif defined(Q_OS_IOS) + qDebug() << "openLink ios"; +#endif + } + else + { + cleanedLink.chop( 1 ); //remove \ from cleanedLink + QDesktopServices::openUrl( QUrl( cleanedLink ) ); + } +} diff --git a/app/inpututils.h b/app/inpututils.h index 78c2c80f79..9bd9b229fd 100644 --- a/app/inpututils.h +++ b/app/inpututils.h @@ -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 void openLink( const QString &homePath, const QString &link ); + Q_INVOKABLE bool acquireCameraPermission(); Q_INVOKABLE bool isBluetoothTurnedOn(); diff --git a/app/qml/form/editors/MMFormTextMultilineEditor.qml b/app/qml/form/editors/MMFormTextMultilineEditor.qml index 985fdf9ec2..00ea32f528 100644 --- a/app/qml/form/editors/MMFormTextMultilineEditor.qml +++ b/app/qml/form/editors/MMFormTextMultilineEditor.qml @@ -117,7 +117,7 @@ MMPrivateComponents.MMBaseInput { radius: __style.radius12 } - onLinkActivated: ( link ) => Qt.openUrlExternally( link ) + onLinkActivated: ( link ) => __inputUtils.openLink( root._fieldHomePath, link.toString() ) onTextChanged: root.editorValueChanged( textArea.text, textArea.text === "" ) }