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 === "" )
}