MOBILE-4047 settings: Change sync on WiFi behavior

main
Pau Ferrer Ocaña 2022-07-12 10:40:01 +02:00
parent 889f7479a0
commit 17440ca3d4
8 changed files with 194 additions and 94 deletions

View File

@ -68,6 +68,8 @@ function do_match {
print_message "$2"
tput setaf 6
grep "$match" $LANGPACKSFOLDER/en/*.php
else
coincidence=0
fi
}

View File

@ -2181,6 +2181,8 @@
"core.settings.colorscheme-system": "local_moodlemobileapp",
"core.settings.colorscheme-system-notice": "local_moodlemobileapp",
"core.settings.compilationinfo": "local_moodlemobileapp",
"core.settings.connecttosync": "local_moodlemobileapp",
"core.settings.connectwifitosync": "local_moodlemobileapp",
"core.settings.copyinfo": "local_moodlemobileapp",
"core.settings.cordovadevicemodel": "local_moodlemobileapp",
"core.settings.cordovadeviceosversion": "local_moodlemobileapp",
@ -2202,7 +2204,6 @@
"core.settings.enablefirebaseanalyticsdescription": "local_moodlemobileapp",
"core.settings.enablerichtexteditor": "local_moodlemobileapp",
"core.settings.enablerichtexteditordescription": "local_moodlemobileapp",
"core.settings.enablesyncwifi": "local_moodlemobileapp",
"core.settings.entriesincache": "local_moodlemobileapp",
"core.settings.estimatedfreespace": "local_moodlemobileapp",
"core.settings.filesystemroot": "local_moodlemobileapp",
@ -2220,6 +2221,7 @@
"core.settings.locationhref": "local_moodlemobileapp",
"core.settings.loggedin": "message",
"core.settings.loggedoff": "message",
"core.settings.logintosync": "local_moodlemobileapp",
"core.settings.navigatorlanguage": "local_moodlemobileapp",
"core.settings.navigatoruseragent": "local_moodlemobileapp",
"core.settings.networkstatus": "local_moodlemobileapp",
@ -2236,6 +2238,7 @@
"core.settings.sites": "moodle",
"core.settings.sitesyncfailed": "local_moodlemobileapp",
"core.settings.spaceusage": "local_moodlemobileapp",
"core.settings.syncdatasaver": "local_moodlemobileapp",
"core.settings.synchronization": "local_moodlemobileapp",
"core.settings.synchronizenow": "local_moodlemobileapp",
"core.settings.synchronizenowhelp": "local_moodlemobileapp",

View File

@ -14,6 +14,8 @@
"colorscheme-system": "System default",
"colorscheme": "Color Scheme",
"compilationinfo": "Compilation info",
"connectwifitosync": "Connect to a Wi-Fi network or turn off Data saver to synchronise sites.",
"connecttosync": "Your device is offline. Connect to the internet to synchronise sites.",
"copyinfo": "Copy device info on the clipboard",
"cordovadevicemodel": "Cordova device model",
"cordovadeviceosversion": "Cordova device OS version",
@ -35,7 +37,6 @@
"enablefirebaseanalyticsdescription": "If enabled, the app will collect anonymous data usage.",
"enablerichtexteditor": "Enable text editor",
"enablerichtexteditordescription": "If enabled, a text editor will be available when entering content.",
"enablesyncwifi": "Allow sync only when on Wi-Fi",
"entriesincache": "{{$a}} entries in cache",
"estimatedfreespace": "Estimated free space",
"filesystemroot": "File system root",
@ -53,6 +54,7 @@
"locationhref": "Web view URL",
"loggedin": "Online",
"loggedoff": "Offline",
"logintosync": "Log in to synchronise",
"navigatorlanguage": "Navigator language",
"navigatoruseragent": "Navigator userAgent",
"networkstatus": "Internet connection status",
@ -69,6 +71,7 @@
"sites": "Sites",
"sitesyncfailed": "Site synchronisation failed",
"spaceusage": "Space usage",
"syncdatasaver": "Data saver: Synchronise only when on Wi-Fi",
"synchronization": "Synchronisation",
"synchronizenow": "Synchronise now",
"synchronizenowhelp": "Synchronising a site will send pending changes and all offline activity stored in the device and will synchronise some data like messages and notifications.",

View File

@ -37,11 +37,20 @@
</p>
</ion-label>
<core-button-with-spinner [loading]="isSynchronizing()" slot="end">
<ion-button fill="clear" (click)="synchronize()" [attr.aria-label]="'core.settings.synchronizenow' | translate">
<ion-button fill="clear" (click)="synchronize()" [attr.aria-label]="'core.settings.synchronizenow' | translate"
[disabled]="!isOnline || (dataSaver && limitedConnection)">
<ion-icon name="fas-sync-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-button-with-spinner>
</ion-item>
<ion-item class="core-warning-item ion-text-wrap" *ngIf="!isOnline || (dataSaver && limitedConnection)">
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<ng-container *ngIf="isOnline && dataSaver && limitedConnection">
{{ 'core.settings.connectwifitosync' | translate }}</ng-container>
<ng-container *ngIf="!isOnline">{{ 'core.settings.connecttosync' | translate }}</ng-container>
</ion-label>
</ion-item>
</ion-card>
</core-loading>
</core-split-view>

View File

@ -25,6 +25,11 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/
import { CoreSettingsHandlersSource } from '@features/settings/classes/settings-handlers-source';
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreNetwork } from '@services/network';
import { Subscription } from 'rxjs';
import { NgZone } from '@singletons';
import { CoreConstants } from '@/core/constants';
import { CoreConfig } from '@services/config';
/**
* Page that displays the list of site settings pages.
@ -39,8 +44,13 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
handlers: CoreListItemsManager<CoreSettingsHandlerToDisplay>;
dataSaver = false;
limitedConnection = false;
isOnline = true;
protected siteId: string;
protected sitesObserver: CoreEventObserver;
protected networkObserver: Subscription;
protected isDestroyed = false;
constructor() {
@ -53,12 +63,25 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
this.refreshData();
}, this.siteId);
this.isOnline = CoreNetwork.isOnline();
this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited();
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();
});
});
}
/**
* View loaded.
* @inheritdoc
*/
async ngAfterViewInit(): Promise<void> {
this.dataSaver = await CoreConfig.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true);
const pageToOpen = CoreNavigator.getRouteParam('page');
try {
@ -121,11 +144,12 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
}
/**
* Page destroyed.
* @inheritdoc
*/
ngOnDestroy(): void {
this.isDestroyed = true;
this.sitesObserver?.off();
this.sitesObserver.off();
this.networkObserver.unsubscribe();
this.handlers.destroy();
}

View File

@ -17,88 +17,97 @@
</ion-header>
<ion-content class="limited-width">
<core-loading [hideUntil]="sitesLoaded">
<ion-list class="core-sitelist">
<ion-list class="core-sitelist limited-width">
<ion-item-divider>
<ion-label>
<h2>{{ 'core.settings.syncsettings' | translate }}</h2>
</ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap">
<ion-label>{{ 'core.settings.enablesyncwifi' | translate }}</ion-label>
<ion-toggle slot="end" [(ngModel)]="syncOnlyOnWifi" (ngModelChange)="syncOnlyOnWifiChanged()">
<ion-label>
<p class="item-heading">
{{ 'core.settings.syncdatasaver' | translate }}
</p>
</ion-label>
<ion-toggle slot="end" [(ngModel)]="dataSaver" (ngModelChange)="syncOnlyOnWifiChanged()">
</ion-toggle>
</ion-item>
<ion-item-divider>
<ion-label>
<h2>{{ 'core.accounts' | translate }}</h2>
</ion-label>
</ion-item-divider>
<ion-card *ngIf="accountsList.currentSite">
<ion-item-divider sticky="true" class="core-sitelist-sitename">
<ion-card class="core-warning-card" *ngIf="!isOnline || (dataSaver && limitedConnection)">
<ion-item class="ion-text-wrap">
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<h2>
<core-format-text [text]="accountsList.currentSite.siteName" clean="true"
[siteId]="accountsList.currentSite.id"></core-format-text>
</h2>
<p><a [href]="accountsList.currentSite.siteUrl" core-link autoLogin="yes">{{
accountsList.currentSite.siteUrlWithoutProtocol }}</a>
</p>
<ng-container *ngIf="isOnline && dataSaver && limitedConnection">
{{ 'core.settings.connectwifitosync' | translate }}</ng-container>
<ng-container *ngIf="!isOnline">{{ 'core.settings.connecttosync' | translate }}</ng-container>
</ion-label>
</ion-item-divider>
<ion-item class="item-current">
<ion-avatar slot="start">
<img [src]="accountsList.currentSite.avatar" core-external-content [siteId]="accountsList.currentSite.id"
alt="{{ 'core.pictureof' | translate:{$a: accountsList.currentSite.fullName} }}"
onError="this.src='assets/img/user-avatar.png'">
</ion-avatar>
<ion-label>
<p class="item-heading">{{accountsList.currentSite.fullName}}</p>
</ion-label>
<core-button-with-spinner [loading]="isSynchronizing(accountsList.currentSite.id)" slot="end">
<ion-button fill="clear" (click)="synchronize(accountsList.currentSite.id)"
[attr.aria-label]="'core.settings.synchronizenow' | translate" [disabled]="accountsList.currentSite.loggedOut">
<ion-icon name="fas-sync-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-button-with-spinner>
</ion-item>
<ng-container *ngTemplateOutlet="siteList; context: {sites: accountsList.sameSite}"></ng-container>
</ion-card>
<ion-card *ngFor="let sites of accountsList.otherSites">
<ion-item-divider sticky="true" *ngIf="sites[0]" class="core-sitelist-sitename">
<ng-container *ngIf="isOnline && (!dataSaver || !limitedConnection)">
<ion-item-divider>
<ion-label>
<h2>
<core-format-text [text]="sites[0].siteName" clean="true" [siteId]="sites[0].id"></core-format-text>
</h2>
<p><a [href]="sites[0].siteUrl" core-link autoLogin="no">{{ sites[0].siteUrlWithoutProtocol }}</a></p>
<h2>{{ 'core.accounts' | translate }}</h2>
</ion-label>
</ion-item-divider>
<ng-container *ngTemplateOutlet="siteList; context: {sites: sites}"></ng-container>
</ion-card>
<ion-card *ngIf="accountsList.currentSite">
<ion-item-divider sticky="true" class="core-sitelist-sitename">
<ion-label>
<p class="item-heading">
<core-format-text [text]="accountsList.currentSite.siteName" clean="true"
[siteId]="accountsList.currentSite.id"></core-format-text>
</p>
<p><a [href]="accountsList.currentSite.siteUrl" core-link autoLogin="yes">{{
accountsList.currentSite.siteUrlWithoutProtocol }}</a>
</p>
</ion-label>
</ion-item-divider>
<ion-item class="item-current">
<ng-container *ngTemplateOutlet="siteSync; context: {site: accountsList.currentSite}"></ng-container>
</ion-item>
<ion-item *ngFor="let site of accountsList.sameSite">
<ng-container *ngTemplateOutlet="siteSync; context: {site: site}"></ng-container>
</ion-item>
</ion-card>
<ion-card *ngFor="let sites of accountsList.otherSites">
<ion-item-divider sticky="true" *ngIf="sites[0]" class="core-sitelist-sitename">
<ion-label>
<p class="item-heading">
<core-format-text [text]="sites[0].siteName" clean="true" [siteId]="sites[0].id"></core-format-text>
</p>
<p><a [href]="sites[0].siteUrl" core-link autoLogin="no">{{ sites[0].siteUrlWithoutProtocol }}</a></p>
</ion-label>
</ion-item-divider>
<ion-item *ngFor="let site of sites">
<ng-container *ngTemplateOutlet="siteSync; context: {site: site}"></ng-container>
</ion-item>
</ion-card>
</ng-container>
</ion-list>
</core-loading>
</ion-content>
<!-- Template to render a list of sites. -->
<ng-template #siteList let-sites="sites">
<ion-item *ngFor="let site of sites">
<ion-avatar slot="start">
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}"
onError="this.src='assets/img/user-avatar.png'">
</ion-avatar>
<ion-label>
<p class="item-heading">{{site.fullName}}</p>
</ion-label>
<core-button-with-spinner [loading]="isSynchronizing(site.id)" slot="end">
<ion-button fill="clear" (click)="synchronize(site.id)" [attr.aria-label]="'core.settings.synchronizenow' | translate"
[disabled]="site.loggedOut">
<ion-icon name="fas-sync-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-button-with-spinner>
</ion-item>
<!-- Template to render a site to sync. -->
<ng-template #siteSync let-site="site">
<ion-avatar slot="start">
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}"
onError="this.src='assets/img/user-avatar.png'">
</ion-avatar>
<ion-label>
<p class="item-heading">{{site.fullName}}</p>
<p class="text-danger" *ngIf="site.loggedOut">{{ 'core.settings.logintosync' | translate }}</p>
</ion-label>
<core-button-with-spinner [loading]="isSynchronizing(site.id)" slot="end" *ngIf="!site.loggedOut">
<ion-button fill="clear" (click)="synchronize(site.id)" [attr.aria-label]="'core.settings.synchronizenow' | translate">
<ion-icon name="fas-sync-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-button-with-spinner>
<ion-button fill="clear" (click)="login(site.id)" [attr.aria-label]="'core.login.login' | translate" *ngIf="site.loggedOut" slot="end">
<ion-icon name="fas-sign-in-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</ng-template>

View File

@ -20,8 +20,11 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreConfig } from '@services/config';
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
import { Translate } from '@singletons';
import { NgZone, Translate } from '@singletons';
import { CoreAccountsList, CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreNetwork } from '@services/network';
import { Subscription } from 'rxjs';
import { CoreNavigator } from '@services/navigator';
/**
* Page that displays the synchronization settings.
@ -40,9 +43,13 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy {
};
sitesLoaded = false;
syncOnlyOnWifi = false;
dataSaver = false;
limitedConnection = false;
isOnline = true;
protected isDestroyed = false;
protected sitesObserver: CoreEventObserver;
protected networkObserver: Subscription;
constructor() {
@ -80,6 +87,18 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy {
siteEntry.fullName = siteInfo.fullname;
}
});
this.isOnline = CoreNetwork.isOnline();
this.limitedConnection = this.isOnline && CoreNetwork.isNetworkAccessLimited();
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();
});
});
}
/**
@ -96,18 +115,18 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy {
this.sitesLoaded = true;
this.syncOnlyOnWifi = await CoreConfig.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true);
this.dataSaver = await CoreConfig.get(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, true);
}
/**
* Called when sync only on wifi setting is enabled or disabled.
*/
syncOnlyOnWifiChanged(): void {
CoreConfig.set(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, this.syncOnlyOnWifi ? 1 : 0);
CoreConfig.set(CoreConstants.SETTINGS_SYNC_ONLY_ON_WIFI, this.dataSaver ? 1 : 0);
}
/**
* Syncrhonizes a site.
* Synchronizes a site.
*
* @param siteId Site ID.
*/
@ -124,6 +143,16 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy {
}
}
/**
* Changes site.
*
* @param siteId Site ID.
*/
async login(siteId: string): Promise<void> {
// This navigation will logout and navigate to the site home.
await CoreNavigator.navigateToSiteHome({ preferCurrentTab: false , siteId });
}
/**
* Returns true if site is beeing synchronized.
*
@ -145,11 +174,12 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy {
}
/**
* Page destroyed.
* @inheritdoc
*/
ngOnDestroy(): void {
this.isDestroyed = true;
this.sitesObserver?.off();
this.sitesObserver.off();
this.networkObserver.unsubscribe();
}
}

View File

@ -18,6 +18,17 @@ import { Network } from '@ionic-native/network/ngx';
import { makeSingleton } from '@singletons';
import { Observable, Subject, merge } from 'rxjs';
enum CoreNetworkConnection {
UNKNOWN = 'unknown',
ETHERNET = 'ethernet',
WIFI = 'wifi',
CELL_2G = '2g',
CELL_3G = '3g',
CELL_4G = '4g',
CELL = 'cellular',
NONE = 'none',
};
/**
* Service to manage network connections.
*/
@ -38,20 +49,25 @@ export class CoreNetworkService extends Network {
this.checkOnline();
if (CorePlatform.isMobile()) {
this.onChange().subscribe(() => {
// We cannot directly listen to onChange because it depends on
// onConnect and onDisconnect that have been already overriden.
super.onConnect().subscribe(() => {
this.fireObservable();
});
super.onDisconnect().subscribe(() => {
this.fireObservable();
});
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any> window).Connection = {
UNKNOWN: 'unknown', // eslint-disable-line @typescript-eslint/naming-convention
ETHERNET: 'ethernet', // eslint-disable-line @typescript-eslint/naming-convention
WIFI: 'wifi', // eslint-disable-line @typescript-eslint/naming-convention
CELL_2G: '2g', // eslint-disable-line @typescript-eslint/naming-convention
CELL_3G: '3g', // eslint-disable-line @typescript-eslint/naming-convention
CELL_4G: '4g', // eslint-disable-line @typescript-eslint/naming-convention
CELL: 'cellular', // eslint-disable-line @typescript-eslint/naming-convention
NONE: 'none', // eslint-disable-line @typescript-eslint/naming-convention
UNKNOWN: CoreNetworkConnection.UNKNOWN, // eslint-disable-line @typescript-eslint/naming-convention
ETHERNET: CoreNetworkConnection.ETHERNET, // eslint-disable-line @typescript-eslint/naming-convention
WIFI: CoreNetworkConnection.WIFI, // eslint-disable-line @typescript-eslint/naming-convention
CELL_2G: CoreNetworkConnection.CELL_2G, // eslint-disable-line @typescript-eslint/naming-convention
CELL_3G: CoreNetworkConnection.CELL_3G, // eslint-disable-line @typescript-eslint/naming-convention
CELL_4G: CoreNetworkConnection.CELL_4G, // eslint-disable-line @typescript-eslint/naming-convention
CELL: CoreNetworkConnection.CELL, // eslint-disable-line @typescript-eslint/naming-convention
NONE: CoreNetworkConnection.NONE, // eslint-disable-line @typescript-eslint/naming-convention
};
window.addEventListener('online', () => {
@ -91,18 +107,22 @@ export class CoreNetworkService extends Network {
checkOnline(): void {
if (this.forceOffline) {
this.online = false;
this.type = CoreNetworkConnection.NONE;
return;
}
if (!CorePlatform.isMobile()) {
this.online = navigator.onLine;
this.type = this.online
? CoreNetworkConnection.WIFI
: CoreNetworkConnection.NONE;
return;
}
let online = this.type !== null && this.type != this.Connection.NONE &&
this.type != this.Connection.UNKNOWN;
let online = this.type !== null && this.type !== CoreNetworkConnection.NONE &&
this.type !== CoreNetworkConnection.UNKNOWN;
// Double check we are not online because we cannot rely 100% in Cordova APIs.
if (!online && navigator.onLine) {
@ -123,6 +143,7 @@ export class CoreNetworkService extends Network {
/**
* Returns an observable to notify when the app is connected.
* It will also be fired when connection type changes.
*
* @return Observable.
*/
@ -143,12 +164,11 @@ export class CoreNetworkService extends Network {
* Fires the correct observable depending on the connection status.
*/
protected fireObservable(): void {
const previousOnline = this.online;
this.checkOnline();
if (this.online && !previousOnline) {
if (this.online) {
this.connectObservable.next('connected');
} else if (!this.online && previousOnline) {
} else {
this.disconnectObservable.next('disconnected');
}
}
@ -163,11 +183,11 @@ export class CoreNetworkService extends Network {
return false;
}
const limited = [
this.Connection.CELL_2G,
this.Connection.CELL_3G,
this.Connection.CELL_4G,
this.Connection.CELL,
const limited: string[] = [
CoreNetworkConnection.CELL_2G,
CoreNetworkConnection.CELL_3G,
CoreNetworkConnection.CELL_4G,
CoreNetworkConnection.CELL,
];
return limited.indexOf(this.type) > -1;