From 0bbfb898d1c59508c201ec0e9b5ca6efb8e757a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 21 Oct 2021 15:44:53 +0200 Subject: [PATCH] MOBILE-3807 user: Edit avatar only from details --- .../components/user-menu/user-menu.scss | 1 - src/core/features/user/pages/about/about.html | 21 ++- .../features/user/pages/about/about.page.ts | 169 ++++++++++++++++-- src/core/features/user/pages/about/about.scss | 44 +++++ .../features/user/pages/profile/profile.html | 12 +- .../user/pages/profile/profile.page.ts | 125 +------------ .../features/user/pages/profile/profile.scss | 15 -- 7 files changed, 225 insertions(+), 162 deletions(-) create mode 100644 src/core/features/user/pages/about/about.scss diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.scss b/src/core/features/mainmenu/components/user-menu/user-menu.scss index 7466e6dd8..e6d9fd3fd 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.scss +++ b/src/core/features/mainmenu/components/user-menu/user-menu.scss @@ -18,7 +18,6 @@ } } - @if ($core-user-hide-siteinfo) { .core-usermenu-siteinfo { display: none; diff --git a/src/core/features/user/pages/about/about.html b/src/core/features/user/pages/about/about.html index fb08137f1..b37d038f4 100644 --- a/src/core/features/user/pages/about/about.html +++ b/src/core/features/user/pages/about/about.html @@ -3,7 +3,7 @@ -

{{ title }}

+

{{ 'core.user.details' | translate }}

@@ -12,6 +12,25 @@ + +

{{ 'core.user.contact' | translate}}

diff --git a/src/core/features/user/pages/about/about.page.ts b/src/core/features/user/pages/about/about.page.ts index 2c3e5808f..33bbdb8f9 100644 --- a/src/core/features/user/pages/about/about.page.ts +++ b/src/core/features/user/pages/about/about.page.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { SafeUrl } from '@angular/platform-browser'; import { IonRefresher } from '@ionic/angular'; @@ -20,10 +20,15 @@ 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 } from '@singletons/events'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreUser, CoreUserProfile, CoreUserProvider } from '@features/user/services/user'; import { CoreUserHelper } from '@features/user/services/user-helper'; import { CoreNavigator } from '@services/navigator'; +import { CoreIonLoadingElement } from '@classes/ion-loading'; +import { CoreSite } from '@classes/site'; +import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper'; +import { CoreMimetypeUtils } from '@services/utils/mimetype'; +import { Translate } from '@singletons'; /** * Page that displays info about a user. @@ -31,11 +36,9 @@ import { CoreNavigator } from '@services/navigator'; @Component({ selector: 'page-core-user-about', templateUrl: 'about.html', + styleUrls: ['about.scss'], }) -export class CoreUserAboutPage implements OnInit { - - protected userId!: number; - protected siteId: string; +export class CoreUserAboutPage implements OnInit, OnDestroy { courseId!: number; userLoaded = false; @@ -45,20 +48,46 @@ export class CoreUserAboutPage implements OnInit { title?: string; formattedAddress?: string; encodedAddress?: SafeUrl; + canChangeProfilePicture = false; + + protected userId!: number; + protected site!: CoreSite; + protected obsProfileRefreshed?: CoreEventObserver; constructor() { - this.siteId = CoreSites.getCurrentSiteId(); + try { + this.site = CoreSites.getRequiredCurrentSite(); + } catch (error) { + CoreDomUtils.showErrorModal(error); + CoreNavigator.back(); + + return; + } + + this.obsProfileRefreshed = CoreEvents.on(CoreUserProvider.PROFILE_REFRESHED, (data) => { + if (!this.user || !data.user) { + return; + } + + this.user.email = data.user.email; + this.user.address = CoreUserHelper.formatAddress('', data.user.city, data.user.country); + }, CoreSites.getCurrentSiteId()); } /** - * On init. - * - * @return Promise resolved when done. + * @inheritdoc */ async ngOnInit(): Promise { this.userId = CoreNavigator.getRouteNumberParam('userId') || 0; this.courseId = CoreNavigator.getRouteNumberParam('courseId') || 0; + // Allow to change the profile image only in the app profile page. + this.canChangeProfilePicture = + !this.courseId && + this.userId == this.site.getUserId() && + this.site.canUploadFiles() && + !CoreUser.isUpdatePictureDisabledInSite(this.site); + this.fetchUser().finally(() => { this.userLoaded = true; }); @@ -83,11 +112,85 @@ export class CoreUserAboutPage implements OnInit { this.user = user; this.title = user.fullname; + + this.user.address = CoreUserHelper.formatAddress('', user.city, user.country); + + await this.checkUserImageUpdated(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'core.user.errorloaduser', true); } } + /** + * Check if current user image has changed. + * + * @return Promise resolved when done. + */ + protected async checkUserImageUpdated(): Promise { + if (!this.site || !this.site.getInfo() || !this.user) { + return; + } + + if (this.userId != this.site.getUserId() || !this.isUserAvatarDirty()) { + // Not current user or hasn't changed. + return; + } + + // The current user image received is different than the one stored in site info. Assume the image was updated. + // Update the site info to get the right avatar in there. + try { + await CoreSites.updateSiteInfo(this.site.getId()); + } catch { + // Cannot update site info. Assume the profile image is the right one. + CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { + userId: this.userId, + picture: this.user.profileimageurl, + }, this.site.getId()); + } + + if (this.isUserAvatarDirty()) { + // The image is still different, this means that the good one is the one in site info. + await this.refreshUser(); + } else { + // Now they're the same, send event to use the right avatar in the rest of the app. + CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { + userId: this.userId, + picture: this.user.profileimageurl, + }, this.site.getId()); + } + } + + /** + * Opens dialog to change profile picture. + */ + async changeProfilePicture(): Promise { + const maxSize = -1; + const title = Translate.instant('core.user.newpicture'); + const mimetypes = CoreMimetypeUtils.getGroupMimeInfo('image', 'mimetypes'); + let modal: CoreIonLoadingElement | undefined; + + try { + const result = await CoreFileUploaderHelper.selectAndUploadFile(maxSize, title, mimetypes); + + modal = await CoreDomUtils.showModalLoading('core.sending', true); + + const profileImageURL = await CoreUser.changeProfilePicture(result.itemid, this.userId, this.site.getId()); + + CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { + userId: this.userId, + picture: profileImageURL, + }, this.site.getId()); + + CoreSites.updateSiteInfo(this.site.getId()); + + this.refreshUser(); + } catch (error) { + CoreDomUtils.showErrorModal(error); + } finally { + modal?.dismiss(); + } + } + /** * Refresh the user data. * @@ -106,8 +209,52 @@ export class CoreUserAboutPage implements OnInit { courseId: this.courseId, userId: this.userId, user: this.user, - }, this.siteId); + }, this.site.getId()); } } + /** + * Check whether the user avatar is not up to date with site info. + * + * @return Whether the user avatar differs from site info cache. + */ + protected isUserAvatarDirty(): boolean { + if (!this.user || !this.site) { + return false; + } + + const courseAvatarUrl = this.normalizeAvatarUrl(this.user.profileimageurl); + const siteAvatarUrl = this.normalizeAvatarUrl(this.site.getInfo()?.userpictureurl); + + return courseAvatarUrl !== siteAvatarUrl; + } + + /** + * Normalize an avatar url regardless of theme. + * + * Given that the default image is the only one that can be changed per theme, any other url will stay the same. Note that + * the values returned by this function may not be valid urls, given that they are intended for string comparison. + * + * @param avatarUrl Avatar url. + * @return Normalized avatar string (may not be a valid url). + */ + protected normalizeAvatarUrl(avatarUrl?: string): string { + if (!avatarUrl) { + return 'undefined'; + } + + if (avatarUrl.startsWith(`${this.site?.siteUrl}/theme/image.php`)) { + return 'default'; + } + + return avatarUrl; + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.obsProfileRefreshed?.off(); + } + } diff --git a/src/core/features/user/pages/about/about.scss b/src/core/features/user/pages/about/about.scss new file mode 100644 index 000000000..c5c1284cb --- /dev/null +++ b/src/core/features/user/pages/about/about.scss @@ -0,0 +1,44 @@ +:host { + + .core-user-profile-maininfo::part(native) { + flex-direction: column; + } + ::ng-deep { + core-user-avatar { + display: block; + --core-avatar-size: var(--core-large-avatar-size); + height: calc(var(--core-avatar-size) + 16px); + + img { + margin: 8px auto; + } + + .contact-status { + width: 24px !important; + height: 24px !important; + right: calc(50% - 12px - var(--core-avatar-size) / 2) !important; + } + + .edit-avatar { + position: absolute; + right: calc(50% - 15px - var(--core-avatar-size) / 2); + bottom: -12px; + + :host-context([dir="rtl"]) & { + left: 0; + right: unset; + } + &::part(native) { + border-radius: 50%; + background: var(--ion-item-background); + } + } + } + } + +} + +:host-context([dir="rtl"]) ::ng-deep core-user-avatar .edit-avatar { + left: -24px; + right: unset; +} diff --git a/src/core/features/user/pages/profile/profile.html b/src/core/features/user/pages/profile/profile.html index 2bedd26e5..a5c7112e8 100644 --- a/src/core/features/user/pages/profile/profile.html +++ b/src/core/features/user/pages/profile/profile.html @@ -14,20 +14,10 @@