MOBILE-3418 ios: Use WKWebView in InAppBrowser
parent
81cb1e2bdf
commit
35236772d3
43
config.xml
43
config.xml
|
@ -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:*" />
|
||||||
|
|
||||||
<!-- See MOBILE-892 -->
|
|
||||||
<preference name="permissions" value="none" />
|
<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">
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue