+
+
+
+
+
+
diff --git a/src/core/viewer/pages/textarea/textarea.module.ts b/src/core/viewer/pages/textarea/textarea.module.ts
new file mode 100644
index 000000000..a17d8cc8e
--- /dev/null
+++ b/src/core/viewer/pages/textarea/textarea.module.ts
@@ -0,0 +1,36 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreViewerTextAreaPage } from './textarea';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+/**
+ * Module to lazy load the page.
+ */
+@NgModule({
+ declarations: [
+ CoreViewerTextAreaPage
+ ],
+ imports: [
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ IonicPageModule.forChild(CoreViewerTextAreaPage),
+ TranslateModule.forChild()
+ ]
+})
+export class CoreViewerTextAreaPageModule {}
diff --git a/src/core/viewer/pages/textarea/textarea.scss b/src/core/viewer/pages/textarea/textarea.scss
new file mode 100644
index 000000000..27a2ea061
--- /dev/null
+++ b/src/core/viewer/pages/textarea/textarea.scss
@@ -0,0 +1,187 @@
+$core-modal-promt-min-width: 320px;
+
+ion-app.app-root ion-modal.core-modal-prompt {
+ /* Some styles have been copied from ionic alert component. */
+ @include position(0, 0, 0, 0);
+ position: absolute;
+ z-index: $z-index-overlay;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ contain: strict;
+
+ ion-backdrop {
+ visibility: visible;
+ }
+
+ .header {
+ &::after {
+ background: none;
+ }
+ .toolbar-background {
+ display: none;
+ }
+ }
+
+ .prompt-button-group {
+ display: flex;
+ flex-direction: row;
+
+ .prompt-button {
+ @include margin(0);
+
+ z-index: 0;
+ display: block;
+
+ font-size: $alert-button-font-size;
+ line-height: $alert-button-line-height;
+ }
+ }
+
+ ion-textarea {
+ @include placeholder($alert-input-placeholder-color);
+ @include padding($alert-md-message-padding-top, $alert-md-message-padding-end, $alert-md-message-padding-bottom, $alert-md-message-padding-start);
+ border: 0;
+ background: inherit;
+
+ textarea {
+ margin: 0;
+ }
+ }
+
+ .prompt-message {
+ @include deprecated-variable(padding, $alert-md-message-padding) {
+ @include padding($alert-md-message-padding-top, $alert-md-message-padding-end, $alert-md-message-padding-bottom, $alert-md-message-padding-start);
+ }
+ }
+
+ .modal-wrapper {
+ z-index: $z-index-overlay-wrapper;
+ display: flex;
+ flex-direction: column;
+ min-width: $core-modal-promt-min-width;
+ max-height: $alert-max-height;
+ opacity: 0;
+ contain: content;
+ height: auto;
+
+ page-core-viewer-textarea,
+ ion-content,
+ .fixed-content,
+ .scroll-content {
+ position: relative;
+ background: $white;
+ overflow: hidden;
+ }
+ .fixed-content {
+ display: none;
+ }
+ .scroll-content {
+ padding: 0 !important;
+ }
+ }
+
+ .content-md .prompt-button-group {
+ flex-wrap: $alert-md-button-group-flex-wrap;
+ justify-content: $alert-md-button-group-justify-content;
+
+ @include deprecated-variable(padding, $alert-md-button-group-padding) {
+ @include padding($alert-md-button-group-padding-top, $alert-md-button-group-padding-end, $alert-md-button-group-padding-bottom, $alert-md-button-group-padding-start);
+ }
+
+ .prompt-button {
+ @include text-align($alert-md-button-text-align);
+ @include border-radius($alert-md-button-border-radius);
+
+ // necessary for ripple to work properly
+ position: relative;
+ overflow: hidden;
+
+ font-weight: $alert-md-button-font-weight;
+ text-transform: $alert-md-button-text-transform;
+ color: $alert-md-button-text-color;
+ background-color: $alert-md-button-background-color;
+
+ @include deprecated-variable(margin, $alert-md-button-margin) {
+ @include margin($alert-md-button-margin-top, $alert-md-button-margin-end, $alert-md-button-margin-bottom, $alert-md-button-margin-start);
+ }
+
+ @include deprecated-variable(padding, $alert-md-button-padding) {
+ @include padding($alert-md-button-padding-top, $alert-md-button-padding-end, $alert-md-button-padding-bottom, $alert-md-button-padding-start);
+ }
+ }
+
+ .prompt-button.activated {
+ background-color: $alert-md-button-background-color-activated;
+ }
+
+ .prompt-button .button-inner {
+ justify-content: $alert-md-button-group-justify-content;
+ }
+ }
+
+ .content-ios .prompt-button-group {
+ @include margin-horizontal(null, -$alert-ios-button-border-width);
+
+ flex-wrap: $alert-ios-button-group-flex-wrap;
+ .prompt-button {
+ @include margin($alert-ios-button-margin);
+ @include border-radius($alert-ios-button-border-radius);
+
+ overflow: hidden;
+
+ flex: $alert-ios-button-flex;
+
+ min-width: $alert-ios-button-min-width;
+ height: $alert-ios-button-min-height;
+
+ border-top: $alert-ios-button-border-width $alert-ios-button-border-style $alert-ios-button-border-color;
+ border-right: $alert-ios-button-border-width $alert-ios-button-border-style $alert-ios-button-border-color;
+ font-size: $alert-ios-button-font-size;
+ color: $alert-ios-button-text-color;
+ background-color: $alert-ios-button-background-color;
+ }
+
+ .prompt-button:last-child {
+ border-right: 0;
+ font-weight: $alert-ios-button-main-font-weight;
+ }
+
+ .prompt-button.activated {
+ background-color: $alert-ios-button-background-color-activated;
+ }
+ }
+}
+
+ion-app.app-root-md ion-modal.core-modal-prompt {
+ .modal-wrapper {
+ @include border-radius($alert-md-border-radius);
+ max-width: $alert-md-max-width;
+ background-color: $alert-md-background-color;
+ box-shadow: $alert-md-box-shadow;
+ }
+
+ .toolbar-content .toolbar-title {
+ color: $alert-md-message-text-color;
+ white-space: normal;
+ }
+}
+
+ion-app.app-root-ios ion-modal.core-modal-prompt {
+ .modal-wrapper {
+ @include border-radius($alert-ios-border-radius);
+ overflow: hidden;
+ max-width: $alert-ios-max-width;
+ background-color: $alert-ios-background;
+ box-shadow: $alert-ios-box-shadow;
+ }
+
+ .toolbar-content .toolbar-title {
+ color: $alert-ios-message-text-color;
+ white-space: normal;
+ }
+
+ ion-title {
+ padding: 0;
+ }
+}
\ No newline at end of file
diff --git a/src/core/viewer/pages/textarea/textarea.ts b/src/core/viewer/pages/textarea/textarea.ts
new file mode 100644
index 000000000..e01e418be
--- /dev/null
+++ b/src/core/viewer/pages/textarea/textarea.ts
@@ -0,0 +1,71 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component } from '@angular/core';
+import { IonicPage, ViewController, NavParams, AlertButton } from 'ionic-angular';
+
+/**
+ * Page to render a textarea prompt.
+ */
+@IonicPage({ segment: 'core-viewer-textarea' })
+@Component({
+ selector: 'page-core-viewer-textarea',
+ templateUrl: 'textarea.html',
+})
+export class CoreViewerTextAreaPage {
+ title: string;
+ message: string;
+ placeholder: string;
+ buttons: AlertButton[];
+ text = '';
+
+ constructor(
+ protected viewCtrl: ViewController,
+ params: NavParams,
+ ) {
+ this.title = params.get('title');
+ this.message = params.get('message');
+ this.placeholder = params.get('placeholder') || '';
+
+ const buttons = params.get('buttons');
+
+ this.buttons = buttons.map((button) => {
+ if (typeof button === 'string') {
+ return { text: button };
+ }
+
+ return button;
+ });
+ }
+
+ /**
+ * Button clicked.
+ *
+ * @param button: Clicked button.
+ */
+ buttonClicked(button: AlertButton): void {
+ let shouldDismiss = true;
+ if (button.handler) {
+ // A handler has been provided, execute it pass the handler the values from the inputs
+ if (button.handler(this.text) === false) {
+ // If the return value of the handler is false then do not dismiss
+ shouldDismiss = false;
+ }
+ }
+
+ if (shouldDismiss) {
+ this.viewCtrl.dismiss(button.role);
+ }
+ }
+}
diff --git a/src/providers/app.ts b/src/providers/app.ts
index 4d29185e5..8f0625765 100644
--- a/src/providers/app.ts
+++ b/src/providers/app.ts
@@ -50,6 +50,51 @@ export interface CoreRedirectData {
timemodified?: number;
}
+/**
+ * Store config data.
+ */
+export interface CoreStoreConfig {
+ /**
+ * ID of the Apple store where the desktop Mac app is uploaded.
+ */
+ mac?: string;
+
+ /**
+ * ID of the Windows store where the desktop Windows app is uploaded.
+ */
+ windows?: string;
+
+ /**
+ * Url with the desktop linux download link.
+ */
+ linux?: string;
+
+ /**
+ * Fallback URL when the desktop options is not set.
+ */
+ desktop?: string;
+
+ /**
+ * ID of the Apple store where the mobile iOS app is uploaded.
+ */
+ ios?: string;
+
+ /**
+ * ID of the Google play store where the android app is uploaded.
+ */
+ android?: string;
+
+ /**
+ * Fallback URL when the mobile options is not set.
+ */
+ mobile?: string;
+
+ /**
+ * Fallback URL when the other fallbacks options are not set.
+ */
+ default?: string;
+}
+
/**
* App DB schema and migration function.
*/
@@ -255,6 +300,44 @@ export class CoreAppProvider {
return this.appCtrl.getRootNavs()[0];
}
+ /**
+ * Get app store URL.
+ *
+ * @param storesConfig Config params to send the user to the right place.
+ * @return Store URL.
+ */
+ getAppStoreUrl(storesConfig: CoreStoreConfig): string {
+ if (this.isMac() && storesConfig.mac) {
+ return 'itms-apps://itunes.apple.com/app/' + storesConfig.mac;
+ }
+
+ if (this.isWindows() && storesConfig.windows) {
+ return 'https://www.microsoft.com/p/' + storesConfig.windows;
+ }
+
+ if (this.isLinux() && storesConfig.linux) {
+ return storesConfig.linux;
+ }
+
+ if (this.isDesktop() && storesConfig.desktop) {
+ return storesConfig.desktop;
+ }
+
+ if (this.isIOS() && storesConfig.ios) {
+ return 'itms-apps://itunes.apple.com/app/' + storesConfig.ios;
+ }
+
+ if (this.isAndroid() && storesConfig.android) {
+ return 'market://details?id=' + storesConfig.android;
+ }
+
+ if (this.isMobile() && storesConfig.mobile) {
+ return storesConfig.mobile;
+ }
+
+ return storesConfig.default || null;
+ }
+
/**
* Returns whether the user agent is controlled by automation. I.e. Behat testing.
*
diff --git a/src/providers/events.ts b/src/providers/events.ts
index a5e6e3bef..81493e9f6 100644
--- a/src/providers/events.ts
+++ b/src/providers/events.ts
@@ -67,6 +67,7 @@ export class CoreEventsProvider {
static WS_CACHE_INVALIDATED = 'ws_cache_invalidated';
static SITE_STORAGE_DELETED = 'site_storage_deleted';
static FORM_ACTION = 'form_action';
+ static ACTIVITY_DATA_SENT = 'activity_data_sent';
protected logger;
protected observables: { [s: string]: Subject