fix: Mobile style fixes and other small ui fixes

This commit is contained in:
2026-05-18 23:14:16 +02:00
parent afb64520ed
commit 94428ed170
32 changed files with 808 additions and 239 deletions

View File

@@ -6,17 +6,39 @@
appThemeNode="dmChatHeader"
class="flex h-14 shrink-0 items-center gap-3 border-b border-border px-4"
>
<app-user-avatar
[name]="peerName()"
[avatarUrl]="peerUser()?.avatarUrl"
[status]="peerUser()?.status"
[showStatusBadge]="true"
size="md"
/>
<div class="min-w-0 flex-1">
<h1 class="truncate text-base font-semibold text-foreground">{{ peerName() }}</h1>
<p class="text-xs text-muted-foreground">{{ isGroupConversation() ? 'Group Chat' : 'Direct Message' }}</p>
</div>
@if (peerUser()) {
<button
type="button"
class="flex min-w-0 flex-1 items-center gap-3 rounded-md py-1 pr-2 text-left transition-colors hover:bg-secondary/60 focus:outline-none focus:ring-2 focus:ring-primary/50"
[attr.aria-label]="'Open profile for ' + peerName()"
[title]="'Open profile for ' + peerName()"
(click)="openHeaderProfileCard($event)"
>
<app-user-avatar
[name]="peerName()"
[avatarUrl]="peerUser()?.avatarUrl"
[status]="peerUser()?.status"
[showStatusBadge]="true"
size="md"
/>
<div class="min-w-0 flex-1">
<h1 class="truncate text-base font-semibold text-foreground">{{ peerName() }}</h1>
<p class="text-xs text-muted-foreground">Direct Message</p>
</div>
</button>
} @else {
<app-user-avatar
[name]="peerName()"
[avatarUrl]="peerUser()?.avatarUrl"
[status]="peerUser()?.status"
[showStatusBadge]="true"
size="md"
/>
<div class="min-w-0 flex-1">
<h1 class="truncate text-base font-semibold text-foreground">{{ peerName() }}</h1>
<p class="text-xs text-muted-foreground">{{ isGroupConversation() ? 'Group Chat' : 'Direct Message' }}</p>
</div>
}
@if (showCallButton() && conversation()) {
<button
type="button"

View File

@@ -16,7 +16,11 @@ import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
import { ViewportService } from '../../../../core/platform';
import { BottomSheetComponent, UserAvatarComponent } from '../../../../shared';
import {
BottomSheetComponent,
ProfileCardService,
UserAvatarComponent
} from '../../../../shared';
import { DirectCallService } from '../../../direct-call';
import { Attachment, AttachmentFacade } from '../../../attachment';
import { ThemeNodeDirective } from '../../../theme';
@@ -82,6 +86,7 @@ export class DmChatComponent {
private readonly attachments = inject(AttachmentFacade);
private readonly klipy = inject(KlipyService);
private readonly linkMetadata = inject(LinkMetadataService);
private readonly profileCard = inject(ProfileCardService);
private readonly viewport = inject(ViewportService);
private readonly metadataRequestKeys = new Set<string>();
private openedConversationId: string | null = null;
@@ -309,6 +314,17 @@ export class DmChatComponent {
}
}
openHeaderProfileCard(event: MouseEvent): void {
const user = this.peerUser();
if (!user) {
return;
}
event.stopPropagation();
this.profileCard.open(event.currentTarget as HTMLElement, user, { editable: false });
}
setReplyTo(message: ChatMessageReplyEvent): void {
this.replyTo.set(message);
}

View File

@@ -3,7 +3,7 @@
<div class="group/server relative flex w-full justify-center">
<button
type="button"
class="relative z-10 flex h-10 w-10 cursor-pointer flex-shrink-0 items-center justify-center border border-transparent text-muted-foreground transition-[border-radius,box-shadow,background-color,color] duration-100 hover:rounded-lg hover:bg-card hover:text-foreground"
class="relative z-10 flex h-11 w-11 cursor-pointer flex-shrink-0 items-center justify-center border border-transparent text-muted-foreground transition-[border-radius,box-shadow,background-color,color] duration-100 hover:rounded-lg hover:bg-card hover:text-foreground md:h-10 md:w-10"
title="Direct Messages"
aria-label="Direct Messages"
[ngClass]="isOnDirectMessages() ? 'rounded-lg ring-2 ring-primary/40 bg-primary/10 text-foreground' : 'rounded-xl bg-card'"
@@ -12,7 +12,7 @@
>
<ng-icon
name="lucideMessageCircle"
class="h-4 w-4"
class="h-[18px] w-[18px] md:h-4 md:w-4"
/>
@if (directMessages.totalUnreadCount() > 0) {
<span class="dm-rail-slide-in absolute -right-1 -top-1 h-3 w-3 rounded-full bg-amber-400 ring-2 ring-card"></span>
@@ -24,7 +24,7 @@
<div class="group/server relative flex w-full justify-center">
<button
type="button"
class="relative z-10 flex h-10 w-10 cursor-pointer flex-shrink-0 items-center justify-center border border-transparent transition-[border-radius,box-shadow,background-color] duration-100 hover:rounded-lg hover:bg-card"
class="relative z-10 flex h-11 w-11 cursor-pointer flex-shrink-0 items-center justify-center border border-transparent transition-[border-radius,box-shadow,background-color] duration-100 hover:rounded-lg hover:bg-card md:h-10 md:w-10"
[class.dm-rail-slide-in]="!item.isExiting"
[class.dm-rail-slide-out]="item.isExiting"
[class.pointer-events-none]="item.isExiting"

View File

@@ -1,6 +1,6 @@
<div
appThemeNode="dmConversationItem"
class="group flex w-full items-center gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-secondary/60"
class="group flex w-full cursor-pointer items-center gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-secondary/60"
[class.bg-primary/10]="isSelected()"
[class.text-foreground]="isSelected()"
[attr.aria-current]="isSelected() ? 'page' : null"

View File

@@ -4,7 +4,8 @@ import {
computed,
effect,
inject,
input
input,
output
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
@@ -48,6 +49,7 @@ export class DmConversationItemComponent {
private readonly directMessages = inject(DirectMessageService);
private readonly directCalls = inject(DirectCallService);
readonly conversation = input.required<DirectMessageConversation>();
readonly conversationOpened = output<string>();
readonly users = this.store.selectSignal(selectAllUsers);
readonly routeConversationId = toSignal(this.route.paramMap.pipe(map((params) => params.get('conversationId'))), {
initialValue: this.route.snapshot.paramMap.get('conversationId')
@@ -71,6 +73,7 @@ export class DmConversationItemComponent {
}
openConversation(): void {
this.conversationOpened.emit(this.conversation().id);
void this.router.navigate(['/dm', this.conversation().id]);
}

View File

@@ -1,6 +1,6 @@
<aside
appThemeNode="dmConversationsPanel"
class="flex min-h-0 overflow-hidden border-r border-border bg-card"
class="flex min-h-0 w-full min-w-0 overflow-hidden border-r border-border bg-card"
[ngStyle]="listPanelStyles()"
>
<section class="flex h-full w-full min-w-0 flex-col">
@@ -28,10 +28,12 @@
<div class="flex h-full items-center justify-center px-4 text-center text-sm text-muted-foreground">No direct messages yet.</div>
} @else {
<div class="space-y-1">
<app-dm-conversation-item
*ngFor="let conversation of directMessages.conversations(); trackBy: trackConversationId"
[conversation]="conversation"
></app-dm-conversation-item>
@for (conversation of directMessages.conversations(); track trackConversationId($index, conversation)) {
<app-dm-conversation-item
[conversation]="conversation"
(conversationOpened)="conversationSelected.emit($event)"
/>
}
</div>
}
</div>

View File

@@ -2,7 +2,8 @@
import {
Component,
computed,
inject
inject,
output
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgIcon, provideIcons } from '@ng-icons/core';
@@ -31,6 +32,7 @@ export class DmConversationsPanelComponent {
private readonly theme = inject(ThemeService);
readonly directMessages = inject(DirectMessageService);
readonly listPanelStyles = computed(() => this.theme.getLayoutItemStyles('dmConversationsPanel'));
readonly conversationSelected = output<string>();
trackConversationId(index: number, conversation: DirectMessageConversation): string {
return conversation.id;

View File

@@ -13,7 +13,10 @@
<div class="flex h-full w-full min-h-0 overflow-hidden">
<app-servers-rail class="block h-full shrink-0" />
<div class="flex min-h-0 flex-1 overflow-hidden border-l border-border">
<app-dm-conversations-panel class="block h-full w-full" />
<app-dm-conversations-panel
(conversationSelected)="setMobilePage('chat')"
class="block h-full w-full"
/>
</div>
</div>
</swiper-slide>
@@ -32,7 +35,21 @@
class="h-5 w-5"
/>
</button>
<p class="truncate text-sm font-semibold text-foreground">Direct messages</p>
<p class="min-w-0 flex-1 truncate text-sm font-semibold text-foreground">Direct messages</p>
@if (activeCall()) {
<button
type="button"
(click)="openActiveCall()"
class="grid h-11 w-11 place-items-center rounded-lg text-emerald-600 transition-colors hover:bg-emerald-500/10 hover:text-emerald-500"
aria-label="Return to call"
title="Return to call"
>
<ng-icon
name="lucidePhoneCall"
class="h-5 w-5"
/>
</button>
}
</div>
<div class="min-h-0 flex-1 overflow-hidden">
<app-dm-chat-panel class="block h-full w-full" />
@@ -50,4 +67,3 @@
<app-dm-chat-panel />
</div>
}

View File

@@ -16,10 +16,11 @@ import { ActivatedRoute, Router } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideChevronLeft } from '@ng-icons/lucide';
import { lucideChevronLeft, lucidePhoneCall } from '@ng-icons/lucide';
import { ServersRailComponent } from '../../../../features/servers/servers-rail/servers-rail.component';
import { ViewportService } from '../../../../core/platform';
import { ThemeService } from '../../../theme';
import { DirectCallService } from '../../../direct-call';
import { DirectMessageService } from '../../application/services/direct-message.service';
import { DmChatPanelComponent } from './dm-chat-panel.component';
import { DmConversationsPanelComponent } from './dm-conversations-panel.component';
@@ -47,7 +48,7 @@ interface SwiperElement extends HTMLElement {
DmConversationsPanelComponent,
ServersRailComponent
],
viewProviders: [provideIcons({ lucideChevronLeft })],
viewProviders: [provideIcons({ lucideChevronLeft, lucidePhoneCall })],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './dm-workspace.component.html'
})
@@ -57,6 +58,7 @@ export class DmWorkspaceComponent implements OnDestroy {
private readonly theme = inject(ThemeService);
private readonly viewport = inject(ViewportService);
private readonly zone = inject(NgZone);
private readonly directCalls = inject(DirectCallService);
private lastSeenConversationId: string | null = null;
private swiperListenerAttached: SwiperElement | null = null;
readonly directMessages = inject(DirectMessageService);
@@ -66,6 +68,12 @@ export class DmWorkspaceComponent implements OnDestroy {
readonly layoutStyles = computed(() => this.theme.getLayoutContainerStyles('dmLayout'));
readonly isMobile = this.viewport.isMobile;
readonly swiperRef = viewChild<ElementRef<SwiperElement>>('swiperEl');
readonly activeCall = computed(() => {
const currentSession = this.directCalls.currentSession();
const visibleSessions = this.directCalls.visibleActiveSessions();
return visibleSessions.find((session) => session.callId === currentSession?.callId) ?? visibleSessions[0] ?? null;
});
/** Active page within the mobile single-pane navigation flow. Ignored on desktop. */
readonly mobilePage = signal<DmWorkspaceMobilePage>('conversations');
@@ -150,6 +158,14 @@ export class DmWorkspaceComponent implements OnDestroy {
this.mobilePage.set(page);
}
openActiveCall(): void {
const call = this.activeCall();
if (call) {
void this.directCalls.openCallView(call.callId);
}
}
ngOnDestroy(): void {
this.directMessages.closeConversationView(this.routeConversationId());
}