diff --git a/src/addons/addons.module.ts b/src/addons/addons.module.ts index a49cffd18..7cedffb7f 100644 --- a/src/addons/addons.module.ts +++ b/src/addons/addons.module.ts @@ -14,63 +14,25 @@ import { NgModule } from '@angular/core'; -import { AddonBlockActivityResultsModule } from './block/activityresults/activityresults.module'; -import { AddonBlockBadgesModule } from './block/badges/badges.module'; -import { AddonBlockBlogMenuModule } from './block/blogmenu/blogmenu.module'; -import { AddonBlockBlogRecentModule } from './block/blogrecent/blogrecent.module'; -import { AddonBlockBlogTagsModule } from './block/blogtags/blogtags.module'; -import { AddonBlockCalendarMonthModule } from './block/calendarmonth/calendarmonth.module'; -import { AddonBlockCalendarUpcomingModule } from './block/calendarupcoming/calendarupcoming.module'; -import { AddonBlockCommentsModule } from './block/comments/comments.module'; -import { AddonBlockCompletionStatusModule } from './block/completionstatus/completionstatus.module'; -import { AddonBlockGlossaryRandomModule } from './block/glossaryrandom/glossaryrandom.module'; -import { AddonBlockHtmlModule } from './block/html/html.module'; -import { AddonBlockLearningPlansModule } from './block/learningplans/learningplans.module'; -import { AddonBlockMyOverviewModule } from './block/myoverview/myoverview.module'; -import { AddonBlockNewsItemsModule } from './block/newsitems/newsitems.module'; -import { AddonBlockOnlineUsersModule } from './block/onlineusers/onlineusers.module'; -import { AddonBlockPrivateFilesModule } from './block/privatefiles/privatefiles.module'; -import { AddonBlockRecentlyAccessedCoursesModule } from './block/recentlyaccessedcourses/recentlyaccessedcourses.module'; -import { AddonBlockRssClientModule } from './block/rssclient/rssclient.module'; -import { AddonBlockSelfCompletionModule } from './block/selfcompletion/selfcompletion.module'; -import { AddonBlockSiteMainMenuModule } from './block/sitemainmenu/sitemainmenu.module'; -import { AddonBlockStarredCoursesModule } from './block/starredcourses/starredcourses.module'; -import { AddonBlockTagsModule } from './block/tags/tags.module'; +import { AddonBlockModule } from './block/block.module'; import { AddonPrivateFilesModule } from './privatefiles/privatefiles.module'; import { AddonFilterModule } from './filter/filter.module'; import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module'; import { AddonBadgesModule } from './badges/badges.module'; import { AddonCalendarModule } from './calendar/calendar.module'; +import { AddonNotificationsModule } from './notifications/notifications.module'; +import { AddonMessageOutputModule } from './messageoutput/messageoutput.module'; @NgModule({ imports: [ + AddonBlockModule, AddonBadgesModule, AddonCalendarModule, AddonPrivateFilesModule, AddonFilterModule, - AddonBlockActivityResultsModule, - AddonBlockBadgesModule, - AddonBlockBlogMenuModule, - AddonBlockBlogRecentModule, - AddonBlockBlogTagsModule, - AddonBlockCalendarMonthModule, - AddonBlockCalendarUpcomingModule, - AddonBlockCommentsModule, - AddonBlockCompletionStatusModule, - AddonBlockGlossaryRandomModule, - AddonBlockHtmlModule, - AddonBlockMyOverviewModule, - AddonBlockLearningPlansModule, - AddonBlockNewsItemsModule, - AddonBlockOnlineUsersModule, - AddonBlockPrivateFilesModule, - AddonBlockRecentlyAccessedCoursesModule, - AddonBlockRssClientModule, - AddonBlockSelfCompletionModule, - AddonBlockSiteMainMenuModule, - AddonBlockStarredCoursesModule, - AddonBlockTagsModule, AddonUserProfileFieldModule, + AddonNotificationsModule, + AddonMessageOutputModule, ], }) export class AddonsModule {} diff --git a/src/addons/badges/pages/issued-badge/issued-badge.module.ts b/src/addons/badges/pages/issued-badge/issued-badge.module.ts index e29cbe77d..dcf0c421c 100644 --- a/src/addons/badges/pages/issued-badge/issued-badge.module.ts +++ b/src/addons/badges/pages/issued-badge/issued-badge.module.ts @@ -18,10 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBadgesIssuedBadgePage } from './issued-badge.page'; const routes: Routes = [ @@ -37,9 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], declarations: [ AddonBadgesIssuedBadgePage, diff --git a/src/addons/badges/pages/user-badges/user-badges.module.ts b/src/addons/badges/pages/user-badges/user-badges.module.ts index 92a9b6978..11da23507 100644 --- a/src/addons/badges/pages/user-badges/user-badges.module.ts +++ b/src/addons/badges/pages/user-badges/user-badges.module.ts @@ -18,10 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBadgesUserBadgesPage } from './user-badges.page'; const routes: Routes = [ @@ -37,9 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], declarations: [ AddonBadgesUserBadgesPage, diff --git a/src/addons/block/activityresults/components/components.module.ts b/src/addons/block/activityresults/components/components.module.ts index c7910cee2..0278e68f8 100644 --- a/src/addons/block/activityresults/components/components.module.ts +++ b/src/addons/block/activityresults/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockActivityResultsComponent } from './activityresults/activityresults'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockActivityResultsComponent } from './activityresults/activityre IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockActivityResultsComponent, diff --git a/src/addons/block/badges/components/components.module.ts b/src/addons/block/badges/components/components.module.ts index b4e67d7c8..db379c8aa 100644 --- a/src/addons/block/badges/components/components.module.ts +++ b/src/addons/block/badges/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockBadgesComponent } from './badges/badges'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockBadgesComponent } from './badges/badges'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockBadgesComponent, diff --git a/src/addons/block/block.module.ts b/src/addons/block/block.module.ts new file mode 100644 index 000000000..420c0e632 --- /dev/null +++ b/src/addons/block/block.module.ts @@ -0,0 +1,69 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; + +import { AddonBlockActivityResultsModule } from './activityresults/activityresults.module'; +import { AddonBlockBadgesModule } from './badges/badges.module'; +import { AddonBlockBlogMenuModule } from './blogmenu/blogmenu.module'; +import { AddonBlockBlogRecentModule } from './blogrecent/blogrecent.module'; +import { AddonBlockBlogTagsModule } from './blogtags/blogtags.module'; +import { AddonBlockCalendarMonthModule } from './calendarmonth/calendarmonth.module'; +import { AddonBlockCalendarUpcomingModule } from './calendarupcoming/calendarupcoming.module'; +import { AddonBlockCommentsModule } from './comments/comments.module'; +import { AddonBlockCompletionStatusModule } from './completionstatus/completionstatus.module'; +import { AddonBlockGlossaryRandomModule } from './glossaryrandom/glossaryrandom.module'; +import { AddonBlockHtmlModule } from './html/html.module'; +import { AddonBlockLearningPlansModule } from './learningplans/learningplans.module'; +import { AddonBlockMyOverviewModule } from './myoverview/myoverview.module'; +import { AddonBlockNewsItemsModule } from './newsitems/newsitems.module'; +import { AddonBlockOnlineUsersModule } from './onlineusers/onlineusers.module'; +import { AddonBlockPrivateFilesModule } from './privatefiles/privatefiles.module'; +import { AddonBlockRecentlyAccessedCoursesModule } from './recentlyaccessedcourses/recentlyaccessedcourses.module'; +import { AddonBlockRssClientModule } from './rssclient/rssclient.module'; +import { AddonBlockSelfCompletionModule } from './selfcompletion/selfcompletion.module'; +import { AddonBlockSiteMainMenuModule } from './sitemainmenu/sitemainmenu.module'; +import { AddonBlockStarredCoursesModule } from './starredcourses/starredcourses.module'; +import { AddonBlockTagsModule } from './tags/tags.module'; + +@NgModule({ + declarations: [], + imports: [ + AddonBlockActivityResultsModule, + AddonBlockBadgesModule, + AddonBlockBlogMenuModule, + AddonBlockBlogRecentModule, + AddonBlockBlogTagsModule, + AddonBlockCalendarMonthModule, + AddonBlockCalendarUpcomingModule, + AddonBlockCommentsModule, + AddonBlockCompletionStatusModule, + AddonBlockGlossaryRandomModule, + AddonBlockHtmlModule, + AddonBlockMyOverviewModule, + AddonBlockLearningPlansModule, + AddonBlockNewsItemsModule, + AddonBlockOnlineUsersModule, + AddonBlockPrivateFilesModule, + AddonBlockRecentlyAccessedCoursesModule, + AddonBlockRssClientModule, + AddonBlockSelfCompletionModule, + AddonBlockSiteMainMenuModule, + AddonBlockStarredCoursesModule, + AddonBlockTagsModule, + ], + providers: [], + exports: [], +}) +export class AddonBlockModule { } diff --git a/src/addons/block/blogmenu/components/components.module.ts b/src/addons/block/blogmenu/components/components.module.ts index ac8ea52ad..1429e23ca 100644 --- a/src/addons/block/blogmenu/components/components.module.ts +++ b/src/addons/block/blogmenu/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockBlogMenuComponent } from './blogmenu/blogmenu'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockBlogMenuComponent } from './blogmenu/blogmenu'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockBlogMenuComponent, diff --git a/src/addons/block/blogrecent/components/components.module.ts b/src/addons/block/blogrecent/components/components.module.ts index 552884705..5e0d74f9c 100644 --- a/src/addons/block/blogrecent/components/components.module.ts +++ b/src/addons/block/blogrecent/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockBlogRecentComponent } from './blogrecent/blogrecent'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockBlogRecentComponent } from './blogrecent/blogrecent'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockBlogRecentComponent, diff --git a/src/addons/block/blogtags/components/components.module.ts b/src/addons/block/blogtags/components/components.module.ts index 8e07f0c0c..d7d3f6a90 100644 --- a/src/addons/block/blogtags/components/components.module.ts +++ b/src/addons/block/blogtags/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockBlogTagsComponent } from './blogtags/blogtags'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockBlogTagsComponent } from './blogtags/blogtags'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockBlogTagsComponent, diff --git a/src/addons/block/myoverview/components/components.module.ts b/src/addons/block/myoverview/components/components.module.ts index 29199d738..d6f88d30f 100644 --- a/src/addons/block/myoverview/components/components.module.ts +++ b/src/addons/block/myoverview/components/components.module.ts @@ -18,10 +18,8 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '@features/courses/components/components.module'; - import { AddonBlockMyOverviewComponent } from './myoverview/myoverview'; @NgModule({ @@ -33,8 +31,7 @@ import { AddonBlockMyOverviewComponent } from './myoverview/myoverview'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreCoursesComponentsModule, ], exports: [ diff --git a/src/addons/block/newsitems/components/components.module.ts b/src/addons/block/newsitems/components/components.module.ts index 55c178d71..0622e150d 100644 --- a/src/addons/block/newsitems/components/components.module.ts +++ b/src/addons/block/newsitems/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockNewsItemsComponent } from './newsitems/newsitems'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockNewsItemsComponent } from './newsitems/newsitems'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockNewsItemsComponent, diff --git a/src/addons/block/onlineusers/components/components.module.ts b/src/addons/block/onlineusers/components/components.module.ts index 384ffd1d2..fdeb6a210 100644 --- a/src/addons/block/onlineusers/components/components.module.ts +++ b/src/addons/block/onlineusers/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockOnlineUsersComponent } from './onlineusers/onlineusers'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockOnlineUsersComponent } from './onlineusers/onlineusers'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockOnlineUsersComponent, diff --git a/src/addons/block/recentactivity/components/components.module.ts b/src/addons/block/recentactivity/components/components.module.ts index ce4fe2ba0..354e0ffff 100644 --- a/src/addons/block/recentactivity/components/components.module.ts +++ b/src/addons/block/recentactivity/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockRecentActivityComponent } from './recentactivity/recentactivity'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockRecentActivityComponent } from './recentactivity/recentactivi IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockRecentActivityComponent, diff --git a/src/addons/block/recentlyaccessedcourses/components/components.module.ts b/src/addons/block/recentlyaccessedcourses/components/components.module.ts index 560df1e3e..9326c73e5 100644 --- a/src/addons/block/recentlyaccessedcourses/components/components.module.ts +++ b/src/addons/block/recentlyaccessedcourses/components/components.module.ts @@ -17,8 +17,7 @@ import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '@features/courses/components/components.module'; import { AddonBlockRecentlyAccessedCoursesComponent } from './recentlyaccessedcourses/recentlyaccessedcourses'; @@ -31,8 +30,7 @@ import { AddonBlockRecentlyAccessedCoursesComponent } from './recentlyaccessedco CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreCoursesComponentsModule, ], exports: [ diff --git a/src/addons/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts b/src/addons/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts index aeac5a5df..ae2ac0928 100644 --- a/src/addons/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts +++ b/src/addons/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts @@ -15,7 +15,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { AddonBlockRecentlyAccessedCoursesComponentsModule } from './components/components.module'; import { AddonBlockRecentlyAccessedCoursesHandler } from './services/block-handler'; @@ -23,7 +23,7 @@ import { AddonBlockRecentlyAccessedCoursesHandler } from './services/block-handl @NgModule({ imports: [ IonicModule, - CoreComponentsModule, + CoreSharedModule, AddonBlockRecentlyAccessedCoursesComponentsModule, TranslateModule.forChild(), ], diff --git a/src/addons/block/rssclient/components/components.module.ts b/src/addons/block/rssclient/components/components.module.ts index b94be30d3..20087cf54 100644 --- a/src/addons/block/rssclient/components/components.module.ts +++ b/src/addons/block/rssclient/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockRssClientComponent } from './rssclient/rssclient'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockRssClientComponent } from './rssclient/rssclient'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockRssClientComponent, diff --git a/src/addons/block/sitemainmenu/components/components.module.ts b/src/addons/block/sitemainmenu/components/components.module.ts index 726a43dbb..52c1968e8 100644 --- a/src/addons/block/sitemainmenu/components/components.module.ts +++ b/src/addons/block/sitemainmenu/components/components.module.ts @@ -17,8 +17,7 @@ import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; // import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { AddonBlockSiteMainMenuComponent } from './sitemainmenu/sitemainmenu'; @@ -32,8 +31,7 @@ import { AddonBlockSiteMainMenuComponent } from './sitemainmenu/sitemainmenu'; CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, // CoreCourseComponentsModule, ], exports: [ diff --git a/src/addons/block/sitemainmenu/sitemainmenu.module.ts b/src/addons/block/sitemainmenu/sitemainmenu.module.ts index bc8310b50..a5b2d4e77 100644 --- a/src/addons/block/sitemainmenu/sitemainmenu.module.ts +++ b/src/addons/block/sitemainmenu/sitemainmenu.module.ts @@ -15,8 +15,7 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockSiteMainMenuComponentsModule } from './components/components.module'; import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { AddonBlockSiteMainMenuHandler } from './services/block-handler'; @@ -24,8 +23,7 @@ import { AddonBlockSiteMainMenuHandler } from './services/block-handler'; @NgModule({ imports: [ IonicModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, AddonBlockSiteMainMenuComponentsModule, TranslateModule.forChild(), ], diff --git a/src/addons/block/starredcourses/components/components.module.ts b/src/addons/block/starredcourses/components/components.module.ts index 8dc0291d0..3265a6dee 100644 --- a/src/addons/block/starredcourses/components/components.module.ts +++ b/src/addons/block/starredcourses/components/components.module.ts @@ -17,8 +17,7 @@ import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '@features/courses/components/components.module'; import { AddonBlockStarredCoursesComponent } from './starredcourses/starredcourses'; @@ -31,8 +30,7 @@ import { AddonBlockStarredCoursesComponent } from './starredcourses/starredcours CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreCoursesComponentsModule, ], exports: [ diff --git a/src/addons/block/tags/components/components.module.ts b/src/addons/block/tags/components/components.module.ts index e26cc198b..bd2774f82 100644 --- a/src/addons/block/tags/components/components.module.ts +++ b/src/addons/block/tags/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonBlockTagsComponent } from './tags/tags'; @NgModule({ @@ -32,8 +30,7 @@ import { AddonBlockTagsComponent } from './tags/tags'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [ AddonBlockTagsComponent, diff --git a/src/addons/calendar/components/components.module.ts b/src/addons/calendar/components/components.module.ts index 7f0b02c5b..078687589 100644 --- a/src/addons/calendar/components/components.module.ts +++ b/src/addons/calendar/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { AddonCalendarCalendarComponent } from './calendar/calendar'; import { AddonCalendarUpcomingEventsComponent } from './upcoming-events/upcoming-events'; @@ -37,9 +35,7 @@ import { AddonCalendarFilterPopoverComponent } from './filter/filter'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], providers: [ ], diff --git a/src/addons/calendar/pages/day/day.module.ts b/src/addons/calendar/pages/day/day.module.ts index 20d149967..b260cdb57 100644 --- a/src/addons/calendar/pages/day/day.module.ts +++ b/src/addons/calendar/pages/day/day.module.ts @@ -18,9 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { AddonCalendarComponentsModule } from '../../components/components.module'; import { AddonCalendarDayPage } from './day.page'; @@ -38,9 +36,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, AddonCalendarComponentsModule, ], declarations: [ diff --git a/src/addons/calendar/pages/edit-event/edit-event.module.ts b/src/addons/calendar/pages/edit-event/edit-event.module.ts index f9cf8c730..f08fef737 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.module.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.module.ts @@ -19,8 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreEditorComponentsModule } from '@features/editor/components/components.module'; import { AddonCalendarEditEventPage } from './edit-event.page'; @@ -40,8 +39,7 @@ const routes: Routes = [ FormsModule, ReactiveFormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreEditorComponentsModule, ], declarations: [ diff --git a/src/addons/calendar/pages/event/event.module.ts b/src/addons/calendar/pages/event/event.module.ts index 6fbc37425..565382336 100644 --- a/src/addons/calendar/pages/event/event.module.ts +++ b/src/addons/calendar/pages/event/event.module.ts @@ -19,9 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { AddonCalendarComponentsModule } from '../../components/components.module'; import { AddonCalendarEventPage } from './event.page'; @@ -40,9 +38,7 @@ const routes: Routes = [ IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, AddonCalendarComponentsModule, ], declarations: [ diff --git a/src/addons/calendar/pages/index/index.module.ts b/src/addons/calendar/pages/index/index.module.ts index 86a385d70..077ed990e 100644 --- a/src/addons/calendar/pages/index/index.module.ts +++ b/src/addons/calendar/pages/index/index.module.ts @@ -18,9 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { AddonCalendarComponentsModule } from '../../components/components.module'; import { AddonCalendarIndexPage } from './index.page'; @@ -38,9 +36,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, AddonCalendarComponentsModule, ], declarations: [ diff --git a/src/addons/calendar/pages/list/list.module.ts b/src/addons/calendar/pages/list/list.module.ts index ebbdeb4c8..18d32c117 100644 --- a/src/addons/calendar/pages/list/list.module.ts +++ b/src/addons/calendar/pages/list/list.module.ts @@ -18,9 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { AddonCalendarListPage } from './list.page'; @@ -37,9 +35,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], declarations: [ AddonCalendarListPage, diff --git a/src/addons/calendar/services/database/calendar.ts b/src/addons/calendar/services/database/calendar.ts index fd088c963..711239fbc 100644 --- a/src/addons/calendar/services/database/calendar.ts +++ b/src/addons/calendar/services/database/calendar.ts @@ -211,15 +211,15 @@ export const CALENDAR_SITE_SCHEMA: CoreSiteSchema = { oldTable = 'addon_calendar_events'; } - await db.tableExists(oldTable); - - // Move the records from the old table. - const events = await db.getAllRecords(oldTable); - const promises = events.map((event) => db.insertRecord(newTable, event)); - - await Promise.all(promises); - try { + await db.tableExists(oldTable); + + // Move the records from the old table. + const events = await db.getAllRecords(oldTable); + const promises = events.map((event) => db.insertRecord(newTable, event)); + + await Promise.all(promises); + db.dropTable(oldTable); } catch { // Old table does not exist, ignore. diff --git a/src/addons/messageoutput/airnotifier/airnotifier.module.ts b/src/addons/messageoutput/airnotifier/airnotifier.module.ts new file mode 100644 index 000000000..2d8b19f1b --- /dev/null +++ b/src/addons/messageoutput/airnotifier/airnotifier.module.ts @@ -0,0 +1,49 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { Routes } from '@angular/router'; + +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { AddonMessageOutputDelegate } from '@addons/messageoutput/services/messageoutput-delegate'; +import { + AddonMessageOutputAirnotifierHandler, + AddonMessageOutputAirnotifierHandlerService, +} from './services/handlers/messageoutput'; + +const routes: Routes = [ + { + path: AddonMessageOutputAirnotifierHandlerService.PAGE_NAME, + loadChildren: () => import('./pages/devices/devices.module').then( m => m.AddonMessageOutputAirnotifierDevicesPageModule), + }, +]; + +@NgModule({ + declarations: [ + ], + imports: [ + CoreMainMenuTabRoutingModule.forChild(routes), + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + AddonMessageOutputDelegate.instance.registerHandler(AddonMessageOutputAirnotifierHandler.instance); + }, + }, + ], +}) +export class AddonMessageOutputAirnotifierModule {} diff --git a/src/addons/messageoutput/airnotifier/lang.json b/src/addons/messageoutput/airnotifier/lang.json new file mode 100644 index 000000000..a6f460bbb --- /dev/null +++ b/src/addons/messageoutput/airnotifier/lang.json @@ -0,0 +1,3 @@ +{ + "processorsettingsdesc": "Configure devices" +} \ No newline at end of file diff --git a/src/addons/messageoutput/airnotifier/pages/devices/devices.html b/src/addons/messageoutput/airnotifier/pages/devices/devices.html new file mode 100644 index 000000000..134300b25 --- /dev/null +++ b/src/addons/messageoutput/airnotifier/pages/devices/devices.html @@ -0,0 +1,27 @@ + + + + + + {{ 'addon.messageoutput_airnotifier.processorsettingsdesc' | translate }} + + + + + + + + + + + {{ device.name }} {{ device.model }} {{ device.platform }} {{ device.version }} + ({{ 'core.currentdevice' | translate }}) + + + + + + + + diff --git a/src/addons/messageoutput/airnotifier/pages/devices/devices.module.ts b/src/addons/messageoutput/airnotifier/pages/devices/devices.module.ts new file mode 100644 index 000000000..1d1926d88 --- /dev/null +++ b/src/addons/messageoutput/airnotifier/pages/devices/devices.module.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonMessageOutputAirnotifierDevicesPage } from './devices'; + +const routes: Routes = [ + { + path: '', + component: AddonMessageOutputAirnotifierDevicesPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CommonModule, + IonicModule, + FormsModule, + TranslateModule.forChild(), + CoreSharedModule, + ], + declarations: [ + AddonMessageOutputAirnotifierDevicesPage, + ], + exports: [RouterModule], +}) +export class AddonMessageOutputAirnotifierDevicesPageModule {} diff --git a/src/addons/messageoutput/airnotifier/pages/devices/devices.ts b/src/addons/messageoutput/airnotifier/pages/devices/devices.ts new file mode 100644 index 000000000..ebd03ea82 --- /dev/null +++ b/src/addons/messageoutput/airnotifier/pages/devices/devices.ts @@ -0,0 +1,161 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; + +import { CoreDomUtils } from '@services/utils/dom'; +import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; +import { AddonMessageOutputAirnotifier, AddonMessageOutputAirnotifierDevice } from '../../services/airnotifier'; +import { CoreUtils } from '@services/utils/utils'; + +/** + * Page that displays the list of devices. + */ +@Component({ + selector: 'page-addon-message-output-airnotifier-devices', + templateUrl: 'devices.html', +}) +export class AddonMessageOutputAirnotifierDevicesPage implements OnInit, OnDestroy { + + devices?: AddonMessageOutputAirnotifierDeviceFormatted[] = []; + devicesLoaded = false; + + protected updateTimeout?: number; + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.fetchDevices(); + } + + /** + * Fetches the list of devices. + * + * @return Promise resolved when done. + */ + protected async fetchDevices(): Promise { + try { + const devices = await AddonMessageOutputAirnotifier.instance.getUserDevices(); + + this.devices = this.formatDevices(devices); + } catch (error) { + CoreDomUtils.instance.showErrorModal(error); + } finally { + this.devicesLoaded = true; + } + } + + /** + * Add some calculated data for devices. + * + * @param devices Devices to format. + * @return Formatted devices. + */ + protected formatDevices(devices: AddonMessageOutputAirnotifierDevice[]): AddonMessageOutputAirnotifierDeviceFormatted[] { + const formattedDevices: AddonMessageOutputAirnotifierDeviceFormatted[] = devices; + const pushId = CorePushNotifications.instance.getPushId(); + + // Convert enabled to boolean and search current device. + formattedDevices.forEach((device) => { + device.enable = !!device.enable; + device.current = !!(pushId && pushId == device.pushid); + }); + + return formattedDevices; + } + + /** + * Update list of devices after a certain time. The purpose is to store the updated data, it won't be reflected in the view. + */ + protected updateDevicesAfterDelay(): void { + // Cancel pending updates. + if (this.updateTimeout) { + clearTimeout(this.updateTimeout); + } + + this.updateTimeout = window.setTimeout(() => { + this.updateTimeout = undefined; + this.updateDevices(); + }, 5000); + } + + /** + * Fetch devices. The purpose is to store the updated data, it won't be reflected in the view. + */ + protected async updateDevices(): Promise { + await CoreUtils.instance.ignoreErrors(AddonMessageOutputAirnotifier.instance.invalidateUserDevices()); + + await AddonMessageOutputAirnotifier.instance.getUserDevices(); + } + + /** + * Refresh the list of devices. + * + * @param refresher Refresher. + */ + async refreshDevices(refresher: CustomEvent): Promise { + try { + await CoreUtils.instance.ignoreErrors(AddonMessageOutputAirnotifier.instance.invalidateUserDevices()); + + await this.fetchDevices(); + } finally { + refresher?.detail.complete(); + } + } + + /** + * Enable or disable a certain device. + * + * @param device The device object. + * @param enable True to enable the device, false to disable it. + */ + async enableDevice(device: AddonMessageOutputAirnotifierDeviceFormatted, enable: boolean): Promise { + device.updating = true; + + try { + await AddonMessageOutputAirnotifier.instance.enableDevice(device.id, enable); + + // Update the list of devices since it was modified. + this.updateDevicesAfterDelay(); + } catch (error) { + // Show error and revert change. + CoreDomUtils.instance.showErrorModal(error); + device.enable = !device.enable; + } finally { + device.updating = false; + } + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + // If there is a pending action to update devices, execute it right now. + if (this.updateTimeout) { + clearTimeout(this.updateTimeout); + this.updateDevices(); + } + } + +} + +/** + * User device with some calculated data. + */ +type AddonMessageOutputAirnotifierDeviceFormatted = AddonMessageOutputAirnotifierDevice & { + current?: boolean; // Calculated in the app. Whether it's the current device. + updating?: boolean; // Calculated in the app. Whether the device enable is being updated right now. +}; diff --git a/src/addons/messageoutput/airnotifier/services/airnotifier.ts b/src/addons/messageoutput/airnotifier/services/airnotifier.ts new file mode 100644 index 000000000..954e29bcf --- /dev/null +++ b/src/addons/messageoutput/airnotifier/services/airnotifier.ts @@ -0,0 +1,190 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { CoreSites } from '@services/sites'; +import { CoreWSExternalWarning } from '@services/ws'; +import { CoreConstants } from '@/core/constants'; +import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreError } from '@classes/errors/error'; +import { CoreWSError } from '@classes/errors/wserror'; +import { makeSingleton } from '@singletons'; +import { CoreEvents, CoreEventSiteData } from '@singletons/events'; + +const ROOT_CACHE_KEY = 'mmaMessageOutputAirnotifier:'; + +/** + * Service to handle Airnotifier message output. + */ +@Injectable({ providedIn: 'root' }) +export class AddonMessageOutputAirnotifierProvider { + + constructor() { + CoreEvents.on(CoreEvents.DEVICE_REGISTERED_IN_MOODLE, async (data: CoreEventSiteData) => { + // Get user devices to make Moodle send the devices data to Airnotifier. + this.getUserDevices(true, data.siteId); + }); + } + + /** + * Enables or disables a device. + * + * @param deviceId Device ID. + * @param enable True to enable, false to disable. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success. + */ + async enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + const data: AddonMessageOutputAirnotifierEnableDeviceWSParams = { + deviceid: deviceId, + enable: !!enable, + }; + + const result = await site.write( + 'message_airnotifier_enable_device', + data, + ); + + if (result.success) { + return; + } + + // Fail. Reject with warning message if any. + if (result.warnings?.length) { + throw new CoreWSError(result.warnings[0]); + } + + throw new CoreError('Error enabling device'); + } + + /** + * Get the cache key for the get user devices call. + * + * @return Cache key. + */ + protected getUserDevicesCacheKey(): string { + return ROOT_CACHE_KEY + 'userDevices'; + } + + /** + * Get user devices. + * + * @param ignoreCache Whether to ignore cache. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the devices. + */ + async getUserDevices(ignoreCache?: boolean, siteId?: string): Promise { + + const site = await CoreSites.instance.getSite(siteId); + + const data: AddonMessageOutputAirnotifierGetUserDevicesWSParams = { + appid: CoreConstants.CONFIG.app_id, + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getUserDevicesCacheKey(), + updateFrequency: CoreSite.FREQUENCY_RARELY, + }; + + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + const result = await site.read( + 'message_airnotifier_get_user_devices', + data, + preSets, + ); + + return result.devices; + } + + /** + * Invalidate get user devices. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. + */ + async invalidateUserDevices(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + return site.invalidateWsCacheForKey(this.getUserDevicesCacheKey()); + } + + /** + * Returns whether or not the plugin is enabled for the current site. + * + * @return True if enabled, false otherwise. + * @since 3.2 + */ + isEnabled(): boolean { + return CoreSites.instance.wsAvailableInCurrentSite('message_airnotifier_enable_device') && + CoreSites.instance.wsAvailableInCurrentSite('message_airnotifier_get_user_devices'); + } + +} + +export class AddonMessageOutputAirnotifier extends makeSingleton(AddonMessageOutputAirnotifierProvider) {} + +/** + * Device data returned by WS message_airnotifier_get_user_devices. + */ +export type AddonMessageOutputAirnotifierDevice = { + id: number; // Device id (in the message_airnotifier table). + appid: string; // The app id, something like com.moodle.moodlemobile. + name: string; // The device name, 'occam' or 'iPhone' etc. + model: string; // The device model 'Nexus4' or 'iPad1,1' etc. + platform: string; // The device platform 'iOS' or 'Android' etc. + version: string; // The device version '6.1.2' or '4.2.2' etc. + pushid: string; // The device PUSH token/key/identifier/registration id. + uuid: string; // The device UUID. + enable: number | boolean; // Whether the device is enabled or not. + timecreated: number; // Time created. + timemodified: number; // Time modified. +}; + +/** + * Params of message_airnotifier_enable_device WS. + */ +export type AddonMessageOutputAirnotifierEnableDeviceWSParams = { + deviceid: number; // The device id. + enable: boolean; // True for enable the device, false otherwise. +}; + +/** + * Result of WS message_airnotifier_enable_device. + */ +export type AddonMessageOutputAirnotifierEnableDeviceWSResponse = { + success: boolean; // True if success. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Params of message_airnotifier_get_user_devices WS. + */ +export type AddonMessageOutputAirnotifierGetUserDevicesWSParams = { + appid: string; // App unique id (usually a reversed domain). + userid?: number; // User id, 0 for current user. +}; + +/** + * Result of WS message_airnotifier_get_user_devices. + */ +export type AddonMessageOutputAirnotifierGetUserDevicesWSResponse = { + devices: AddonMessageOutputAirnotifierDevice[]; // List of devices. + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addons/messageoutput/airnotifier/services/handlers/messageoutput.ts b/src/addons/messageoutput/airnotifier/services/handlers/messageoutput.ts new file mode 100644 index 000000000..c2668eb39 --- /dev/null +++ b/src/addons/messageoutput/airnotifier/services/handlers/messageoutput.ts @@ -0,0 +1,61 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { AddonMessageOutputHandler, AddonMessageOutputHandlerData } from '@addons/messageoutput/services/messageoutput-delegate'; +import { AddonMessageOutputAirnotifierProvider } from '../airnotifier'; +import { makeSingleton } from '@singletons'; + +/** + * Airnotifier message output handler. + */ +@Injectable({ providedIn: 'root' }) +export class AddonMessageOutputAirnotifierHandlerService implements AddonMessageOutputHandler { + + static readonly PAGE_NAME = 'messageoutput-airnotifier'; + + name = 'AddonMessageOutputAirnotifier'; + processorName = 'airnotifier'; + + constructor(private airnotifierProvider: AddonMessageOutputAirnotifierProvider) {} + + /** + * Whether or not the module is enabled for the site. + * + * @return True if enabled, false otherwise. + */ + async isEnabled(): Promise { + return this.airnotifierProvider.isEnabled(); + } + + /** + * Returns the data needed to render the handler. + * + * @param processor The processor object. + * @return Data. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getDisplayData(processor: Record): AddonMessageOutputHandlerData { + return { + priority: 600, + label: 'addon.messageoutput_airnotifier.processorsettingsdesc', + icon: 'fas-cog', + page: AddonMessageOutputAirnotifierHandlerService.PAGE_NAME, + }; + } + +} + +export class AddonMessageOutputAirnotifierHandler extends makeSingleton(AddonMessageOutputAirnotifierHandlerService) {} diff --git a/src/addons/messageoutput/messageoutput.module.ts b/src/addons/messageoutput/messageoutput.module.ts new file mode 100644 index 000000000..c12500d44 --- /dev/null +++ b/src/addons/messageoutput/messageoutput.module.ts @@ -0,0 +1,28 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; + +import { AddonMessageOutputAirnotifierModule } from './airnotifier/airnotifier.module'; + +@NgModule({ + declarations: [ + ], + imports: [ + AddonMessageOutputAirnotifierModule, + ], + providers: [ + ], +}) +export class AddonMessageOutputModule {} diff --git a/src/addons/messageoutput/services/messageoutput-delegate.ts b/src/addons/messageoutput/services/messageoutput-delegate.ts new file mode 100644 index 000000000..6a82ce5a6 --- /dev/null +++ b/src/addons/messageoutput/services/messageoutput-delegate.ts @@ -0,0 +1,93 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { Params } from '@angular/router'; + +import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; +import { makeSingleton } from '@singletons'; + +/** + * Interface that all message output handlers must implement. + */ +export interface AddonMessageOutputHandler extends CoreDelegateHandler { + /** + * The name of the processor. E.g. 'airnotifier'. + */ + processorName: string; + + /** + * Returns the data needed to render the handler. + * + * @param processor The processor object. + * @return Data. + */ + getDisplayData(processor: Record): AddonMessageOutputHandlerData; +} + +/** + * Data needed to render a message output handler. It's returned by the handler. + */ +export interface AddonMessageOutputHandlerData { + /** + * Handler's priority. + */ + priority: number; + + /** + * Name of the page to load for the handler. + */ + page: string; + + /** + * Label to display for the handler. + */ + label: string; + + /** + * Name of the icon to display for the handler. + */ + icon: string; + + /** + * Params to pass to the page. + */ + pageParams?: Params; +} + +/** + * Delegate to register processors (message/output) to be used in places like notification preferences. + */ +@Injectable({ providedIn: 'root' }) +export class AddonMessageOutputDelegateService extends CoreDelegate { + + protected handlerNameProperty = 'processorName'; + + constructor() { + super('AddonMessageOutputDelegate', true); + } + + /** + * Get the display data of the handler. + * + * @param processor The processor object. + * @return Data. + */ + getDisplayData(processor: Record): AddonMessageOutputHandlerData | undefined { + return this.executeFunctionOnEnabled( processor.name, 'getDisplayData', [processor]); + } + +} + +export class AddonMessageOutputDelegate extends makeSingleton(AddonMessageOutputDelegateService) {} diff --git a/src/addons/notifications/components/actions/actions.ts b/src/addons/notifications/components/actions/actions.ts new file mode 100644 index 000000000..cd0ceb35b --- /dev/null +++ b/src/addons/notifications/components/actions/actions.ts @@ -0,0 +1,91 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, OnInit } from '@angular/core'; + +import { CoreSites } from '@services/sites'; +import { CoreContentLinksDelegate, CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; + +/** + * Component that displays the actions for a notification. + */ +@Component({ + selector: 'addon-notifications-actions', + templateUrl: 'addon-notifications-actions.html', +}) +export class AddonNotificationsActionsComponent implements OnInit { + + @Input() contextUrl?: string; + @Input() courseId?: number; + @Input() data?: Record; // Extra data to handle the URL. + + actions: CoreContentLinksAction[] = []; + + /** + * Component being initialized. + */ + async ngOnInit(): Promise { + if (!this.contextUrl && (!this.data || !this.data.appurl)) { + // No URL, nothing to do. + return; + } + + let actions: CoreContentLinksAction[] = []; + + // Treat appurl first if any. + if (this.data?.appurl) { + actions = await CoreContentLinksDelegate.instance.getActionsFor( + this.data.appurl, + this.courseId, + undefined, + this.data, + ); + } + + if (!actions.length && this.contextUrl) { + // No appurl or cannot handle it. Try with contextUrl. + actions = await CoreContentLinksDelegate.instance.getActionsFor(this.contextUrl, this.courseId, undefined, this.data); + } + + if (!actions.length) { + // URL is not supported. Add an action to open it in browser. + actions.push({ + message: 'core.view', + icon: 'fas-eye', + action: this.openInBrowser.bind(this), + }); + } + + this.actions = actions; + } + + /** + * Default action. Open in browser. + * + * @param siteId Site ID to use. + * @param navCtrl NavController. + */ + protected async openInBrowser(siteId?: string): Promise { + const url = this.data?.appurl || this.contextUrl; + + if (!url) { + return; + } + + const site = await CoreSites.instance.getSite(siteId); + + site.openInBrowserWithAutoLogin(url); + } + +} diff --git a/src/addons/notifications/components/actions/addon-notifications-actions.html b/src/addons/notifications/components/actions/addon-notifications-actions.html new file mode 100644 index 000000000..1bb8682ca --- /dev/null +++ b/src/addons/notifications/components/actions/addon-notifications-actions.html @@ -0,0 +1,8 @@ + + + + + {{ action.message | translate }} + + + diff --git a/src/addons/notifications/components/components.module.ts b/src/addons/notifications/components/components.module.ts new file mode 100644 index 000000000..4d873ecd1 --- /dev/null +++ b/src/addons/notifications/components/components.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { AddonNotificationsActionsComponent } from './actions/actions'; + +@NgModule({ + declarations: [ + AddonNotificationsActionsComponent, + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + ], + exports: [ + AddonNotificationsActionsComponent, + ], +}) +export class AddonNotificationsComponentsModule {} diff --git a/src/addons/notifications/lang.json b/src/addons/notifications/lang.json new file mode 100644 index 000000000..3844a5525 --- /dev/null +++ b/src/addons/notifications/lang.json @@ -0,0 +1,8 @@ +{ + "errorgetnotifications": "Error getting notifications.", + "markallread": "Mark all as read", + "notificationpreferences": "Notification preferences", + "notifications": "Notifications", + "playsound": "Play sound", + "therearentnotificationsyet": "There are no notifications." +} \ No newline at end of file diff --git a/src/addons/notifications/notifications-lazy.module.ts b/src/addons/notifications/notifications-lazy.module.ts new file mode 100644 index 000000000..556467262 --- /dev/null +++ b/src/addons/notifications/notifications-lazy.module.ts @@ -0,0 +1,49 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injector, NgModule } from '@angular/core'; +import { RouterModule, ROUTES, Routes } from '@angular/router'; + +import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { AddonNotificationsSettingsHandlerService } from './services/handlers/settings'; + +function buildRoutes(injector: Injector): Routes { + return [ + { + path: 'list', + loadChildren: () => import('./pages/list/list.module').then(m => m.AddonNotificationsListPageModule), + }, + { + path: AddonNotificationsSettingsHandlerService.PAGE_NAME, + loadChildren: () => import('./pages/settings/settings.module').then(m => m.AddonNotificationsSettingsPageModule), + }, + ...buildTabMainRoutes(injector, { + redirectTo: 'list', + pathMatch: 'full', + }), + ]; +} + +@NgModule({ + exports: [RouterModule], + providers: [ + { + provide: ROUTES, + multi: true, + deps: [Injector], + useFactory: buildRoutes, + }, + ], +}) +export class AddonNotificationsLazyModule {} diff --git a/src/addons/notifications/notifications.module.ts b/src/addons/notifications/notifications.module.ts new file mode 100644 index 000000000..49d012e57 --- /dev/null +++ b/src/addons/notifications/notifications.module.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { Routes } from '@angular/router'; + +import { CoreCronDelegate } from '@services/cron'; +import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate'; +import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; +import { CoreSettingsDelegate } from '@features/settings/services/settings-delegate'; +import { AddonNotificationsMainMenuHandler, AddonNotificationsMainMenuHandlerService } from './services/handlers/mainmenu'; +import { AddonNotificationsCronHandler } from './services/handlers/cron'; +import { AddonNotificationsPushClickHandler } from './services/handlers/push-click'; +import { AddonNotificationsSettingsHandler } from './services/handlers/settings'; + +const routes: Routes = [ + { + path: AddonNotificationsMainMenuHandlerService.PAGE_NAME, + loadChildren: () => import('@/addons/notifications/notifications-lazy.module').then(m => m.AddonNotificationsLazyModule), + }, +]; + +@NgModule({ + imports: [ + CoreMainMenuRoutingModule.forChild({ children: routes }), + CoreMainMenuTabRoutingModule.forChild(routes), + ], + exports: [CoreMainMenuRoutingModule], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + deps: [], + useFactory: () => () => { + CoreMainMenuDelegate.instance.registerHandler(AddonNotificationsMainMenuHandler.instance); + CoreCronDelegate.instance.register(AddonNotificationsCronHandler.instance); + CorePushNotificationsDelegate.instance.registerClickHandler(AddonNotificationsPushClickHandler.instance); + CoreSettingsDelegate.instance.registerHandler(AddonNotificationsSettingsHandler.instance); + }, + }, + ], +}) +export class AddonNotificationsModule {} diff --git a/src/addons/notifications/pages/list/list.html b/src/addons/notifications/pages/list/list.html new file mode 100644 index 000000000..b454f0f24 --- /dev/null +++ b/src/addons/notifications/pages/list/list.html @@ -0,0 +1,65 @@ + + + + + + {{ 'addon.notifications.notifications' | translate }} + + + + + + + +
+ + + {{ 'addon.notifications.markallread' | translate }} + + + + +
+ + + + + + + + +

{{ notification.subject }}

+

{{ notification.userfromfullname }}

+
+ + {{ notification.timecreated | coreDateDayOrTime }} + + + + + +
+ + + + + + + + +
+ + + + + + +
+
diff --git a/src/addons/notifications/pages/list/list.module.ts b/src/addons/notifications/pages/list/list.module.ts new file mode 100644 index 000000000..284696909 --- /dev/null +++ b/src/addons/notifications/pages/list/list.module.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule, Routes } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonNotificationsComponentsModule } from '../../components/components.module'; +import { AddonNotificationsListPage } from './list'; + +const routes: Routes = [ + { + path: '', + component: AddonNotificationsListPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreSharedModule, + AddonNotificationsComponentsModule, + ], + declarations: [ + AddonNotificationsListPage, + ], + exports: [RouterModule], +}) +export class AddonNotificationsListPageModule {} diff --git a/src/addons/notifications/pages/list/list.scss b/src/addons/notifications/pages/list/list.scss new file mode 100644 index 000000000..be91e7a5c --- /dev/null +++ b/src/addons/notifications/pages/list/list.scss @@ -0,0 +1,61 @@ +:host { + .core-notification-icon { + width: 34px; + height: 34px; + margin: 10px !important; + } + + .item core-format-text ::ng-deep { + .forumpost { + border: 1px solid var(--gray-light); + width: 100%; + margin: 0 0 1em 0; + + td { + padding: 10px; + } + + .header { + background-color: var(--gray-lighter); + } + + .picture { + width: auto; + text-align: center; + } + + .subject { + font-weight: 700; + margin-bottom: 1rem; + } + } + + a { + text-decoration: none; + } + + .userpicture { + border-radius: 50%; + } + + .mdl-right { + text-align: end; + a { + display: none; + } + font { + font-size: 0.9em; + } + } + + .commands { + display: none; + } + + hr { + margin-top: 1.5rem; + margin-bottom: 1.5rem; + background-color: var(--gray-light); + } + } +} diff --git a/src/addons/notifications/pages/list/list.ts b/src/addons/notifications/pages/list/list.ts new file mode 100644 index 000000000..b1c7b03e7 --- /dev/null +++ b/src/addons/notifications/pages/list/list.ts @@ -0,0 +1,263 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; +import { Subscription } from 'rxjs'; + +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreEvents, CoreEventObserver } from '@singletons/events'; +import { AddonNotifications, AddonNotificationsAnyNotification, AddonNotificationsProvider } from '../../services/notifications'; +import { AddonNotificationsHelper } from '../../services/notifications-helper'; +import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; + +/** + * Page that displays the list of notifications. + */ +@Component({ + selector: 'page-addon-notifications-list', + templateUrl: 'list.html', + styleUrls: ['list.scss'], +}) +export class AddonNotificationsListPage implements OnInit, OnDestroy { + + notifications: FormattedNotification[] = []; + notificationsLoaded = false; + canLoadMore = false; + loadMoreError = false; + canMarkAllNotificationsAsRead = false; + loadingMarkAllNotificationsAsRead = false; + + protected isCurrentView?: boolean; + protected cronObserver?: CoreEventObserver; + protected pushObserver?: Subscription; + protected pendingRefresh = false; + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.fetchNotifications(); + + this.cronObserver = CoreEvents.on(AddonNotificationsProvider.READ_CRON_EVENT, () => { + if (!this.isCurrentView) { + return; + } + + this.notificationsLoaded = false; + this.refreshNotifications(); + }, CoreSites.instance.getCurrentSiteId()); + + this.pushObserver = CorePushNotificationsDelegate.instance.on('receive').subscribe((notification) => { + // New notification received. If it's from current site, refresh the data. + if (!this.isCurrentView) { + this.pendingRefresh = true; + + return; + } + + if (!CoreUtils.instance.isTrueOrOne(notification.notif) || !CoreSites.instance.isCurrentSite(notification.site)) { + return; + } + + this.notificationsLoaded = false; + this.refreshNotifications(); + }); + } + + /** + * Convenience function to get notifications. Gets unread notifications first. + * + * @param refreh Whether we're refreshing data. + * @return Resolved when done. + */ + protected async fetchNotifications(refresh?: boolean): Promise { + this.loadMoreError = false; + + try { + const result = await AddonNotificationsHelper.instance.getNotifications(refresh ? [] : this.notifications); + + const notifications = result.notifications.map((notification) => this.formatText(notification)); + + if (refresh) { + this.notifications = notifications; + } else { + this.notifications = this.notifications.concat(notifications); + } + this.canLoadMore = result.canLoadMore; + + this.markNotificationsAsRead(notifications); + } catch (error) { + CoreDomUtils.instance.showErrorModalDefault(error, 'addon.notifications.errorgetnotifications', true); + this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. + } finally { + this.notificationsLoaded = true; + } + } + + /** + * Mark all notifications as read. + * + * @return Promise resolved when done. + */ + async markAllNotificationsAsRead(): Promise { + this.loadingMarkAllNotificationsAsRead = true; + + await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.markAllNotificationsAsRead()); + + CoreEvents.trigger(AddonNotificationsProvider.READ_CHANGED_EVENT, {}, CoreSites.instance.getCurrentSiteId()); + + // All marked as read, refresh the list. + this.notificationsLoaded = false; + + await this.refreshNotifications(); + } + + /** + * Mark notifications as read. + * + * @param notifications Array of notification objects. + */ + protected async markNotificationsAsRead(notifications: FormattedNotification[]): Promise { + if (notifications.length > 0) { + const promises = notifications.map(async (notification) => { + if (notification.read) { + // Already read, don't mark it. + return; + } + + await AddonNotifications.instance.markNotificationRead(notification.id); + }); + + await CoreUtils.instance.ignoreErrors(Promise.all(promises)); + + await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.invalidateNotificationsList()); + + CoreEvents.trigger(AddonNotificationsProvider.READ_CHANGED_EVENT, {}, CoreSites.instance.getCurrentSiteId()); + } + + // Check if mark all notifications as read is enabled and there are some to read. + if (!AddonNotifications.instance.isMarkAllNotificationsAsReadEnabled()) { + this.canMarkAllNotificationsAsRead = false; + + return; + } + + try { + this.loadingMarkAllNotificationsAsRead = true; + + const unread = await AddonNotifications.instance.getUnreadNotificationsCount(); + + this.canMarkAllNotificationsAsRead = unread > 0; + } finally { + this.loadingMarkAllNotificationsAsRead = false; + } + } + + /** + * Refresh notifications. + * + * @param refresher Refresher. + * @return Promise Promise resolved when done. + */ + async refreshNotifications(refresher?: CustomEvent): Promise { + await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.invalidateNotificationsList()); + + try { + await this.fetchNotifications(true); + } finally { + refresher?.detail.complete(); + } + } + + /** + * Load more results. + * + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + */ + async loadMoreNotifications(infiniteComplete?: () => void): Promise { + try { + await this.fetchNotifications(); + } finally { + infiniteComplete?.(); + } + } + + /** + * Formats the text of a notification. + * + * @param notification The notification object. + */ + protected formatText(notification: AddonNotificationsAnyNotification): FormattedNotification { + const formattedNotification: FormattedNotification = notification; + formattedNotification.displayfullhtml = this.shouldDisplayFullHtml(notification); + formattedNotification.iconurl = formattedNotification.iconurl || undefined; // Make sure the property exists. + + formattedNotification.mobiletext = formattedNotification.displayfullhtml ? + notification.fullmessagehtml : + CoreTextUtils.instance.replaceNewLines(formattedNotification.mobiletext!.replace(/-{4,}/ig, ''), '
'); + + return formattedNotification; + } + + /** + * Check whether we should display full HTML of the notification. + * + * @param notification Notification. + * @return Whether to display full HTML. + */ + protected shouldDisplayFullHtml(notification: FormattedNotification): boolean { + return notification.component == 'mod_forum' && notification.eventtype == 'digests'; + } + + /** + * User entered the page. + */ + ionViewDidEnter(): void { + this.isCurrentView = true; + + if (!this.pendingRefresh) { + return; + } + + this.pendingRefresh = false; + this.notificationsLoaded = false; + + this.refreshNotifications(); + } + + /** + * User left the page. + */ + ionViewDidLeave(): void { + this.isCurrentView = false; + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + this.cronObserver?.off(); + this.pushObserver?.unsubscribe(); + } + +} + +type FormattedNotification = AddonNotificationsAnyNotification & { + displayfullhtml?: boolean; // Whether to display the full HTML of the notification. + iconurl?: string; +}; diff --git a/src/addons/notifications/pages/settings/settings.html b/src/addons/notifications/pages/settings/settings.html new file mode 100644 index 000000000..4209719f3 --- /dev/null +++ b/src/addons/notifications/pages/settings/settings.html @@ -0,0 +1,115 @@ + + + + + + {{ 'addon.notifications.notifications' | translate }} + + + + + + + + + + + + + + + + + + {{ 'addon.notifications.playsound' | translate }} + + + + + + + {{ 'addon.notifications.notifications' | translate }} + + + + {{ 'addon.notifications.playsound' | translate }} + + + + + + + + + {{ processor.displayname }} + + + + + + + + {{ component.displayname }} + + {{ 'core.settings.loggedin' | translate }} + + + {{ 'core.settings.loggedoff' | translate }} + + + + + + + + + {{ notification.displayname }} + + + + + + + + {{'core.settings.locked' | translate }} + + + {{ 'core.settings.disabled' | translate }} + + + + + + + {{ notification.displayname }} + + + + {{ 'core.settings.' + state | translate }} + + + + + + {{'core.settings.locked' | translate }} + + {{ 'core.settings.disabled' | translate }} + + + + + + \ No newline at end of file diff --git a/src/addons/notifications/pages/settings/settings.module.ts b/src/addons/notifications/pages/settings/settings.module.ts new file mode 100644 index 000000000..f01846203 --- /dev/null +++ b/src/addons/notifications/pages/settings/settings.module.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule, Routes } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonNotificationsSettingsPage } from './settings'; + +const routes: Routes = [ + { + path: '', + component: AddonNotificationsSettingsPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CommonModule, + FormsModule, + IonicModule, + TranslateModule.forChild(), + CoreSharedModule, + ], + declarations: [ + AddonNotificationsSettingsPage, + ], + exports: [RouterModule], +}) +export class AddonNotificationsSettingsPageModule {} diff --git a/src/addons/notifications/pages/settings/settings.scss b/src/addons/notifications/pages/settings/settings.scss new file mode 100644 index 000000000..8fea99c17 --- /dev/null +++ b/src/addons/notifications/pages/settings/settings.scss @@ -0,0 +1,5 @@ +:host { + .addon-notifications-table-content ion-row { + min-height: 35px; + } +} diff --git a/src/addons/notifications/pages/settings/settings.ts b/src/addons/notifications/pages/settings/settings.ts new file mode 100644 index 000000000..fd24867d5 --- /dev/null +++ b/src/addons/notifications/pages/settings/settings.ts @@ -0,0 +1,308 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { IonRefresher, NavController } from '@ionic/angular'; + +import { CoreConfig } from '@services/config'; +import { CoreLocalNotifications } from '@services/local-notifications'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreUser } from '@features/user/services/user'; +import { AddonMessageOutputDelegate, AddonMessageOutputHandlerData } from '@addons/messageoutput/services/messageoutput-delegate'; +import { CoreConstants } from '@/core/constants'; +import { CoreError } from '@classes/errors/error'; +import { CoreEventNotificationSoundChangedData, CoreEvents } from '@singletons/events'; +import { + AddonNotifications, + AddonNotificationsPreferencesProcessor, + AddonNotificationsPreferencesNotificationProcessorState, +} from '../../services/notifications'; +import { + AddonNotificationsHelper, + AddonNotificationsPreferencesComponentFormatted, + AddonNotificationsPreferencesFormatted, + AddonNotificationsPreferencesNotificationFormatted, + AddonNotificationsPreferencesProcessorFormatted, +} from '@addons/notifications/services/notifications-helper'; +import { CoreNavigator } from '@services/navigator'; +// import { CoreSplitViewComponent } from '@components/split-view/split-view'; + +/** + * Page that displays notifications settings. + */ +@Component({ + selector: 'page-addon-notifications-settings', + templateUrl: 'settings.html', + styleUrls: ['settings.scss'], +}) +export class AddonNotificationsSettingsPage implements OnInit, OnDestroy { + + preferences?: AddonNotificationsPreferencesFormatted; + components?: AddonNotificationsPreferencesComponentFormatted[]; + currentProcessor?: AddonNotificationsPreferencesProcessor; + preferencesLoaded = false; + notificationSound = false; + notifPrefsEnabled: boolean; + canChangeSound: boolean; + processorHandlers: AddonMessageOutputHandlerData[] = []; + + protected updateTimeout?: number; + + constructor( + protected navCtrl: NavController, + // @Optional() protected svComponent: CoreSplitViewComponent, + ) { + this.notifPrefsEnabled = AddonNotifications.instance.isNotificationPreferencesEnabled(); + this.canChangeSound = CoreLocalNotifications.instance.canDisableSound(); + } + + /** + * Component being initialized. + */ + async ngOnInit(): Promise { + if (this.canChangeSound) { + this.notificationSound = await CoreConfig.instance.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true); + } + + if (this.notifPrefsEnabled) { + this.fetchPreferences(); + } else { + this.preferencesLoaded = true; + } + } + + /** + * Fetches preferences data. + * + * @return Resolved when done. + */ + protected async fetchPreferences(): Promise { + try { + const preferences = await AddonNotifications.instance.getNotificationPreferences(); + + if (!this.currentProcessor) { + // Initialize current processor. Load "Mobile" (airnotifier) if available. + this.currentProcessor = AddonNotificationsHelper.instance.getProcessor(preferences.processors, 'airnotifier'); + } + + if (!this.currentProcessor) { + // Shouldn't happen. + throw new CoreError('No processor found'); + } + + preferences.enableall = !preferences.disableall; + this.preferences = AddonNotificationsHelper.instance.formatPreferences(preferences); + this.loadProcessor(this.currentProcessor); + + } catch (error) { + CoreDomUtils.instance.showErrorModal(error); + } finally { + this.preferencesLoaded = true; + } + } + + /** + * Load a processor. + * + * @param processor Processor object. + */ + protected loadProcessor(processor: AddonNotificationsPreferencesProcessorFormatted): void { + if (!processor) { + return; + } + + this.currentProcessor = processor; + this.processorHandlers = []; + this.components = AddonNotificationsHelper.instance.getProcessorComponents( + processor.name, + this.preferences?.components || [], + ); + + if (!processor.hassettings || !processor.supported) { + return; + } + + const handlerData = AddonMessageOutputDelegate.instance.getDisplayData(processor); + if (handlerData) { + this.processorHandlers.push(handlerData); + } + } + + /** + * Update preferences after a certain time. The purpose is to store the updated data, it won't be reflected in the view. + */ + protected updatePreferencesAfterDelay(): void { + // Cancel pending updates. + clearTimeout(this.updateTimeout); + + this.updateTimeout = window.setTimeout(() => { + this.updateTimeout = undefined; + this.updatePreferences(); + }, 5000); + } + + /** + * Update preferences. The purpose is to store the updated data, it won't be reflected in the view. + * + * @return Promise resolved when done. + */ + protected async updatePreferences(): Promise { + await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.invalidateNotificationPreferences()); + + await AddonNotifications.instance.getNotificationPreferences(); + } + + /** + * The selected processor was changed. + * + * @param name Name of the selected processor. + */ + changeProcessor(name: string): void { + const processor = this.preferences!.processors.find((processor) => processor.name == name); + + if (processor) { + this.loadProcessor(processor); + } + } + + /** + * Refresh the list of preferences. + * + * @param refresher Refresher. + */ + async refreshPreferences(refresher?: CustomEvent): Promise { + try { + await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.invalidateNotificationPreferences()); + + await this.fetchPreferences(); + } finally { + refresher?.detail.complete(); + } + } + + /** + * Open extra preferences. + * + * @param handlerData + */ + openExtraPreferences(handlerData: AddonMessageOutputHandlerData): void { + // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. + // @todo const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; + CoreNavigator.instance.navigateToSitePath(handlerData.page, { params: handlerData.pageParams }); + } + + /** + * Change the value of a certain preference. + * + * @param notification Notification object. + * @param state State name, ['loggedin', 'loggedoff']. + * @return Promise resolved when done. + */ + async changePreference(notification: AddonNotificationsPreferencesNotificationFormatted, state: string): Promise { + const processor = notification.processorsByName?.[this.currentProcessor?.name || '']; + if (!processor) { + return; + } + + const processorState: ProcessorStateFormatted = processor[state]; + const preferenceName = notification.preferencekey + '_' + processorState.name; + let value: string | undefined; + + notification.processors.forEach((processor) => { + if (processor[state].checked) { + if (!value) { + value = processor.name; + } else { + value += ',' + processor.name; + } + } + }); + + if (!value) { + value = 'none'; + } + + processorState.updating = true; + + try { + await CoreUser.instance.updateUserPreference(preferenceName, value); + + // Update the preferences since they were modified. + this.updatePreferencesAfterDelay(); + } catch (error) { + // Show error and revert change. + CoreDomUtils.instance.showErrorModal(error); + processor[state].checked = !processor[state].checked; + } finally { + processorState.updating = false; + } + } + + /** + * Enable all notifications changed. + * + * @param enable Whether to enable or disable. + * @return Promise resolved when done. + */ + async enableAll(enable?: boolean): Promise { + const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); + + try { + CoreUser.instance.updateUserPreferences([], !enable); + + // Update the preferences since they were modified. + this.updatePreferencesAfterDelay(); + } catch (error) { + // Show error and revert change. + CoreDomUtils.instance.showErrorModal(error); + this.preferences!.enableall = !this.preferences!.enableall; + } finally { + modal.dismiss(); + } + } + + /** + * Change the notification sound setting. + * + * @param enabled True to enable the notification sound, false to disable it. + */ + async changeNotificationSound(enabled: boolean): Promise { + await CoreUtils.instance.ignoreErrors(CoreConfig.instance.set(CoreConstants.SETTINGS_NOTIFICATION_SOUND, enabled ? 1 : 0)); + + const siteId = CoreSites.instance.getCurrentSiteId(); + CoreEvents.trigger(CoreEvents.NOTIFICATION_SOUND_CHANGED, { enabled }, siteId); + CoreLocalNotifications.instance.rescheduleAll(); + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + // If there is a pending action to update preferences, execute it right now. + if (this.updateTimeout) { + clearTimeout(this.updateTimeout); + this.updatePreferences(); + } + } + +} + +/** + * State in notification processor in notification preferences component with some calculated data. + */ +type ProcessorStateFormatted = AddonNotificationsPreferencesNotificationProcessorState & { + updating?: boolean; // Calculated in the app. Whether the state is being updated. +}; diff --git a/src/addons/notifications/services/handlers/cron.ts b/src/addons/notifications/services/handlers/cron.ts new file mode 100644 index 000000000..42831a38b --- /dev/null +++ b/src/addons/notifications/services/handlers/cron.ts @@ -0,0 +1,80 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { CoreApp } from '@services/app'; +import { CoreCronHandler } from '@services/cron'; +import { CoreSites } from '@services/sites'; +import { makeSingleton } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { AddonNotifications, AddonNotificationsProvider } from '../notifications'; + +/** + * Notifications cron handler. + */ +@Injectable({ providedIn: 'root' }) +export class AddonNotificationsCronHandlerService implements CoreCronHandler { + + name = 'AddonNotificationsCronHandler'; + + /** + * Get the time between consecutive executions. + * + * @return Time between consecutive executions (in ms). + */ + getInterval(): number { + return CoreApp.instance.isMobile() ? 600000 : 60000; // 1 or 10 minutes. + } + + /** + * Check whether it's a synchronization process or not. True if not defined. + * + * @return Whether it's a synchronization process or not. + */ + isSync(): boolean { + // This is done to use only wifi if using the fallback function. + return !AddonNotifications.instance.isPreciseNotificationCountEnabled(); + } + + /** + * Check whether the sync can be executed manually. Call isSync if not defined. + * + * @return Whether the sync can be executed manually. + */ + canManualSync(): boolean { + return true; + } + + /** + * Execute the process. + * Receives the ID of the site affected, undefined for all sites. + * + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. If the promise is rejected, this function + * will be called again often, it shouldn't be abused. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async execute(siteId?: string, force?: boolean): Promise { + if (!CoreSites.instance.isCurrentSite(siteId)) { + return; + } + + CoreEvents.trigger(AddonNotificationsProvider.READ_CRON_EVENT, {}, CoreSites.instance.getCurrentSiteId()); + } + +} + +export class AddonNotificationsCronHandler extends makeSingleton(AddonNotificationsCronHandlerService) {} diff --git a/src/addons/notifications/services/handlers/mainmenu.ts b/src/addons/notifications/services/handlers/mainmenu.ts new file mode 100644 index 000000000..ee646c8c2 --- /dev/null +++ b/src/addons/notifications/services/handlers/mainmenu.ts @@ -0,0 +1,125 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { CoreSites } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; +import { CoreEvents, CoreEventSiteData } from '@singletons/events'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate'; +import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; +import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; +import { AddonNotifications, AddonNotificationsProvider } from '../notifications'; + +/** + * Handler to inject an option into main menu. + */ +@Injectable({ providedIn: 'root' }) +export class AddonNotificationsMainMenuHandlerService implements CoreMainMenuHandler { + + static readonly PAGE_NAME = 'notifications'; + + name = 'AddonNotifications'; + priority = 700; + + protected handlerData: CoreMainMenuHandlerData = { + icon: 'fas-bell', + title: 'addon.notifications.notifications', + page: AddonNotificationsMainMenuHandlerService.PAGE_NAME, + class: 'addon-notifications-handler', + showBadge: true, + badge: '', + loading: true, + }; + + /** + * Initialize the handler. + */ + initialize(): void { + CoreEvents.on(AddonNotificationsProvider.READ_CHANGED_EVENT, (data: CoreEventSiteData) => { + this.updateBadge(data.siteId); + }); + + CoreEvents.on(AddonNotificationsProvider.READ_CRON_EVENT, (data: CoreEventSiteData) => { + this.updateBadge(data.siteId); + }); + + // Reset info on logout. + CoreEvents.on(CoreEvents.LOGOUT, () => { + this.handlerData.badge = ''; + this.handlerData.loading = true; + }); + + // If a push notification is received, refresh the count. + CorePushNotificationsDelegate.instance.on('receive').subscribe((notification) => { + // New notification received. If it's from current site, refresh the data. + if (CoreUtils.instance.isTrueOrOne(notification.notif) && CoreSites.instance.isCurrentSite(notification.site)) { + this.updateBadge(notification.site); + } + }); + + // Register Badge counter. + CorePushNotificationsDelegate.instance.registerCounterHandler('AddonNotifications'); + } + + /** + * Check if the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + async isEnabled(): Promise { + return true; + } + + /** + * Returns the data needed to render the handler. + * + * @return Data needed to render the handler. + */ + getDisplayData(): CoreMainMenuHandlerData { + if (this.handlerData.loading) { + this.updateBadge(); + } + + return this.handlerData; + } + + /** + * Triggers an update for the badge number and loading status. Mandatory if showBadge is enabled. + * + * @param siteId Site ID or current Site if undefined. + * @return Promise resolved when done. + */ + protected async updateBadge(siteId?: string): Promise { + siteId = siteId || CoreSites.instance.getCurrentSiteId(); + if (!siteId) { + return; + } + + try { + const unreadCount = await AddonNotifications.instance.getUnreadNotificationsCount(undefined, siteId); + + this.handlerData.badge = unreadCount > 0 ? String(unreadCount) : ''; + CorePushNotifications.instance.updateAddonCounter('AddonNotifications', unreadCount, siteId); + } catch { + this.handlerData.badge = ''; + } finally { + this.handlerData.loading = false; + } + } + +} + +export class AddonNotificationsMainMenuHandler extends makeSingleton(AddonNotificationsMainMenuHandlerService) {} diff --git a/src/addons/notifications/services/handlers/push-click.ts b/src/addons/notifications/services/handlers/push-click.ts new file mode 100644 index 000000000..8b8e00723 --- /dev/null +++ b/src/addons/notifications/services/handlers/push-click.ts @@ -0,0 +1,140 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { CoreNavigator } from '@services/navigator'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate'; +import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; +import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; +import { AddonNotifications, AddonNotificationsProvider } from '../notifications'; +import { AddonNotificationsMainMenuHandlerService } from './mainmenu'; + +/** + * Handler for non-messaging push notifications clicks. + */ +@Injectable({ providedIn: 'root' }) +export class AddonNotificationsPushClickHandlerService implements CorePushNotificationsClickHandler { + + name = 'AddonNotificationsPushClickHandler'; + priority = 0; // Low priority so it's used as a fallback if no other handler treats the notification. + featureName = 'CoreMainMenuDelegate_AddonNotifications'; + + /** + * Check if a notification click is handled by this handler. + * + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler + */ + async handles(notification: NotificationData): Promise { + if (!notification.moodlecomponent) { + // The notification doesn't come from Moodle. Handle it. + return true; + } + + if (CoreUtils.instance.isTrueOrOne(notification.notif)) { + // Notification clicked, mark as read. Don't block for this. + this.markAsRead(notification); + + return true; + } + + return false; + } + + /** + * Mark the notification as read. + * + * @param notification Notification to mark. + * @return Promise resolved when done. + */ + protected async markAsRead(notification: NotificationData): Promise { + const notifId = notification.savedmessageid || notification.id; + + if (!notifId) { + return; + } + + await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.markNotificationRead(notifId, notification.site)); + + CoreEvents.trigger(AddonNotificationsProvider.READ_CHANGED_EVENT, {}, notification.site); + } + + /** + * Handle the notification click. + * + * @param notification The notification to check. + * @return Promise resolved when done. + */ + async handleClick(notification: NotificationData): Promise { + + if (notification.customdata?.extendedtext) { + // Display the text in a modal. + return CoreTextUtils.instance.viewText(notification.title || '', notification.customdata.extendedtext, { + displayCopyButton: true, + modalOptions: { cssClass: 'core-modal-fullscreen' }, + }); + } + + // Try to handle the appurl. + if (notification.customdata?.appurl) { + const url = notification.customdata.appurl; + + switch (notification.customdata.appurlopenin) { + case 'inapp': + CoreUtils.instance.openInApp(url); + + return; + + case 'browser': + return CoreUtils.instance.openInBrowser(url); + + default: + if (CoreContentLinksHelper.instance.handleLink(url, undefined, undefined, true)) { + // Link treated, stop. + return; + } + } + } + + // No appurl or cannot be handled by the app. Try to handle the contexturl now. + if (notification.contexturl) { + if (CoreContentLinksHelper.instance.handleLink(notification.contexturl)) { + // Link treated, stop. + return; + } + } + + // No contexturl or cannot be handled by the app. Open the notifications page. + await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.invalidateNotificationsList(notification.site)); + + await CoreNavigator.instance.navigateToSitePath( + AddonNotificationsMainMenuHandlerService.PAGE_NAME, + { siteId: notification.site }, + ); + } + +} + +export class AddonNotificationsPushClickHandler extends makeSingleton(AddonNotificationsPushClickHandlerService) {} + +type NotificationData = CorePushNotificationsNotificationBasicData & { + contexturl?: string; // URL related to the notification. + savedmessageid?: number; // Notification ID (optional). + id?: number; // Notification ID (optional). +}; diff --git a/src/addons/notifications/services/handlers/settings.ts b/src/addons/notifications/services/handlers/settings.ts new file mode 100644 index 000000000..ff127ae90 --- /dev/null +++ b/src/addons/notifications/services/handlers/settings.ts @@ -0,0 +1,60 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { CoreLocalNotifications } from '@services/local-notifications'; +import { makeSingleton } from '@singletons'; +import { CoreSettingsHandler, CoreSettingsHandlerData } from '@features/settings/services/settings-delegate'; +import { AddonNotifications } from '../notifications'; +import { AddonNotificationsMainMenuHandlerService } from './mainmenu'; + +/** + * Notifications settings handler. + */ +@Injectable({ providedIn: 'root' }) +export class AddonNotificationsSettingsHandlerService implements CoreSettingsHandler { + + static readonly PAGE_NAME = 'settings'; + + name = 'AddonNotifications'; + priority = 500; + + /** + * Check if the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + async isEnabled(): Promise { + // Preferences or notification sound setting available. + return CoreLocalNotifications.instance.isAvailable() || AddonNotifications.instance.isNotificationPreferencesEnabled(); + } + + /** + * Returns the data needed to render the handler. + * + * @return Data needed to render the handler. + */ + getDisplayData(): CoreSettingsHandlerData { + return { + icon: 'fas-bell', + title: 'addon.notifications.notifications', + page: AddonNotificationsMainMenuHandlerService.PAGE_NAME + '/' + AddonNotificationsSettingsHandlerService.PAGE_NAME, + class: 'addon-notifications-settings-handler', + }; + } + +} + +export class AddonNotificationsSettingsHandler extends makeSingleton(AddonNotificationsSettingsHandlerService) {} diff --git a/src/addons/notifications/services/notifications-helper.ts b/src/addons/notifications/services/notifications-helper.ts new file mode 100644 index 000000000..9491b28a8 --- /dev/null +++ b/src/addons/notifications/services/notifications-helper.ts @@ -0,0 +1,211 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { CoreSites } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; +import { AddonMessageOutputDelegate } from '@addons/messageoutput/services/messageoutput-delegate'; +import { + AddonNotifications, + AddonNotificationsAnyNotification, + AddonNotificationsGetNotificationsOptions, + AddonNotificationsPreferences, + AddonNotificationsPreferencesComponent, + AddonNotificationsPreferencesNotification, + AddonNotificationsPreferencesNotificationProcessor, + AddonNotificationsPreferencesProcessor, + AddonNotificationsProvider, +} from './notifications'; + +/** + * Service that provides some helper functions for notifications. + */ +@Injectable({ providedIn: 'root' }) +export class AddonNotificationsHelperProvider { + + /** + * Format preferences data. + * + * @param preferences Preferences to format. + * @return Formatted preferences. + */ + formatPreferences(preferences: AddonNotificationsPreferences): AddonNotificationsPreferencesFormatted { + const formattedPreferences: AddonNotificationsPreferencesFormatted = preferences; + + formattedPreferences.processors.forEach((processor) => { + processor.supported = AddonMessageOutputDelegate.instance.hasHandler(processor.name, true); + }); + + formattedPreferences.components.forEach((component) => { + component.notifications.forEach((notification) => { + notification.processorsByName = CoreUtils.instance.arrayToObject(notification.processors, 'name'); + }); + }); + + return formattedPreferences; + } + + /** + * Get some notifications. It will try to use the new WS if available. + * + * @param notifications Current list of loaded notifications. It's used to calculate the offset. + * @param options Other options. + * @return Promise resolved with notifications and if can load more. + */ + async getNotifications( + notifications: AddonNotificationsAnyNotification[], + options?: AddonNotificationsGetNotificationsOptions, + ): Promise<{notifications: AddonNotificationsAnyNotification[]; canLoadMore: boolean}> { + + notifications = notifications || []; + options = options || {}; + options.limit = options.limit || AddonNotificationsProvider.LIST_LIMIT; + options.siteId = options.siteId || CoreSites.instance.getCurrentSiteId(); + + const available = await AddonNotifications.instance.isPopupAvailable(options.siteId); + + if (available) { + return AddonNotifications.instance.getPopupNotifications(notifications.length, options); + } + + // Fallback to get_messages. We need 2 calls, one for read and the other one for unread. + const unreadFrom = notifications.reduce((total, current) => total + (current.read ? 0 : 1), 0); + + const unread = await AddonNotifications.instance.getUnreadNotifications(unreadFrom, options); + + let newNotifications = unread; + + if (unread.length < options.limit) { + // Limit not reached. Get read notifications until reach the limit. + const readLimit = options.limit - unread.length; + const readFrom = notifications.length - unreadFrom; + const readOptions = Object.assign({}, options, { limit: readLimit }); + + try { + const read = await AddonNotifications.instance.getReadNotifications(readFrom, readOptions); + + newNotifications = unread.concat(read); + } catch (error) { + if (unread.length <= 0) { + throw error; + } + } + } + + return { + notifications: newNotifications, + canLoadMore: notifications.length >= options.limit, + }; + } + + /** + * Get a certain processor from a list of processors. + * + * @param processors List of processors. + * @param name Name of the processor to get. + * @param fallback True to return first processor if not found, false to not return any. Defaults to true. + * @return Processor. + */ + getProcessor( + processors: AddonNotificationsPreferencesProcessor[], + name: string, + fallback: boolean = true, + ): AddonNotificationsPreferencesProcessor | undefined { + if (!processors || !processors.length) { + return; + } + + const processor = processors.find((processor) => processor.name == name); + if (processor) { + return processor; + } + + // Processor not found, return first if requested. + if (fallback) { + return processors[0]; + } + } + + /** + * Return the components and notifications that have a certain processor. + * + * @param processorName Name of the processor to filter. + * @param components Array of components. + * @return Filtered components. + */ + getProcessorComponents( + processorName: string, + components: AddonNotificationsPreferencesComponentFormatted[], + ): AddonNotificationsPreferencesComponentFormatted[] { + const result: AddonNotificationsPreferencesComponentFormatted[] = []; + + components.forEach((component) => { + // Check if the component has any notification with this processor. + const notifications: AddonNotificationsPreferencesNotificationFormatted[] = []; + + component.notifications.forEach((notification) => { + const processor = notification.processorsByName?.[processorName]; + + if (processor) { + // Add the notification. + notifications.push(notification); + } + }); + + if (notifications.length) { + // At least 1 notification added, add the component to the result. + result.push({ + displayname: component.displayname, + notifications, + }); + } + }); + + return result; + } + +} + +export class AddonNotificationsHelper extends makeSingleton(AddonNotificationsHelperProvider) {} + +/** + * Preferences with some calculated data. + */ +export type AddonNotificationsPreferencesFormatted = Omit & { + processors: AddonNotificationsPreferencesProcessorFormatted[]; // Config form values. + components: AddonNotificationsPreferencesComponentFormatted[]; // Available components. +}; + +/** + * Preferences component with some calculated data. + */ +export type AddonNotificationsPreferencesComponentFormatted = Omit & { + notifications: AddonNotificationsPreferencesNotificationFormatted[]; // List of notificaitons for the component. +}; + +/** + * Preferences notification with some calculated data. + */ +export type AddonNotificationsPreferencesNotificationFormatted = AddonNotificationsPreferencesNotification & { + processorsByName?: Record; // Calculated in the app. +}; + +/** + * Preferences processor with some calculated data. + */ +export type AddonNotificationsPreferencesProcessorFormatted = AddonNotificationsPreferencesProcessor & { + supported?: boolean; // Calculated in the app. Whether the processor is supported in the app. +}; diff --git a/src/addons/notifications/services/notifications.ts b/src/addons/notifications/services/notifications.ts new file mode 100644 index 000000000..987259289 --- /dev/null +++ b/src/addons/notifications/services/notifications.ts @@ -0,0 +1,658 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; + +import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites'; +import { CoreWSExternalWarning } from '@services/ws'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreTimeUtils } from '@services/utils/time'; +import { CoreUser } from '@features/user/services/user'; +// @todo import { AddonMessages, AddonMessagesMarkMessageReadResult } from '@addon/messages/services/messages'; +import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreLogger } from '@singletons/logger'; +import { makeSingleton } from '@singletons'; + +const ROOT_CACHE_KEY = 'mmaNotifications:'; + +/** + * Service to handle notifications. + */ +@Injectable({ providedIn: 'root' }) +export class AddonNotificationsProvider { + + static readonly READ_CHANGED_EVENT = 'addon_notifications_read_changed_event'; + static readonly READ_CRON_EVENT = 'addon_notifications_read_cron_event'; + static readonly PUSH_SIMULATION_COMPONENT = 'AddonNotificationsPushSimulation'; + static readonly LIST_LIMIT = 20; + + protected logger: CoreLogger; + + constructor() { + this.logger = CoreLogger.getInstance('AddonNotificationsProvider'); + } + + /** + * Function to format notification data. + * + * @param notifications List of notifications. + * @param read Whether the notifications are read or unread. + * @return Promise resolved with notifications. + */ + protected async formatNotificationsData( + notifications: AddonNotificationsGetMessagesMessage[], + read?: boolean, + ): Promise; + protected async formatNotificationsData( + notifications: AddonNotificationsPopupNotification[], + read?: boolean, + ): Promise; + protected async formatNotificationsData( + notifications: (AddonNotificationsGetMessagesMessage | AddonNotificationsPopupNotification)[], + read?: boolean, + ): Promise { + + const promises = notifications.map(async (notificationRaw) => { + const notification = notificationRaw; + + // Set message to show. + if (notification.component && notification.component == 'mod_forum') { + notification.mobiletext = notification.smallmessage; + } else { + notification.mobiletext = notification.fullmessage; + } + + notification.moodlecomponent = notification.component; + notification.notification = 1; + notification.notif = 1; + if (typeof read != 'undefined') { + notification.read = read; + } + + if (typeof notification.customdata == 'string') { + notification.customdata = CoreTextUtils.instance.parseJSON>(notification.customdata, {}); + } + + // Try to set courseid the notification belongs to. + if (notification.customdata?.courseid) { + notification.courseid = notification.customdata.courseid; + } else if (!notification.courseid) { + const courseIdMatch = notification.fullmessagehtml.match(/course\/view\.php\?id=([^"]*)/); + if (courseIdMatch?.[1]) { + notification.courseid = parseInt(courseIdMatch[1], 10); + } + } + + if (notification.useridfrom > 0) { + // Try to get the profile picture of the user. + try { + const user = await CoreUser.instance.getProfile(notification.useridfrom, notification.courseid, true); + + notification.profileimageurlfrom = user.profileimageurl; + notification.userfromfullname = user.fullname; + } catch { + // Error getting user. This can happen if device is offline or the user is deleted. + } + } + + return notification; + }); + + return Promise.all(promises); + } + + /** + * Get the cache key for the get notification preferences call. + * + * @return Cache key. + */ + protected getNotificationPreferencesCacheKey(): string { + return ROOT_CACHE_KEY + 'notificationPreferences'; + } + + /** + * Get notification preferences. + * + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the notification preferences. + */ + async getNotificationPreferences(siteId?: string): Promise { + this.logger.debug('Get notification preferences'); + + const site = await CoreSites.instance.getSite(siteId); + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getNotificationPreferencesCacheKey(), + updateFrequency: CoreSite.FREQUENCY_SOMETIMES, + }; + + const data = await site.read( + 'core_message_get_user_notification_preferences', + {}, + preSets, + ); + + return data.preferences; + } + + /** + * Get cache key for notification list WS calls. + * + * @return Cache key. + */ + protected getNotificationsCacheKey(): string { + return ROOT_CACHE_KEY + 'list'; + } + + /** + * Get notifications from site. + * + * @param read True if should get read notifications, false otherwise. + * @param offset Position of the first notification to get. + * @param options Other options. + * @return Promise resolved with notifications. + */ + async getNotifications( + read: boolean, + offset: number, + options?: AddonNotificationsGetNotificationsOptions, + ): Promise { + options = options || {}; + options.limit = options.limit || AddonNotificationsProvider.LIST_LIMIT; + + this.logger.debug(`Get ${(read ? 'read' : 'unread')} notifications from ${offset}. Limit: ${options.limit}`); + + const site = await CoreSites.instance.getSite(options.siteId); + const data: AddonNotificationsGetMessagesWSParams = { + useridto: site.getUserId(), + useridfrom: 0, + type: 'notifications', + read: !!read, + newestfirst: true, + limitfrom: offset, + limitnum: options.limit, + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getNotificationsCacheKey(), + ...CoreSites.instance.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. + }; + + // Get unread notifications. + const response = await site.read('core_message_get_messages', data, preSets); + + const notifications = response.messages; + + return this.formatNotificationsData(notifications, read); + } + + /** + * Get notifications from site using the new WebService. + * + * @param offset Position of the first notification to get. + * @param options Other options. + * @return Promise resolved with notifications and if can load more. + * @since 3.2 + */ + async getPopupNotifications( + offset: number, + options?: AddonNotificationsGetNotificationsOptions, + ): Promise<{notifications: AddonNotificationsPopupNotificationFormatted[]; canLoadMore: boolean}> { + options = options || {}; + options.limit = options.limit || AddonNotificationsProvider.LIST_LIMIT; + + this.logger.debug(`Get popup notifications from ${offset}. Limit: ${options.limit}`); + + const site = await CoreSites.instance.getSite(options.siteId); + const data: AddonNotificationsPopupGetPopupNotificationsWSParams = { + useridto: site.getUserId(), + newestfirst: true, + offset, + limit: options.limit + 1, // Get one more to calculate canLoadMore. + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getNotificationsCacheKey(), + ...CoreSites.instance.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. + }; + + // Get notifications. + const response = await site.read( + 'message_popup_get_popup_notifications', + data, + preSets, + ); + + const notifications = await this.formatNotificationsData(response.notifications.slice(0, options.limit)); + + return { + canLoadMore: response.notifications.length > options.limit, + notifications, + }; + } + + /** + * Get read notifications from site. + * + * @param offset Position of the first notification to get. + * @param options Other options. + * @return Promise resolved with notifications. + */ + getReadNotifications( + offset: number, + options?: AddonNotificationsGetNotificationsOptions, + ): Promise { + return this.getNotifications(true, offset, options); + } + + /** + * Get unread notifications from site. + * + * @param offset Position of the first notification to get. + * @param options Other options. + * @return Promise resolved with notifications. + */ + getUnreadNotifications( + offset: number, + options?: AddonNotificationsGetNotificationsOptions, + ): Promise { + return this.getNotifications(false, offset, options); + } + + /** + * Get unread notifications count. Do not cache calls. + * + * @param userId The user id who received the notification. If not defined, use current user. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the message notifications count. + */ + async getUnreadNotificationsCount(userId?: number, siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + // @since 3.2 + if (site.wsAvailable('message_popup_get_unread_popup_notification_count')) { + userId = userId || site.getUserId(); + const params: AddonNotificationsPopupGetUnreadPopupNotificationCountWSParams = { + useridto: userId, + }; + const preSets: CoreSiteWSPreSets = { + getFromCache: false, + emergencyCache: false, + saveToCache: false, + typeExpected: 'number', + }; + + try { + return await site.read('message_popup_get_unread_popup_notification_count', params, preSets); + } catch { + // Return no messages if the call fails. + return 0; + } + } + + // Fallback call + try { + const unread = await this.getUnreadNotifications(0, { limit: AddonNotificationsProvider.LIST_LIMIT, siteId }); + + // The app used to add a + sign if needed, but 3.1 will be dropped soon so it's easier to always return a number. + return unread.length; + } catch { + // Return no messages if the call fails. + return 0; + } + } + + /** + * Returns whether or not popup WS is available for a certain site. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if available, resolved with false or rejected otherwise. + * @since 3.2 + */ + async isPopupAvailable(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + return site.wsAvailable('message_popup_get_popup_notifications'); + } + + /** + * Mark all message notification as read. + * + * @return Resolved when done. + * @since 3.2 + */ + async markAllNotificationsAsRead(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + const params: CoreMessageMarkAllNotificationsAsReadWSParams = { + useridto: CoreSites.instance.getCurrentSiteUserId(), + }; + + return site.write('core_message_mark_all_notifications_as_read', params); + } + + /** + * Mark a single notification as read. + * + * @param notificationId ID of notification to mark as read + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + * @since 3.5 + */ + async markNotificationRead( + notificationId: number, + siteId?: string, + ): Promise { // @todo | AddonMessagesMarkMessageReadResult + + const site = await CoreSites.instance.getSite(siteId); + + if (site.wsAvailable('core_message_mark_notification_read')) { + const params: CoreMessageMarkNotificationReadWSParams = { + notificationid: notificationId, + timeread: CoreTimeUtils.instance.timestamp(), + }; + + return site.write('core_message_mark_notification_read', params); + } else { + // Fallback for versions prior to 3.5. + // @todo return AddonMessageProvider.instance.markMessageRead(notificationId, site.id); + } + } + + /** + * Invalidate get notification preferences. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. + */ + async invalidateNotificationPreferences(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getNotificationPreferencesCacheKey()); + } + + /** + * Invalidates notifications list WS calls. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the list is invalidated. + */ + async invalidateNotificationsList(siteId?: string): Promise { + const site = await CoreSites.instance.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getNotificationsCacheKey()); + } + + /** + * Returns whether or not we can mark all notifications as read. + * + * @return True if enabled, false otherwise. + * @since 3.2 + */ + isMarkAllNotificationsAsReadEnabled(): boolean { + return CoreSites.instance.wsAvailableInCurrentSite('core_message_mark_all_notifications_as_read'); + } + + /** + * Returns whether or not we can count unread notifications precisely. + * + * @return True if enabled, false otherwise. + * @since 3.2 + */ + isPreciseNotificationCountEnabled(): boolean { + return CoreSites.instance.wsAvailableInCurrentSite('message_popup_get_unread_popup_notification_count'); + } + + /** + * Returns whether or not the notification preferences are enabled for the current site. + * + * @return True if enabled, false otherwise. + * @since 3.2 + */ + isNotificationPreferencesEnabled(): boolean { + return CoreSites.instance.wsAvailableInCurrentSite('core_message_get_user_notification_preferences'); + } + +} + +export class AddonNotifications extends makeSingleton(AddonNotificationsProvider) {} + +/** + * Preferences returned by core_message_get_user_notification_preferences. + */ +export type AddonNotificationsPreferences = { + userid: number; // User id. + disableall: number | boolean; // Whether all the preferences are disabled. + processors: AddonNotificationsPreferencesProcessor[]; // Config form values. + components: AddonNotificationsPreferencesComponent[]; // Available components. + enableall?: boolean; // Calculated in the app. Whether all the preferences are enabled. +}; + +/** + * Processor in notification preferences. + */ +export type AddonNotificationsPreferencesProcessor = { + displayname: string; // Display name. + name: string; // Processor name. + hassettings: boolean; // Whether has settings. + contextid: number; // Context id. + userconfigured: number; // Whether is configured by the user. +}; + +/** + * Component in notification preferences. + */ +export type AddonNotificationsPreferencesComponent = { + displayname: string; // Display name. + notifications: AddonNotificationsPreferencesNotification[]; // List of notificaitons for the component. +}; + +/** + * Notification processor in notification preferences component. + */ +export type AddonNotificationsPreferencesNotification = { + displayname: string; // Display name. + preferencekey: string; // Preference key. + processors: AddonNotificationsPreferencesNotificationProcessor[]; // Processors values for this notification. +}; + +/** + * Notification processor in notification preferences component. + */ +export type AddonNotificationsPreferencesNotificationProcessor = { + displayname: string; // Display name. + name: string; // Processor name. + locked: boolean; // Is locked by admin?. + lockedmessage?: string; // @since 3.6. Text to display if locked. + userconfigured: number; // Is configured?. + loggedin: AddonNotificationsPreferencesNotificationProcessorState; + loggedoff: AddonNotificationsPreferencesNotificationProcessorState; +}; + +/** + * State in notification processor in notification preferences component. + */ +export type AddonNotificationsPreferencesNotificationProcessorState = { + name: string; // Name. + displayname: string; // Display name. + checked: boolean; // Is checked?. +}; + +/** + * Params of core_message_get_messages WS. + */ +export type AddonNotificationsGetMessagesWSParams = { + useridto: number; // The user id who received the message, 0 for any user. + useridfrom?: number; // The user id who send the message, 0 for any user. -10 or -20 for no-reply or support user. + type?: string; // Type of message to return, expected values are: notifications, conversations and both. + read?: boolean; // True for getting read messages, false for unread. + newestfirst?: boolean; // True for ordering by newest first, false for oldest first. + limitfrom?: number; // Limit from. + limitnum?: number; // Limit number. +}; + +/** + * Data returned by core_message_get_messages WS. + */ +export type AddonNotificationsGetMessagesWSResponse = { + messages: AddonNotificationsGetMessagesMessage[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Message data returned by core_message_get_messages. + */ +export type AddonNotificationsGetMessagesMessage = { + id: number; // Message id. + useridfrom: number; // User from id. + useridto: number; // User to id. + subject: string; // The message subject. + text: string; // The message text formated. + fullmessage: string; // The message. + fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + fullmessagehtml: string; // The message in html. + smallmessage: string; // The shorten message. + notification: number; // Is a notification?. + contexturl: string; // Context URL. + contexturlname: string; // Context URL link name. + timecreated: number; // Time created. + timeread: number; // Time read. + usertofullname: string; // User to full name. + userfromfullname: string; // User from full name. + component?: string; // @since 3.7. The component that generated the notification. + eventtype?: string; // @since 3.7. The type of notification. + customdata?: string; // @since 3.7. Custom data to be passed to the message processor. +}; + +/** + * Message data returned by core_message_get_messages with some calculated data. + */ +export type AddonNotificationsGetMessagesMessageFormatted = + Omit & AddonNotificationsNotificationCalculatedData; + +/** + * Params of message_popup_get_popup_notifications WS. + */ +export type AddonNotificationsPopupGetPopupNotificationsWSParams = { + useridto: number; // The user id who received the message, 0 for current user. + newestfirst?: boolean; // True for ordering by newest first, false for oldest first. + limit?: number; // The number of results to return. + offset?: number; // Offset the result set by a given amount. +}; + +/** + * Result of WS message_popup_get_popup_notifications. + */ +export type AddonNotificationsGetPopupNotificationsResult = { + notifications: AddonNotificationsPopupNotification[]; + unreadcount: number; // The number of unread message for the given user. +}; + +/** + * Notification returned by message_popup_get_popup_notifications. + */ +export type AddonNotificationsPopupNotification = { + id: number; // Notification id (this is not guaranteed to be unique within this result set). + useridfrom: number; // User from id. + useridto: number; // User to id. + subject: string; // The notification subject. + shortenedsubject: string; // The notification subject shortened with ellipsis. + text: string; // The message text formated. + fullmessage: string; // The message. + fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + fullmessagehtml: string; // The message in html. + smallmessage: string; // The shorten message. + contexturl: string; // Context URL. + contexturlname: string; // Context URL link name. + timecreated: number; // Time created. + timecreatedpretty: string; // Time created in a pretty format. + timeread: number; // Time read. + read: boolean; // Notification read status. + deleted: boolean; // Notification deletion status. + iconurl: string; // URL for notification icon. + component?: string; // The component that generated the notification. + eventtype?: string; // The type of notification. + customdata?: string; // @since 3.7. Custom data to be passed to the message processor. +}; + +/** + * Notification returned by message_popup_get_popup_notifications. + */ +export type AddonNotificationsPopupNotificationFormatted = + Omit & AddonNotificationsNotificationCalculatedData; + +/** + * Any kind of notification that can be retrieved. + */ +export type AddonNotificationsAnyNotification = + AddonNotificationsPopupNotificationFormatted | AddonNotificationsGetMessagesMessageFormatted; + +/** + * Result of WS core_message_get_user_notification_preferences. + */ +export type AddonNotificationsGetUserNotificationPreferencesResult = { + preferences: AddonNotificationsPreferences; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Calculated data for messages returned by core_message_get_messages. + */ +export type AddonNotificationsNotificationCalculatedData = { + mobiletext?: string; // Calculated in the app. Text to display for the notification. + moodlecomponent?: string; // Calculated in the app. Moodle's component. + notif?: number; // Calculated in the app. Whether it's a notification. + notification?: number; // Calculated in the app in some cases. Whether it's a notification. + read?: boolean; // Calculated in the app. Whether the notifications is read. + courseid?: number; // Calculated in the app. Course the notification belongs to. + profileimageurlfrom?: string; // Calculated in the app. Avatar of user that sent the notification. + userfromfullname?: string; // Calculated in the app in some cases. User from full name. + customdata?: Record; // Parsed custom data. +}; + +/** + * Params of message_popup_get_unread_popup_notification_count WS. + */ +export type AddonNotificationsPopupGetUnreadPopupNotificationCountWSParams = { + useridto: number; // The user id who received the message, 0 for any user. +}; + +/** + * Params of core_message_mark_all_notifications_as_read WS. + */ +export type CoreMessageMarkAllNotificationsAsReadWSParams = { + useridto: number; // The user id who received the message, 0 for any user. + useridfrom?: number; // The user id who send the message, 0 for any user. -10 or -20 for no-reply or support user. + timecreatedto?: number; // Mark messages created before this time as read, 0 for all messages. +}; + +/** + * Params of core_message_mark_notification_read WS. + */ +export type CoreMessageMarkNotificationReadWSParams = { + notificationid: number; // Id of the notification. + timeread?: number; // Timestamp for when the notification should be marked read. +}; + +/** + * Data returned by core_message_mark_notification_read WS. + */ +export type CoreMessageMarkNotificationReadWSResponse = { + notificationid: number; // Id of the notification. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Options to pass to getNotifications and getPopupNotifications. + */ +export type AddonNotificationsGetNotificationsOptions = CoreSitesCommonWSOptions & { + limit?: number; // Number of notifications to get. Defaults to LIST_LIMIT. +}; diff --git a/src/addons/privatefiles/pages/index/index.module.ts b/src/addons/privatefiles/pages/index/index.module.ts index 7b071b02c..5f35fd906 100644 --- a/src/addons/privatefiles/pages/index/index.module.ts +++ b/src/addons/privatefiles/pages/index/index.module.ts @@ -19,9 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { AddonPrivateFilesIndexPage } from '.'; const routes: Routes = [ @@ -38,8 +36,7 @@ const routes: Routes = [ IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ AddonPrivateFilesIndexPage, diff --git a/src/addons/userprofilefield/checkbox/checkbox.module.ts b/src/addons/userprofilefield/checkbox/checkbox.module.ts index 4b232c5e2..37083eded 100644 --- a/src/addons/userprofilefield/checkbox/checkbox.module.ts +++ b/src/addons/userprofilefield/checkbox/checkbox.module.ts @@ -21,7 +21,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { AddonUserProfileFieldCheckboxHandler } from './services/handlers/checkbox'; import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; import { AddonUserProfileFieldCheckboxComponent } from './component/checkbox'; -import { CoreComponentsModule } from '@components/components.module'; +import { CoreSharedModule } from '@/core/shared.module'; @NgModule({ declarations: [ @@ -33,7 +33,7 @@ import { CoreComponentsModule } from '@components/components.module'; TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, + CoreSharedModule, ], providers: [ { diff --git a/src/addons/userprofilefield/datetime/datetime.module.ts b/src/addons/userprofilefield/datetime/datetime.module.ts index 4d9625d8a..31b638042 100644 --- a/src/addons/userprofilefield/datetime/datetime.module.ts +++ b/src/addons/userprofilefield/datetime/datetime.module.ts @@ -21,8 +21,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { AddonUserProfileFieldDatetimeHandler } from './services/handlers/datetime'; import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; import { AddonUserProfileFieldDatetimeComponent } from './component/datetime'; -import { CoreComponentsModule } from '@components/components.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; @NgModule({ declarations: [ @@ -34,8 +33,7 @@ import { CorePipesModule } from '@pipes/pipes.module'; TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CorePipesModule, + CoreSharedModule, ], providers: [ { diff --git a/src/addons/userprofilefield/menu/menu.module.ts b/src/addons/userprofilefield/menu/menu.module.ts index 792d82624..54430b13c 100644 --- a/src/addons/userprofilefield/menu/menu.module.ts +++ b/src/addons/userprofilefield/menu/menu.module.ts @@ -21,8 +21,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { AddonUserProfileFieldMenuHandler } from './services/handlers/menu'; import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; import { AddonUserProfileFieldMenuComponent } from './component/menu'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; @NgModule({ declarations: [ @@ -34,8 +33,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], providers: [ { diff --git a/src/addons/userprofilefield/text/text.module.ts b/src/addons/userprofilefield/text/text.module.ts index edc4f1299..48d7faabe 100644 --- a/src/addons/userprofilefield/text/text.module.ts +++ b/src/addons/userprofilefield/text/text.module.ts @@ -21,8 +21,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { AddonUserProfileFieldTextHandler } from './services/handlers/text'; import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; import { AddonUserProfileFieldTextComponent } from './component/text'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; @NgModule({ declarations: [ @@ -34,8 +33,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], providers: [ { diff --git a/src/addons/userprofilefield/textarea/textarea.module.ts b/src/addons/userprofilefield/textarea/textarea.module.ts index e07ef0e71..a417f7b55 100644 --- a/src/addons/userprofilefield/textarea/textarea.module.ts +++ b/src/addons/userprofilefield/textarea/textarea.module.ts @@ -21,8 +21,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { AddonUserProfileFieldTextareaHandler } from './services/handlers/textarea'; import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; import { AddonUserProfileFieldTextareaComponent } from './component/textarea'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreEditorComponentsModule } from '@features/editor/components/components.module'; @NgModule({ @@ -35,8 +34,7 @@ import { CoreEditorComponentsModule } from '@features/editor/components/componen TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreEditorComponentsModule, ], providers: [ diff --git a/src/core/features/block/components/components.module.ts b/src/core/features/block/components/components.module.ts index 70aec8124..261667ffa 100644 --- a/src/core/features/block/components/components.module.ts +++ b/src/core/features/block/components/components.module.ts @@ -16,12 +16,11 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreBlockComponent } from './block/block'; import { CoreBlockOnlyTitleComponent } from './only-title-block/only-title-block'; import { CoreBlockPreRenderedComponent } from './pre-rendered-block/pre-rendered-block'; import { CoreBlockCourseBlocksComponent } from './course-blocks/course-blocks'; -import { CoreComponentsModule } from '@components/components.module'; +import { CoreSharedModule } from '@/core/shared.module'; @NgModule({ declarations: [ @@ -33,9 +32,8 @@ import { CoreComponentsModule } from '@components/components.module'; imports: [ CommonModule, IonicModule, - CoreDirectivesModule, TranslateModule.forChild(), - CoreComponentsModule, + CoreSharedModule, ], exports: [ CoreBlockComponent, diff --git a/src/core/features/contentlinks/pages/choose-site/choose-site.module.ts b/src/core/features/contentlinks/pages/choose-site/choose-site.module.ts index 1c493fa46..090742124 100644 --- a/src/core/features/contentlinks/pages/choose-site/choose-site.module.ts +++ b/src/core/features/contentlinks/pages/choose-site/choose-site.module.ts @@ -19,9 +19,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreContentLinksChooseSitePage } from './choose-site'; const routes: Routes = [ @@ -40,8 +38,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], }) export class CoreContentLinksChooseSitePageModule {} diff --git a/src/core/features/courses/components/components.module.ts b/src/core/features/courses/components/components.module.ts index 6b9eae348..83fa47b8f 100644 --- a/src/core/features/courses/components/components.module.ts +++ b/src/core/features/courses/components/components.module.ts @@ -18,10 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; import { FormsModule } from '@angular/forms'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesCourseListItemComponent } from './course-list-item/course-list-item'; import { CoreCoursesCourseProgressComponent } from './course-progress/course-progress'; import { CoreCoursesCourseOptionsMenuComponent } from './course-options-menu/course-options-menu'; @@ -39,9 +36,7 @@ import { CoreCoursesSelfEnrolPasswordComponent } from './self-enrol-password/sel IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], exports: [ CoreCoursesCourseListItemComponent, diff --git a/src/core/features/courses/pages/available-courses/available-courses.module.ts b/src/core/features/courses/pages/available-courses/available-courses.module.ts index 9d7232458..604374b35 100644 --- a/src/core/features/courses/pages/available-courses/available-courses.module.ts +++ b/src/core/features/courses/pages/available-courses/available-courses.module.ts @@ -18,8 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; import { CoreCoursesAvailableCoursesPage } from './available-courses'; @@ -38,8 +37,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreCoursesComponentsModule, ], declarations: [ diff --git a/src/core/features/courses/pages/categories/categories.module.ts b/src/core/features/courses/pages/categories/categories.module.ts index 3e57c7348..38561da93 100644 --- a/src/core/features/courses/pages/categories/categories.module.ts +++ b/src/core/features/courses/pages/categories/categories.module.ts @@ -18,8 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; import { CoreCoursesCategoriesPage } from './categories'; @@ -38,8 +37,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreCoursesComponentsModule, ], declarations: [ diff --git a/src/core/features/courses/pages/course-preview/course-preview.module.ts b/src/core/features/courses/pages/course-preview/course-preview.module.ts index acf3098bb..d91a92516 100644 --- a/src/core/features/courses/pages/course-preview/course-preview.module.ts +++ b/src/core/features/courses/pages/course-preview/course-preview.module.ts @@ -18,10 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesCoursePreviewPage } from './course-preview'; import { CoreCoursesComponentsModule } from '../../components/components.module'; @@ -38,9 +35,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, CoreCoursesComponentsModule, ], declarations: [ diff --git a/src/core/features/courses/pages/dashboard/dashboard.module.ts b/src/core/features/courses/pages/dashboard/dashboard.module.ts index 40924f020..6ed3f27e1 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.module.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.module.ts @@ -18,8 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreBlockComponentsModule } from '@features/block/components/components.module'; import { CoreCoursesDashboardPage } from './dashboard'; @@ -37,8 +36,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreBlockComponentsModule, ], declarations: [ diff --git a/src/core/features/courses/pages/my-courses/my-courses.module.ts b/src/core/features/courses/pages/my-courses/my-courses.module.ts index 2fa39ccd9..631d6ec86 100644 --- a/src/core/features/courses/pages/my-courses/my-courses.module.ts +++ b/src/core/features/courses/pages/my-courses/my-courses.module.ts @@ -19,9 +19,7 @@ import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; import { FormsModule } from '@angular/forms'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesMyCoursesPage } from './my-courses'; import { CoreCoursesComponentsModule } from '../../components/components.module'; @@ -39,8 +37,7 @@ const routes: Routes = [ FormsModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreCoursesComponentsModule, ], declarations: [ diff --git a/src/core/features/courses/pages/search/search.module.ts b/src/core/features/courses/pages/search/search.module.ts index 8b2e02cb9..fdcb74580 100644 --- a/src/core/features/courses/pages/search/search.module.ts +++ b/src/core/features/courses/pages/search/search.module.ts @@ -18,8 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; import { CoreSearchComponentsModule } from '@features/search/components/components.module'; @@ -38,8 +37,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreCoursesComponentsModule, CoreSearchComponentsModule, ], diff --git a/src/core/features/editor/components/components.module.ts b/src/core/features/editor/components/components.module.ts index b57a73036..01dfd5469 100644 --- a/src/core/features/editor/components/components.module.ts +++ b/src/core/features/editor/components/components.module.ts @@ -18,7 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; import { CoreEditorRichTextEditorComponent } from './rich-text-editor/rich-text-editor'; -import { CoreComponentsModule } from '@components/components.module'; +import { CoreSharedModule } from '@/core/shared.module'; @NgModule({ declarations: [ @@ -28,7 +28,7 @@ import { CoreComponentsModule } from '@components/components.module'; CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, + CoreSharedModule, ], providers: [ ], diff --git a/src/core/features/emulator/components/components.module.ts b/src/core/features/emulator/components/components.module.ts index 831546f26..c655fcd74 100644 --- a/src/core/features/emulator/components/components.module.ts +++ b/src/core/features/emulator/components/components.module.ts @@ -17,9 +17,7 @@ import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreEmulatorCaptureMediaComponent } from './capture-media/capture-media'; @NgModule({ @@ -30,9 +28,7 @@ import { CoreEmulatorCaptureMediaComponent } from './capture-media/capture-media CommonModule, IonicModule.forRoot(), TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], exports: [ CoreEmulatorCaptureMediaComponent, diff --git a/src/core/features/features.module.ts b/src/core/features/features.module.ts index 57ed4343e..010657163 100644 --- a/src/core/features/features.module.ts +++ b/src/core/features/features.module.ts @@ -27,6 +27,7 @@ import { CoreTagModule } from './tag/tag.module'; import { CoreUserModule } from './user/user.module'; import { CorePushNotificationsModule } from './pushnotifications/pushnotifications.module'; import { CoreXAPIModule } from './xapi/xapi.module'; +import { CoreViewerModule } from './viewer/viewer.module'; @NgModule({ imports: [ @@ -43,6 +44,7 @@ import { CoreXAPIModule } from './xapi/xapi.module'; CorePushNotificationsModule, CoreXAPIModule, CoreH5PModule, + CoreViewerModule, ], }) export class CoreFeaturesModule {} diff --git a/src/core/features/h5p/components/components.module.ts b/src/core/features/h5p/components/components.module.ts index e7842c6f6..be4ec55f8 100644 --- a/src/core/features/h5p/components/components.module.ts +++ b/src/core/features/h5p/components/components.module.ts @@ -17,8 +17,7 @@ import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CoreComponentsModule } from '@components/components.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreH5PPlayerComponent } from './h5p-player/h5p-player'; import { CoreH5PIframeComponent } from './h5p-iframe/h5p-iframe'; @@ -30,9 +29,8 @@ import { CoreH5PIframeComponent } from './h5p-iframe/h5p-iframe'; imports: [ CommonModule, IonicModule, - CoreDirectivesModule, + CoreSharedModule, TranslateModule.forChild(), - CoreComponentsModule, ], providers: [ ], diff --git a/src/core/features/login/login-lazy.module.ts b/src/core/features/login/login-lazy.module.ts index dfe077134..ae934e357 100644 --- a/src/core/features/login/login-lazy.module.ts +++ b/src/core/features/login/login-lazy.module.ts @@ -18,8 +18,7 @@ import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginSiteHelpComponent } from './components/site-help/site-help'; import { CoreLoginSiteOnboardingComponent } from './components/site-onboarding/site-onboarding'; @@ -74,8 +73,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, RouterModule.forChild(routes), ], declarations: [ diff --git a/src/core/features/login/pages/change-password/change-password.module.ts b/src/core/features/login/pages/change-password/change-password.module.ts index b32d3b3ef..5d81313fd 100644 --- a/src/core/features/login/pages/change-password/change-password.module.ts +++ b/src/core/features/login/pages/change-password/change-password.module.ts @@ -18,7 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginChangePasswordPage } from './change-password'; const routes: Routes = [ @@ -34,7 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreLoginChangePasswordPage, diff --git a/src/core/features/login/pages/credentials/credentials.module.ts b/src/core/features/login/pages/credentials/credentials.module.ts index 9fd8a8a93..0a77cc9fd 100644 --- a/src/core/features/login/pages/credentials/credentials.module.ts +++ b/src/core/features/login/pages/credentials/credentials.module.ts @@ -19,9 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginCredentialsPage } from './credentials'; const routes: Routes = [ @@ -39,8 +37,7 @@ const routes: Routes = [ TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreLoginCredentialsPage, diff --git a/src/core/features/login/pages/email-signup/email-signup.module.ts b/src/core/features/login/pages/email-signup/email-signup.module.ts index b1a454123..3ea6bc521 100644 --- a/src/core/features/login/pages/email-signup/email-signup.module.ts +++ b/src/core/features/login/pages/email-signup/email-signup.module.ts @@ -19,8 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreUserComponentsModule } from '@features/user/components/components.module'; import { CoreLoginEmailSignupPage } from './email-signup'; @@ -40,8 +39,7 @@ const routes: Routes = [ TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreUserComponentsModule, ], declarations: [ diff --git a/src/core/features/login/pages/forgotten-password/forgotten-password.module.ts b/src/core/features/login/pages/forgotten-password/forgotten-password.module.ts index f98af2e8c..4570064dc 100644 --- a/src/core/features/login/pages/forgotten-password/forgotten-password.module.ts +++ b/src/core/features/login/pages/forgotten-password/forgotten-password.module.ts @@ -19,7 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginForgottenPasswordPage } from './forgotten-password'; const routes: Routes = [ @@ -37,7 +37,7 @@ const routes: Routes = [ TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreLoginForgottenPasswordPage, diff --git a/src/core/features/login/pages/reconnect/reconnect.module.ts b/src/core/features/login/pages/reconnect/reconnect.module.ts index 132f56699..40d964dc1 100644 --- a/src/core/features/login/pages/reconnect/reconnect.module.ts +++ b/src/core/features/login/pages/reconnect/reconnect.module.ts @@ -19,9 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginReconnectPage } from './reconnect'; const routes: Routes = [ @@ -39,8 +37,7 @@ const routes: Routes = [ TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreLoginReconnectPage, diff --git a/src/core/features/login/pages/site-policy/site-policy.module.ts b/src/core/features/login/pages/site-policy/site-policy.module.ts index 10aed7b8c..07e675296 100644 --- a/src/core/features/login/pages/site-policy/site-policy.module.ts +++ b/src/core/features/login/pages/site-policy/site-policy.module.ts @@ -18,8 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginSitePolicyPage } from './site-policy'; const routes: Routes = [ @@ -35,8 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreLoginSitePolicyPage, diff --git a/src/core/features/login/pages/site/site.module.ts b/src/core/features/login/pages/site/site.module.ts index ef21782f9..36d1c4999 100644 --- a/src/core/features/login/pages/site/site.module.ts +++ b/src/core/features/login/pages/site/site.module.ts @@ -19,9 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginSitePage } from './site'; const routes: Routes = [ @@ -39,8 +37,7 @@ const routes: Routes = [ TranslateModule.forChild(), FormsModule, ReactiveFormsModule, - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreLoginSitePage, diff --git a/src/core/features/login/pages/sites/sites.module.ts b/src/core/features/login/pages/sites/sites.module.ts index e20a7dd13..f95a68134 100644 --- a/src/core/features/login/pages/sites/sites.module.ts +++ b/src/core/features/login/pages/sites/sites.module.ts @@ -18,9 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreLoginSitesPage } from './sites'; const routes: Routes = [ @@ -36,8 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreLoginSitesPage, diff --git a/src/core/features/mainmenu/mainmenu-lazy.module.ts b/src/core/features/mainmenu/mainmenu-lazy.module.ts index 0ba61adb2..16a1171a1 100644 --- a/src/core/features/mainmenu/mainmenu-lazy.module.ts +++ b/src/core/features/mainmenu/mainmenu-lazy.module.ts @@ -15,17 +15,16 @@ import { CommonModule } from '@angular/common'; import { Injector, NgModule } from '@angular/core'; import { ROUTES, Routes } from '@angular/router'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { resolveModuleRoutes } from '@/app/app-routing.module'; import { MAIN_MENU_ROUTES } from './mainmenu-routing.module'; import { CoreMainMenuPage } from './pages/menu/menu'; import { CoreMainMenuHomeHandlerService } from './services/handlers/mainmenu'; +import { CoreMainMenuProvider } from './services/mainmenu'; function buildRoutes(injector: Injector): Routes { const routes = resolveModuleRoutes(injector, MAIN_MENU_ROUTES); @@ -45,7 +44,7 @@ function buildRoutes(injector: Injector): Routes { loadChildren: () => import('./pages/home/home.module').then(m => m.CoreMainMenuHomePageModule), }, { - path: 'more', + path: CoreMainMenuProvider.MORE_PAGE_NAME, loadChildren: () => import('./pages/more/more.module').then(m => m.CoreMainMenuMorePageModule), }, ...routes.children, @@ -60,9 +59,7 @@ function buildRoutes(injector: Injector): Routes { CommonModule, IonicModule, TranslateModule, - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], declarations: [ CoreMainMenuPage, diff --git a/src/core/features/mainmenu/pages/home/home.module.ts b/src/core/features/mainmenu/pages/home/home.module.ts index c61770912..ac5ab6b19 100644 --- a/src/core/features/mainmenu/pages/home/home.module.ts +++ b/src/core/features/mainmenu/pages/home/home.module.ts @@ -19,8 +19,7 @@ import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; import { resolveModuleRoutes } from '@/app/app-routing.module'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreMainMenuHomePage } from './home'; import { MAIN_MENU_HOME_ROUTES } from './home-routing.module'; @@ -44,8 +43,7 @@ function buildRoutes(injector: Injector): Routes { CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], providers: [ { provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] }, diff --git a/src/core/features/mainmenu/pages/menu/menu.html b/src/core/features/mainmenu/pages/menu/menu.html index c86ab2ea3..916230a83 100644 --- a/src/core/features/mainmenu/pages/menu/menu.html +++ b/src/core/features/mainmenu/pages/menu/menu.html @@ -9,7 +9,7 @@ {{ tab.badge }} - + {{ 'core.more' | translate }} diff --git a/src/core/features/mainmenu/pages/menu/menu.ts b/src/core/features/mainmenu/pages/menu/menu.ts index 45dbddbe4..d0298183f 100644 --- a/src/core/features/mainmenu/pages/menu/menu.ts +++ b/src/core/features/mainmenu/pages/menu/menu.ts @@ -21,7 +21,7 @@ import { CoreApp } from '@services/app'; import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; import { CoreEvents, CoreEventObserver } from '@singletons/events'; -import { CoreMainMenu } from '../../services/mainmenu'; +import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate'; import { CoreDomUtils } from '@services/utils/dom'; import { Translate } from '@singletons'; @@ -43,6 +43,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { showTabs = false; tabsPlacement = 'bottom'; hidden = false; + morePageName = CoreMainMenuProvider.MORE_PAGE_NAME; protected subscription?: Subscription; protected redirectObs?: CoreEventObserver; @@ -152,7 +153,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { if (this.loaded && this.mainTabs && !this.mainTabs.getSelected()) { // Select the first tab. setTimeout(() => { - this.mainTabs!.select(this.tabs[0]?.page || 'more'); + this.mainTabs!.select(this.tabs[0]?.page || this.morePageName); }); } } diff --git a/src/core/features/mainmenu/pages/more/more.html b/src/core/features/mainmenu/pages/more/more.html index 20a7eb21b..7afb0f4cb 100644 --- a/src/core/features/mainmenu/pages/more/more.html +++ b/src/core/features/mainmenu/pages/more/more.html @@ -10,7 +10,7 @@ - +

{{siteInfo.fullname}}

diff --git a/src/core/features/mainmenu/pages/more/more.module.ts b/src/core/features/mainmenu/pages/more/more.module.ts index 0893e966c..03acaf749 100644 --- a/src/core/features/mainmenu/pages/more/more.module.ts +++ b/src/core/features/mainmenu/pages/more/more.module.ts @@ -18,8 +18,7 @@ import { RouterModule, ROUTES } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreMainMenuMorePage } from './more'; import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module'; @@ -28,8 +27,7 @@ import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.modu CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], providers: [ { diff --git a/src/core/features/mainmenu/services/mainmenu.ts b/src/core/features/mainmenu/services/mainmenu.ts index ac344f718..dc897890b 100644 --- a/src/core/features/mainmenu/services/mainmenu.ts +++ b/src/core/features/mainmenu/services/mainmenu.ts @@ -29,6 +29,7 @@ export class CoreMainMenuProvider { static readonly NUM_MAIN_HANDLERS = 4; static readonly ITEM_MIN_WIDTH = 72; // Min with of every item, based on 5 items on a 360 pixel wide screen. + static readonly MORE_PAGE_NAME = 'more'; protected tablet = false; @@ -191,11 +192,24 @@ export class CoreMainMenuProvider { return tablet ? 'side' : 'bottom'; } + /** + * Check if a certain page is the root of a main menu tab. + * + * @param page Name of the page. + * @return Promise resolved with boolean: whether it's the root of a main menu tab. + */ + async isMainMenuTab(pageName: string): Promise { + if (pageName == CoreMainMenuProvider.MORE_PAGE_NAME) { + return true; + } + + return this.isCurrentMainMenuHandler(pageName); + } + /** * Check if a certain page is the root of a main menu handler currently displayed. * * @param page Name of the page. - * @param pageParams Page params. * @return Promise resolved with boolean: whether it's the root of a main menu handler. */ async isCurrentMainMenuHandler(pageName: string): Promise { diff --git a/src/core/features/pushnotifications/services/push-delegate.ts b/src/core/features/pushnotifications/services/push-delegate.ts index 2fb972b6d..6a062181f 100644 --- a/src/core/features/pushnotifications/services/push-delegate.ts +++ b/src/core/features/pushnotifications/services/push-delegate.ts @@ -149,7 +149,7 @@ export class CorePushNotificationsDelegateService { * @param eventName Only receive is permitted. * @return Observer to subscribe. */ - on(eventName: string): Subject { + on(eventName: string): Subject { if (typeof this.observables[eventName] == 'undefined') { const eventNames = Object.keys(this.observables).join(', '); this.logger.warn(`'${eventName}' event name is not allowed. Use one of the following: '${eventNames}'.`); diff --git a/src/core/features/search/components/components.module.ts b/src/core/features/search/components/components.module.ts index 132197f91..498e2e359 100644 --- a/src/core/features/search/components/components.module.ts +++ b/src/core/features/search/components/components.module.ts @@ -18,9 +18,7 @@ import { IonicModule } from '@ionic/angular'; import { FormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CoreComponentsModule } from '@components/components.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSearchBoxComponent } from './search-box/search-box'; @@ -33,8 +31,7 @@ import { CoreSearchBoxComponent } from './search-box/search-box'; IonicModule, FormsModule, TranslateModule.forChild(), - CoreDirectivesModule, - CoreComponentsModule, + CoreSharedModule, ], exports: [ CoreSearchBoxComponent, diff --git a/src/core/features/settings/pages/about/about.module.ts b/src/core/features/settings/pages/about/about.module.ts index 1de71b78f..768b416cb 100644 --- a/src/core/features/settings/pages/about/about.module.ts +++ b/src/core/features/settings/pages/about/about.module.ts @@ -18,9 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSettingsAboutPage } from './about'; const routes: Routes = [ @@ -48,8 +46,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreSettingsAboutPage, diff --git a/src/core/features/settings/pages/app/app.module.ts b/src/core/features/settings/pages/app/app.module.ts index b68b72b02..90a8ef602 100644 --- a/src/core/features/settings/pages/app/app.module.ts +++ b/src/core/features/settings/pages/app/app.module.ts @@ -18,9 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSettingsAppPage } from './app'; const routes: Routes = [ @@ -36,8 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreSettingsAppPage, diff --git a/src/core/features/settings/pages/deviceinfo/deviceinfo.module.ts b/src/core/features/settings/pages/deviceinfo/deviceinfo.module.ts index 55873a40f..011669519 100644 --- a/src/core/features/settings/pages/deviceinfo/deviceinfo.module.ts +++ b/src/core/features/settings/pages/deviceinfo/deviceinfo.module.ts @@ -18,10 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSettingsDeviceInfoPage } from './deviceinfo'; const routes: Routes = [ @@ -37,9 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], declarations: [ CoreSettingsDeviceInfoPage, diff --git a/src/core/features/settings/pages/general/general.module.ts b/src/core/features/settings/pages/general/general.module.ts index 3724f1686..568ed194b 100644 --- a/src/core/features/settings/pages/general/general.module.ts +++ b/src/core/features/settings/pages/general/general.module.ts @@ -19,9 +19,7 @@ import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; import { FormsModule } from '@angular/forms'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSettingsGeneralPage } from './general'; const routes: Routes = [ @@ -38,8 +36,7 @@ const routes: Routes = [ IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreSettingsGeneralPage, diff --git a/src/core/features/settings/pages/licenses/licenses.module.ts b/src/core/features/settings/pages/licenses/licenses.module.ts index 3009471b9..668db6008 100644 --- a/src/core/features/settings/pages/licenses/licenses.module.ts +++ b/src/core/features/settings/pages/licenses/licenses.module.ts @@ -18,9 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSettingsLicensesPage } from './licenses'; const routes: Routes = [ @@ -36,8 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreSettingsLicensesPage, diff --git a/src/core/features/settings/pages/site/site.module.ts b/src/core/features/settings/pages/site/site.module.ts index db7d710b8..cde02bba8 100644 --- a/src/core/features/settings/pages/site/site.module.ts +++ b/src/core/features/settings/pages/site/site.module.ts @@ -18,10 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSitePreferencesPage } from './site'; const routes: Routes = [ @@ -40,9 +37,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], }) export class CoreSitePreferencesPageModule {} diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts index ca5bc7b65..9d3402e4b 100644 --- a/src/core/features/settings/pages/site/site.ts +++ b/src/core/features/settings/pages/site/site.ts @@ -26,6 +26,7 @@ import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../services/settings- import { CoreApp } from '@services/app'; import { CoreSiteInfo } from '@classes/site'; import { Translate } from '@singletons'; +import { CoreNavigator } from '@services/navigator'; /** * Page that displays the list of site settings pages. @@ -182,7 +183,7 @@ export class CoreSitePreferencesPage implements OnInit, OnDestroy { openHandler(page: string, params?: Params): void { this.selectedPage = page; // this.splitviewCtrl.push(page, params); - this.router.navigate([page], { relativeTo: this.route, queryParams: params }); + CoreNavigator.instance.navigateToSitePath(page, { params }); } /** diff --git a/src/core/features/settings/pages/space-usage/space-usage.module.ts b/src/core/features/settings/pages/space-usage/space-usage.module.ts index b52333454..4db70ccd8 100644 --- a/src/core/features/settings/pages/space-usage/space-usage.module.ts +++ b/src/core/features/settings/pages/space-usage/space-usage.module.ts @@ -18,10 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSettingsSpaceUsagePage } from './space-usage'; const routes: Routes = [ @@ -37,9 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], declarations: [ CoreSettingsSpaceUsagePage, diff --git a/src/core/features/settings/pages/synchronization/synchronization.module.ts b/src/core/features/settings/pages/synchronization/synchronization.module.ts index 8e898e4a1..0cadd1822 100644 --- a/src/core/features/settings/pages/synchronization/synchronization.module.ts +++ b/src/core/features/settings/pages/synchronization/synchronization.module.ts @@ -19,9 +19,7 @@ import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSettingsSynchronizationPage } from './synchronization'; import { FormsModule } from '@angular/forms'; @@ -39,8 +37,7 @@ const routes: Routes = [ IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreSettingsSynchronizationPage, diff --git a/src/core/features/settings/services/settings-helper.ts b/src/core/features/settings/services/settings-helper.ts index 3e442ed16..a42e5d872 100644 --- a/src/core/features/settings/services/settings-helper.ts +++ b/src/core/features/settings/services/settings-helper.ts @@ -184,66 +184,23 @@ export class CoreSettingsHelperProvider { * @param name Name of the processor to get. * @param fallback True to return first processor if not found, false to not return any. Defaults to true. * @return Processor. - * @todo typings + * @deprecated since 3.9.5. This function has been moved to AddonNotificationsHelperProvider. */ - getProcessor(processors: any[], name: string, fallback: boolean = true): any { - if (!processors || !processors.length) { - return; - } - - const processor = processors.find((processor) => processor.name == name); - if (processor) { - return processor; - } - - // Processor not found, return first if requested. - if (fallback) { - return processors[0]; - } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getProcessor(processors: unknown[], name: string, fallback: boolean = true): undefined { + return; } /** * Return the components and notifications that have a certain processor. * - * @param processor Name of the processor to filter. + * @param processorName Name of the processor to filter. * @param components Array of components. * @return Filtered components. - * @todo + * @deprecated since 3.9.5. This function has been moved to AddonNotificationsHelperProvider. */ - getProcessorComponents(processor: string, components: any[]): any[] { - return processor? components : []; - /* - const result = []; - - components.forEach((component) => { - // Create a copy of the component with an empty list of notifications. - const componentCopy = CoreUtils.instance.clone(component); - componentCopy.notifications = []; - - component.notifications.forEach((notification) => { - let hasProcessor = false; - for (let i = 0; i < notification.processors.length; i++) { - const proc = notification.processors[i]; - if (proc.name == processor) { - hasProcessor = true; - notification.currentProcessor = proc; - break; - } - } - - if (hasProcessor) { - // Add the notification. - componentCopy.notifications.push(notification); - } - }); - - if (componentCopy.notifications.length) { - // At least 1 notification added, add the component to the result. - result.push(componentCopy); - } - }); - - return result;*/ + getProcessorComponents(processorName: string, components: unknown[]): unknown[] { + return components; } /** diff --git a/src/core/features/sitehome/pages/index/index.module.ts b/src/core/features/sitehome/pages/index/index.module.ts index b0a5457d4..2122eafe3 100644 --- a/src/core/features/sitehome/pages/index/index.module.ts +++ b/src/core/features/sitehome/pages/index/index.module.ts @@ -18,8 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CoreComponentsModule } from '@components/components.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreBlockComponentsModule } from '@/core/features/block/components/components.module'; import { CoreSiteHomeIndexPage } from '.'; @@ -37,8 +36,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreDirectivesModule, - CoreComponentsModule, + CoreSharedModule, CoreBlockComponentsModule, ], declarations: [ diff --git a/src/core/features/tag/pages/index-area/index-area.page.module.ts b/src/core/features/tag/pages/index-area/index-area.page.module.ts index cbda0ac1b..5bc977a9b 100644 --- a/src/core/features/tag/pages/index-area/index-area.page.module.ts +++ b/src/core/features/tag/pages/index-area/index-area.page.module.ts @@ -18,9 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { RouterModule, Routes } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreTagIndexAreaPage } from './index-area.page'; const routes: Routes = [ @@ -39,8 +37,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [RouterModule], }) diff --git a/src/core/features/tag/pages/index/index.page.module.ts b/src/core/features/tag/pages/index/index.page.module.ts index ff96bc857..2f21892ed 100644 --- a/src/core/features/tag/pages/index/index.page.module.ts +++ b/src/core/features/tag/pages/index/index.page.module.ts @@ -18,9 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreTagIndexPage } from './index.page'; const routes: Routes = [ @@ -39,8 +37,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], exports: [RouterModule], }) diff --git a/src/core/features/tag/pages/search/search.page.module.ts b/src/core/features/tag/pages/search/search.page.module.ts index f370c592a..87511074e 100644 --- a/src/core/features/tag/pages/search/search.page.module.ts +++ b/src/core/features/tag/pages/search/search.page.module.ts @@ -18,8 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreSearchComponentsModule } from '@features/search/components/components.module'; import { CoreTagSearchPage } from './search.page'; @@ -41,8 +40,7 @@ const routes: Routes = [ IonicModule, FormsModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreSearchComponentsModule, ], exports: [RouterModule], diff --git a/src/core/features/user/components/components.module.ts b/src/core/features/user/components/components.module.ts index 00837117a..2b4d53afe 100644 --- a/src/core/features/user/components/components.module.ts +++ b/src/core/features/user/components/components.module.ts @@ -17,9 +17,7 @@ import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; -import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreUserProfileFieldComponent } from './user-profile-field/user-profile-field'; import { CoreUserTagAreaComponent } from './tag-area/tag-area'; @@ -32,9 +30,7 @@ import { CoreUserTagAreaComponent } from './tag-area/tag-area'; CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, - CorePipesModule, + CoreSharedModule, ], providers: [ ], diff --git a/src/core/features/user/pages/about/about.module.ts b/src/core/features/user/pages/about/about.module.ts index 2c2e2c340..98b7c73a4 100644 --- a/src/core/features/user/pages/about/about.module.ts +++ b/src/core/features/user/pages/about/about.module.ts @@ -18,8 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSharedModule } from '@/core/shared.module'; import { CoreUserComponentsModule } from '@features/user/components/components.module'; import { CoreUserAboutPage } from './about.page'; @@ -37,8 +36,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, CoreUserComponentsModule, ], declarations: [ diff --git a/src/core/features/user/pages/profile/profile.module.ts b/src/core/features/user/pages/profile/profile.module.ts index 67f81126b..c408e3b0f 100644 --- a/src/core/features/user/pages/profile/profile.module.ts +++ b/src/core/features/user/pages/profile/profile.module.ts @@ -18,9 +18,7 @@ import { RouterModule, Routes } from '@angular/router'; import { IonicModule } from '@ionic/angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreComponentsModule } from '@components/components.module'; -import { CoreDirectivesModule } from '@directives/directives.module'; - +import { CoreSharedModule } from '@/core/shared.module'; import { CoreUserProfilePage } from './profile.page'; const routes: Routes = [ @@ -36,8 +34,7 @@ const routes: Routes = [ CommonModule, IonicModule, TranslateModule.forChild(), - CoreComponentsModule, - CoreDirectivesModule, + CoreSharedModule, ], declarations: [ CoreUserProfilePage, diff --git a/src/core/features/viewer/components/components.module.ts b/src/core/features/viewer/components/components.module.ts new file mode 100644 index 000000000..6403e090c --- /dev/null +++ b/src/core/features/viewer/components/components.module.ts @@ -0,0 +1,37 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreViewerTextComponent } from './text/text'; + +@NgModule({ + declarations: [ + CoreViewerTextComponent, + ], + imports: [ + CommonModule, + IonicModule.forRoot(), + TranslateModule.forChild(), + CoreSharedModule, + ], + exports: [ + CoreViewerTextComponent, + ], +}) +export class CoreViewerComponentsModule {} diff --git a/src/core/features/viewer/components/text/text.html b/src/core/features/viewer/components/text/text.html new file mode 100644 index 000000000..824ff42a7 --- /dev/null +++ b/src/core/features/viewer/components/text/text.html @@ -0,0 +1,29 @@ + + + + + + {{ title }} + + + + + + + + + + + + + + + + + + + + {{ 'core.copytoclipboard' | translate }} + + diff --git a/src/core/features/viewer/components/text/text.scss b/src/core/features/viewer/components/text/text.scss new file mode 100644 index 000000000..4377e35e3 --- /dev/null +++ b/src/core/features/viewer/components/text/text.scss @@ -0,0 +1,5 @@ +ion-app.app-root page-core-viewer-text { + ion-footer { + padding: 6px; + } +} diff --git a/src/core/features/viewer/components/text/text.ts b/src/core/features/viewer/components/text/text.ts new file mode 100644 index 000000000..8baeaf033 --- /dev/null +++ b/src/core/features/viewer/components/text/text.ts @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input } from '@angular/core'; +import { FileEntry } from '@ionic-native/file/ngx'; + +import { CoreUtils } from '@services/utils/utils'; +import { CoreWSExternalFile } from '@services/ws'; +import { ModalController } from '@singletons'; + +/** + * Modal component to render a certain text. + */ +@Component({ + selector: 'page-core-viewer-text', + templateUrl: 'text.html', +}) +export class CoreViewerTextComponent { + + @Input() title?: string; // Modal title. + @Input() content?: string; // Modal content. + @Input() component?: string; // Component to use in format-text. + @Input() componentId?: string | number; // Component ID to use in format-text. + @Input() files?: (CoreWSExternalFile | FileEntry)[]; // List of files. + @Input() filter?: boolean; // Whether to filter the text. + @Input() contextLevel?: string; // The context level. + @Input() instanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. + @Input() displayCopyButton?: boolean; // Whether to display a button to copy the contents. + + /** + * Close modal. + */ + closeModal(): void { + ModalController.instance.dismiss(); + } + + /** + * Copy the text to clipboard. + */ + copyText(): void { + CoreUtils.instance.copyToClipboard(this.content || ''); + } + +} diff --git a/src/core/features/viewer/viewer.module.ts b/src/core/features/viewer/viewer.module.ts new file mode 100644 index 000000000..ae9dc3816 --- /dev/null +++ b/src/core/features/viewer/viewer.module.ts @@ -0,0 +1,24 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; + +import { CoreViewerComponentsModule } from './components/components.module'; + +@NgModule({ + imports: [ + CoreViewerComponentsModule, + ], +}) +export class CoreViewerModule {} diff --git a/src/core/pipes/date-day-or-time.ts b/src/core/pipes/date-day-or-time.ts new file mode 100644 index 000000000..6b8f68714 --- /dev/null +++ b/src/core/pipes/date-day-or-time.ts @@ -0,0 +1,72 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Pipe, PipeTransform } from '@angular/core'; +import moment from 'moment'; + +import { CoreTimeUtils } from '@services/utils/time'; +import { Translate } from '@singletons'; +import { CoreLogger } from '@singletons/logger'; + +/** + * Filter to display a date using the day, or the time. + * + * This shows a short version of a date. Use this filter when you want + * the user to visualise when the action was done relatively to today's date. + * + * For instance, if the action happened during this day it will display the time, + * but when the action happened few days ago, it will display the day of the week. + * + * The older the date is, the more information about it will be displayed. + * + * This filter expects a timestamp NOT including milliseconds. + */ +@Pipe({ + name: 'coreDateDayOrTime', +}) +export class CoreDateDayOrTimePipe implements PipeTransform { + + protected logger: CoreLogger; + + constructor() { + this.logger = CoreLogger.getInstance('CoreDateDayOrTimePipe'); + } + + /** + * Format a timestamp. + * + * @param timestamp The UNIX timestamp (without milliseconds). + * @return Formatted time. + */ + transform(timestamp: string | number): string { + if (typeof timestamp == 'string') { + // Convert the value to a number. + const numberTimestamp = parseInt(timestamp, 10); + if (isNaN(numberTimestamp)) { + this.logger.error('Invalid value received', timestamp); + + return timestamp; + } + timestamp = numberTimestamp; + } + + return moment(timestamp * 1000).calendar(null, { + sameDay: CoreTimeUtils.instance.convertPHPToMoment(Translate.instance.instant('core.strftimetime')), + lastDay: Translate.instance.instant('core.dflastweekdate'), + lastWeek: Translate.instance.instant('core.dflastweekdate'), + sameElse: CoreTimeUtils.instance.convertPHPToMoment(Translate.instance.instant('core.strftimedatefullshort')), + }); + } + +} diff --git a/src/core/pipes/pipes.module.ts b/src/core/pipes/pipes.module.ts index e27839889..8e32caa7f 100644 --- a/src/core/pipes/pipes.module.ts +++ b/src/core/pipes/pipes.module.ts @@ -20,6 +20,7 @@ import { CoreSecondsToHMSPipe } from './seconds-to-hms'; import { CoreTimeAgoPipe } from './time-ago'; import { CoreBytesToSizePipe } from './bytes-to-size'; import { CoreDurationPipe } from './duration'; +import { CoreDateDayOrTimePipe } from './date-day-or-time'; @NgModule({ declarations: [ @@ -30,6 +31,7 @@ import { CoreDurationPipe } from './duration'; CoreBytesToSizePipe, CoreSecondsToHMSPipe, CoreDurationPipe, + CoreDateDayOrTimePipe, ], imports: [], exports: [ @@ -40,6 +42,7 @@ import { CoreDurationPipe } from './duration'; CoreBytesToSizePipe, CoreSecondsToHMSPipe, CoreDurationPipe, + CoreDateDayOrTimePipe, ], }) export class CorePipesModule {} diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts index 7c03690fc..a74d4f9e1 100644 --- a/src/core/services/navigator.ts +++ b/src/core/services/navigator.ts @@ -203,6 +203,7 @@ export class CoreNavigatorService { * If the path belongs to a visible tab, that tab will be selected. * If it doesn't, the current tab or the default tab will be used instead. * + * @param path Main menu path. * @param options Navigation options. * @return Whether navigation suceeded. */ @@ -213,22 +214,21 @@ export class CoreNavigatorService { path = path.replace(/^(\.|\/main)?\//, ''); - // Open the path within the corresponding main tab. const pathRoot = /^[^/]+/.exec(path)?.[0] ?? ''; - const isCurrentMainMenuHandler = await CoreUtils.instance.ignoreErrors( - CoreMainMenu.instance.isCurrentMainMenuHandler(pathRoot), + const currentMainMenuTab = this.getCurrentMainMenuTab(); + const isMainMenuTab = await CoreUtils.instance.ignoreErrors( + CoreMainMenu.instance.isMainMenuTab(pathRoot), false, ); - if (isCurrentMainMenuHandler) { - return this.navigate(`/main/${path}`, options); + // Open the path within the current main tab. + if (currentMainMenuTab && (!isMainMenuTab || pathRoot !== currentMainMenuTab)) { + return this.navigate(`/main/${currentMainMenuTab}/${path}`, options); } - // Open the path within the current main tab. - const currentMainMenuTab = this.getCurrentMainMenuTab(); - - if (currentMainMenuTab) { - return this.navigate(`/main/${currentMainMenuTab}/${path}`, options); + // Open the path within the corresponding main tab. + if (isMainMenuTab) { + return this.navigate(`/main/${path}`, options); } // Open the path within the default main tab. diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 2c4ee721f..bac5b6d49 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -1016,7 +1016,7 @@ export class CoreSitesProvider { * @param site Site object or siteId to be compared. If not defined, use current site. * @return Whether site or siteId is the current one. */ - isCurrentSite(site: string | CoreSite): boolean { + isCurrentSite(site?: string | CoreSite): boolean { if (!site || !this.currentSite) { return !!this.currentSite; } diff --git a/src/core/services/tests/navigator.test.ts b/src/core/services/tests/navigator.test.ts index 0d80c2270..6a68f7055 100644 --- a/src/core/services/tests/navigator.test.ts +++ b/src/core/services/tests/navigator.test.ts @@ -44,7 +44,7 @@ describe('CoreNavigator', () => { mockSingleton(CoreUtils, new CoreUtilsProvider(mock())); mockSingleton(CoreUrlUtils, new CoreUrlUtilsProvider()); mockSingleton(CoreSites, { getCurrentSiteId: () => 42, isLoggedIn: () => true }); - mockSingleton(CoreMainMenu, { isCurrentMainMenuHandler: path => Promise.resolve(currentMainMenuHandlers.includes(path)) }); + mockSingleton(CoreMainMenu, { isMainMenuTab: path => Promise.resolve(currentMainMenuHandlers.includes(path)) }); }); it('matches against current path', () => { diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index facc5b870..919a313bb 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -14,13 +14,15 @@ import { Injectable } from '@angular/core'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { ModalOptions } from '@ionic/core'; import { CoreApp } from '@services/app'; import { CoreLang } from '@services/lang'; import { CoreError } from '@classes/errors/error'; -import { makeSingleton, Translate } from '@singletons'; +import { makeSingleton, ModalController, Translate } from '@singletons'; import { CoreWSExternalFile } from '@services/ws'; import { Locutus } from '@singletons/locutus'; +import { CoreViewerTextComponent } from '@features/viewer/components/text/text'; /** * Different type of errors the app can treat. @@ -461,7 +463,7 @@ export class CoreTextUtilsProvider { contextLevel?: string, instanceId?: number, courseId?: number, - ): void { + ): Promise { return this.viewText(title, text, { component, componentId, @@ -947,13 +949,31 @@ export class CoreTextUtilsProvider { * Shows a text on a new page. * * @param title Title of the new state. - * @param text Content of the text to be expanded. + * @param content Content of the text to be expanded. * @param component Component to link the embedded files to. * @param options Options. + * @return Promise resolved when the modal is displayed. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - viewText(title: string, text: string, options?: CoreTextUtilsViewTextOptions): void { - // @todo + async viewText(title: string, content: string, options?: CoreTextUtilsViewTextOptions): Promise { + if (!content.length) { + return; + } + + options = options || {}; + + const modalOptions: ModalOptions = Object.assign(options.modalOptions || {}, { + component: CoreViewerTextComponent, + }); + delete options.modalOptions; + modalOptions.componentProps = { + title, + content, + ...options, + }; + + const modal = await ModalController.instance.create(modalOptions); + + await modal.present(); } } @@ -970,7 +990,7 @@ export type CoreTextUtilsViewTextOptions = { instanceId?: number; // The instance ID related to the context. courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. displayCopyButton?: boolean; // Whether to display a button to copy the text. - // modalOptions?: ModalOptions; // Modal options. @todo + modalOptions?: Partial; // Modal options. }; export class CoreTextUtils extends makeSingleton(CoreTextUtilsProvider) {} diff --git a/src/core/shared.module.ts b/src/core/shared.module.ts index 5f7987b34..ab2842e22 100644 --- a/src/core/shared.module.ts +++ b/src/core/shared.module.ts @@ -18,7 +18,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; @NgModule({ - imports: [ + exports: [ CoreComponentsModule, CoreDirectivesModule, CorePipesModule, diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts index 745e3d058..6c6029688 100644 --- a/src/core/singletons/events.ts +++ b/src/core/singletons/events.ts @@ -263,3 +263,11 @@ export type CoreEventFormActionData = CoreEventSiteData & { form: HTMLElement; // Form element. online?: boolean; // Whether the data was sent to server or not. Only when submitting. }; + + +/** + * Data passed to NOTIFICATION_SOUND_CHANGED event. + */ +export type CoreEventNotificationSoundChangedData = CoreEventSiteData & { + enabled: boolean; +}; diff --git a/src/theme/app.scss b/src/theme/app.scss index 12f1c8a3e..8ef64fe3e 100644 --- a/src/theme/app.scss +++ b/src/theme/app.scss @@ -167,6 +167,11 @@ ion-toolbar { --border-color: var(--ion-color-danger); } +// Extra text colors. +.text-gray { + color: var(--gray-dark); +} + // Card styles // Message cards. diff --git a/src/theme/variables.scss b/src/theme/variables.scss index 6e85594df..58a18f945 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -91,6 +91,7 @@ --ion-text-color: #3a3a3a; --ion-text-color-rgb: 58,58,58; + --ion-card-color: var(--ion-text-color); ion-content { --background: var(--gray-light); diff --git a/tsconfig.app.json b/tsconfig.app.json index 69cd4cf31..feedfbd74 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -11,6 +11,7 @@ "webpack-env" ], "paths": { + "@addons/*": ["addons/*"], "@classes/*": ["core/classes/*"], "@components/*": ["core/components/*"], "@directives/*": ["core/directives/*"], diff --git a/tsconfig.json b/tsconfig.json index f94ebb411..0e32c7acc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,6 +30,7 @@ "webpack-env" ], "paths": { + "@addons/*": ["addons/*"], "@classes/*": ["core/classes/*"], "@components/*": ["core/components/*"], "@directives/*": ["core/directives/*"], diff --git a/tsconfig.test.json b/tsconfig.test.json index 7c1e71c67..34d2a3698 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -15,6 +15,7 @@ "node" ], "paths": { + "@addons/*": ["addons/*"], "@classes/*": ["core/classes/*"], "@components/*": ["core/components/*"], "@directives/*": ["core/directives/*"], diff --git a/upgrade.txt b/upgrade.txt new file mode 100644 index 000000000..0f6b475e9 --- /dev/null +++ b/upgrade.txt @@ -0,0 +1,53 @@ +This files describes API changes in the Moodle Mobile app, +information provided here is intended especially for developers. + +=== 3.9.5 === + +- Several functions inside AddonNotificationsProvider have been modified to accept an "options" parameter instead of having several optional parameters. + +=== 3.9.3 === + +- In the core-attachments component, passing a -1 as maxSize or maxSubmissions used to mean "unknown limit". Now -1 means unlimited. Also, passing a 0 to maxSize used to mean "unknown" too, now 0 means user max size. +- Most functions that call a WebService in addons/mod have been modified to accept an "options" parameter instead of having several optional parameters. + +=== 3.8.3 === + +- CoreFileProvider.writeFileDataInFile has been deprecated. Please use CoreFileHelperProvider.writeFileDataInFile instead. + +=== 3.8.0 === + +- CoreDomUtilsProvider.extractDownloadableFilesFromHtml and CoreDomUtilsProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects have been deprecated. Please use CoreFilepoolProvider.extractDownloadableFilesFromHtml and CoreFilepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects. We had to move them to prevent a circular dependency. + +=== 3.7.1 === + +- CoreGroupsProvider.getActivityAllowedGroups and CoreGroupsProvider.getActivityAllowedGroupsIfEnabled now return the full response of core_group_get_activity_allowed_groups instead of just the groups. + +=== 3.7.0 === + +- The pushnotifications addon has been moved to core. All imports of that addon need to be fixed to use the right path and name. +- Now the expirationTime of cache entries contains the time the entry was modified, not the expiration time. This is to allow calculating dynamic expiration times. We decided to reuse the same field to prevent modifying the database table. + +=== 3.6.1 === + +- The local notifications plugin was updated to its latest version. The new API has some breaking changes, so please check its documentation if you're using local notifications. Also, you need to run "npm install" to update the ionic-native library. +- New method CoreSitesProvider.registerSiteSchema allows to register table schemas and migration functions for site databases. Prefer this method over CoreSitesProvider.createTablesFromSchema: it supports schema migrations and it tracks the installed version of the schema, so it does not try to create the tables on every app boot. + +=== 3.6.0 === + +- gulp was updated to v4. In order for gulp to work, you need to install gulp-cli: npm install -g gulp-cli + It's also recommended to update ionic cli to v4, otherwise some errors could be raised while building: npm install -g ionic +- The value of the constant CoreCourseProvider.ALL_SECTIONS_ID has changed from -1 to -2. +- Use of completionstatus on the module object has been deprecated, use completiondata instead. +- The function CoreSitesProvider.loadSite has changed, now it will trigger SESSION_EXPIRED event if the site is logged out. Its params and return value have changed. +- When using CoreDomUtils.showAlert, please use alert.didDismiss.subscribe() instead of alert.onDidDismiss(). +- The page CoreSharedFilesChooseSitePage now expects to receive the full path to the file (file.toURL()) instead of the relative path. +- The following strings have been deprecated: + core.dfdaymonthyear. Please use core.strftimedatefullshort instead. + core.dfdayweekmonth. Please use core.strftimedayshort instead. + core.dffulldate. Please use core.strftimedaydatetime instead. + core.dfmediumdate. Please use core.strftimedaydatetime instead. + core.dftimedate. Please use core.strftimetime instead. + +=== 3.5.2 === + +- CoreChartDirective changed from directive to component (CoreChartComponent) and the selector to use it changed from canvas[core-chart] to core-chart.