MOBILE-3021 calendar: UX improvements
This commit is contained in:
		
							parent
							
								
									0868a4646c
								
							
						
					
					
						commit
						b24cbab400
					
				| @ -91,6 +91,7 @@ | ||||
|   "addon.calendar.calendarreminders": "local_moodlemobileapp", | ||||
|   "addon.calendar.confirmeventdelete": "calendar", | ||||
|   "addon.calendar.confirmeventseriesdelete": "calendar", | ||||
|   "addon.calendar.currentmonth": "local_moodlemobileapp", | ||||
|   "addon.calendar.daynext": "calendar", | ||||
|   "addon.calendar.dayprev": "calendar", | ||||
|   "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| 
 | ||||
| <!-- Add buttons to the nav bar. --> | ||||
| <core-navbar-buttons end prepend> | ||||
|     <button [hidden]="!canNavigate || isCurrentMonth || !displayNavButtons" ion-button icon-only clear (click)="goToCurrentMonth()"> | ||||
|         <core-icon  name="fa-calendar-times-o"></core-icon> | ||||
|     </button> | ||||
|     <core-context-menu> | ||||
|         <core-context-menu-item *ngIf="canNavigate && !isCurrentMonth && displayNavButtons" [priority]="900" [content]="'addon.calendar.currentmonth' | translate" [iconAction]="'fa-calendar-times-o'" (action)="goToCurrentMonth()"></core-context-menu-item> | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| 
 | ||||
| <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||
| @ -31,15 +31,16 @@ | ||||
|         <!-- List of days. --> | ||||
|         <ion-row> | ||||
|             <ion-col text-center *ngFor="let day of weekDays" class="addon-calendar-weekday"> | ||||
|                 {{ day.shortname | translate }} | ||||
|                 <span class="hidden-tablet" [title]="day.fullname | translate">{{ day.shortname | translate }}</span> | ||||
|                 <span class="hidden-phone">{{ day.fullname | translate }}</span> | ||||
|             </ion-col> | ||||
|         </ion-row> | ||||
| 
 | ||||
|         <!-- Weeks. --> | ||||
|         <ion-row *ngFor="let week of weeks" class="addon-calendar-week"> | ||||
|             <ion-col *ngFor="let value of week.prepadding" class="dayblank addon-calendar-day"></ion-col> <!-- Empty slots (first week). --> | ||||
|             <ion-col text-center *ngFor="let day of week.days"  (click)="dayClicked(day.mday)" [ngClass]='{"hasevents": day.hasevents, "today": day.istoday, "weekend": day.isweekend, "duration_finish": day.haslastdayofevent}' class="addon-calendar-day"> | ||||
|                 <p class="addon-calendar-day-number">{{ day.mday }}</p> | ||||
|             <ion-col text-center *ngFor="let day of week.days"  (click)="dayClicked(day.mday)" [ngClass]='{"hasevents": day.hasevents, "today": day.istoday, "weekend": day.isweekend, "duration_finish": day.haslastdayofevent}' class="addon-calendar-day" [class.addon-calendar-event-past-day]="isPastMonth || day.ispast"> | ||||
|                 <p class="addon-calendar-day-number"><span>{{ day.mday }}</span></p> | ||||
| 
 | ||||
|                 <!-- In phone, display some dots to indicate the type of events. --> | ||||
|                 <p class="hidden-tablet addon-calendar-dot-types"><span *ngFor="let type of day.calendareventtypes" class="calendar_event_type calendar_event_{{type}}"></span></p> | ||||
| @ -47,12 +48,12 @@ | ||||
|                 <!-- In tablet, display list of events. --> | ||||
|                 <div class="hidden-phone addon-calendar-day-events"> | ||||
|                     <ng-container *ngFor="let event of day.filteredEvents | slice:0:4; let index = index"> | ||||
|                         <p *ngIf="index < 3 || day.filteredEvents.length == 4" class="addon-calendar-event" (click)="eventClicked(event, $event)"> | ||||
|                         <p *ngIf="index < 3 || day.filteredEvents.length == 4" class="addon-calendar-event" (click)="eventClicked(event, $event)" [class.addon-calendar-event-past]="event.ispast"> | ||||
|                             <span class="calendar_event_type calendar_event_{{event.formattedType}}"></span> | ||||
|                             <ion-icon *ngIf="event.offline && !event.deleted" name="time"></ion-icon> | ||||
|                             <ion-icon *ngIf="event.deleted" name="trash"></ion-icon> | ||||
|                             <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon"> | ||||
|                             <span class="addon-calendar-event-time">{{ event.timestart * 1000 | coreFormatDate: timeFormat }}</span> | ||||
|                             <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon"> | ||||
|                             <span class="addon-calendar-event-name">{{event.name}}</span> | ||||
|                         </p> | ||||
|                     </ng-container> | ||||
|  | ||||
| @ -32,17 +32,34 @@ ion-app.app-root addon-calendar-calendar { | ||||
|             @include border-end(0, null, null); | ||||
|             @include padding(null, 8px, null, null); | ||||
|         } | ||||
|         .addon-calendar-day-number { | ||||
|             height: 24px; | ||||
|             line-height: 24px; | ||||
|             width: max-content; | ||||
|             min-width: 24px; | ||||
|             text-align: center; | ||||
|             font-weight: 500; | ||||
|             display: inline-block; | ||||
|             margin: 3px; | ||||
| 
 | ||||
|         &.addon-calendar-event-past-day > .addon-calendar-dot-types, | ||||
|         &.addon-calendar-event-past-day > .addon-calendar-day-events { | ||||
|             opacity: 0.5; | ||||
|         } | ||||
|         &.today .addon-calendar-day-number { | ||||
| 
 | ||||
|         .addon-calendar-day-number { | ||||
|             margin: 0; | ||||
| 
 | ||||
|             span { | ||||
|                 line-height: 24px; | ||||
|                 font-weight: 500; | ||||
|                 display: inline-block; | ||||
|                 margin: 3px; | ||||
|                 width: max-content; | ||||
|                 width: 24px; | ||||
|                 height: 24px; | ||||
|                 text-align: center; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @include media-breakpoint-up(md) { | ||||
|             .addon-calendar-day-number { | ||||
|                 text-align: left; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         &.today .addon-calendar-day-number span { | ||||
|             background-color: $calendar-today-bgcolor; | ||||
|             color: $calendar-today-color; | ||||
| 
 | ||||
| @ -58,6 +75,10 @@ ion-app.app-root addon-calendar-calendar { | ||||
|             overflow: hidden; | ||||
|             white-space: nowrap; | ||||
| 
 | ||||
|             &.addon-calendar-event-past { | ||||
|                 opacity: 0.5; | ||||
|             } | ||||
| 
 | ||||
|             .addon-calendar-event-name { | ||||
|                 font-weight: 500; | ||||
|             } | ||||
| @ -81,7 +102,6 @@ ion-app.app-root addon-calendar-calendar { | ||||
|     } | ||||
| 
 | ||||
|     .addon-calendar-weekday { | ||||
|         color: $gray-dark; | ||||
|         border-bottom: 1px solid $list-md-border-color; | ||||
|     } | ||||
| 
 | ||||
| @ -130,6 +150,6 @@ ion-app.app-root addon-calendar-calendar { | ||||
|         width: 16px; | ||||
|         height: 16px; | ||||
|         display: inline-block; | ||||
|         vertical-align: middle; | ||||
|         vertical-align: bottom; | ||||
|     } | ||||
| } | ||||
| @ -48,6 +48,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
|     loaded = false; | ||||
|     timeFormat: string; | ||||
|     isCurrentMonth: boolean; | ||||
|     isPastMonth: boolean; | ||||
| 
 | ||||
|     protected year: number; | ||||
|     protected month: number; | ||||
| @ -57,6 +58,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
|     protected offlineEvents: {[monthId: string]: {[day: number]: any[]}} = {}; // Offline events classified in month & day.
 | ||||
|     protected offlineEditedEventsIds = []; // IDs of events edited in offline.
 | ||||
|     protected deletedEvents = []; // Events deleted in offline. | ||||
|     protected currentTime: number; | ||||
| 
 | ||||
|     // Observers.
 | ||||
|     protected undeleteEventObserver: any; | ||||
| @ -200,6 +202,28 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
|             this.weekDays = this.calendarProvider.getWeekDays(result.daynames[0].dayno); | ||||
|             this.weeks = result.weeks; | ||||
| 
 | ||||
|             this.calculateIsCurrentMonth(); | ||||
| 
 | ||||
|             if (this.isCurrentMonth) { | ||||
|                 let isPast = true; | ||||
|                 this.weeks.forEach((week) => { | ||||
|                     week.days.some((day) => { | ||||
|                         day.ispast = isPast && !day.istoday; | ||||
|                         isPast = day.ispast; | ||||
| 
 | ||||
|                         if (day.istoday) { | ||||
|                             day.events.forEach((event) => { | ||||
|                                 event.ispast = this.isEventPast(event); | ||||
|                             }); | ||||
| 
 | ||||
|                             return true; | ||||
|                         } | ||||
| 
 | ||||
|                         return day.istoday; | ||||
|                     }); | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             // Merge the online events with offline data.
 | ||||
|             this.mergeEvents(); | ||||
| 
 | ||||
| @ -288,7 +312,6 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
|             this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); | ||||
|             this.decreaseMonth(); | ||||
|         }).finally(() => { | ||||
|             this.calculateIsCurrentMonth(); | ||||
|             this.loaded = true; | ||||
|         }); | ||||
|     } | ||||
| @ -305,7 +328,6 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
|             this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); | ||||
|             this.increaseMonth(); | ||||
|         }).finally(() => { | ||||
|             this.calculateIsCurrentMonth(); | ||||
|             this.loaded = true; | ||||
|         }); | ||||
|     } | ||||
| @ -336,7 +358,10 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
|     calculateIsCurrentMonth(): void { | ||||
|         const now = new Date(); | ||||
| 
 | ||||
|         this.currentTime = this.timeUtils.timestamp(); | ||||
| 
 | ||||
|         this.isCurrentMonth = this.year == now.getFullYear() && this.month == now.getMonth() + 1; | ||||
|         this.isPastMonth = this.year < now.getFullYear() || (this.year == now.getFullYear() && this.month < now.getMonth() + 1); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -466,6 +491,15 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns if the event is in the past or not. | ||||
|      * @param  {any}     event Event object. | ||||
|      * @return {boolean}       True if it's in the past. | ||||
|      */ | ||||
|     isEventPast(event: any): boolean { | ||||
|         return (event.timestart + event.timeduration) < this.currentTime; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Component destroyed. | ||||
|      */ | ||||
|  | ||||
| @ -6,6 +6,7 @@ | ||||
|     "calendarreminders": "Calendar reminders", | ||||
|     "confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?", | ||||
|     "confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?", | ||||
|     "currentmonth": "Current Month", | ||||
|     "daynext": "Next day", | ||||
|     "dayprev": "Previous day", | ||||
|     "defaultnotificationtime": "Default notification time", | ||||
|  | ||||
| @ -2,13 +2,12 @@ | ||||
|     <ion-navbar core-back-button> | ||||
|         <ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title> | ||||
|         <ion-buttons end> | ||||
|             <button *ngIf="!isCurrentDay" ion-button icon-only clear (click)="goToCurrentDay()"> | ||||
|                 <core-icon name="fa-calendar-times-o"></core-icon> | ||||
|             </button> | ||||
|             <button *ngIf="courses && courses.length" ion-button icon-only (click)="openCourseFilter($event)" [attr.aria-label]="'core.courses.filter' | translate"> | ||||
|                 <ion-icon name="funnel"></ion-icon> | ||||
|                 <ion-icon name="funnel" *ngIf="courseId"></ion-icon> | ||||
|                 <ion-icon name="ios-funnel-outline" *ngIf="!courseId"></ion-icon> | ||||
|             </button> | ||||
|             <core-context-menu> | ||||
|                 <core-context-menu-item *ngIf="!isCurrentDay" [priority]="900" [content]="'addon.calendar.today' | translate" [iconAction]="'fa-calendar-times-o'" (action)="goToCurrentDay()"></core-context-menu-item> | ||||
|                 <core-context-menu-item [hidden]="!loaded || !hasOffline || !isOnline"  [priority]="400" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item> | ||||
|             </core-context-menu> | ||||
|         </ion-buttons> | ||||
| @ -49,7 +48,7 @@ | ||||
| 
 | ||||
|         <ion-list *ngIf="filteredEvents && filteredEvents.length" no-margin> | ||||
|             <ng-container *ngFor="let event of filteredEvents"> | ||||
|                 <a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)"> | ||||
|                 <ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast"> | ||||
|                     <img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon"> | ||||
|                     <core-icon *ngIf="event.icon && !event.moduleIcon" [name]="event.icon" item-start></core-icon> | ||||
|                     <h2><core-format-text [text]="event.name"></core-format-text></h2> | ||||
| @ -62,7 +61,7 @@ | ||||
|                         <ion-icon name="trash"></ion-icon> | ||||
|                         <span text-wrap>{{ 'core.deletedoffline' | translate }}</span> | ||||
|                     </ion-note> | ||||
|                 </a> | ||||
|                 </ion-item> | ||||
|             </ng-container> | ||||
|         </ion-list> | ||||
| 
 | ||||
|  | ||||
| @ -51,6 +51,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|     protected deletedEvents = []; // Events deleted in offline.
 | ||||
|     protected timeFormat: string; | ||||
|     protected currentMoment: moment.Moment; | ||||
|     protected currentTime: number; | ||||
| 
 | ||||
|     // Observers.
 | ||||
|     protected newEventObserver: any; | ||||
| @ -74,6 +75,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|     isOnline = false; | ||||
|     syncIcon: string; | ||||
|     isCurrentDay: boolean; | ||||
|     isPastDay: boolean; | ||||
| 
 | ||||
|     constructor(localNotificationsProvider: CoreLocalNotificationsProvider, | ||||
|             navParams: NavParams, | ||||
| @ -308,9 +310,12 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|             // Filter events by course.
 | ||||
|             this.filterEvents(); | ||||
| 
 | ||||
|             this.calculateIsCurrentDay(); | ||||
| 
 | ||||
|             // Re-calculate the formatted time so it uses the device date.
 | ||||
|             const dayTime = this.currentMoment.unix() * 1000; | ||||
|             this.events.forEach((event) => { | ||||
|                 event.ispast = this.isPastDay || (this.isCurrentDay && this.isEventPast(event)); | ||||
|                 promises.push(this.calendarProvider.formatEventTime(event, this.timeFormat, true, dayTime).then((time) => { | ||||
|                     event.formattedtime = time; | ||||
|                 })); | ||||
| @ -555,7 +560,11 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|     calculateIsCurrentDay(): void { | ||||
|         const now = new Date(); | ||||
| 
 | ||||
|         this.currentTime = this.timeUtils.timestamp(); | ||||
| 
 | ||||
|         this.isCurrentDay = this.year == now.getFullYear() && this.month == now.getMonth() + 1 && this.day == now.getDate(); | ||||
|         this.isPastDay = this.year < now.getFullYear() || (this.year == now.getFullYear() && this.month < now.getMonth()) || | ||||
|             (this.year == now.getFullYear() && this.month == now.getMonth() + 1 && this.day < now.getDate()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -600,7 +609,6 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|             this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); | ||||
|             this.decreaseDay(); | ||||
|         }).finally(() => { | ||||
|             this.calculateIsCurrentDay(); | ||||
|             this.loaded = true; | ||||
|         }); | ||||
|     } | ||||
| @ -617,7 +625,6 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|             this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true); | ||||
|             this.increaseDay(); | ||||
|         }).finally(() => { | ||||
|             this.calculateIsCurrentDay(); | ||||
|             this.loaded = true; | ||||
|         }); | ||||
|     } | ||||
| @ -665,6 +672,15 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns if the event is in the past or not. | ||||
|      * @param  {any}     event Event object. | ||||
|      * @return {boolean}       True if it's in the past. | ||||
|      */ | ||||
|     isEventPast(event: any): boolean { | ||||
|         return (event.timestart + event.timeduration) < this.currentTime; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Page destroyed. | ||||
|      */ | ||||
|  | ||||
| @ -2,16 +2,13 @@ | ||||
|     <ion-navbar core-back-button> | ||||
|         <ion-title>{{ (showCalendar ? 'addon.calendar.calendarevents' : 'addon.calendar.upcomingevents') | translate }}</ion-title> | ||||
|         <ion-buttons end> | ||||
|             <button *ngIf="showCalendar" ion-button icon-only (click)="toggleDisplay()" [attr.aria-label]="'addon.calendar.upcomingevents' | translate"> | ||||
|                 <ion-icon name="list"></ion-icon> | ||||
|             </button> | ||||
|             <button *ngIf="!showCalendar" ion-button icon-only (click)="toggleDisplay()" [attr.aria-label]="'addon.calendar.monthlyview' | translate"> | ||||
|                 <core-icon name="fa-calendar-o"></core-icon> | ||||
|             </button> | ||||
|             <button *ngIf="courses && courses.length" ion-button icon-only (click)="openCourseFilter($event)" [attr.aria-label]="'core.courses.filter' | translate"> | ||||
|                 <ion-icon name="funnel"></ion-icon> | ||||
|                 <ion-icon name="funnel" *ngIf="courseId"></ion-icon> | ||||
|                 <ion-icon name="ios-funnel-outline" *ngIf="!courseId"></ion-icon> | ||||
|             </button> | ||||
|             <core-context-menu> | ||||
|                 <core-context-menu-item *ngIf="showCalendar" [priority]="800" [content]="'addon.calendar.upcomingevents' | translate" [iconAction]="'list'" (action)="toggleDisplay()"></core-context-menu-item> | ||||
|                 <core-context-menu-item *ngIf="!showCalendar" [priority]="800" [content]="'addon.calendar.monthlyview' | translate" [iconAction]="'fa-calendar-o'" (action)="toggleDisplay()"></core-context-menu-item> | ||||
|                 <core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600" [content]="'core.settings.settings' | translate" (action)="openSettings()" [iconAction]="'cog'"></core-context-menu-item> | ||||
|                 <core-context-menu-item [hidden]="!loaded || !hasOffline || !isOnline"  [priority]="400" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item> | ||||
|             </core-context-menu> | ||||
|  | ||||
| @ -1,3 +0,0 @@ | ||||
| page-addon-calendar-index .toolbar-ios ion-title { | ||||
|     @include padding-horizontal(null, 120px); | ||||
| } | ||||
| @ -3,7 +3,8 @@ | ||||
|         <ion-title>{{ 'addon.calendar.calendarevents' | translate }}</ion-title> | ||||
|         <ion-buttons end> | ||||
|             <button *ngIf="courses && courses.length" ion-button icon-only (click)="openCourseFilter($event)" [attr.aria-label]="'core.courses.filter' | translate"> | ||||
|                 <ion-icon name="funnel"></ion-icon> | ||||
|                 <ion-icon name="funnel" *ngIf="courseId"></ion-icon> | ||||
|                 <ion-icon name="ios-funnel-outline" *ngIf="!courseId"></ion-icon> | ||||
|             </button> | ||||
|             <core-context-menu> | ||||
|                 <core-context-menu-item [hidden]="!notificationsEnabled" [priority]="600" [content]="'core.settings.settings' | translate" (action)="openSettings()" [iconAction]="'cog'"></core-context-menu-item> | ||||
|  | ||||
| @ -90,6 +90,7 @@ | ||||
|     "addon.calendar.calendarreminders": "Calendar reminders", | ||||
|     "addon.calendar.confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?", | ||||
|     "addon.calendar.confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?", | ||||
|     "addon.calendar.currentmonth": "Current Month", | ||||
|     "addon.calendar.daynext": "Next day", | ||||
|     "addon.calendar.dayprev": "Previous day", | ||||
|     "addon.calendar.defaultnotificationtime": "Default notification time", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user