-
-
-
+
+
{{profile.fullname}}
@@ -66,9 +65,7 @@
-
-
-
+
{{ 'addon.mod_workshop.feedbackby' | translate : {$a: evaluateGradingByProfile.fullname} }}
diff --git a/src/addon/mod/workshop/pages/submission/submission.html b/src/addon/mod/workshop/pages/submission/submission.html
index d2373d42c..79b103899 100644
--- a/src/addon/mod/workshop/pages/submission/submission.html
+++ b/src/addon/mod/workshop/pages/submission/submission.html
@@ -36,9 +36,7 @@
-
-
-
+
{{ 'addon.mod_workshop.feedbackby' | translate : {$a: evaluateByProfile.fullname} }}
@@ -97,9 +95,7 @@
-
-
-
+
{{ 'addon.mod_workshop.feedbackby' | translate : {$a: evaluateGradingByProfile.fullname} }}
diff --git a/src/addon/notes/components/list/addon-notes-list.html b/src/addon/notes/components/list/addon-notes-list.html
index 21f24aba2..c2a0a52d2 100644
--- a/src/addon/notes/components/list/addon-notes-list.html
+++ b/src/addon/notes/components/list/addon-notes-list.html
@@ -27,9 +27,7 @@
0">
-
-
-
+
{{note.userfullname}}
{{note.lastmodified | coreDateDayOrTime}}
{{ 'core.notsent' | translate }}
diff --git a/src/addon/notifications/pages/list/list.html b/src/addon/notifications/pages/list/list.html
index 234690dd6..19b3805cd 100644
--- a/src/addon/notifications/pages/list/list.html
+++ b/src/addon/notifications/pages/list/list.html
@@ -19,9 +19,7 @@
-
-
-
+
{{notification.userfromfullname}}
{{notification.timecreated | coreDateDayOrTime}}
diff --git a/src/app/app.scss b/src/app/app.scss
index a527a7016..6a3b55d09 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -173,6 +173,7 @@ ion-app.app-root {
border-radius : 50%;
padding: 4px;
border: 1px solid #ddd;
+ background-color: white;
&.avatar-full {
border-radius: 2%;
diff --git a/src/components/chrono/chrono.ts b/src/components/chrono/chrono.ts
index ca8757a41..5770db45f 100644
--- a/src/components/chrono/chrono.ts
+++ b/src/components/chrono/chrono.ts
@@ -52,7 +52,7 @@ export class CoreChronoComponent implements OnChanges, OnDestroy {
}
/**
- * Component being initialized.
+ * Component being changed.
*/
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
if (changes && changes.running) {
diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index 487778e49..0e631c2b9 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -50,6 +50,7 @@ import { CoreAttachmentsComponent } from './attachments/attachments';
import { CoreIonTabsComponent } from './ion-tabs/ion-tabs';
import { CoreIonTabComponent } from './ion-tabs/ion-tab';
import { CoreInfiniteLoadingComponent } from './infinite-loading/infinite-loading';
+import { CoreUserAvatarComponent } from './user-avatar/user-avatar';
@NgModule({
declarations: [
@@ -85,7 +86,8 @@ import { CoreInfiniteLoadingComponent } from './infinite-loading/infinite-loadin
CoreAttachmentsComponent,
CoreIonTabsComponent,
CoreIonTabComponent,
- CoreInfiniteLoadingComponent
+ CoreInfiniteLoadingComponent,
+ CoreUserAvatarComponent
],
entryComponents: [
CoreContextMenuPopoverComponent,
@@ -128,7 +130,8 @@ import { CoreInfiniteLoadingComponent } from './infinite-loading/infinite-loadin
CoreAttachmentsComponent,
CoreIonTabsComponent,
CoreIonTabComponent,
- CoreInfiniteLoadingComponent
+ CoreInfiniteLoadingComponent,
+ CoreUserAvatarComponent
]
})
export class CoreComponentsModule {}
diff --git a/src/components/user-avatar/core-user-avatar.html b/src/components/user-avatar/core-user-avatar.html
new file mode 100644
index 000000000..d9b0c7228
--- /dev/null
+++ b/src/components/user-avatar/core-user-avatar.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/components/user-avatar/user-avatar.scss b/src/components/user-avatar/user-avatar.scss
new file mode 100644
index 000000000..775b0cacf
--- /dev/null
+++ b/src/components/user-avatar/user-avatar.scss
@@ -0,0 +1,15 @@
+ion-avatar[core-user-avatar] {
+ position: relative;
+
+ .contact-status {
+ position: absolute;
+ @include position(null, 0, 0, null);
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ &.online {
+ border: 1px solid white;
+ background-color: $core-online-color;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/user-avatar/user-avatar.ts b/src/components/user-avatar/user-avatar.ts
new file mode 100644
index 000000000..20aab67fd
--- /dev/null
+++ b/src/components/user-avatar/user-avatar.ts
@@ -0,0 +1,107 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// 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, OnChanges, SimpleChange } from '@angular/core';
+import { NavController } from 'ionic-angular';
+import { CoreSitesProvider } from '@providers/sites';
+
+/**
+ * Component to display a "user avatar".
+ *
+ * Example:
+ */
+@Component({
+ selector: 'ion-avatar[core-user-avatar]',
+ templateUrl: 'core-user-avatar.html'
+})
+export class CoreUserAvatarComponent implements OnInit, OnChanges {
+ @Input() user: any;
+ // The following params will override the ones in user object.
+ @Input() profileUrl?: string;
+ @Input() protected linkProfile = true; // Avoid linking to the profile if wanted.
+ @Input() fullname?: string;
+ @Input() protected userId?: number; // If provided or found it will be used to link the image to the profile.
+ @Input() protected courseId?: number;
+ @Input() checkOnline = false; // If want to check and show online status.
+
+ // Variable to check if we consider this user online or not.
+ // @TODO: Use setting when available (see MDL-63972) so we can use site setting.
+ protected timetoshowusers = 300000; // Miliseconds default.
+ protected myUser = false;
+ protected currentUserId: number;
+
+ constructor(private navCtrl: NavController, private sitesProvider: CoreSitesProvider) {
+ this.currentUserId = this.sitesProvider.getCurrentSiteUserId();
+ }
+
+ /**
+ * Component being initialized.
+ */
+ ngOnInit(): void {
+ this.setFields();
+ }
+
+ /**
+ * Listen to changes.
+ */
+ ngOnChanges(changes: { [name: string]: SimpleChange }): void {
+ // If something change, update the fields.
+ if (changes) {
+ this.setFields();
+ }
+ }
+
+ /**
+ * Set fields from user.
+ */
+ protected setFields(): void {
+ this.profileUrl = this.profileUrl || (this.user && (this.user.profileimageurl || this.user.userprofileimageurl ||
+ this.user.userpictureurl || this.user.profileimageurlsmall));
+
+ if (typeof this.profileUrl != 'string') {
+ this.profileUrl = '';
+ }
+
+ this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname));
+
+ this.userId = this.userId || (this.user && this.user.userid);
+ this.courseId = this.courseId || (this.user && this.user.courseid);
+
+ // If not available we cannot ensure the avatar is from the current user.
+ this.myUser = this.userId && this.userId == this.currentUserId;
+
+ }
+
+ /**
+ * Helper function for checking the time meets the 'online' condition.
+ *
+ * @return boolean
+ */
+ isOnline(): boolean {
+ const time = new Date().getTime() - this.timetoshowusers;
+
+ return !this.myUser && ((this.user.lastaccess && this.user.lastaccess * 1000 >= time) || this.user.isonline);
+ }
+
+ /**
+ * Function executed image clicked.
+ */
+ gotoProfile(event: any): void {
+ if (this.linkProfile && this.userId) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.navCtrl.push('CoreUserProfilePage', { userId: this.userId, courseId: this.courseId });
+ }
+ }
+}
diff --git a/src/core/comments/pages/viewer/viewer.html b/src/core/comments/pages/viewer/viewer.html
index 5087edd96..bcd909ecd 100644
--- a/src/core/comments/pages/viewer/viewer.html
+++ b/src/core/comments/pages/viewer/viewer.html
@@ -12,9 +12,7 @@
-
-
-
+
{{ comment.fullname }}
{{ comment.time }}
diff --git a/src/core/course/course.module.ts b/src/core/course/course.module.ts
index 541c4333b..e6e01a772 100644
--- a/src/core/course/course.module.ts
+++ b/src/core/course/course.module.ts
@@ -13,7 +13,9 @@
// limitations under the License.
import { NgModule } from '@angular/core';
+import { Platform } from 'ionic-angular';
import { CoreCronDelegate } from '@providers/cron';
+import { CoreEventsProvider } from '@providers/events';
import { CoreCourseProvider } from './providers/course';
import { CoreCourseHelperProvider } from './providers/helper';
import { CoreCourseFormatDelegate } from './providers/format-delegate';
@@ -29,6 +31,7 @@ import { CoreCourseFormatTopicsModule } from './formats/topics/topics.module';
import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module';
import { CoreCourseSyncProvider } from './providers/sync';
import { CoreCourseSyncCronHandler } from './providers/sync-cron-handler';
+import { CoreCourseLogCronHandler } from './providers/log-cron-handler';
// List of providers (without handlers).
export const CORE_COURSE_PROVIDERS: any[] = [
@@ -61,12 +64,29 @@ export const CORE_COURSE_PROVIDERS: any[] = [
CoreCourseSyncProvider,
CoreCourseFormatDefaultHandler,
CoreCourseModuleDefaultHandler,
- CoreCourseSyncCronHandler
+ CoreCourseSyncCronHandler,
+ CoreCourseLogCronHandler
],
exports: []
})
export class CoreCourseModule {
- constructor(cronDelegate: CoreCronDelegate, syncHandler: CoreCourseSyncCronHandler) {
+ constructor(cronDelegate: CoreCronDelegate, syncHandler: CoreCourseSyncCronHandler, logHandler: CoreCourseLogCronHandler,
+ platform: Platform, eventsProvider: CoreEventsProvider) {
cronDelegate.register(syncHandler);
+ cronDelegate.register(logHandler);
+
+ platform.resume.subscribe(() => {
+ // Log the app is open to keep user in online status.
+ setTimeout(() => {
+ cronDelegate.forceCronHandlerExecution(logHandler.name);
+ }, 1000);
+ });
+
+ eventsProvider.on(CoreEventsProvider.LOGIN, () => {
+ // Log the app is open to keep user in online status.
+ setTimeout(() => {
+ cronDelegate.forceCronHandlerExecution(logHandler.name);
+ }, 1000);
+ });
}
}
diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts
index c915b4b4e..1a048fc84 100644
--- a/src/core/course/pages/section/section.ts
+++ b/src/core/course/pages/section/section.ts
@@ -202,6 +202,11 @@ export class CoreCourseSectionPage implements OnDestroy {
}).then((sections) => {
let promise;
+ // Add log in Moodle.
+ this.courseProvider.logView(this.course.id, this.sectionNumber).catch(() => {
+ // Ignore errors.
+ });
+
// Get the completion status.
if (this.course.enablecompletion === false) {
// Completion not enabled.
diff --git a/src/core/course/providers/log-cron-handler.ts b/src/core/course/providers/log-cron-handler.ts
new file mode 100644
index 000000000..108c8765d
--- /dev/null
+++ b/src/core/course/providers/log-cron-handler.ts
@@ -0,0 +1,59 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// 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 { CoreCronHandler } from '@providers/cron';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreCourseProvider } from '@core/course/providers/course';
+
+/**
+ * Log cron handler. It will update last access of the user while app is open.
+ */
+@Injectable()
+export class CoreCourseLogCronHandler implements CoreCronHandler {
+ name = 'CoreCourseLogCronHandler';
+
+ constructor(private coreProvider: CoreCourseProvider, private sitesProvider: CoreSitesProvider) {}
+
+ /**
+ * Execute the process.
+ * Receives the ID of the site affected, undefined for the current site.
+ *
+ * @param {string} [siteId] ID of the site affected, undefined for the current site.
+ * @return {Promise} Promise resolved when done, rejected if failure.
+ */
+ execute(siteId?: string): Promise {
+ return this.sitesProvider.getSite(siteId).then((site) => {
+ return this.coreProvider.logView(site.getSiteHomeId(), undefined, site.getId());
+ });
+ }
+
+ /**
+ * Check whether it's a synchronization process or not.
+ *
+ * @return {boolean} Whether it's a synchronization process or not.
+ */
+ isSync(): boolean {
+ return false;
+ }
+
+ /**
+ * Get the time between consecutive executions.
+ *
+ * @return {number} Time between consecutive executions (in ms).
+ */
+ getInterval(): number {
+ return 240000; // 4 minutes. By default platform will see the user as online if lastaccess is less than 5 minutes.
+ }
+}
diff --git a/src/core/courses/pages/course-preview/course-preview.html b/src/core/courses/pages/course-preview/course-preview.html
index 2a52bad10..e29da97d7 100644
--- a/src/core/courses/pages/course-preview/course-preview.html
+++ b/src/core/courses/pages/course-preview/course-preview.html
@@ -26,10 +26,8 @@
{{ 'core.teachers' | translate }}
-
-
-
-
+
+
{{contact.fullname}}
diff --git a/src/core/mainmenu/pages/more/more.html b/src/core/mainmenu/pages/more/more.html
index b7b585c76..7f5b97a70 100644
--- a/src/core/mainmenu/pages/more/more.html
+++ b/src/core/mainmenu/pages/more/more.html
@@ -5,10 +5,8 @@
-
-
-
-
+
+
{{siteInfo.fullname}}
diff --git a/src/core/user/components/participants/core-user-participants.html b/src/core/user/components/participants/core-user-participants.html
index 893b5a78a..9f8f67182 100644
--- a/src/core/user/components/participants/core-user-participants.html
+++ b/src/core/user/components/participants/core-user-participants.html
@@ -9,9 +9,7 @@
0" no-margin>
-
-
-
+
{{ 'core.lastaccess' | translate }}: {{ participant.lastaccess * 1000 | coreFormatDate:"dfmediumdate"}}
diff --git a/src/core/user/pages/profile/profile.html b/src/core/user/pages/profile/profile.html
index 67737ecb4..57d96674e 100644
--- a/src/core/user/pages/profile/profile.html
+++ b/src/core/user/pages/profile/profile.html
@@ -10,10 +10,9 @@
-
-
![{{ 'core.pictureof' | translate:{$a: user.fullname} }}]()
+
-
+
diff --git a/src/core/user/pages/profile/profile.scss b/src/core/user/pages/profile/profile.scss
index 496e1ae0c..edee0fc71 100644
--- a/src/core/user/pages/profile/profile.scss
+++ b/src/core/user/pages/profile/profile.scss
@@ -1,9 +1,27 @@
ion-app.app-root page-core-user-profile {
.core-icon-foreground {
- position: relative;
- @include position(null, null, 30px, 60px);
+ position: absolute;
+ @include position(null, 0, 0, null);
font-size: 24px;
+ line-height: 30px;
+ text-align: center;
+
+ width: 30px;
+ height: 30px;
+ border-radius: 50%;
+ background-color: white;
}
+ [core-user-avatar].item-avatar-center {
+ display: inline-block;
+ img {
+ margin: 0;
+ }
+ .contact-status {
+ width: 24px;
+ height: 24px;
+ }
+ }
+
.core-user-communication-handlers {
background: $list-background-color;
border-bottom: 1px solid $list-border-color;
diff --git a/src/providers/cron.ts b/src/providers/cron.ts
index 74eae363e..b82335476 100644
--- a/src/providers/cron.ts
+++ b/src/providers/cron.ts
@@ -236,23 +236,37 @@ export class CoreCronDelegate {
const promises = [];
for (const name in this.handlers) {
- const handler = this.handlers[name];
if (this.isHandlerManualSync(name)) {
- // Mark the handler as running (it might be running already).
- handler.running = true;
-
- // Cancel pending timeout.
- clearTimeout(handler.timeout);
- delete handler.timeout;
-
// Now force the execution of the handler.
- promises.push(this.checkAndExecuteHandler(name, true, siteId));
+ promises.push(this.forceCronHandlerExecution(name, siteId));
}
}
return this.utils.allPromises(promises);
}
+ /**
+ * Force execution of a cron tasks without waiting for the scheduled time.
+ * Please notice that some tasks may not be executed depending on the network connection and sync settings.
+ *
+ * @param {string} [name] If provided, the name of the handler.
+ * @param {string} [siteId] Site ID. If not defined, all sites.
+ * @return {Promise} Promise resolved if handler has been executed successfully, rejected otherwise.
+ */
+ forceCronHandlerExecution(name?: string, siteId?: string): Promise {
+ const handler = this.handlers[name];
+
+ // Mark the handler as running (it might be running already).
+ handler.running = true;
+
+ // Cancel pending timeout.
+ clearTimeout(handler.timeout);
+ delete handler.timeout;
+
+ // Now force the execution of the handler.
+ return this.checkAndExecuteHandler(name, true, siteId);
+ }
+
/**
* Get a handler's interval.
*
diff --git a/src/theme/variables.scss b/src/theme/variables.scss
index 44d572b52..7eeb788e9 100644
--- a/src/theme/variables.scss
+++ b/src/theme/variables.scss
@@ -111,6 +111,8 @@ $button-md-box-shadow: 0 2px 2px 1px rgba(0, 0, 0, .14), 0 3px 1px -2px rgba(0,
$refresher-icon-color: $core-color !default;
+$core-online-color: #5cb85c;
+
// Moodle Mobile variables
// --------------------------------------------------