Skip to content

Commit 29792e3

Browse files
feat: profile view on mobile screen
Signed-off-by: Kristian Zendato <kristian.zendato@nextcloud.com>
1 parent 4cf0888 commit 29792e3

7 files changed

Lines changed: 128 additions & 43 deletions

File tree

3rdparty

Submodule 3rdparty updated 40 files

apps/profile/src/views/ProfileApp.vue

Lines changed: 122 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { subscribe, unsubscribe } from '@nextcloud/event-bus'
1212
import { loadState } from '@nextcloud/initial-state'
1313
import { translate as t } from '@nextcloud/l10n'
1414
import { generateUrl } from '@nextcloud/router'
15+
import { useIsMobile } from '@nextcloud/vue/composables/useIsMobile'
1516
import { computed, onBeforeMount, onBeforeUnmount, onMounted, ref } from 'vue'
1617
import NcActionLink from '@nextcloud/vue/components/NcActionLink'
1718
import NcActions from '@nextcloud/vue/components/NcActions'
@@ -22,7 +23,8 @@ import NcContent from '@nextcloud/vue/components/NcContent'
2223
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
2324
import NcRichText from '@nextcloud/vue/components/NcRichText'
2425
import AccountIcon from 'vue-material-design-icons/AccountOutline.vue'
25-
import MapMarkerIcon from 'vue-material-design-icons/MapMarker.vue'
26+
import BriefcaseIcon from 'vue-material-design-icons/BriefcaseOutline.vue'
27+
import MapMarkerIcon from 'vue-material-design-icons/MapMarkerOutline.vue'
2628
import PencilIcon from 'vue-material-design-icons/PencilOutline.vue'
2729
import ProfileSection from '../components/ProfileSection.vue'
2830
@@ -60,9 +62,13 @@ onBeforeMount(() => {
6062
})
6163
6264
const isCurrentUser = getCurrentUser()?.uid === profileParameters.userId
65+
const isMobile = useIsMobile()
6366
6467
const primaryAction = profileParameters.actions[0]
65-
const otherActions = profileParameters.actions.slice(1)
68+
const showEditAsPrimaryAction = computed(() => isMobile.value && isCurrentUser)
69+
const otherActions = computed(() => showEditAsPrimaryAction.value
70+
? profileParameters.actions
71+
: profileParameters.actions.slice(1))
6672
6773
const settingsUrl = generateUrl('/settings/user')
6874
const emptyProfileMessage = isCurrentUser
@@ -113,35 +119,49 @@ function openStatusModal() {
113119
<NcAppContent>
114120
<div class="profile__header">
115121
<div class="profile__header__container">
122+
<NcAvatar
123+
v-if="isMobile"
124+
class="avatar profile__header__container__avatar"
125+
:class="{ interactive: isCurrentUser }"
126+
:user="profileParameters.userId"
127+
:size="120"
128+
:disableMenu="true"
129+
:disableTooltip="true"
130+
:isNoUser="!profileParameters.isUserAvatarVisible"
131+
@click.prevent.stop="openStatusModal" />
116132
<div class="profile__header__container__placeholder" />
117133
<div class="profile__header__container__displayname">
118134
<h2>{{ profileParameters.displayname || profileParameters.userId }}</h2>
119-
<span v-if="profileParameters.pronouns">·</span>
135+
<span v-if="profileParameters.pronouns" class="profile__header__container__pronoun-separator">·</span>
120136
<span v-if="profileParameters.pronouns" class="profile__header__container__pronouns">{{ profileParameters.pronouns }}</span>
137+
</div>
138+
<div class="profile__header__container__controls">
139+
<NcButton
140+
v-if="userStatus.icon || userStatus.message"
141+
class="profile__header__container__status"
142+
:disabled="!isCurrentUser"
143+
:variant="isCurrentUser ? 'tertiary' : 'tertiary-no-background'"
144+
@click="openStatusModal">
145+
<span class="profile__header__container__status-content">
146+
<span>{{ userStatus.icon }} {{ userStatus.message }}</span>
147+
<PencilIcon v-if="isCurrentUser" :size="20" />
148+
</span>
149+
</NcButton>
121150
<NcButton
122-
v-if="isCurrentUser"
151+
v-if="isCurrentUser && !isMobile"
123152
variant="primary"
124153
:href="settingsUrl">
125-
<template #icon>
126-
<PencilIcon :size="20" />
127-
</template>
128154
{{ t('profile', 'Edit Profile') }}
129155
</NcButton>
130156
</div>
131-
<NcButton
132-
v-if="userStatus.icon || userStatus.message"
133-
:disabled="!isCurrentUser"
134-
:variant="isCurrentUser ? 'tertiary' : 'tertiary-no-background'"
135-
@click="openStatusModal">
136-
{{ userStatus.icon }} {{ userStatus.message }}
137-
</NcButton>
138157
</div>
139158
</div>
140159

141160
<div class="profile__wrapper">
142161
<div class="profile__content">
143162
<div class="profile__sidebar">
144163
<NcAvatar
164+
v-if="!isMobile"
145165
class="avatar"
146166
:class="{ interactive: isCurrentUser }"
147167
:user="profileParameters.userId"
@@ -152,9 +172,16 @@ function openStatusModal() {
152172
@click.prevent.stop="openStatusModal" />
153173

154174
<div class="user-actions">
175+
<NcButton
176+
v-if="showEditAsPrimaryAction"
177+
variant="primary"
178+
class="user-actions__primary"
179+
:href="settingsUrl">
180+
{{ t('profile', 'Edit Profile') }}
181+
</NcButton>
155182
<!-- When a tel: URL is opened with target="_blank", a blank new tab is opened which is inconsistent with the handling of other URLs so we set target="_self" for the phone action -->
156183
<NcButton
157-
v-if="primaryAction"
184+
v-if="primaryAction && !showEditAsPrimaryAction"
158185
variant="primary"
159186
class="user-actions__primary"
160187
:href="primaryAction.target"
@@ -167,7 +194,10 @@ function openStatusModal() {
167194
</template>
168195
{{ primaryAction.title }}
169196
</NcButton>
170-
<NcActions class="user-actions__other" :inline="4">
197+
<NcActions
198+
v-if="otherActions.length > 0"
199+
class="user-actions__other"
200+
:inline="isMobile ? 5 : 4">
171201
<NcActionLink
172202
v-for="action in otherActions"
173203
:key="action.id"
@@ -188,12 +218,17 @@ function openStatusModal() {
188218
<div class="profile__blocks">
189219
<div v-if="profileParameters.organisation || profileParameters.role || profileParameters.address" class="profile__blocks-details">
190220
<div v-if="profileParameters.organisation || profileParameters.role" class="detail">
191-
<p>{{ profileParameters.organisation }} <span v-if="profileParameters.organisation && profileParameters.role">•</span> {{ profileParameters.role }}</p>
221+
<p>
222+
<BriefcaseIcon
223+
class="detail-icon"
224+
:size="16" />
225+
{{ profileParameters.organisation }} <span v-if="profileParameters.organisation && profileParameters.role">•</span> {{ profileParameters.role }}
226+
</p>
192227
</div>
193228
<div v-if="profileParameters.address" class="detail">
194229
<p>
195230
<MapMarkerIcon
196-
class="map-icon"
231+
class="detail-icon"
197232
:size="16" />
198233
{{ profileParameters.address }}
199234
</p>
@@ -233,7 +268,7 @@ $profile-max-width: 1024px;
233268
$content-max-width: 640px;
234269
235270
:deep(#app-content-vue) {
236-
background-color: unset;
271+
min-width: 320px;
237272
}
238273
239274
.profile {
@@ -282,6 +317,18 @@ $content-max-width: 640px;
282317
font-size: 20px;
283318
}
284319
}
320+
321+
&__controls {
322+
display: flex;
323+
align-items: center;
324+
gap: 8px;
325+
}
326+
327+
&__status-content {
328+
display: flex;
329+
align-items: center;
330+
gap: 8px;
331+
}
285332
}
286333
}
287334
@@ -293,7 +340,10 @@ $content-max-width: 640px;
293340
min-width: 220px;
294341
margin-block: -150px 0;
295342
margin-inline: 0 20px;
343+
}
296344
345+
&__header,
346+
&__sidebar {
297347
// Specificity hack is needed to override Avatar component styles
298348
:deep(.avatar.avatardiv) {
299349
text-align: center;
@@ -336,7 +386,7 @@ $content-max-width: 640px;
336386
}
337387
338388
&__blocks {
339-
margin: 18px 0 80px 0;
389+
margin: 18px 0 80px 16px;
340390
display: grid;
341391
gap: 16px 0;
342392
width: $content-max-width;
@@ -355,7 +405,7 @@ $content-max-width: 640px;
355405
display: inline-block;
356406
color: var(--color-text-maxcontrast);
357407
358-
p .map-icon {
408+
p .detail-icon {
359409
display: inline-block;
360410
vertical-align: middle;
361411
}
@@ -380,7 +430,7 @@ $content-max-width: 640px;
380430
381431
&__primary {
382432
margin: 0 auto;
383-
max-width: 100%;
433+
width: 180px !important;
384434
385435
&__icon {
386436
filter: var(--primary-invert-if-dark);
@@ -406,39 +456,58 @@ $content-max-width: 640px;
406456
@media only screen and (max-width: 1024px) {
407457
.profile {
408458
&__header {
409-
height: 250px;
459+
height: 190px;
410460
position: unset;
411461
412462
&__container {
413-
grid-template-columns: unset;
414-
margin-bottom: 110px;
463+
align-self: center;
464+
grid-template-columns: 136px minmax(0, 1fr);
465+
grid-template-rows: max-content max-content;
466+
max-width: 600px;
467+
margin: 0 auto;
468+
padding-inline: 16px;
469+
row-gap: 0;
470+
471+
&__avatar {
472+
grid-column: 1;
473+
grid-row: 1 / 3;
474+
justify-self: center;
475+
}
476+
477+
&__placeholder {
478+
display: none;
479+
}
415480
416481
&__displayname {
417-
margin: 80px 20px 0px 0px!important;
482+
grid-column: 2;
483+
grid-row: 1;
484+
margin: 0;
418485
width: unset;
419-
text-align: center;
420-
padding-inline: 12px;
486+
height: unset;
487+
flex-direction: column;
488+
align-items: flex-start;
489+
gap: 2px;
490+
491+
h2 {
492+
font-size: 24px;
493+
overflow-wrap: anywhere;
494+
}
421495
}
422496
423-
&__edit-button {
424-
width: fit-content;
425-
display: block;
426-
margin: 60px auto;
497+
&__pronoun-separator {
498+
display: none;
427499
}
428500
429-
&__status-text {
430-
margin: 4px auto;
501+
&__status {
502+
grid-column: 2;
503+
grid-row: 2;
504+
justify-self: start;
431505
}
432506
}
433507
}
434508
435509
&__content {
436510
display: block;
437-
438-
.avatar {
439-
// Overlap avatar to top header
440-
margin-top: -110px !important;
441-
}
442511
}
443512
444513
&__blocks {
@@ -452,13 +521,27 @@ $content-max-width: 640px;
452521
margin: unset;
453522
position: unset;
454523
}
524+
525+
&__header,
526+
&__sidebar {
527+
:deep(.avatar.avatardiv) {
528+
.avatardiv__user-status {
529+
inset-inline-end: 0;
530+
bottom: 0;
531+
}
532+
}
533+
}
455534
}
456535
457536
.user-actions {
458537
width: unset;
459538
max-width: 600px;
460539
margin: 0 auto;
461540
padding: 20px 50px 0px 50px;
541+
542+
&__primary {
543+
width: 220px !important;
544+
}
462545
}
463546
}
464547
</style>

core/img/actions/mail-outline.svg

Lines changed: 1 addition & 0 deletions
Loading

core/img/actions/phone-outline.svg

Lines changed: 1 addition & 0 deletions
Loading

core/img/actions/timezone.svg

Lines changed: 1 addition & 1 deletion
Loading

lib/private/Profile/Actions/EmailAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function getPriority(): int {
5858

5959
#[\Override]
6060
public function getIcon(): string {
61-
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/mail.svg'));
61+
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/mail-outline.svg'));
6262
}
6363

6464
#[\Override]

lib/private/Profile/Actions/PhoneAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function getPriority(): int {
5858

5959
#[\Override]
6060
public function getIcon(): string {
61-
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/phone.svg'));
61+
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/phone-outline.svg'));
6262
}
6363

6464
#[\Override]

0 commit comments

Comments
 (0)