diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts
index afc95a694..b16efbbcb 100644
--- a/src/app/components/components.module.ts
+++ b/src/app/components/components.module.ts
@@ -20,6 +20,7 @@ import { TranslateModule } from '@ngx-translate/core';
 import { CoreIconComponent } from './icon/icon';
 import { CoreLoadingComponent } from './loading/loading';
 import { CoreShowPasswordComponent } from './show-password/show-password';
+import { CoreEmptyBoxComponent } from './empty-box/empty-box';
 import { CoreDirectivesModule } from '@app/directives/directives.module';
 import { CorePipesModule } from '@app/pipes/pipes.module';
 
@@ -28,6 +29,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
         CoreIconComponent,
         CoreLoadingComponent,
         CoreShowPasswordComponent,
+        CoreEmptyBoxComponent,
     ],
     imports: [
         CommonModule,
@@ -40,6 +42,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
         CoreIconComponent,
         CoreLoadingComponent,
         CoreShowPasswordComponent,
+        CoreEmptyBoxComponent,
     ],
 })
 export class CoreComponentsModule {}
diff --git a/src/app/components/empty-box/core-empty-box.html b/src/app/components/empty-box/core-empty-box.html
new file mode 100644
index 000000000..b15958be0
--- /dev/null
+++ b/src/app/components/empty-box/core-empty-box.html
@@ -0,0 +1,9 @@
+
+    
+        
![]()
+        
+        
+        
{{ message }}
+        
+    
+
     
      
-
\ No newline at end of file
+
diff --git a/src/app/components/loading/loading.scss b/src/app/components/loading/loading.scss
index afe335a58..b8289607b 100644
--- a/src/app/components/loading/loading.scss
+++ b/src/app/components/loading/loading.scss
@@ -1,67 +1,43 @@
-ion-app.app-root {
-    core-loading {
-        // @todo @include core-transition(height, 200ms);
+:host {
+    position: static;
+    -webkit-transition: height 200ms ease-in-out;
+    transition: height 200ms ease-in-out;
 
-        .core-loading-container {
-            width: 100%;
+    > .core-loading-container {
+        position: absolute;
+        top: 0;
+        right: 0;
+        left: 0;
+        bottom: 0;
+        display: table;
+        height: 100%;
+        width: 100%;
+        text-align: center;
+        clear: both;
+        z-index: 3;
+        margin: 0;
+        padding: 10px 0 0 0;
+        background-color: rgba(255, 255, 255, 0.26);
+        -webkit-transition: all 200ms ease-in-out;
+        transition: all 200ms ease-in-out;
+
+        .core-loading-spinner {
+            display: table-cell;
             text-align: center;
-            padding-top: 10px;
-            clear: both;
-            /* @todo @include darkmode() {
-                color: $core-dark-text-color;
-            } */
-        }
-
-        .core-loading-content {
-            display: inline;
-            padding-bottom: 1px; /* This makes height be real */
-        }
-
-        &.core-loading-noheight .core-loading-content {
-            height: auto;
-        }
-
-        &.safe-area-page {
-            padding-left: 0 !important;
-            padding-right: 0 !important;
-
-            > .core-loading-content > *:not[padding],
-            > .core-loading-content-loading > *:not[padding] {
-                // @todo @include safe-area-padding-horizontal(0px, 0px);
-            }
+            vertical-align: middle;
         }
     }
 
-    .scroll-content > core-loading,
-    ion-content > .scroll-content > core-loading,
-    core-tab core-loading,
-    .core-loading-center {
-        position: static !important;
+    .core-loading-content {
+        display: inline;
+        padding-bottom: 1px; /* This makes height be real */
     }
 
-    .scroll-content > core-loading,
-    ion-content > .scroll-content > core-loading,
-    core-tab core-loading,
-    .core-loading-center,
-    core-loading.core-loading-loaded {
+    &.core-loading-noheight .core-loading-content {
+        height: auto;
+    }
+
+    &.core-loading-loaded {
         position: relative;
-
-        > .core-loading-container {
-            position: absolute;
-            // @todo @include position(0, 0, 0, 0);
-            display: table;
-            height: 100%;
-            width: 100%;
-            z-index: 1;
-            margin: 0;
-            padding: 0;
-            clear: both;
-
-            .core-loading-spinner {
-                display: table-cell;
-                text-align: center;
-                vertical-align: middle;
-            }
-        }
     }
 }
diff --git a/src/app/components/show-password/core-show-password.html b/src/app/components/show-password/core-show-password.html
index d90db3f49..ef0ac0ce1 100644
--- a/src/app/components/show-password/core-show-password.html
+++ b/src/app/components/show-password/core-show-password.html
@@ -1,4 +1,4 @@
-
-
-    
+
+
+    
 
diff --git a/src/app/components/show-password/show-password.scss b/src/app/components/show-password/show-password.scss
index 26188a26b..1f98c810c 100644
--- a/src/app/components/show-password/show-password.scss
+++ b/src/app/components/show-password/show-password.scss
@@ -1,38 +1,35 @@
-ion-app.app-root core-show-password {
-    padding: 0px;
-    width: 100%;
-    position: relative;
+:host {
+    display: contents;
 
-    ion-input input.text-input {
-        // @todo @include padding(null, 47px, null, null);
-    }
-
-    .button[icon-only] {
+    ion-button {
         background: transparent;
-        // @todo padding: 0 ($content-padding / 2);
+        padding: 0 calc(var(--padding-start) / 2);
         position: absolute;
-        // @todo @include position(null, 0, $content-padding / 2, null);
+        right: 0;
+        bottom: calc(var(--padding-bottom) / 2);
         margin-top: 0;
         margin-bottom: 0;
-    }
-
-    .core-ioninput-password {
-        padding-top: 0;
-        padding-bottom: 0;
+        z-index: 3;
     }
 }
 
-ion-app.app-root.md {
-    .item-label-stacked core-show-password .button[icon-only] {
-        bottom: 0;
-    }
+::slotted(ion-input) {
+    --padding-end: 47px !important;
 }
 
-ion-app.app-root.ios {
-    .item-label-stacked core-show-password .button[icon-only] {
-        bottom: -5px;
-    }
-    core-show-password .button[icon-only] {
-        bottom: 0;
-    }
+:host-context([dir="rtl"]) ion-button {
+    left: 0;
+    right: unset;
+}
+
+:host-context(.md.item-label.stacked) ion-button {
+    bottom: 0;
+}
+
+:host-context(.iositem-label.stacked) ion-button {
+    bottom: -5px;
+}
+
+:host-context(.ios) ion-button {
+    bottom: 0;
 }
diff --git a/src/app/components/show-password/show-password.ts b/src/app/components/show-password/show-password.ts
index 6cec76522..d90cf3615 100644
--- a/src/app/components/show-password/show-password.ts
+++ b/src/app/components/show-password/show-password.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { Component, OnInit, AfterViewInit, Input, ElementRef, ContentChild } from '@angular/core';
+import { Component, OnInit, AfterViewInit, Input, ElementRef, ContentChild, ViewEncapsulation } from '@angular/core';
 import { IonInput } from '@ionic/angular';
 
 import { CoreApp } from '@services/app';
@@ -29,7 +29,7 @@ import { CoreUtils } from '@services/utils/utils';
  *
  * Example:
  *
- * 
+ * 
  *     
  * 
  */
@@ -37,6 +37,7 @@ import { CoreUtils } from '@services/utils/utils';
     selector: 'core-show-password',
     templateUrl: 'core-show-password.html',
     styleUrls: ['show-password.scss'],
+    encapsulation: ViewEncapsulation.ShadowDom,
 })
 export class CoreShowPasswordComponent implements OnInit, AfterViewInit {
 
diff --git a/src/app/core/courses/courses.module.ts b/src/app/core/courses/courses.module.ts
index 38449fa0a..4dd76b82a 100644
--- a/src/app/core/courses/courses.module.ts
+++ b/src/app/core/courses/courses.module.ts
@@ -14,17 +14,8 @@
 
 import { NgModule } from '@angular/core';
 
-import { CoreMainMenuDelegate } from '@core/mainmenu/services/delegate';
-import { CoreHomeMainMenuHandler } from './handlers/mainmenu';
-
 @NgModule({
     imports: [],
     declarations: [],
 })
-export class CoreCoursesModule {
-
-    constructor(mainMenuDelegate: CoreMainMenuDelegate) {
-        mainMenuDelegate.registerHandler(new CoreHomeMainMenuHandler());
-    }
-
-}
+export class CoreCoursesModule { }
diff --git a/src/app/core/login/pages/site/site.page.ts b/src/app/core/login/pages/site/site.page.ts
index 158594e0d..67aef30cb 100644
--- a/src/app/core/login/pages/site/site.page.ts
+++ b/src/app/core/login/pages/site/site.page.ts
@@ -79,7 +79,7 @@ export class CoreLoginSitePage implements OnInit {
         // Load fixed sites if they're set.
         if (CoreLoginHelper.instance.hasSeveralFixedSites()) {
             url = this.initSiteSelector();
-        } else if (CoreConstants.CONFIG.enableonboarding && !CoreApp.instance.isIOS() && !CoreApp.instance.isMac()) {
+        } else if (CoreConstants.CONFIG.enableonboarding && !CoreApp.instance.isIOS()) {
             this.initOnboarding();
         }
 
diff --git a/src/app/core/login/services/helper.ts b/src/app/core/login/services/helper.ts
index 5c7d9609b..1d48d43fd 100644
--- a/src/app/core/login/services/helper.ts
+++ b/src/app/core/login/services/helper.ts
@@ -601,11 +601,6 @@ export class CoreLoginHelperProvider {
      * @return True if embedded browser, false othwerise.
      */
     isSSOEmbeddedBrowser(code: number): boolean {
-        if (CoreApp.instance.isLinux()) {
-            // In Linux desktop app, always use embedded browser.
-            return true;
-        }
-
         return code == CoreConstants.LOGIN_SSO_INAPP_CODE;
     }
 
@@ -722,16 +717,11 @@ export class CoreLoginHelperProvider {
             oauthsso: params.id,
         });
 
-        if (CoreApp.instance.isLinux()) {
-            // In Linux desktop app, always use embedded browser.
-            CoreUtils.instance.openInApp(loginUrl);
-        } else {
-            // Always open it in browser because the user might have the session stored in there.
-            CoreUtils.instance.openInBrowser(loginUrl);
+        // Always open it in browser because the user might have the session stored in there.
+        CoreUtils.instance.openInBrowser(loginUrl);
 
-            const nav =  window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
-            nav.app?.exitApp();
-        }
+        const nav =  window.navigator; // eslint-disable-line @typescript-eslint/no-explicit-any
+        nav.app?.exitApp();
 
         return true;
     }
@@ -1071,7 +1061,6 @@ export class CoreLoginHelperProvider {
      */
     protected showMoodleAppNoticeModal(message: string): void {
         const storesConfig: CoreStoreConfig = CoreConstants.CONFIG.appstores;
-        storesConfig.desktop = 'https://download.moodle.org/desktop/';
         storesConfig.mobile = 'https://download.moodle.org/mobile/';
         storesConfig.default = 'https://download.moodle.org/mobile/';
 
diff --git a/src/app/core/courses/handlers/mainmenu.ts b/src/app/core/mainmenu/handlers/mainmenu.ts
similarity index 92%
rename from src/app/core/courses/handlers/mainmenu.ts
rename to src/app/core/mainmenu/handlers/mainmenu.ts
index 337ddaed5..a4c2d1fad 100644
--- a/src/app/core/courses/handlers/mainmenu.ts
+++ b/src/app/core/mainmenu/handlers/mainmenu.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/services/delegate';
+import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../services/delegate';
 
 /**
  * Handler to add Home into main menu.
@@ -51,7 +51,7 @@ export class CoreHomeMainMenuHandler implements CoreMainMenuHandler {
     getDisplayData(): CoreMainMenuHandlerData {
         return {
             icon: 'fa-home',
-            title: 'core.courses.mymoodle',
+            title: 'core.mainmenu.home',
             page: 'home',
             class: 'core-home-handler',
         };
diff --git a/src/app/core/mainmenu/lang/en.json b/src/app/core/mainmenu/lang/en.json
index 4ff96fbf7..a6558e06e 100644
--- a/src/app/core/mainmenu/lang/en.json
+++ b/src/app/core/mainmenu/lang/en.json
@@ -1,6 +1,7 @@
 {
     "changesite": "Change site",
     "help": "Help",
+    "home": "Home",
     "logout": "Log out",
     "website": "Website"
-}
\ No newline at end of file
+}
diff --git a/src/app/core/mainmenu/mainmenu-routing.module.ts b/src/app/core/mainmenu/mainmenu-routing.module.ts
index 9633807c5..9e45e3e5e 100644
--- a/src/app/core/mainmenu/mainmenu-routing.module.ts
+++ b/src/app/core/mainmenu/mainmenu-routing.module.ts
@@ -25,7 +25,7 @@ const routes: Routes = [
         children: [
             {
                 path: 'home', // @todo: Add this route dynamically.
-                loadChildren: () => import('../courses/pages/home/home.page.module').then( m => m.CoreCoursesHomePageModule),
+                loadChildren: () => import('./pages/home/home.page.module').then( m => m.CoreHomePageModule),
             },
             {
                 path: 'more',
diff --git a/src/app/core/mainmenu/mainmenu.module.ts b/src/app/core/mainmenu/mainmenu.module.ts
index 1cd8f561e..8e4e5fde5 100644
--- a/src/app/core/mainmenu/mainmenu.module.ts
+++ b/src/app/core/mainmenu/mainmenu.module.ts
@@ -18,12 +18,16 @@ import { CommonModule } from '@angular/common';
 import { IonicModule } from '@ionic/angular';
 import { TranslateModule } from '@ngx-translate/core';
 
-import { CoreComponentsModule } from '@/app/components/components.module';
-import { CoreDirectivesModule } from '@/app/directives/directives.module';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+
+import { CoreMainMenuDelegate } from './services/delegate';
 
 import { CoreMainMenuRoutingModule } from './mainmenu-routing.module';
 import { CoreMainMenuPage } from './pages/menu/menu.page';
 import { CoreMainMenuMorePage } from './pages/more/more.page';
+import { CoreHomeMainMenuHandler } from './handlers/mainmenu';
+
 
 @NgModule({
     imports: [
@@ -39,4 +43,10 @@ import { CoreMainMenuMorePage } from './pages/more/more.page';
         CoreMainMenuMorePage,
     ],
 })
-export class CoreMainMenuModule {}
+export class CoreMainMenuModule {
+
+    constructor(mainMenuDelegate: CoreMainMenuDelegate) {
+        mainMenuDelegate.registerHandler(new CoreHomeMainMenuHandler());
+    }
+
+}
diff --git a/src/app/core/courses/pages/home/home.html b/src/app/core/mainmenu/pages/home/home.html
similarity index 79%
rename from src/app/core/courses/pages/home/home.html
rename to src/app/core/mainmenu/pages/home/home.html
index 79b79100d..2b428a553 100644
--- a/src/app/core/courses/pages/home/home.html
+++ b/src/app/core/mainmenu/pages/home/home.html
@@ -16,5 +16,7 @@
 
 
     
-    Home page.
-
\ No newline at end of file
+    
+        Home page
+    
+
diff --git a/src/app/core/courses/pages/home/home.page.module.ts b/src/app/core/mainmenu/pages/home/home.page.module.ts
similarity index 89%
rename from src/app/core/courses/pages/home/home.page.module.ts
rename to src/app/core/mainmenu/pages/home/home.page.module.ts
index 9397ce678..ef2e8f653 100644
--- a/src/app/core/courses/pages/home/home.page.module.ts
+++ b/src/app/core/mainmenu/pages/home/home.page.module.ts
@@ -21,12 +21,12 @@ import { TranslateModule } from '@ngx-translate/core';
 import { CoreComponentsModule } from '@components/components.module';
 import { CoreDirectivesModule } from '@directives/directives.module';
 
-import { CoreCoursesHomePage } from './home.page';
+import { CoreHomePage } from './home.page';
 
 const routes: Routes = [
     {
         path: '',
-        component: CoreCoursesHomePage,
+        component: CoreHomePage,
     },
 ];
 
@@ -40,8 +40,8 @@ const routes: Routes = [
         CoreDirectivesModule,
     ],
     declarations: [
-        CoreCoursesHomePage,
+        CoreHomePage,
     ],
     exports: [RouterModule],
 })
-export class CoreCoursesHomePageModule {}
+export class CoreHomePageModule {}
diff --git a/src/app/core/courses/pages/home/home.page.ts b/src/app/core/mainmenu/pages/home/home.page.ts
similarity index 90%
rename from src/app/core/courses/pages/home/home.page.ts
rename to src/app/core/mainmenu/pages/home/home.page.ts
index 1664c9d14..b5b2410b2 100644
--- a/src/app/core/courses/pages/home/home.page.ts
+++ b/src/app/core/mainmenu/pages/home/home.page.ts
@@ -18,11 +18,11 @@ import { Component, OnInit } from '@angular/core';
  * Page that displays the Home.
  */
 @Component({
-    selector: 'page-core-courses-home',
+    selector: 'page-core-home',
     templateUrl: 'home.html',
     styleUrls: ['home.scss'],
 })
-export class CoreCoursesHomePage implements OnInit {
+export class CoreHomePage implements OnInit {
 
     siteName = 'Hello world';
 
diff --git a/src/app/core/courses/pages/home/home.scss b/src/app/core/mainmenu/pages/home/home.scss
similarity index 100%
rename from src/app/core/courses/pages/home/home.scss
rename to src/app/core/mainmenu/pages/home/home.scss
diff --git a/src/app/core/mainmenu/pages/more/more.html b/src/app/core/mainmenu/pages/more/more.html
index be3de959f..5edaa06e7 100644
--- a/src/app/core/mainmenu/pages/more/more.html
+++ b/src/app/core/mainmenu/pages/more/more.html
@@ -22,7 +22,7 @@
             
         
         
+            (click)="openHandler(handler)" title="{{ handler.title | translate }}" detail="true" details>
             
             
                 {{ handler.title | translate}}
@@ -32,47 +32,47 @@
         
         
             
             
         
-        
+        
             
             
                 {{ 'core.scanqr' | translate }}
             
         
         
+            title="{{ 'core.mainmenu.website' | translate }}" details>
             
             
                 {{ 'core.mainmenu.website' | translate }}
             
         
         
+            title="{{ 'core.mainmenu.help' | translate }}" details>
             
             
                 {{ 'core.mainmenu.help' | translate }}
             
         
-        
+        
             
             
                 {{ 'core.settings.preferences' | translate }}
             
         
-        
+        
             
             
                 {{ logoutLabel | translate }}
@@ -80,7 +80,7 @@
         
         
         
+            title="{{ 'core.settings.appsettings' | translate }}" details>
             
             
                 {{ 'core.settings.appsettings' | translate }}
diff --git a/src/app/core/settings/pages/about/about.html b/src/app/core/settings/pages/about/about.html
index c0d25bb80..8a47fbf42 100644
--- a/src/app/core/settings/pages/about/about.html
+++ b/src/app/core/settings/pages/about/about.html
@@ -1,4 +1,3 @@
-
 
     
         
@@ -11,18 +10,18 @@
 
 
 
-    
+    
         {{ appName }} {{ versionName }}
     
-    
+    
         
         {{ 'core.settings.opensourcelicenses' | translate }}
     
-    
+    
         
         {{ 'core.settings.privacypolicy' | translate }}
     
-    
+    
         
         {{ 'core.settings.deviceinfo' | translate }}
     
diff --git a/src/app/core/settings/pages/about/about.page.ts b/src/app/core/settings/pages/about/about.page.ts
index cf00be122..bc35c3244 100644
--- a/src/app/core/settings/pages/about/about.page.ts
+++ b/src/app/core/settings/pages/about/about.page.ts
@@ -33,7 +33,7 @@ export class CoreSettingsAboutPage {
     ) {
         const currentSite = CoreSites.instance.getCurrentSite();
 
-        this.appName = CoreApp.instance.isDesktop() ? CoreConstants.CONFIG.desktopappname : CoreConstants.CONFIG.appname;
+        this.appName = CoreConstants.CONFIG.appname;
         this.versionName = CoreConstants.CONFIG.versionname;
 
         // Calculate the privacy policy to use.
diff --git a/src/app/core/settings/pages/deviceinfo/deviceinfo.page.ts b/src/app/core/settings/pages/deviceinfo/deviceinfo.page.ts
index 322fb6585..6149b260b 100644
--- a/src/app/core/settings/pages/deviceinfo/deviceinfo.page.ts
+++ b/src/app/core/settings/pages/deviceinfo/deviceinfo.page.ts
@@ -117,25 +117,14 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy {
                 }
             }
         } else {
-            this.deviceInfo.deviceType = appProvider.isDesktop() ? 'desktop' : 'browser';
-            if (appProvider.isLinux()) {
-                this.deviceInfo.deviceOs = 'linux';
-                this.deviceOsTranslated = 'Linux';
-            } else if (appProvider.isMac()) {
-                this.deviceInfo.deviceOs = 'mac';
-                this.deviceOsTranslated = 'MacOS';
-            } else if (appProvider.isWindows()) {
-                this.deviceInfo.deviceOs = 'windows';
-                this.deviceOsTranslated = 'Windows';
+            this.deviceInfo.deviceType = 'browser';
+            const matches = navigator.userAgent.match(/\(([^)]*)\)/);
+            if (matches && matches.length > 1) {
+                this.deviceInfo.deviceOs = matches[1];
+                this.deviceOsTranslated = matches[1];
             } else {
-                const matches = navigator.userAgent.match(/\(([^)]*)\)/);
-                if (matches && matches.length > 1) {
-                    this.deviceInfo.deviceOs = matches[1];
-                    this.deviceOsTranslated = matches[1];
-                } else {
-                    this.deviceInfo.deviceOs = 'unknown';
-                    this.deviceOsTranslated = translate.instant('core.unknown');
-                }
+                this.deviceInfo.deviceOs = 'unknown';
+                this.deviceOsTranslated = translate.instant('core.unknown');
             }
         }
 
diff --git a/src/app/directives/fa-icon.ts b/src/app/directives/fa-icon.ts
index c68c7bdf5..f2f60c509 100644
--- a/src/app/directives/fa-icon.ts
+++ b/src/app/directives/fa-icon.ts
@@ -32,8 +32,6 @@ export class CoreFaIconDirective implements OnChanges {
 
     @Input() name = '';
 
-    // TODO: Support slash, RTL and fixed width.
-
     protected element: HTMLElement;
 
     protected logger: CoreLogger;
diff --git a/src/app/lang/en.json b/src/app/lang/en.json
index 00ce91b93..bd3da88ca 100644
--- a/src/app/lang/en.json
+++ b/src/app/lang/en.json
@@ -1,11 +1,18 @@
 {
     "back": "Back",
     "browser": "Browser",
+    "cannotconnect": "Cannot connect",
+    "cannotconnecttrouble": "We're having trouble connecting to your site.",
+    "cannotconnectverify": "Please check the address is correct.",
     "copiedtoclipboard": "Text copied to clipboard",
+    "loading": "Loading",
+    "needhelp": "Need help?",
+    "networkerrormsg": "There was a problem connecting to the site. Please check your connection and try again.",
     "no": "No",
     "offline": "Offline",
     "ok": "OK",
     "online": "Online",
+    "tryagain": "Try again",
     "unknown": "Unknown",
     "yes": "Yes"
 }
diff --git a/src/app/services/app.ts b/src/app/services/app.ts
index 2862dc336..a22293047 100644
--- a/src/app/services/app.ts
+++ b/src/app/services/app.ts
@@ -226,22 +226,6 @@ export class CoreAppProvider {
      * @return Store URL.
      */
     getAppStoreUrl(storesConfig: CoreStoreConfig): string | undefined {
-        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;
         }
@@ -260,10 +244,11 @@ export class CoreAppProvider {
     /**
      * Checks if the app is running in a 64 bits desktop environment (not browser).
      *
-     * @return Whether the app is running in a 64 bits desktop environment (not browser).
+     * @return false.
+     * @deprecated Desktop support has been removed.
      */
     is64Bits(): boolean {
-        return this.isDesktop() && window.process.arch == 'x64';
+        return false;
     }
 
     /**
@@ -278,10 +263,10 @@ export class CoreAppProvider {
     /**
      * Checks if the app is running in a desktop environment (not browser).
      *
-     * @return Whether the app is running in a desktop environment (not browser).
+     * @return false.
+     * @deprecated Desktop support has been removed.
      */
     isDesktop(): boolean {
-        // @todo
         return false;
     }
 
@@ -324,39 +309,21 @@ export class CoreAppProvider {
     /**
      * Check if the app is running in a Linux environment.
      *
-     * @return Whether it's running in a Linux environment.
+     * @return false.
+     * @deprecated Desktop support has been removed.
      */
     isLinux(): boolean {
-        if (!this.isDesktop()) {
-            return false;
-        }
-
-        try {
-            // @todo return require('os').platform().indexOf('linux') === 0;
-
-            return false;
-        } catch (ex) {
-            return false;
-        }
+        return false;
     }
 
     /**
      * Check if the app is running in a Mac OS environment.
      *
-     * @return Whether it's running in a Mac OS environment.
+     * @return false.
+     * @deprecated Desktop support has been removed.
      */
     isMac(): boolean {
-        if (!this.isDesktop()) {
-            return false;
-        }
-
-        try {
-            // @todo return require('os').platform().indexOf('darwin') === 0;
-
-            return false;
-        } catch (ex) {
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -435,20 +402,11 @@ export class CoreAppProvider {
     /**
      * Check if the app is running in a Windows environment.
      *
-     * @return Whether it's running in a Windows environment.
+     * @return false.
+     * @deprecated Desktop support has been removed.
      */
     isWindows(): boolean {
-        if (!this.isDesktop()) {
-            return false;
-        }
-
-        try {
-            // @todo return require('os').platform().indexOf('win') === 0;
-
-            return false;
-        } catch (ex) {
-            return false;
-        }
+        return false;
     }
 
     /**
@@ -724,26 +682,6 @@ export type CoreRedirectData = {
  * Store config data.
  */
 export type 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.
      */
diff --git a/src/app/services/cron.ts b/src/app/services/cron.ts
index a4e6cdf38..fd13576ab 100644
--- a/src/app/services/cron.ts
+++ b/src/app/services/cron.ts
@@ -35,7 +35,6 @@ export class CoreCronDelegate {
     // Constants.
     static readonly DEFAULT_INTERVAL = 3600000; // Default interval is 1 hour.
     static readonly MIN_INTERVAL = 300000; // Minimum interval is 5 minutes.
-    static readonly DESKTOP_MIN_INTERVAL = 60000; // Minimum interval in desktop is 1 minute.
     static readonly MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes.
 
     // Variables for database.
@@ -237,7 +236,7 @@ export class CoreCronDelegate {
         }
 
         // Don't allow intervals lower than the minimum.
-        const minInterval = CoreApp.instance.isDesktop() ? CoreCronDelegate.DESKTOP_MIN_INTERVAL : CoreCronDelegate.MIN_INTERVAL;
+        const minInterval = CoreCronDelegate.MIN_INTERVAL;
         const handlerInterval = this.handlers[name].getInterval!();
 
         if (!handlerInterval) {
diff --git a/src/app/services/file.ts b/src/app/services/file.ts
index 6d9702625..980bc83d0 100644
--- a/src/app/services/file.ts
+++ b/src/app/services/file.ts
@@ -568,7 +568,7 @@ export class CoreFileProvider {
         // Create file (and parent folders) to prevent errors.
         const fileEntry = await this.createFile(path);
 
-        if (this.isHTMLAPI && !CoreApp.instance.isDesktop() &&
+        if (this.isHTMLAPI &&
                 (typeof data == 'string' || data.toString() == '[object ArrayBuffer]')) {
             // We need to write Blobs.
             const extension = CoreMimetypeUtils.instance.getFileExtension(path);
diff --git a/src/app/services/filepool.ts b/src/app/services/filepool.ts
index f1b2ec9f2..6baddc81c 100644
--- a/src/app/services/filepool.ts
+++ b/src/app/services/filepool.ts
@@ -1756,12 +1756,8 @@ export class CoreFilepoolProvider {
         const path = await this.getFilePath(siteId, fileId);
         const fileEntry = await CoreFile.instance.getFile(path);
 
-        // This URL is usually used to launch files or put them in HTML. In desktop we need the internal URL.
-        if (CoreApp.instance.isDesktop()) {
-            return fileEntry.toInternalURL();
-        } else {
-            return fileEntry.toURL();
-        }
+        // This URL is usually used to launch files or put them in HTML.
+        return fileEntry.toURL();
     }
 
     /**
@@ -2937,11 +2933,6 @@ export class CoreFilepoolProvider {
             return;
         }
 
-        if (CoreApp.instance.isDesktop()) {
-            // In desktop always download first.
-            return;
-        }
-
         const mimetype = await CoreUtils.instance.getMimeTypeFromUrl(url);
         // If the file is streaming (audio or video) we reject.
         if (mimetype.indexOf('video') != -1 || mimetype.indexOf('audio') != -1) {
diff --git a/src/app/services/local-notifications.ts b/src/app/services/local-notifications.ts
index 2c38641f7..5f1c7e3d7 100644
--- a/src/app/services/local-notifications.ts
+++ b/src/app/services/local-notifications.ts
@@ -222,8 +222,7 @@ export class CoreLocalNotificationsProvider {
      */
     canDisableSound(): boolean {
         // Only allow disabling sound in Android 7 or lower. In iOS and Android 8+ it can easily be done with system settings.
-        return this.isAvailable() &&!CoreApp.instance.isDesktop() && CoreApp.instance.isAndroid() &&
-            Number(Device.instance.version?.split('.')[0]) < 8;
+        return this.isAvailable() && CoreApp.instance.isAndroid() && Number(Device.instance.version?.split('.')[0]) < 8;
     }
 
     /**
@@ -363,7 +362,7 @@ export class CoreLocalNotificationsProvider {
     isAvailable(): boolean {
         const win =  window; // eslint-disable-line @typescript-eslint/no-explicit-any
 
-        return CoreApp.instance.isDesktop() || !!win.cordova?.plugins?.notification?.local;
+        return !!win.cordova?.plugins?.notification?.local;
     }
 
     /**
diff --git a/src/app/services/sites.ts b/src/app/services/sites.ts
index 9292eacf4..71604d276 100644
--- a/src/app/services/sites.ts
+++ b/src/app/services/sites.ts
@@ -888,7 +888,6 @@ export class CoreSitesProvider {
             const storesConfig: CoreStoreConfig = {
                 android: config.tool_mobile_androidappid,
                 ios: config.tool_mobile_iosappid,
-                desktop: config.tool_mobile_setuplink || 'https://download.moodle.org/desktop/',
                 mobile: config.tool_mobile_setuplink || 'https://download.moodle.org/mobile/',
                 default: config.tool_mobile_setuplink,
             };
diff --git a/src/app/services/utils/dom.ts b/src/app/services/utils/dom.ts
index 5dfa487d2..86b2ccf07 100644
--- a/src/app/services/utils/dom.ts
+++ b/src/app/services/utils/dom.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import { Injectable, SimpleChange, ElementRef, KeyValueChanges } from '@angular/core';
-import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
+import { DomSanitizer } from '@angular/platform-browser';
 import { IonContent } from '@ionic/angular';
 import { AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core';
 import { Md5 } from 'ts-md5';
@@ -144,11 +144,6 @@ export class CoreDomUtilsProvider {
         const readableSize = CoreTextUtils.instance.bytesToSize(size.size, 2);
 
         const getAvailableBytes = async (): Promise => {
-            if (CoreApp.instance.isDesktop()) {
-                // Free space calculation is not supported on desktop.
-                return null;
-            }
-
             const availableBytes = await CoreFile.instance.calculateFreeSpace();
 
             if (CoreApp.instance.isAndroid()) {
@@ -575,18 +570,6 @@ export class CoreDomUtilsProvider {
         return parseInt(style[measure], 10) || 0;
     }
 
-    /**
-     * Get the HTML code to render a connection warning icon.
-     *
-     * @return HTML Code.
-     */
-    getConnectionWarningIconHtml(): string {
-        return '' +
-                '' +
-                '' +
-            '
';
-    }
-
     /**
      * Returns width of an element.
      *
@@ -655,18 +638,14 @@ export class CoreDomUtilsProvider {
     }
 
     /**
-     * Given an error message, return a suitable error title.
+     * Given a message, it deduce if it's a network error.
      *
-     * @param message The error message.
-     * @return Title.
+     * @param message Message text.
+     * @return True if the message error is a network error, false otherwise.
      */
-    private getErrorTitle(message: string): SafeHtml | string {
-        if (message == Translate.instance.instant('core.networkerrormsg') ||
-                message == Translate.instance.instant('core.fileuploader.errormustbeonlinetoupload')) {
-            return this.domSanitizer.bypassSecurityTrustHtml(this.getConnectionWarningIconHtml());
-        }
-
-        return CoreTextUtils.instance.decodeHTML(Translate.instance.instant('core.error'));
+    protected isNetworkError(message: string): boolean {
+        return message == Translate.instance.instant('core.networkerrormsg') ||
+            message == Translate.instance.instant('core.fileuploader.errormustbeonlinetoupload');
     }
 
     /**
@@ -1245,7 +1224,7 @@ export class CoreDomUtilsProvider {
         // Store the alert and remove it when dismissed.
         this.displayedAlerts[alertId] = alert;
 
-        // // Set the callbacks to trigger an observable event.
+        // Set the callbacks to trigger an observable event.
         // eslint-disable-next-line promise/catch-or-return, promise/always-return
         alert.onDidDismiss().then(() => {
             delete this.displayedAlerts[alertId];
@@ -1371,7 +1350,18 @@ export class CoreDomUtilsProvider {
             return Promise.resolve(null);
         }
 
-        return this.showAlert( this.getErrorTitle(message), message, undefined, autocloseTime);
+        const alertOptions: AlertOptions = {
+            message: message,
+            buttons: [Translate.instance.instant('core.ok')],
+        };
+
+        if (this.isNetworkError(message)) {
+            alertOptions.cssClass = 'core-alert-network-error';
+        } else {
+            alertOptions.header = Translate.instance.instant('core.error');
+        }
+
+        return this.showAlertWithOptions(alertOptions, autocloseTime);
     }
 
     /**
diff --git a/src/app/services/utils/utils.ts b/src/app/services/utils/utils.ts
index 84f793f3e..e8eedbb87 100644
--- a/src/app/services/utils/utils.ts
+++ b/src/app/services/utils/utils.ts
@@ -227,15 +227,10 @@ export class CoreUtilsProvider {
 
     /**
      * Close the InAppBrowser window.
-     *
-     * @param closeAll Desktop only. True to close all secondary windows, false to close only the "current" one.
      */
-    closeInAppBrowser(closeAll?: boolean): void {
+    closeInAppBrowser(): void {
         if (this.iabInstance) {
             this.iabInstance.close();
-            if (closeAll && CoreApp.instance.isDesktop()) {
-                // @todo require('electron').ipcRenderer.send('closeSecondaryWindows');
-            }
         }
     }
 
@@ -959,7 +954,7 @@ export class CoreUtilsProvider {
 
         this.iabInstance = InAppBrowser.instance.create(url, '_blank', options);
 
-        if (CoreApp.instance.isDesktop() || CoreApp.instance.isMobile()) {
+        if (CoreApp.instance.isMobile()) {
             let loadStopSubscription;
             const loadStartUrls: string[] = [];
 
@@ -1011,11 +1006,7 @@ export class CoreUtilsProvider {
      * @param url The URL to open.
      */
     openInBrowser(url: string): void {
-        if (CoreApp.instance.isDesktop()) {
-            // @todo
-        } else {
-            window.open(url, '_system');
-        }
+        window.open(url, '_system');
     }
 
     /**
@@ -1513,7 +1504,7 @@ export class CoreUtilsProvider {
 
 
         if (!CoreApp.instance.isMobile()) {
-            return Promise.reject('QRScanner isn\'t available in desktop apps.');
+            return Promise.reject('QRScanner isn\'t available in browser.');
         }
 
         // Ask the user for permission to use the camera.
diff --git a/src/assets/icon/favicon.ico b/src/assets/icon/favicon.ico
deleted file mode 100644
index 483c9647c..000000000
Binary files a/src/assets/icon/favicon.ico and /dev/null differ
diff --git a/src/assets/icon/favicon.png b/src/assets/icon/favicon.png
new file mode 100644
index 000000000..7abfc0a4f
Binary files /dev/null and b/src/assets/icon/favicon.png differ
diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json
index 7eeea14b8..5b7aed812 100644
--- a/src/assets/lang/en.json
+++ b/src/assets/lang/en.json
@@ -302,6 +302,9 @@
     "assets.mimetypes.video": "Video file ({{$a.EXT}})",
     "core.back": "Back",
     "core.browser": "Browser",
+    "core.cannotconnect": "Cannot connect",
+    "core.cannotconnecttrouble": "We're having trouble connecting to your site.",
+    "core.cannotconnectverify": "Please check the address is correct.",
     "core.copiedtoclipboard": "Text copied to clipboard",
     "core.courses.addtofavourites": "Star this course",
     "core.courses.allowguests": "This course allows guest users to enter",
@@ -340,6 +343,7 @@
     "core.courses.sendpaymentbutton": "Send payment via PayPal",
     "core.courses.show": "Restore to view",
     "core.courses.totalcoursesearchresults": "Total courses: {{$a}}",
+    "core.loading": "Loading",
     "core.login.auth_email": "Email-based self-registration",
     "core.login.authenticating": "Authenticating",
     "core.login.cancel": "Cancel",
@@ -464,8 +468,11 @@
     "core.login.yourenteredsite": "Connect to your site",
     "core.mainmenu.changesite": "Change site",
     "core.mainmenu.help": "Help",
+    "core.mainmenu.home": "Home",
     "core.mainmenu.logout": "Log out",
     "core.mainmenu.website": "Website",
+    "core.needhelp": "Need help?",
+    "core.networkerrormsg": "There was a problem connecting to the site. Please check your connection and try again.",
     "core.no": "No",
     "core.offline": "Offline",
     "core.ok": "OK",
@@ -541,6 +548,7 @@
     "core.settings.syncsettings": "Synchronisation settings",
     "core.settings.total": "Total",
     "core.settings.wificonnection": "Wi-Fi connection",
+    "core.tryagain": "Try again",
     "core.unknown": "Unknown",
     "core.yes": "Yes"
 }
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index dfdd5afe6..df229b489 100644
--- a/src/index.html
+++ b/src/index.html
@@ -3,7 +3,7 @@
 
 
     
-    Ionic App
+    Moodle App
 
     
 
diff --git a/src/theme/app.scss b/src/theme/app.scss
index 033a25fa9..6a8b5c921 100644
--- a/src/theme/app.scss
+++ b/src/theme/app.scss
@@ -1,6 +1,72 @@
-// Add here base app styles.
 
+// Ionic toolbar.
 ion-toolbar ion-back-button,
 ion-toolbar .in-toolbar.button-clear {
     --color: var(--ion-color-primary-contrast);
 }
+
+// Ionic icon.
+ion-icon {
+    &.icon-slash::after,
+    &.icon-backslash::after {
+        content: " ";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: var(--ion-color-danger);
+        -webkit-mask: url("/assets/fonts/font-awesome/solid/slash.svg") no-repeat 50% 50%;
+        mask: url("/assets/fonts/font-awesome/solid/slash.svg") no-repeat 50% 50%;
+    }
+
+    &.icon-slash::after {
+        -webkit-transform: scale(-1, 1);
+        transform: scale(-1, 1);
+    }
+
+    &.fa-fw {
+        text-align: center;
+        width: 1.25em;
+    }
+}
+
+[dir=rtl] ion-icon.icon-flip-rtl {
+    -webkit-transform: scale(-1, 1);
+    transform: scale(-1, 1);
+}
+
+// Ionic alert.
+ion-alert.core-alert-network-error .alert-head {
+    position: relative;
+    content: " ";
+    background: url("/assets/fonts/font-awesome/solid/wifi.svg") no-repeat 50% 50%;
+    margin: 25px auto;
+
+    &::after {
+        content: " ";
+        position: absolute;
+        top: -20%;
+        right: -15%;
+        width: 50%;
+        height: 50%;
+        background-color: var(--ion-color-danger);
+        -webkit-mask: url("/assets/fonts/font-awesome/solid/exclamation-triangle.svg") no-repeat 50% 50%;
+        mask: url("/assets/fonts/font-awesome/solid/exclamation-triangle.svg") no-repeat 50% 50%;
+    }
+}
+[dir=rtl] ion-alert.core-alert-network-error .alert-head::after {
+    right: unset;
+    left: -15%;
+}
+
+// Ionic item divider.
+ion-item-divider {
+    --background: var(--gray-lighter);
+    border: 0;
+}
+
+// Ionic list.
+ion-list.list-md {
+    padding-bottom: 0;
+}
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index 1e05ea6d4..19e8234c4 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -27,7 +27,6 @@ declare global {
             CONFIG: {
                 app_id: string;
                 appname: string;
-                desktopappname: string;
                 versioncode: number;
                 versionname: string;
                 cache_update_frequency_usually: number;