diff --git a/local_moodleappbehat/tests/behat/behat_app.php b/local_moodleappbehat/tests/behat/behat_app.php
index 20c957c2b2f..95d456154f2 100644
--- a/local_moodleappbehat/tests/behat/behat_app.php
+++ b/local_moodleappbehat/tests/behat/behat_app.php
@@ -1179,15 +1179,16 @@ public function i_switch_offline_mode(string $offline) {
public function i_switch_network_connection(string $mode) {
switch ($mode) {
case 'wifi':
- $this->runtime_js("network.setForceConnectionMode('$mode');");
+ $this->runtime_js("network.setForceConnectionMode('wifi');");
break;
case 'cellular':
- $this->runtime_js("network.setForceConnectionMode('$mode');");
+ $this->runtime_js("network.setForceConnectionMode('cellular');");
break;
case 'offline':
- $this->runtime_js("network.setForceConnectionMode('none');");
+ $this->runtime_js("network.setForceConnectionMode('offline');");
break;
default:
+ $this->runtime_js("network.setForceConnectionMode('unknown');");
break;
}
}
diff --git a/src/addons/blog/pages/index/index.ts b/src/addons/blog/pages/index/index.ts
index 293823fec74..f9f392a66d4 100644
--- a/src/addons/blog/pages/index/index.ts
+++ b/src/addons/blog/pages/index/index.ts
@@ -41,7 +41,6 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTime } from '@singletons/time';
import { CorePopovers } from '@services/overlays/popovers';
import { CoreLoadings } from '@services/overlays/loadings';
-import { Subscription } from 'rxjs';
import { CoreAlerts } from '@services/overlays/alerts';
import { Translate } from '@singletons';
import { CoreCommentsCommentsComponent } from '@features/comments/components/comments/comments';
@@ -89,10 +88,9 @@ export default class AddonBlogIndexPage implements OnInit, OnDestroy {
contextInstanceId = 0;
entryUpdateObserver: CoreEventObserver;
syncObserver: CoreEventObserver;
- onlineObserver: Subscription;
optionsAvailable = false;
readonly hasOfflineDataToSync = signal(false);
- readonly isOnline = signal(false);
+ readonly isOnline = CoreNetwork.onlineSignal();
siteId: string;
syncIcon = CoreConstants.ICON_SYNC;
readonly syncHidden = computed(() => !this.loaded() || !this.isOnline() || !this.hasOfflineDataToSync());
@@ -101,7 +99,6 @@ export default class AddonBlogIndexPage implements OnInit, OnDestroy {
this.currentUserId = CoreSites.getCurrentSiteUserId();
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
this.siteId = CoreSites.getCurrentSiteId();
- this.isOnline.set(CoreNetwork.isOnline());
this.logView = CoreTime.once(async () => {
await CorePromiseUtils.ignoreErrors(AddonBlog.logView(this.filter));
@@ -137,11 +134,6 @@ export default class AddonBlogIndexPage implements OnInit, OnDestroy {
await CorePromiseUtils.ignoreErrors(this.refresh(false));
this.loaded.set(true);
});
-
- // Refresh online status when changes.
- this.onlineObserver = CoreNetwork.onChange().subscribe(async () => {
- this.isOnline.set(CoreNetwork.isOnline());
- });
}
/**
@@ -516,7 +508,6 @@ export default class AddonBlogIndexPage implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.entryUpdateObserver.off();
this.syncObserver.off();
- this.onlineObserver.unsubscribe();
}
}
diff --git a/src/addons/mod/page/tests/behat/basic_usage.feature b/src/addons/mod/page/tests/behat/basic_usage.feature
index c9c8b85d69c..a44160b5ca2 100644
--- a/src/addons/mod/page/tests/behat/basic_usage.feature
+++ b/src/addons/mod/page/tests/behat/basic_usage.feature
@@ -24,7 +24,7 @@ Feature: Test basic usage of page activity in app
| name | activity | activityname | course |
| \mod_page\event\course_module_viewed | page | Test page title | Course 1 |
- Scenario: Prefecth page
+ Scenario: Prefetch page
Given I entered the course "Course 1" as "student1" in the app
When I press "Course downloads" in the app
And I press "Download" within "Test page title" "ion-item" in the app
diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts
index 345eb651cba..eb3309254d7 100644
--- a/src/core/classes/sites/authenticated-site.ts
+++ b/src/core/classes/sites/authenticated-site.ts
@@ -1592,7 +1592,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
let expirationDelay = CoreAuthenticatedSite.UPDATE_FREQUENCIES[updateFrequency] ||
CoreAuthenticatedSite.UPDATE_FREQUENCIES[CoreCacheUpdateFrequency.USUALLY];
- if (CoreNetwork.isNetworkAccessLimited()) {
+ if (CoreNetwork.isCellular()) {
// Not WiFi, increase the expiration delay a 50% to decrease the data usage in this case.
expirationDelay *= 1.5;
}
diff --git a/src/core/directives/external-content.ts b/src/core/directives/external-content.ts
index 6345c16bd55..04d608f272e 100644
--- a/src/core/directives/external-content.ts
+++ b/src/core/directives/external-content.ts
@@ -518,7 +518,8 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
clickableEl.addEventListener(eventName, () => {
// User played media or opened a downloadable link.
// Download the file if in wifi and it hasn't been downloaded already (for big files).
- if (state !== DownloadStatus.DOWNLOADED && state !== DownloadStatus.DOWNLOADING && CoreNetwork.isWifi()) {
+ if (state !== DownloadStatus.DOWNLOADED && state !== DownloadStatus.DOWNLOADING &&
+ CoreNetwork.isWifi()) {
// We aren't using the result, so it doesn't matter which of the 2 functions we call.
CoreFilepool.getUrlByUrl(site.getId(), url, this.component, this.componentId, 0, false);
}
diff --git a/src/core/features/comments/pages/viewer/viewer.ts b/src/core/features/comments/pages/viewer/viewer.ts
index 96448472f83..e502b36de7c 100644
--- a/src/core/features/comments/pages/viewer/viewer.ts
+++ b/src/core/features/comments/pages/viewer/viewer.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, OnDestroy, OnInit, ViewChild, AfterViewInit, inject } from '@angular/core';
+import { Component, OnDestroy, OnInit, ViewChild, AfterViewInit, effect, inject } from '@angular/core';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { ActivatedRoute } from '@angular/router';
import { CoreSites } from '@services/sites';
@@ -49,6 +49,7 @@ import { CoreDom } from '@singletons/dom';
import { CoreSharedModule } from '@/core/shared.module';
import { ADDON_MOD_ASSIGN_COMMENTS_COMPONENT_NAME } from '@addons/mod/assign/submission/comments/constants';
import { CoreCourses } from '@features/courses/services/courses';
+import { CoreKeyboard } from '@singletons/keyboard';
/**
* Page that displays comments.
@@ -93,7 +94,6 @@ export default class CoreCommentsViewerPage implements OnInit, OnDestroy, AfterV
protected addDeleteCommentsAvailable = false;
protected syncObserver?: CoreEventObserver;
protected onlineObserver: Subscription;
- protected keyboardObserver: CoreEventObserver;
protected viewDestroyed = false;
protected scrollBottom = true;
protected scrollElement?: HTMLElement;
@@ -128,9 +128,11 @@ export default class CoreCommentsViewerPage implements OnInit, OnDestroy, AfterV
});
});
- this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, (keyboardHeight: number) => {
- // Force when opening.
- this.scrollToBottom(keyboardHeight > 0);
+ effect(() => {
+ const shown = CoreKeyboard.getKeyboardShownSignal();
+
+ /// Force when opening.
+ this.scrollToBottom(shown());
});
}
@@ -700,7 +702,6 @@ export default class CoreCommentsViewerPage implements OnInit, OnDestroy, AfterV
this.syncObserver?.off();
this.onlineObserver.unsubscribe();
this.viewDestroyed = true;
- this.keyboardObserver.off();
}
}
diff --git a/src/core/features/course/components/module/core-course-module.html b/src/core/features/course/components/module/core-course-module.html
index 9685d51309e..640616f9f55 100644
--- a/src/core/features/course/components/module/core-course-module.html
+++ b/src/core/features/course/components/module/core-course-module.html
@@ -30,9 +30,9 @@
@if (module.visible === 0 || module.uservisible === false) {
}
- @if (prefetchStatusIcon$ | async; as prefetchStatusIcon) {
-
+ @if (prefetchStatusIcon()) {
+
}
diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts
index 4e6b43e1cec..a097a80cf6b 100644
--- a/src/core/features/course/components/module/module.ts
+++ b/src/core/features/course/components/module/module.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, HostBinding } from '@angular/core';
+import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, HostBinding, signal } from '@angular/core';
import { CoreSites } from '@services/sites';
import {
@@ -29,7 +29,6 @@ import {
} from '@features/course/services/module-prefetch-delegate';
import { CoreConstants, DownloadStatus } from '@/core/constants';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
-import { BehaviorSubject } from 'rxjs';
import { toBoolean } from '@/core/transforms/boolean';
import { CoreRemindersDateComponent } from '../../../reminders/components/date/date';
import { CoreCourseModuleCompletionComponent } from '../module-completion/module-completion';
@@ -74,9 +73,9 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
modNameTranslated = '';
hasCompletion = false; // Whether activity has completion to be shown.
showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
- prefetchStatusIcon$ = new BehaviorSubject(''); // Module prefetch status icon.
- prefetchStatusText$ = new BehaviorSubject(''); // Module prefetch status text.
moduleHasView = true;
+ readonly prefetchStatusIcon = signal(''); // Module prefetch status icon.
+ readonly prefetchStatusText = signal(''); // Module prefetch status text.
protected prefetchHandler?: CoreCourseModulePrefetchHandler;
@@ -152,16 +151,16 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
switch (prefetchStatus) {
case DownloadStatus.OUTDATED:
- this.prefetchStatusIcon$.next(CoreConstants.ICON_OUTDATED);
- this.prefetchStatusText$.next('core.outdated');
+ this.prefetchStatusIcon.set(CoreConstants.ICON_OUTDATED);
+ this.prefetchStatusText.set('core.outdated');
break;
case DownloadStatus.DOWNLOADED:
- this.prefetchStatusIcon$.next(CoreConstants.ICON_DOWNLOADED);
- this.prefetchStatusText$.next('core.downloaded');
+ this.prefetchStatusIcon.set(CoreConstants.ICON_DOWNLOADED);
+ this.prefetchStatusText.set('core.downloaded');
break;
default:
- this.prefetchStatusIcon$.next('');
- this.prefetchStatusText$.next('');
+ this.prefetchStatusIcon.set('');
+ this.prefetchStatusText.set('');
break;
}
diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts
index f85f0440c72..6c69ca584a3 100644
--- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts
+++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts
@@ -25,6 +25,7 @@ import {
Output,
EventEmitter,
inject,
+ effect,
} from '@angular/core';
import { IonContent } from '@ionic/angular';
import { CoreSharedModule } from '@/core/shared.module';
@@ -50,6 +51,7 @@ import { CoreLoadingComponent } from '@components/loading/loading';
import { CoreToasts } from '@services/overlays/toasts';
import { CorePromiseUtils } from '@singletons/promise-utils';
import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
+import { CoreKeyboard } from '@singletons/keyboard';
/**
* Component that displays a rich text editor.
@@ -85,7 +87,6 @@ export class CoreEditorRichTextEditorComponent implements AfterViewInit, OnDestr
@ViewChild(CoreDynamicComponent) dynamicComponent!: CoreDynamicComponent;
- protected keyboardObserver?: CoreEventObserver;
protected resizeListener?: CoreEventObserver;
protected editorComponentClass?: Type;
protected editorComponentData: Record = {};
@@ -107,6 +108,15 @@ export class CoreEditorRichTextEditorComponent implements AfterViewInit, OnDestr
constructor() {
// Generate a "unique" ID based on timestamp.
this.pageInstance = `app_${Date.now()}`;
+
+ effect(() => {
+ // Signal will be triggered when the keyboard is shown or hidden.
+ CoreKeyboard.getKeyboardShownSignal();
+
+ // Opening or closing the keyboard also calls the resize function, but sometimes the resize is called too soon.
+ // Check the height again, now the window height should have been updated.
+ this.maximizeEditorSize();
+ });
}
/**
@@ -188,12 +198,6 @@ export class CoreEditorRichTextEditorComponent implements AfterViewInit, OnDestr
this.controlSubscription = this.control?.valueChanges.subscribe((newValue) => {
this.onControlValueChange(newValue);
});
-
- // Opening or closing the keyboard also calls the resize function, but sometimes the resize is called too soon.
- // Check the height again, now the window height should have been updated.
- this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, () => {
- this.maximizeEditorSize();
- });
}
/**
@@ -201,7 +205,6 @@ export class CoreEditorRichTextEditorComponent implements AfterViewInit, OnDestr
*/
ngOnDestroy(): void {
this.resizeListener?.off();
- this.keyboardObserver?.off();
this.controlSubscription?.unsubscribe();
this.resetObserver?.off();
this.labelObserver?.disconnect();
diff --git a/src/core/features/fileuploader/services/fileuploader-helper.ts b/src/core/features/fileuploader/services/fileuploader-helper.ts
index 06ddb44ba9f..edd9e963bc1 100644
--- a/src/core/features/fileuploader/services/fileuploader-helper.ts
+++ b/src/core/features/fileuploader/services/fileuploader-helper.ts
@@ -140,7 +140,7 @@ export class CoreFileUploaderHelperProvider {
if (size < 0) {
return CoreAlerts.confirm(Translate.instant('core.fileuploader.confirmuploadunknownsize'));
- } else if (size >= wifiThreshold || (CoreNetwork.isNetworkAccessLimited() && size >= limitedThreshold)) {
+ } else if (size >= wifiThreshold || (CoreNetwork.isCellular() && size >= limitedThreshold)) {
const readableSize = CoreText.bytesToSize(size, 2);
return CoreAlerts.confirm(Translate.instant('core.fileuploader.confirmuploadfile', { size: readableSize }));
diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts
index ca201752e0a..d8c80850d1b 100644
--- a/src/core/features/mainmenu/pages/menu/menu.ts
+++ b/src/core/features/mainmenu/pages/menu/menu.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
+import { Component, OnInit, OnDestroy, ViewChild, effect } from '@angular/core';
import { IonTabs } from '@ionic/angular';
import { BackButtonEvent } from '@ionic/core';
import { Subscription } from 'rxjs';
@@ -44,6 +44,7 @@ import {
import { CoreSharedModule } from '@/core/shared.module';
import { CoreMainMenuUserButtonComponent } from '../../components/user-menu-button/user-menu-button';
import { BackButtonPriority } from '@/core/constants';
+import { CoreKeyboard } from '@singletons/keyboard';
const ANIMATION_DURATION = 500;
@@ -119,6 +120,22 @@ export default class CoreMainMenuPage implements OnInit, OnDestroy {
this.isMainScreen = !this.mainTabs?.outlet.canGoBack();
this.updateVisibility();
});
+
+ if (CorePlatform.isIOS()) {
+ effect(() => {
+ const shown = CoreKeyboard.getKeyboardShownSignal();
+ // In iOS, the resize event is triggered before the keyboard is opened/closed and not triggered again once done.
+ // Init handlers again once keyboard is closed since the resize event doesn't have the updated height.
+ if (!shown) {
+ this.updateHandlers();
+
+ // If the device is slow it can take a bit more to update the window height. Retry in a few ms.
+ setTimeout(() => {
+ this.updateHandlers();
+ }, 250);
+ }
+ });
+ }
}
/**
@@ -150,20 +167,6 @@ export default class CoreMainMenuPage implements OnInit, OnDestroy {
});
document.addEventListener('ionBackButton', this.backButtonFunction);
- if (CorePlatform.isIOS()) {
- // In iOS, the resize event is triggered before the keyboard is opened/closed and not triggered again once done.
- // Init handlers again once keyboard is closed since the resize event doesn't have the updated height.
- this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, (kbHeight: number) => {
- if (kbHeight === 0) {
- this.updateHandlers();
-
- // If the device is slow it can take a bit more to update the window height. Retry in a few ms.
- setTimeout(() => {
- this.updateHandlers();
- }, 250);
- }
- });
- }
CoreEvents.trigger(CoreEvents.MAIN_HOME_LOADED);
}
diff --git a/src/core/features/settings/pages/deviceinfo/deviceinfo.html b/src/core/features/settings/pages/deviceinfo/deviceinfo.html
index 57d2ae90a37..e5386aabb0b 100644
--- a/src/core/features/settings/pages/deviceinfo/deviceinfo.html
+++ b/src/core/features/settings/pages/deviceinfo/deviceinfo.html
@@ -139,7 +139,7 @@
{{ 'core.settings.networkstatus' | translate}}
- @if (deviceInfo.isOnline) {
+ @if (deviceInfo.isOnline()) {
{{ 'core.online' | translate }}
} @else {
{{ 'core.offline' | translate }}
@@ -149,7 +149,11 @@
{{ 'core.settings.wificonnection' | translate}}
- {{ 'core.' + deviceInfo.wifiConnection | translate }}
+ @if (deviceInfo.wifiConnection()) {
+ {{ 'core.yes' | translate }}
+ } @else {
+ {{ 'core.no' | translate }}
+ }
@if (deviceInfo.cordovaVersion) {
diff --git a/src/core/features/settings/pages/deviceinfo/deviceinfo.ts b/src/core/features/settings/pages/deviceinfo/deviceinfo.ts
index 03e89d80c29..9821a3f1280 100644
--- a/src/core/features/settings/pages/deviceinfo/deviceinfo.ts
+++ b/src/core/features/settings/pages/deviceinfo/deviceinfo.ts
@@ -12,21 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, OnDestroy } from '@angular/core';
+import { Component, computed, Signal } from '@angular/core';
import { CoreConstants } from '@/core/constants';
import { CoreLocalNotifications } from '@services/local-notifications';
-import { Device, Translate, NgZone } from '@singletons';
+import { Device, Translate } from '@singletons';
import { CoreLang } from '@services/lang';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
import { CorePromiseUtils } from '@singletons/promise-utils';
-import { Subscription } from 'rxjs';
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreConfig } from '@services/config';
import { CoreToasts } from '@services/overlays/toasts';
import { CoreNavigator } from '@services/navigator';
import { CorePlatform } from '@services/platform';
-import { CoreNetwork } from '@services/network';
+import { CoreNetwork, CoreNetworkConnectionType } from '@services/network';
import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSitesFactory } from '@services/sites-factory';
import { CoreText } from '@singletons/text';
@@ -53,8 +52,8 @@ interface CoreSettingsDeviceInfo {
locationHref?: string;
deviceType: string;
screen?: string;
- isOnline: boolean;
- wifiConnection: string;
+ isOnline: Signal;
+ wifiConnection: Signal;
cordovaVersion?: string;
platform?: string;
osVersion?: string;
@@ -76,7 +75,7 @@ interface CoreSettingsDeviceInfo {
CoreSharedModule,
],
})
-export default class CoreSettingsDeviceInfoPage implements OnDestroy {
+export default class CoreSettingsDeviceInfoPage {
deviceInfo: CoreSettingsDeviceInfo;
deviceOsTranslated?: string;
@@ -88,8 +87,6 @@ export default class CoreSettingsDeviceInfoPage implements OnDestroy {
protected devOptionsForced = false;
protected devOptionsClickTimeout?: number;
- protected onlineObserver?: Subscription;
-
constructor() {
const navigator = window.navigator;
@@ -98,8 +95,8 @@ export default class CoreSettingsDeviceInfoPage implements OnDestroy {
versionCode: CoreConstants.CONFIG.versioncode,
compilationTime: CoreConstants.BUILD.compilationTime || 0,
lastCommit: CoreConstants.BUILD.lastCommitHash || '',
- isOnline: CoreNetwork.isOnline(),
- wifiConnection: CoreNetwork.isWifi() ? 'yes' : 'no',
+ isOnline: CoreNetwork.onlineSignal(),
+ wifiConnection: computed(() => CoreNetwork.connectionTypeSignal()() === CoreNetworkConnectionType.WIFI),
localNotifAvailable: CoreLocalNotifications.isPluginAvailable() ? 'yes' : 'no',
pushId: CorePushNotifications.getPushId(),
deviceType: '',
@@ -172,14 +169,6 @@ export default class CoreSettingsDeviceInfoPage implements OnDestroy {
this.deviceInfo.siteId = currentSite?.getId();
this.deviceInfo.siteVersion = currentSite?.getInfo()?.release;
- // Refresh online status when changes.
- this.onlineObserver = CoreNetwork.onChange().subscribe(() => {
- // Execute the callback in the Angular zone, so change detection doesn't stop working.
- NgZone.run(() => {
- this.deviceInfo.isOnline = CoreNetwork.isOnline();
- });
- });
-
this.asyncInit();
}
@@ -220,6 +209,12 @@ export default class CoreSettingsDeviceInfoPage implements OnDestroy {
*/
copyInfo(): void {
CoreText.copyToClipboard(JSON.stringify(this.deviceInfo));
+ const deviceInfo = {
+ ...this.deviceInfo,
+ isOnline: this.deviceInfo.isOnline(),
+ wifiConnection: this.deviceInfo.wifiConnection(),
+ };
+ CoreText.copyToClipboard(JSON.stringify(deviceInfo));
}
/**
@@ -234,13 +229,6 @@ export default class CoreSettingsDeviceInfoPage implements OnDestroy {
text && CoreText.copyToClipboard(text);
}
- /**
- * Page destroyed.
- */
- ngOnDestroy(): void {
- this.onlineObserver && this.onlineObserver.unsubscribe();
- }
-
/**
* 5 clicks will enable dev options.
*/
diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts
index 7063aeee1e7..8bab0db3284 100644
--- a/src/core/features/settings/pages/site/site.ts
+++ b/src/core/features/settings/pages/site/site.ts
@@ -73,13 +73,13 @@ export default class CoreSitePreferencesPage implements AfterViewInit, OnDestroy
}, this.siteId);
this.isOnline = CoreNetwork.isOnline();
- this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited();
+ this.limitedConnection = CoreNetwork.isCellular();
this.networkObserver = CoreNetwork.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.run(() => {
this.isOnline = CoreNetwork.isOnline();
- this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited();
+ this.limitedConnection = CoreNetwork.isCellular();
});
});
}
diff --git a/src/core/features/settings/pages/synchronization/synchronization.ts b/src/core/features/settings/pages/synchronization/synchronization.ts
index 627ed67ef7a..9da24d55b43 100644
--- a/src/core/features/settings/pages/synchronization/synchronization.ts
+++ b/src/core/features/settings/pages/synchronization/synchronization.ts
@@ -93,13 +93,13 @@ export default class CoreSettingsSynchronizationPage implements OnInit, OnDestro
});
this.isOnline = CoreNetwork.isOnline();
- this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited();
+ this.limitedConnection = CoreNetwork.isCellular();
this.networkObserver = CoreNetwork.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.run(() => {
this.isOnline = CoreNetwork.isOnline();
- this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited();
+ this.limitedConnection = CoreNetwork.isCellular();
});
});
diff --git a/src/core/features/settings/services/settings-helper.ts b/src/core/features/settings/services/settings-helper.ts
index 2dd2f95bcea..6f7dd3b69a5 100644
--- a/src/core/features/settings/services/settings-helper.ts
+++ b/src/core/features/settings/services/settings-helper.ts
@@ -241,7 +241,7 @@ export class CoreSettingsHelperProvider {
} else if (hasSyncHandlers && !CoreNetwork.isOnline()) {
// We need connection to execute sync.
throw new CoreError(Translate.instant('core.settings.cannotsyncoffline'));
- } else if (hasSyncHandlers && syncOnlyOnWifi && CoreNetwork.isNetworkAccessLimited()) {
+ } else if (hasSyncHandlers && syncOnlyOnWifi && CoreNetwork.isCellular()) {
throw new CoreError(Translate.instant('core.settings.cannotsyncwithoutwifi'));
}
diff --git a/src/core/initializers/subscribe-to-keyboard-events.ts b/src/core/initializers/subscribe-to-keyboard-events.ts
index a160dabc0cc..b4f9f6684f1 100644
--- a/src/core/initializers/subscribe-to-keyboard-events.ts
+++ b/src/core/initializers/subscribe-to-keyboard-events.ts
@@ -25,6 +25,6 @@ export default function(): void {
// Execute callbacks in the Angular zone, so change detection doesn't stop working.
keyboard.onKeyboardShow().subscribe(data => zone.run(() => CoreKeyboard.onKeyboardShow(data.keyboardHeight)));
keyboard.onKeyboardHide().subscribe(() => zone.run(() => CoreKeyboard.onKeyboardHide()));
- keyboard.onKeyboardWillShow().subscribe(() => zone.run(() => CoreKeyboard.onKeyboardWillShow()));
+ keyboard.onKeyboardWillShow().subscribe((data) => zone.run(() => CoreKeyboard.onKeyboardWillShow(data.keyboardHeight)));
keyboard.onKeyboardWillHide().subscribe(() => zone.run(() => CoreKeyboard.onKeyboardWillHide()));
}
diff --git a/src/core/services/app.ts b/src/core/services/app.ts
index 1492fd5ea3b..ca7ff7f82a8 100644
--- a/src/core/services/app.ts
+++ b/src/core/services/app.ts
@@ -90,7 +90,7 @@ export class CoreAppProvider {
/**
* Closes the keyboard.
*
- * @deprecated sinde 4.5.0. Use CoreKeyboard.closeKeyboard instead.
+ * @deprecated since 4.5.0. Use CoreKeyboard.closeKeyboard instead.
*/
closeKeyboard(): void {
CoreKeyboard.close();
diff --git a/src/core/services/network.ts b/src/core/services/network.ts
index 35597f4f51a..f2d13169e26 100644
--- a/src/core/services/network.ts
+++ b/src/core/services/network.ts
@@ -12,14 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Injectable } from '@angular/core';
+import { effect, Injectable, Signal, signal } from '@angular/core';
import { CorePlatform } from '@services/platform';
import { Network } from '@awesome-cordova-plugins/network/ngx';
-import { NgZone, makeSingleton } from '@singletons';
+import { makeSingleton } from '@singletons';
import { Observable, Subject, merge } from 'rxjs';
import { CoreHTMLClasses } from '@singletons/html-classes';
-export enum CoreNetworkConnection {
+enum CoreNetworkConnection {
UNKNOWN = 'unknown',
ETHERNET = 'ethernet',
WIFI = 'wifi',
@@ -30,6 +30,13 @@ export enum CoreNetworkConnection {
NONE = 'none',
}
+export enum CoreNetworkConnectionType {
+ UNKNOWN = 'unknown',
+ WIFI = 'wifi', // Usually a non-metered connection.
+ CELL = 'cellular', // Usually a metered connection.
+ OFFLINE = 'offline',
+}
+
/**
* Service to manage network connections.
*/
@@ -41,27 +48,44 @@ export class CoreNetworkService extends Network {
protected connectObservable = new Subject<'connected'>();
protected connectStableObservable = new Subject<'connected'>();
protected disconnectObservable = new Subject<'disconnected'>();
- protected forceConnectionMode?: CoreNetworkConnection;
- protected online = false;
+ protected forceConnectionMode?: CoreNetworkConnectionType;
protected connectStableTimeout?: number;
+ protected readonly online = signal(false);
+ private readonly _connectionType = signal(CoreNetworkConnectionType.UNKNOWN);
- get connectionType(): CoreNetworkConnection {
- if (this.forceConnectionMode !== undefined) {
- return this.forceConnectionMode;
- }
+ constructor() {
+ super();
- if (CorePlatform.isMobile()) {
- return this.type as CoreNetworkConnection;
- }
+ effect(() => {
+ const isOnline = this.online();
+
+ const hadOfflineMessage = CoreHTMLClasses.hasModeClass('core-offline');
+
+ CoreHTMLClasses.toggleModeClass('core-offline', !isOnline);
+
+ if (isOnline && hadOfflineMessage) {
+ CoreHTMLClasses.toggleModeClass('core-online', true);
+
+ setTimeout(() => {
+ CoreHTMLClasses.toggleModeClass('core-online', false);
+ }, 3000);
+ } else if (!isOnline) {
+ CoreHTMLClasses.toggleModeClass('core-online', false);
+ }
+ });
+ }
- return this.online ? CoreNetworkConnection.WIFI : CoreNetworkConnection.NONE;
+ get connectionType(): CoreNetworkConnectionType {
+ CoreNetwork.updateConnectionType();
+
+ return this._connectionType();
}
/**
* Initialize the service.
*/
initialize(): void {
- this.checkOnline();
+ this.updateOnline();
if (CorePlatform.isMobile()) {
// We cannot directly listen to onChange because it depends on
@@ -73,6 +97,7 @@ export class CoreNetworkService extends Network {
this.fireObservable();
});
} else {
+ // Match the Cordova constants to the ones used in the app.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
( window).Connection = {
UNKNOWN: CoreNetworkConnection.UNKNOWN, // eslint-disable-line @typescript-eslint/naming-convention
@@ -103,30 +128,7 @@ export class CoreNetworkService extends Network {
async onPlaformReady(): Promise {
await CorePlatform.ready();
- // Refresh online status when changes.
- CoreNetwork.onChange().subscribe(() => {
- // Execute the callback in the Angular zone, so change detection doesn't stop working.
- NgZone.run(() => {
- const isOnline = this.isOnline();
-
- const hadOfflineMessage = CoreHTMLClasses.hasModeClass('core-offline');
-
- CoreHTMLClasses.toggleModeClass('core-offline', !isOnline);
-
- if (isOnline && hadOfflineMessage) {
- CoreHTMLClasses.toggleModeClass('core-online', true);
-
- setTimeout(() => {
- CoreHTMLClasses.toggleModeClass('core-online', false);
- }, 3000);
- } else if (!isOnline) {
- CoreHTMLClasses.toggleModeClass('core-online', false);
- }
- });
- });
-
- const isOnline = this.isOnline();
- CoreHTMLClasses.toggleModeClass('core-offline', !isOnline);
+ CoreHTMLClasses.toggleModeClass('core-offline', !this.online());
}
/**
@@ -135,7 +137,7 @@ export class CoreNetworkService extends Network {
*
* @param value Value to set.
*/
- setForceConnectionMode(value: CoreNetworkConnection): void {
+ setForceConnectionMode(value: CoreNetworkConnectionType): void {
this.forceConnectionMode = value;
this.fireObservable();
}
@@ -146,15 +148,18 @@ export class CoreNetworkService extends Network {
* @returns Whether the app is online.
*/
isOnline(): boolean {
- return this.online;
+ return this.online();
}
/**
- * Returns whether we are online.
+ * Updates online status.
*/
- checkOnline(): void {
- if (this.forceConnectionMode === CoreNetworkConnection.NONE) {
- this.online = false;
+ protected updateOnline(): void {
+ // Recalculate connection type.
+ CoreNetwork.updateConnectionType();
+
+ if (this.forceConnectionMode === CoreNetworkConnectionType.OFFLINE) {
+ this.online.set(false);
return;
}
@@ -162,20 +167,59 @@ export class CoreNetworkService extends Network {
// We cannot use navigator.onLine because it has issues in some devices.
// See https://bugs.chromium.org/p/chromium/issues/detail?id=811122
if (!CorePlatform.isAndroid()) {
- this.online = navigator.onLine;
+ this.online.set(navigator.onLine);
return;
}
- const type = this.connectionType;
- let online = type !== null && type !== CoreNetworkConnection.NONE && type !== CoreNetworkConnection.UNKNOWN;
+ const type = this._connectionType();
+ let online = type !== null && type !== CoreNetworkConnectionType.OFFLINE && type !== CoreNetworkConnectionType.UNKNOWN;
// Double check we are not online because we cannot rely 100% in Cordova APIs.
if (!online && navigator.onLine) {
online = true;
}
- this.online = online;
+ this.online.set(online);
+ }
+
+ /**
+ * Check and update the connection type.
+ */
+ protected updateConnectionType(): void {
+ if (this.forceConnectionMode !== undefined) {
+ this._connectionType.set(this.forceConnectionMode);
+
+ return;
+ }
+
+ if (CorePlatform.isMobile()) {
+ switch (this.type) {
+ case CoreNetworkConnection.WIFI:
+ case CoreNetworkConnection.ETHERNET:
+ this._connectionType.set(CoreNetworkConnectionType.WIFI);
+
+ return;
+ case CoreNetworkConnection.CELL:
+ case CoreNetworkConnection.CELL_2G:
+ case CoreNetworkConnection.CELL_3G:
+ case CoreNetworkConnection.CELL_4G:
+ this._connectionType.set(CoreNetworkConnectionType.CELL);
+
+ return;
+ case CoreNetworkConnection.NONE:
+ this._connectionType.set(CoreNetworkConnectionType.OFFLINE);
+
+ return;
+ default:
+ case CoreNetworkConnection.UNKNOWN:
+ this._connectionType.set(CoreNetworkConnectionType.UNKNOWN);
+
+ return;
+ }
+ }
+
+ this._connectionType.set(this.online() ? CoreNetworkConnectionType.WIFI : CoreNetworkConnectionType.OFFLINE);
}
/**
@@ -187,6 +231,24 @@ export class CoreNetworkService extends Network {
return merge(this.connectObservable, this.disconnectObservable);
}
+ /**
+ * Returns a signal to watch online status.
+ *
+ * @returns Signal.
+ */
+ onlineSignal(): Signal {
+ return this.online.asReadonly();
+ }
+
+ /**
+ * Returns a signal to watch connection type.
+ *
+ * @returns Signal.
+ */
+ connectionTypeSignal(): Signal {
+ return this._connectionType.asReadonly();
+ }
+
/**
* Returns an observable to notify when the app is connected.
* It will also be fired when connection type changes.
@@ -224,9 +286,9 @@ export class CoreNetworkService extends Network {
*/
protected fireObservable(): void {
clearTimeout(this.connectStableTimeout);
- this.checkOnline();
+ this.updateOnline();
- if (this.online) {
+ if (this.online()) {
this.connectObservable.next('connected');
this.connectStableTimeout = window.setTimeout(() => {
this.connectStableObservable.next('connected');
@@ -240,18 +302,10 @@ export class CoreNetworkService extends Network {
* Check if device uses a limited connection.
*
* @returns Whether the device uses a limited connection.
+ * @deprecated since 5.1. Use isCellular instead.
*/
isNetworkAccessLimited(): boolean {
- const limited: CoreNetworkConnection[] = [
- CoreNetworkConnection.CELL_2G,
- CoreNetworkConnection.CELL_3G,
- CoreNetworkConnection.CELL_4G,
- CoreNetworkConnection.CELL,
- ];
-
- const type = this.connectionType;
-
- return limited.indexOf(type) > -1;
+ return this.isCellular();
}
/**
@@ -260,7 +314,16 @@ export class CoreNetworkService extends Network {
* @returns Whether the device uses a wifi connection.
*/
isWifi(): boolean {
- return this.isOnline() && !this.isNetworkAccessLimited();
+ return this.connectionType === CoreNetworkConnectionType.WIFI;
+ }
+
+ /**
+ * Check if device uses a limited connection.
+ *
+ * @returns Whether the device uses a limited connection.
+ */
+ isCellular(): boolean {
+ return this.connectionType === CoreNetworkConnectionType.CELL;
}
}
diff --git a/src/core/services/overlays/alerts.ts b/src/core/services/overlays/alerts.ts
index c69f500057f..c8e901ccc24 100644
--- a/src/core/services/overlays/alerts.ts
+++ b/src/core/services/overlays/alerts.ts
@@ -186,7 +186,7 @@ export class CoreAlertsService {
const limitedThreshold = options.limitedThreshold ?? CoreConstants.DOWNLOAD_THRESHOLD;
let wifiPrefix = '';
- if (CoreNetwork.isNetworkAccessLimited()) {
+ if (CoreNetwork.isCellular()) {
wifiPrefix = Translate.instant('core.course.confirmlimiteddownload');
}
@@ -203,7 +203,7 @@ export class CoreAlertsService {
{ size: readableSize, availableSpace: availableSpace },
));
} else if (options.alwaysConfirm || size.size >= wifiThreshold ||
- (CoreNetwork.isNetworkAccessLimited() && size.size >= limitedThreshold)) {
+ (CoreNetwork.isCellular() && size.size >= limitedThreshold)) {
return this.confirm(wifiPrefix + Translate.instant(
options.message ?? (size.size === 0 ? 'core.course.confirmdownloadzerosize' : 'core.course.confirmdownload'),
diff --git a/src/core/services/ws.ts b/src/core/services/ws.ts
index e038ceba1cd..6b9072f7a9b 100644
--- a/src/core/services/ws.ts
+++ b/src/core/services/ws.ts
@@ -398,7 +398,7 @@ export class CoreWSProvider {
* @returns Timeout in ms.
*/
getRequestTimeout(): number {
- return CoreNetwork.isNetworkAccessLimited() ? CoreConstants.WS_TIMEOUT : CoreConstants.WS_TIMEOUT_WIFI;
+ return CoreNetwork.isCellular() ? CoreConstants.WS_TIMEOUT : CoreConstants.WS_TIMEOUT_WIFI;
}
/**
diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts
index f5aba91d55b..dfd305c5864 100644
--- a/src/core/singletons/events.ts
+++ b/src/core/singletons/events.ts
@@ -107,6 +107,9 @@ export class CoreEvents {
static readonly IAB_MESSAGE = 'inappbrowser_message';
static readonly APP_LAUNCHED_URL = 'app_launched_url'; // App opened with a certain URL (custom URL scheme).
static readonly FILE_SHARED = 'file_shared';
+ /**
+ * @deprecated since 5.0.0. Use CoreKeyboard.getKeyboardShownSignal signal.
+ */
static readonly KEYBOARD_CHANGE = 'keyboard_change';
static readonly ORIENTATION_CHANGE = 'orientation_change';
static readonly SEND_ON_ENTER_CHANGED = 'send_on_enter_changed';
diff --git a/src/core/singletons/keyboard.ts b/src/core/singletons/keyboard.ts
index 2c3c0126943..4da3cd1dc36 100644
--- a/src/core/singletons/keyboard.ts
+++ b/src/core/singletons/keyboard.ts
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import { effect, Signal, signal } from '@angular/core';
import { CorePlatform } from '@services/platform';
import { Keyboard } from '@singletons';
import { CoreEvents } from '@singletons/events';
@@ -21,13 +22,16 @@ import { CoreEvents } from '@singletons/events';
*/
export class CoreKeyboard {
- protected static isKeyboardShown = false;
- protected static keyboardOpening = false;
- protected static keyboardClosing = false;
+ protected static readonly IS_KEYBOARD_SHOWN = signal(false);
+ protected static readonly KEYBOARD_OPENING = signal(false);
+ protected static readonly KEYBOARD_CLOSING = signal(false);
+ protected static readonly KEYBOARD_HEIGHT = signal(0);
// Avoid creating singleton instances.
private constructor() {
- // Nothing to do.
+ effect(() => {
+ document.body.classList.toggle('keyboard-is-open', CoreKeyboard.IS_KEYBOARD_SHOWN());
+ });
}
/**
@@ -49,53 +53,80 @@ export class CoreKeyboard {
}
}
+ /**
+ * Get a signal that indicates whether the keyboard is shown or not.
+ *
+ * @returns Signal indicating whether the keyboard is shown.
+ */
+ static getKeyboardShownSignal(): Signal {
+ return CoreKeyboard.IS_KEYBOARD_SHOWN.asReadonly();
+ }
+
+ /**
+ * Get a signal that indicates the keyboard height.
+ *
+ * @returns Signal indicating the keyboard height.
+ */
+ static getKeyboardHeightSignal(): Signal {
+ return CoreKeyboard.KEYBOARD_HEIGHT.asReadonly();
+ }
+
/**
* Notify that Keyboard has been shown.
*
* @param keyboardHeight Keyboard height.
*/
static onKeyboardShow(keyboardHeight: number): void {
- document.body.classList.add('keyboard-is-open');
- CoreKeyboard.setKeyboardShown(true);
// Error on iOS calculating size.
- // More info: https://github.com/ionic-team/ionic-plugin-keyboard/issues/276 .
- CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, keyboardHeight);
+ // More info: https://github.com/ionic-team/ionic-plugin-keyboard/issues/276
+ CoreKeyboard.setKeyboardShown(true, keyboardHeight);
}
/**
* Notify that Keyboard has been hidden.
*/
static onKeyboardHide(): void {
- document.body.classList.remove('keyboard-is-open');
- CoreKeyboard.setKeyboardShown(false);
- CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, 0);
+ CoreKeyboard.setKeyboardShown(false, 0);
}
/**
* Notify that Keyboard is about to be shown.
+ *
+ * @param keyboardHeight Keyboard height.
*/
- static onKeyboardWillShow(): void {
- CoreKeyboard.keyboardOpening = true;
- CoreKeyboard.keyboardClosing = false;
+ static onKeyboardWillShow(keyboardHeight?: number): void {
+ CoreKeyboard.KEYBOARD_OPENING.set(true);
+ CoreKeyboard.KEYBOARD_CLOSING.set(false);
+
+ if (keyboardHeight !== undefined) {
+ this.KEYBOARD_HEIGHT.set(keyboardHeight);
+ }
}
/**
* Notify that Keyboard is about to be hidden.
*/
static onKeyboardWillHide(): void {
- CoreKeyboard.keyboardOpening = false;
- CoreKeyboard.keyboardClosing = true;
+ CoreKeyboard.KEYBOARD_OPENING.set(false);
+ CoreKeyboard.KEYBOARD_CLOSING.set(true);
+
+ this.KEYBOARD_HEIGHT.set(0);
}
/**
* Set keyboard shown or hidden.
*
* @param shown Whether the keyboard is shown or hidden.
+ * @param keyboardHeight Keyboard height.
*/
- protected static setKeyboardShown(shown: boolean): void {
- CoreKeyboard.isKeyboardShown = shown;
- CoreKeyboard.keyboardOpening = false;
- CoreKeyboard.keyboardClosing = false;
+ protected static setKeyboardShown(shown: boolean, keyboardHeight: number): void {
+ CoreKeyboard.IS_KEYBOARD_SHOWN.set(shown);
+ CoreKeyboard.KEYBOARD_OPENING.set(false);
+ CoreKeyboard.KEYBOARD_CLOSING.set(false);
+ this.KEYBOARD_HEIGHT.set(keyboardHeight);
+
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
+ CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, keyboardHeight);
}
/**
@@ -104,7 +135,7 @@ export class CoreKeyboard {
* @returns Whether keyboard is closing (animating).
*/
static isKeyboardClosing(): boolean {
- return CoreKeyboard.keyboardClosing;
+ return CoreKeyboard.KEYBOARD_CLOSING();
}
/**
@@ -113,7 +144,7 @@ export class CoreKeyboard {
* @returns Whether keyboard is opening (animating).
*/
static isKeyboardOpening(): boolean {
- return CoreKeyboard.keyboardOpening;
+ return CoreKeyboard.KEYBOARD_OPENING();
}
/**
@@ -122,7 +153,7 @@ export class CoreKeyboard {
* @returns Whether keyboard is visible.
*/
static isKeyboardVisible(): boolean {
- return CoreKeyboard.isKeyboardShown;
+ return CoreKeyboard.IS_KEYBOARD_SHOWN();
}
}