feat(ui): implement premium mobile-first reader layout with three-tab bottom navigation and assistant FAB (#57)

This pull request delivers a comprehensive mobile-first user experience overhaul for the NexusReader SaaS platform, specifically optimizing the Reader Canvas, D3.js Knowledge Graph representation, Dashboard card grid layout, and the application-wide navigation shell on mobile viewports (< 768px).

### Key Enhancements:
1. **Interactive Three-Tab Bottom Navigation**: Added premium, frosted glassy bottom-bar for mobile viewports to switch between standard reading, D3.js graph visual workspace, and structural concept reviews/quizzes.
2. **Contextual Floating Action Button (FAB)**: Introduced the AI Assistant FAB on mobile canvas layout with responsive animation, state-synchronization to trigger corresponding quiz views, and pulsing badge notification when new quizzes are dynamically generated.
3. **Adaptive D3.js Simulation & Rendering**: Integrated `setMobileMode(isMobile)` logic inside the D3 simulation engine, optimizing forces, rendering compact glyph pills, and installing auto-resize observers.
4. **Architectural & Native AOT Cleanliness**: Clean separation of layouts, fully scoped CSS configurations, functional-safe event orchestration inside `IReaderInteractionService`, and zero compiler errors.

---------

Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Reviewed-on: #57
Co-authored-by: Antigravity <antigravity@google.com>
Co-committed-by: Antigravity <antigravity@google.com>
This commit was merged in pull request #57.
This commit is contained in:
2026-05-27 07:44:44 +00:00
committed by Marek Jaisński
parent a9a670d776
commit 30f445ea89
9 changed files with 947 additions and 111 deletions
@@ -13,6 +13,8 @@
@inject IReaderInteractionService InteractionService
@inject ISyncService SyncService
@inject AuthenticationStateProvider AuthStateProvider
@inject IQuizStateService QuizService
@inject IPlatformService PlatformService
@inject ILogger<ReaderCanvas> Logger
<div class="reader-canvas @(ThemeService.IsLightMode ? "theme-light" : "theme-dark")">
@@ -53,6 +55,17 @@
BlockId="@_selectedBlockId"
Coordinates="@_selectionCoords"
FullPageContent="@GetFullPageContent()" />
@if (_isMobile)
{
<button class="nexus-mobile-assistant-fab @(QuizService.HasNewQuiz ? "has-new-quiz" : "")" @onclick="HandleAssistantFabClick" aria-label="Asystent AI">
<NexusIcon Name="robot" Size="24" Class="neon-glow" />
@if (QuizService.HasNewQuiz)
{
<span class="fab-badge"></span>
}
</button>
}
</div>
@code {
@@ -68,17 +81,29 @@
private ElementReference _containerRef;
private bool _isInteractive;
private string? _currentActiveBlockId;
private bool _isMobile = false;
protected override async Task OnInitializedAsync()
{
await Coordinator.ClearAsync();
ThemeService.OnThemeChanged += HandleUpdate;
NavigationService.OnNavigationChanged += OnNavigationChanged;
QuizService.OnQuizUpdated += HandleUpdate;
InteractionService.OnScrollToBlockRequested += HandleScrollRequested;
InteractionService.OnHighlightBlockRequested += HandleHighlightRequested;
InteractionService.OnTextSelected += HandleTextSelected;
SyncService.OnProgressReceived += HandleSyncProgressReceived;
var context = PlatformService.GetDeviceContext();
if (context.IsSuccess)
{
_isMobile = context.Value.DeviceType switch
{
DeviceType.Phone or DeviceType.Tablet => true,
_ => false
};
}
}
protected override async Task OnParametersSetAsync()
@@ -286,10 +311,16 @@
private Task HandleUpdate() => InvokeAsync(StateHasChanged);
private async Task HandleAssistantFabClick()
{
await InteractionService.RequestAssistant();
}
public void Dispose()
{
ThemeService.OnThemeChanged -= HandleUpdate;
NavigationService.OnNavigationChanged -= OnNavigationChanged;
QuizService.OnQuizUpdated -= HandleUpdate;
InteractionService.OnScrollToBlockRequested -= HandleScrollRequested;
InteractionService.OnHighlightBlockRequested -= HandleHighlightRequested;