MOBILE-3418 ios: Use WKWebView in InAppBrowser

main
Dani Palou 2020-05-11 08:50:55 +02:00
parent 81cb1e2bdf
commit 35236772d3
3 changed files with 187 additions and 160 deletions

View File

@ -1,37 +1,24 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<widget id="com.moodle.moodlemobile" <widget android-versionCode="38200" id="com.moodle.moodlemobile" ios-CFBundleVersion="3.8.2.0" version="3.8.2" versionCode="38200" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
version="3.8.2"
xmlns="http://www.w3.org/ns/widgets"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:cdv="http://cordova.apache.org/ns/1.0"
versionCode = "38200"
android-versionCode = "38200"
ios-CFBundleVersion = "3.8.2.0">
<name>Moodle</name> <name>Moodle</name>
<description>Moodle official app</description> <description>Moodle official app</description>
<author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author> <author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author>
<content src="index.html" /> <content src="index.html" />
<access origin="*" /> <access origin="*" />
<access launch-external="yes" origin="tel:*" /> <access launch-external="yes" origin="tel:*" />
<access launch-external="yes" origin="mailto:*" /> <access launch-external="yes" origin="mailto:*" />
<access launch-external="yes" origin="geo:*" /> <access launch-external="yes" origin="geo:*" />
<allow-navigation href="moodleappfs:*" /> <allow-navigation href="moodleappfs:*" />
<allow-navigation href="cdvfile:*" /> <allow-navigation href="cdvfile:*" />
<allow-navigation href="content:*" /> <allow-navigation href="content:*" />
<allow-navigation href="data:*" /> <allow-navigation href="data:*" />
<allow-navigation href="*" /> <allow-navigation href="*" />
<allow-intent href="*" /> <allow-intent href="*" />
<allow-intent href="tel:*" /> <allow-intent href="tel:*" />
<allow-intent href="sms:*" /> <allow-intent href="sms:*" />
<allow-intent href="mailto:*" /> <allow-intent href="mailto:*" />
<allow-intent href="geo:*" /> <allow-intent href="geo:*" />
<preference name="permissions" value="none" />
<!-- See MOBILE-892 -->
<preference name="permissions" value="none"/>
<preference name="orientation" value="default" /> <preference name="orientation" value="default" />
<preference name="target-device" value="universal" /> <preference name="target-device" value="universal" />
<preference name="fullscreen" value="false" /> <preference name="fullscreen" value="false" />
@ -51,21 +38,17 @@
<preference name="SplashScreenDelay" value="15000" /> <preference name="SplashScreenDelay" value="15000" />
<preference name="SplashMaintainAspectRatio" value="true" /> <preference name="SplashMaintainAspectRatio" value="true" />
<preference name="SplashShowOnlyFirstTime" value="false" /> <preference name="SplashShowOnlyFirstTime" value="false" />
<preference name="android-minSdkVersion" value="19" /> <preference name="android-minSdkVersion" value="19" />
<preference name="android-targetSdkVersion" value="29" /> <preference name="android-targetSdkVersion" value="29" />
<preference name="AndroidPersistentFileLocation" value="Compatibility" /> <preference name="AndroidPersistentFileLocation" value="Compatibility" />
<preference name="CustomURLSchemePluginClearsAndroidIntent" value="true" /> <preference name="CustomURLSchemePluginClearsAndroidIntent" value="true" />
<preference name="iosPersistentFileLocation" value="Compatibility" /> <preference name="iosPersistentFileLocation" value="Compatibility" />
<preference name="Scheme" value="moodleappfs" /> <preference name="Scheme" value="moodleappfs" />
<preference name="iosScheme" value="moodleappfs" /> <preference name="iosScheme" value="moodleappfs" />
<preference name="WKWebViewOnly" value="true" />
<feature name="StatusBar"> <feature name="StatusBar">
<param name="ios-package" onload="true" value="CDVStatusBar" /> <param name="ios-package" onload="true" value="CDVStatusBar" />
</feature> </feature>
<platform name="android"> <platform name="android">
<resource-file src="MainActivity.java" target="app/src/main/java/com/moodle/moodlemobile/MainActivity.java" /> <resource-file src="MainActivity.java" target="app/src/main/java/com/moodle/moodlemobile/MainActivity.java" />
<resource-file src="google-services.json" target="app/google-services.json" /> <resource-file src="google-services.json" target="app/google-services.json" />
@ -73,11 +56,6 @@
<resource-file src="resources/android/icon/drawable-mdpi-smallicon.png" target="app/src/main/res/mipmap-mdpi/smallicon.png" /> <resource-file src="resources/android/icon/drawable-mdpi-smallicon.png" target="app/src/main/res/mipmap-mdpi/smallicon.png" />
<resource-file src="resources/android/icon/drawable-hdpi-smallicon.png" target="app/src/main/res/mipmap-hdpi/smallicon.png" /> <resource-file src="resources/android/icon/drawable-hdpi-smallicon.png" target="app/src/main/res/mipmap-hdpi/smallicon.png" />
<resource-file src="resources/android/icon/drawable-xhdpi-smallicon.png" target="app/src/main/res/mipmap-xhdpi/smallicon.png" /> <resource-file src="resources/android/icon/drawable-xhdpi-smallicon.png" target="app/src/main/res/mipmap-xhdpi/smallicon.png" />
<!--
This should be added by the file-opener2 plugin, but it's not working at the moment. This can be removed
once the following issue is resolved and the plugin is updated to include the fix:
https://github.com/pwlin/cordova-plugin-file-opener2/issues/246
-->
<config-file parent="/manifest/application" target="AndroidManifest.xml"> <config-file parent="/manifest/application" target="AndroidManifest.xml">
<provider android:authorities="${applicationId}.opener.provider" android:exported="false" android:grantUriPermissions="true" android:name="io.github.pwlin.cordova.plugins.fileopener2.FileProvider"> <provider android:authorities="${applicationId}.opener.provider" android:exported="false" android:grantUriPermissions="true" android:name="io.github.pwlin.cordova.plugins.fileopener2.FileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/opener_paths" /> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/opener_paths" />
@ -89,15 +67,13 @@
<edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application"> <edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application">
<application android:usesCleartextTraffic="true" /> <application android:usesCleartextTraffic="true" />
</edit-config> </edit-config>
<!-- Make gps location optional, otherwise apps that don't support it won't be able to install the app. -->
<plugin name="cordova-plugin-geolocation" spec="4.0.2"> <plugin name="cordova-plugin-geolocation" spec="4.0.2">
<variable name="GEOLOCATION_USAGE_DESCRIPTION" value="We need your location so you can attach it as part of your submissions." /> <variable name="GEOLOCATION_USAGE_DESCRIPTION" value="We need your location so you can attach it as part of your submissions." />
<edit-config file="AndroidManifest.xml" mode="overwrite" target="/manifest/uses-feature[@android:name='android.hardware.location.gps']"> <edit-config file="AndroidManifest.xml" mode="overwrite" target="/manifest/uses-feature[@android:name='android.hardware.location.gps']">
<uses-feature android:name="android.hardware.location.gps" android:required="false" /> <uses-feature android:name="android.hardware.location.gps" android:required="false" />
</edit-config> </edit-config>
</plugin> </plugin>
<!-- Disable firebase analytics. --> <config-file parent="/manifest/application" target="AndroidManifest.xml">
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" /> <meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
</config-file> </config-file>
</platform> </platform>
@ -109,23 +85,22 @@
<edit-config file="*-Info.plist" mode="merge" target="NSLocationAlwaysUsageDescription"> <edit-config file="*-Info.plist" mode="merge" target="NSLocationAlwaysUsageDescription">
<string>We need your location so you can attach it as part of your submissions.</string> <string>We need your location so you can attach it as part of your submissions.</string>
</edit-config> </edit-config>
<edit-config target="NSCameraUsageDescription" file="*-Info.plist" mode="merge"> <edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription">
<string>We need camera access to take pictures so you can attach them as part of your submissions.</string> <string>We need camera access to take pictures so you can attach them as part of your submissions.</string>
</edit-config> </edit-config>
<edit-config target="NSMicrophoneUsageDescription" file="*-Info.plist" mode="merge"> <edit-config file="*-Info.plist" mode="merge" target="NSMicrophoneUsageDescription">
<string>We need microphone access to record sounds so you can attach them as part of your submissions.</string> <string>We need microphone access to record sounds so you can attach them as part of your submissions.</string>
</edit-config> </edit-config>
<edit-config target="NSPhotoLibraryUsageDescription" file="*-Info.plist" mode="merge"> <edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription">
<string>We need photo library access to get pictures from there so you can attach them as part of your submissions.</string> <string>We need photo library access to get pictures from there so you can attach them as part of your submissions.</string>
</edit-config> </edit-config>
<edit-config target="UISupportsDocumentBrowser" file="*-Info.plist" mode="merge"> <edit-config file="*-Info.plist" mode="merge" target="UISupportsDocumentBrowser">
<true/> <true />
</edit-config> </edit-config>
<edit-config target="CFBundleShortVersionString" file="*-Info.plist" mode="merge"> <edit-config file="*-Info.plist" mode="merge" target="CFBundleShortVersionString">
<string>3.8.2</string> <string>3.8.2</string>
</edit-config> </edit-config>
<config-file parent="FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED" target="*-Info.plist">
<config-file target="*-Info.plist" parent="FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED">
<string>YES</string> <string>YES</string>
</config-file> </config-file>
<config-file parent="UISupportedInterfaceOrientations" target="*-Info.plist"> <config-file parent="UISupportedInterfaceOrientations" target="*-Info.plist">

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreFileProvider } from '@providers/file'; import { CoreFileProvider } from '@providers/file';
@ -41,7 +41,8 @@ export class AddonModLtiProvider {
private utils: CoreUtilsProvider, private utils: CoreUtilsProvider,
private translate: TranslateService, private translate: TranslateService,
private appProvider: CoreAppProvider, private appProvider: CoreAppProvider,
private logHelper: CoreCourseLogHelperProvider) {} private logHelper: CoreCourseLogHelperProvider,
protected zone: NgZone) {}
/** /**
* Delete launcher. * Delete launcher.
@ -59,38 +60,61 @@ export class AddonModLtiProvider {
* @param params Launch params. * @param params Launch params.
* @return Promise resolved with the file URL. * @return Promise resolved with the file URL.
*/ */
generateLauncher(url: string, params: AddonModLtiParam[]): Promise<string> { async generateLauncher(url: string, params: AddonModLtiParam[]): Promise<string> {
if (!this.fileProvider.isAvailable()) { if (!this.fileProvider.isAvailable()) {
return Promise.resolve(url); return url;
} }
// Generate a form with the params. // Generate an empty page with the JS code.
let text = '<form action="' + url + '" name="ltiLaunchForm" ' + const text = '<script type="text/javascript"> \n' +
'method="post" encType="application/x-www-form-urlencoded">\n';
params.forEach((p) => {
if (p.name == 'ext_submit') {
text += ' <input type="submit"';
} else {
text += ' <input type="hidden" name="' + this.textUtils.escapeHTML(p.name) + '"';
}
text += ' value="' + this.textUtils.escapeHTML(p.value) + '"/>\n';
});
text += '</form>\n';
// Add an in-line script to automatically submit the form.
text += '<script type="text/javascript"> \n' +
' window.onload = function() { \n' + ' window.onload = function() { \n' +
' document.ltiLaunchForm.submit(); \n' + this.getLaunchJSCode(url, params) +
' }; \n' + ' }; \n' +
'</script> \n'; '</script> \n';
return this.fileProvider.writeFile(this.LAUNCHER_FILE_NAME, text).then((entry) => { const entry = await this.fileProvider.writeFile(this.LAUNCHER_FILE_NAME, text);
if (this.appProvider.isDesktop()) { if (this.appProvider.isDesktop()) {
return entry.toInternalURL(); return entry.toInternalURL();
} else { } else {
return entry.toURL(); return entry.toURL();
} }
}
/**
* Get the Javascript code to launch the LTI tool.
*
* @param url Launch URL.
* @param params Launch params.
* @return Javascript code.
*/
getLaunchJSCode(url: string, params: AddonModLtiParam[]): string {
// Create the form.
let jsCode = 'var form = document.createElement("form");\n' +
'form.method = "post";\n' +
'form.setAttribute("encType", "application/x-www-form-urlencoded");\n' +
`form.setAttribute("action", "${url}");\n`;
// Create the inputs based on the params.
params.forEach((p) => {
jsCode += 'var input = document.createElement("input");\n';
if (p.name == 'ext_submit') {
jsCode += 'input.type = "submit";\n';
} else {
jsCode += 'input.type = "hidden";\n' +
'input.name = "' + this.textUtils.escapeHTML(p.name) + '";\n';
}
jsCode += 'input.value = "' + this.textUtils.escapeHTML(p.value) + '";\n' +
'form.appendChild(input);\n';
}); });
// Add the form to the document and submit it.
jsCode += 'document.body.appendChild(form);\n' +
'form.submit();\n';
return jsCode;
} }
/** /**
@ -200,20 +224,47 @@ export class AddonModLtiProvider {
* @param params Launch params. * @param params Launch params.
* @return Promise resolved when the WS call is successful. * @return Promise resolved when the WS call is successful.
*/ */
launch(url: string, params: AddonModLtiParam[]): Promise<any> { async launch(url: string, params: AddonModLtiParam[]): Promise<void> {
if (!this.urlUtils.isHttpURL(url)) { if (!this.urlUtils.isHttpURL(url)) {
return Promise.reject(this.translate.instant('addon.mod_lti.errorinvalidlaunchurl')); throw this.translate.instant('addon.mod_lti.errorinvalidlaunchurl');
} }
// Generate launcher and open it.
return this.generateLauncher(url, params).then((url) => {
if (this.appProvider.isMobile()) { if (this.appProvider.isMobile()) {
this.utils.openInApp(url); // Open it in InAppBrowser. Use JS code because IAB has a bug in iOS when opening local files.
} else { const jsCode = this.getLaunchJSCode(url, params);
// In desktop open in browser, we found some cases where inapp caused JS issues.
this.utils.openInBrowser(url); const iabInstance = this.utils.openInApp('about:blank');
// Execute the JS code when the page is loaded.
let codeExecuted = false;
const executeCode = (): void => {
if (codeExecuted) {
return;
} }
codeExecuted = true;
loadStopSubscription && loadStopSubscription.unsubscribe();
// Execute the callback in the Angular zone, so change detection doesn't stop working.
this.zone.run(() => {
iabInstance.executeScript({code: jsCode});
}); });
};
const loadStopSubscription = iabInstance.on('loadstop').subscribe((event) => {
executeCode();
});
// If loadstop hasn't triggered after 1 second, execute the code anyway.
setTimeout(() => {
executeCode();
}, 1000);
} else {
// Generate launched and open it in system browser, we found some cases where inapp caused JS issues.
const launcherUrl = await this.generateLauncher(url, params);
this.utils.openInBrowser(launcherUrl);
}
} }
/** /**

View File

@ -921,6 +921,7 @@ export class CoreUtilsProvider {
} }
options = options || {}; options = options || {};
options.usewkwebview = 'yes'; // Force WKWebView in iOS.
if (!options.enableViewPortScale) { if (!options.enableViewPortScale) {
options.enableViewPortScale = 'yes'; // Enable zoom on iOS. options.enableViewPortScale = 'yes'; // Enable zoom on iOS.