Merge pull request #4022 from crazyserver/MOBILE-4579

Mobile 4579
main
Dani Palou 2024-04-29 11:46:53 +02:00 committed by GitHub
commit eaedb0841a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 153 additions and 49 deletions

View File

@ -35,9 +35,21 @@
padding: 0px; padding: 0px;
.core-bar-button-image { .core-bar-button-image {
--userpicture-padding: 4px;
@include margin-horizontal(null, 6px); @include margin-horizontal(null, 6px);
} }
// Group avatar.
img.core-bar-button-image {
padding: var(--userpicture-padding);
width: var(--core-header-toolbar-button-image-size);
height: var(--core-header-toolbar-button-image-size);
max-width: var(--core-header-toolbar-button-image-size);
max-height: var(--core-header-toolbar-button-image-size);
border-radius: var(--core-avatar-radius);
display: block;
}
core-format-text { core-format-text {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -34,13 +34,14 @@
img { img {
width: var(--icon-size); width: var(--icon-size);
height: var(--icon-size); height: var(--icon-size);
padding: 4px;
} }
ion-icon { ion-icon {
font-size: var(--icon-size); font-size: var(--icon-size);
} }
padding: 0px; padding: 0px;
background: var(--background-color); background: var(--background-color);
border-radius: var(--mdl-shape-borderRadius-xs); border-radius: var(--core-avatar-radius);
@include margin(6px, 8px, 6px, 0px); @include margin(6px, 8px, 6px, 0px);
} }

View File

@ -119,7 +119,7 @@ export class AddonNotificationsProvider {
notification.notif = 1; notification.notif = 1;
notification.read = notification.timeread > 0; notification.read = notification.timeread > 0;
if (typeof notification.customdata == 'string') { if (typeof notification.customdata === 'string') {
notification.customdata = CoreTextUtils.parseJSON<Record<string, string|number>>(notification.customdata, {}); notification.customdata = CoreTextUtils.parseJSON<Record<string, string|number>>(notification.customdata, {});
} }
@ -142,9 +142,6 @@ export class AddonNotificationsProvider {
} }
} }
const imgUrl = notification.customdata?.notificationpictureurl || notification.customdata?.notificationiconurl;
notification.imgUrl = imgUrl ? String(imgUrl) : undefined;
if (notification.useridfrom > 0) { if (notification.useridfrom > 0) {
// Try to get the profile picture of the user. // Try to get the profile picture of the user.
try { try {
@ -155,6 +152,12 @@ export class AddonNotificationsProvider {
} catch { } catch {
// Error getting user. This can happen if device is offline or the user is deleted. // Error getting user. This can happen if device is offline or the user is deleted.
} }
} else {
// Do not assign avatar for newlogin notifications.
if (notification.eventtype !== 'newlogin') {
const imgUrl = notification.customdata?.notificationpictureurl || notification.customdata?.notificationiconurl;
notification.imgUrl = imgUrl ? String(imgUrl) : undefined;
}
} }
return notification; return notification;

View File

@ -25,9 +25,9 @@ describe('CoreUserAvatarComponent', () => {
// Assert. // Assert.
expect(nativeElement.innerHTML.trim()).not.toHaveLength(0); expect(nativeElement.innerHTML.trim()).not.toHaveLength(0);
const image = nativeElement.querySelector('img'); const initials = nativeElement.querySelector('.userinitials');
expect(image).not.toBeNull(); expect(initials).not.toBeNull();
expect(image?.src).toEqual(document.location.href + 'assets/img/user-avatar.png'); expect(initials?.getAttribute('data-initials')?.trim()).toEqual('UNK');
}); });
}); });

View File

@ -7,20 +7,15 @@
</ng-container> </ng-container>
<ng-container *ngIf="!avatarUrl && initials"> <ng-container *ngIf="!avatarUrl && initials">
<div class="userinitials" *ngIf="linkProfile" [attr.aria-label]="'core.pictureof' | translate:{$a: fullname}" <div class="userinitials" *ngIf="linkProfile" [attr.aria-label]="'core.pictureof' | translate:{$a: fullname}"
(ariaButtonClick)="gotoProfile($event)"> [title]="'core.pictureof' | translate:{$a: fullname}" (ariaButtonClick)="gotoProfile($event)" [attr.data-initials]="initials">
{{ initials }}
</div> </div>
<div class="userinitials" *ngIf="!linkProfile" [attr.aria-label]="'core.pictureof' | translate:{$a: fullname}" aria-hidden="true"> <div class="userinitials" *ngIf="!linkProfile" [attr.aria-label]="'core.pictureof' | translate:{$a: fullname}"
{{ initials }} [title]="'core.pictureof' | translate:{$a: fullname}" role="img" [attr.data-initials]="initials" aria-hidden="true">
</div> </div>
</ng-container> </ng-container>
<ng-container *ngIf="!avatarUrl && !initials"> <ng-container *ngIf="!avatarUrl && !initials"><!-- During loading -->
<img class="userpicture" *ngIf="linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}" <img class="userpicture_loading" src="assets/img/user-avatar.png" role="presentation" aria-hidden="true" alt="">
(ariaButtonClick)="gotoProfile($event)">
<img class="userpicture" *ngIf="!linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
aria-hidden="true">
</ng-container> </ng-container>
<span *ngIf="checkOnline && isOnline()" class="contact-status online" role="status" [attr.aria-label]="'core.online' | translate"> <span *ngIf="checkOnline && isOnline()" class="contact-status online" role="status" [attr.aria-label]="'core.online' | translate">

View File

@ -51,12 +51,8 @@
height: var(--core-header-toolbar-button-image-size); height: var(--core-header-toolbar-button-image-size);
max-width: var(--core-header-toolbar-button-image-size); max-width: var(--core-header-toolbar-button-image-size);
max-height: var(--core-header-toolbar-button-image-size); max-height: var(--core-header-toolbar-button-image-size);
border-radius: 50%; border-radius: var(--core-avatar-radius);
display: block; display: block;
img {
border-radius: 50%;
}
} }
.contact-status { .contact-status {
@ -83,8 +79,14 @@
font-weight: normal; font-weight: normal;
width: calc(var(--core-avatar-size) - var(--userpicture-padding) - var(--userpicture-padding)); width: calc(var(--core-avatar-size) - var(--userpicture-padding) - var(--userpicture-padding));
height: calc(var(--core-avatar-size) - var(--userpicture-padding) - var(--userpicture-padding)); height: calc(var(--core-avatar-size) - var(--userpicture-padding) - var(--userpicture-padding));
min-height: 0px;
min-width: 0px;
font-size: calc(var(--core-avatar-size)*0.3); font-size: calc(var(--core-avatar-size)*0.3);
margin: var(--userpicture-padding); margin: var(--userpicture-padding);
&::after {
content: attr(data-initials);
}
} }
&.large-avatar .userinitials { &.large-avatar .userinitials {

View File

@ -114,7 +114,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
/** /**
* Set fields from user. * Set fields from user.
*/ */
protected setFields(): void { protected async setFields(): Promise<void> {
const profileUrl = this.profileUrl || (this.user && (this.user.profileimageurl || this.user.userprofileimageurl || const profileUrl = this.profileUrl || (this.user && (this.user.profileimageurl || this.user.userprofileimageurl ||
this.user.userpictureurl || this.user.profileimageurlsmall || (this.user.urls && this.user.urls.profileimage))); this.user.userpictureurl || this.user.profileimageurlsmall || (this.user.urls && this.user.urls.profileimage)));
@ -124,16 +124,20 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname)); this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname));
if (this.user) { if (this.avatarUrl && CoreUrlUtils.isThemeImageUrl(this.avatarUrl)) {
this.initials = CoreUserHelper.getUserInitials(this.user);
}
if (this.initials && this.avatarUrl && CoreUrlUtils.isThemeImageUrl(this.avatarUrl)) {
this.avatarUrl = undefined; this.avatarUrl = undefined;
} }
this.userId = this.userId || (this.user && (this.user.userid || this.user.id)); this.userId = this.userId || (this.user && (this.user.userid || this.user.id));
this.courseId = this.courseId || (this.user && this.user.courseid); this.courseId = this.courseId || (this.user && this.user.courseid);
this.initials =
await CoreUserHelper.getUserInitialsFromParts({
firstname: this.user?.firstname,
lastname: this.user?.lastname,
fullname: this.fullname,
userId: this.userId,
});
} }
/** /**

View File

@ -17,7 +17,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { makeSingleton, Translate } from '@singletons'; import { makeSingleton, Translate } from '@singletons';
import { CoreUserProfile, CoreUserRole } from './user'; import { CoreUser, CoreUserProfile, CoreUserRole } from './user';
/** /**
* Service that provides some features regarding users information. * Service that provides some features regarding users information.
@ -145,7 +145,8 @@ export class CoreUserHelperProvider {
* Get the user initials. * Get the user initials.
* *
* @param user User object. * @param user User object.
* @returns Promise resolved with the user data. * @returns User initials.
* @deprecated since 4.4. Use getUserInitialsFromParts instead.
*/ */
getUserInitials(user: Partial<CoreUserProfile>): string { getUserInitials(user: Partial<CoreUserProfile>): string {
if (!user.firstname && !user.lastname) { if (!user.firstname && !user.lastname) {
@ -156,6 +157,36 @@ export class CoreUserHelperProvider {
return (user.firstname?.charAt(0) || '') + (user.lastname?.charAt(0) || ''); return (user.firstname?.charAt(0) || '') + (user.lastname?.charAt(0) || '');
} }
/**
* Get the user initials.
*
* @param parts User name parts. Containing firstname, lastname, fullname and userId.
* @returns User initials.
*/
async getUserInitialsFromParts(parts: CoreUserNameParts): Promise<string> {
if (!parts.firstname && !parts.lastname) {
if (!parts.fullname && parts.userId) {
const user = await CoreUser.getProfile(parts.userId, undefined, true);
parts.fullname = user.fullname || '';
}
if (parts.fullname) {
const split = parts.fullname.split(' ');
parts.firstname = split[0];
if (split.length > 1) {
parts.lastname = split[split.length - 1];
}
}
}
if (!parts.firstname && !parts.lastname) {
return 'UNK';
}
return (parts.firstname?.charAt(0) || '') + (parts.lastname?.charAt(0) || '');
}
/** /**
* Translates legacy timezone names. * Translates legacy timezone names.
* *
@ -169,3 +200,5 @@ export class CoreUserHelperProvider {
} }
export const CoreUserHelper = makeSingleton(CoreUserHelperProvider); export const CoreUserHelper = makeSingleton(CoreUserHelperProvider);
type CoreUserNameParts = { firstname?: string; lastname?: string; fullname?: string; userId?: number };

View File

@ -26,7 +26,6 @@ import { CoreEvents, CoreEventSiteData, CoreEventUserDeletedData, CoreEventUserS
import { CoreStatusWithWarningsWSResponse, CoreWSExternalWarning } from '@services/ws'; import { CoreStatusWithWarningsWSResponse, CoreWSExternalWarning } from '@services/ws';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { USERS_TABLE_NAME, CoreUserDBRecord } from './database/user'; import { USERS_TABLE_NAME, CoreUserDBRecord } from './database/user';
import { CoreUserHelper } from './user-helper';
import { CoreUrlUtils } from '@services/utils/url'; import { CoreUrlUtils } from '@services/utils/url';
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
@ -669,12 +668,9 @@ export class CoreUserProvider {
} }
// Do not prefetch when initials are set and image is default. // Do not prefetch when initials are set and image is default.
if ('firstname' in entry || 'lastname' in entry) { if (imageUrl && CoreUrlUtils.isThemeImageUrl(imageUrl)) {
const initials = CoreUserHelper.getUserInitials(entry);
if (initials && imageUrl && CoreUrlUtils.isThemeImageUrl(imageUrl)) {
return; return;
} }
}
treated[imageUrl] = true; treated[imageUrl] = true;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,70 @@
// (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 { CoreUserHelper } from '../services/user-helper';
describe('getUserInitialsFromParts', () => {
it('should return initials based on firstname and lastname', async () => {
const parts = {
firstname: 'John',
lastname: 'Doe',
fullname: '',
userId: 123,
};
const result = await CoreUserHelper.getUserInitialsFromParts(parts);
expect(result).toEqual('JD');
});
it('should return initials based on fullname if firstname and lastname are missing', async () => {
let parts = {
firstname: '',
lastname: '',
fullname: 'John Doe',
userId: 123,
};
let result = await CoreUserHelper.getUserInitialsFromParts(parts);
expect(result).toEqual('JD');
parts = {
firstname: '',
lastname: '',
fullname: 'John Fitzgerald Doe',
userId: 123,
};
result = await CoreUserHelper.getUserInitialsFromParts(parts);
expect(result).toEqual('JD');
});
it('should return UNK string if empty parts', async () => {
const parts = {
firstname: '',
lastname: '',
fullname: '',
};
let result = await CoreUserHelper.getUserInitialsFromParts(parts);
expect(result).toEqual('UNK');
result = await CoreUserHelper.getUserInitialsFromParts({});
expect(result).toEqual('UNK');
});
});

View File

@ -636,18 +636,6 @@ ion-content.limited-width > :not([slot]) {
min-height: 100%; min-height: 100%;
} }
ion-toolbar h1 img.core-bar-button-image,
ion-toolbar h1 .core-bar-button-image img {
padding: 4px;
--userpicture-padding: 4px;
width: var(--core-header-toolbar-button-image-size);
height: var(--core-header-toolbar-button-image-size);
max-width: var(--core-header-toolbar-button-image-size);
max-height: var(--core-header-toolbar-button-image-size);
border-radius: 50%;
display: block;
}
// Radio. // Radio.
ion-radio, ion-radio,
input[type=radio], input[type=radio],