All pending fixes – UI, auth, cover image, script removal #84
@@ -8,7 +8,7 @@
|
||||
@inject IReaderStateService StateService
|
||||
@inject IThemeService ThemeService
|
||||
|
||||
<div class="nexus-unified-mobile-toolbar @(ThemeService.IsLightMode ? "theme-light" : "theme-dark")">
|
||||
<div class="nexus-unified-mobile-toolbar @(ThemeService.IsLightMode ? "theme-light" : "theme-dark") @(StateService.IsBarsHidden ? "immersive-zen-mode" : "")">
|
||||
<!-- Tab 1: Progress (Postęp) -->
|
||||
<button class="nav-toggle-btn progress-btn" @onclick="ToggleCheckpoints" aria-label="Postęp" title="Rozdziały i checkpoints">
|
||||
<div class="progress-ring-wrapper">
|
||||
@@ -112,8 +112,11 @@
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
ThemeService.OnThemeChanged += HandleThemeChanged;
|
||||
StateService.OnBarsHiddenChanged += HandleBarsHiddenChanged;
|
||||
}
|
||||
|
||||
private Task HandleBarsHiddenChanged() => InvokeAsync(StateHasChanged);
|
||||
|
||||
private void HandleThemeChanged(ThemeMode mode) => InvokeAsync(StateHasChanged);
|
||||
|
||||
private double GetDashOffset()
|
||||
@@ -160,5 +163,6 @@
|
||||
public void Dispose()
|
||||
{
|
||||
ThemeService.OnThemeChanged -= HandleThemeChanged;
|
||||
StateService.OnBarsHiddenChanged -= HandleBarsHiddenChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,16 @@
|
||||
box-sizing: border-box;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
overflow: visible; /* Critical to show elevated FAB */
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.nexus-unified-mobile-toolbar.immersive-zen-mode {
|
||||
transform: translateY(calc(100% + 24px)) !important;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* Light Mode: Premium Paper Look */
|
||||
.nexus-unified-mobile-toolbar.theme-light {
|
||||
background: rgba(244, 241, 234, 0.9);
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
@inject NavigationManager Navigation
|
||||
@inject ILogger<ReaderCanvas> Logger
|
||||
|
||||
<div class="reader-canvas @(ThemeService.IsLightMode ? "theme-light" : "theme-dark")">
|
||||
<div class="reader-canvas @(ThemeService.IsLightMode ? "theme-light" : "theme-dark") @(StateService.IsBarsHidden ? "immersive-zen-mode" : "")">
|
||||
@if (_isMobile && ViewModel != null)
|
||||
{
|
||||
<header class="nexus-mobile-reader-header">
|
||||
<header class="nexus-mobile-reader-header @(StateService.IsBarsHidden ? "immersive-zen-mode" : "")">
|
||||
<button class="nexus-mobile-escape-btn" @onclick="HandleEscape" aria-label="Powrót do pulpitu">
|
||||
<NexusIcon Name="chevron-left" Size="18" />
|
||||
<span>Pulpit</span>
|
||||
@@ -130,6 +130,7 @@
|
||||
ThemeService.OnThemeChanged += HandleThemeChanged;
|
||||
NavigationService.OnNavigationChanged += OnNavigationChanged;
|
||||
QuizService.OnQuizUpdated += HandleUpdate;
|
||||
StateService.OnBarsHiddenChanged += HandleBarsHiddenChanged;
|
||||
|
||||
InteractionService.OnScrollToBlockRequested += HandleScrollRequested;
|
||||
InteractionService.OnHighlightBlockRequested += HandleHighlightRequested;
|
||||
@@ -250,7 +251,7 @@
|
||||
if (_selfReference != null)
|
||||
{
|
||||
await module.InvokeVoidAsync("initObserver", _selfReference, ".reader-flow-container", ".block-wrapper");
|
||||
_scrollListenerReference = await module.InvokeAsync<IJSObjectReference>("initScrollListener", _selfReference, ".reader-flow-container");
|
||||
_scrollListenerReference = await module.InvokeAsync<IJSObjectReference>("initScrollListener", _selfReference, ".reader-canvas");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -266,6 +267,17 @@
|
||||
await InteractionService.NotifyScrollPercentChanged(percent);
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task HandleScrollDelta(bool hideBars)
|
||||
{
|
||||
if (StateService.IsBarsHidden != hideBars)
|
||||
{
|
||||
StateService.IsBarsHidden = hideBars;
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleBarsHiddenChanged() => InvokeAsync(StateHasChanged);
|
||||
|
||||
[JSInvokable]
|
||||
public async Task HandleBlockReached(string blockId, string content)
|
||||
{
|
||||
@@ -471,6 +483,7 @@
|
||||
ThemeService.OnThemeChanged -= HandleThemeChanged;
|
||||
NavigationService.OnNavigationChanged -= OnNavigationChanged;
|
||||
QuizService.OnQuizUpdated -= HandleUpdate;
|
||||
StateService.OnBarsHiddenChanged -= HandleBarsHiddenChanged;
|
||||
|
||||
InteractionService.OnScrollToBlockRequested -= HandleScrollRequested;
|
||||
InteractionService.OnHighlightBlockRequested -= HandleHighlightRequested;
|
||||
|
||||
@@ -344,7 +344,14 @@
|
||||
/* Ensure content is clear of bottom toolbar */
|
||||
}
|
||||
|
||||
.reader-canvas.immersive-zen-mode {
|
||||
padding-top: calc(10px + env(safe-area-inset-top, 0px)) !important;
|
||||
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)) !important;
|
||||
}
|
||||
|
||||
.reader-flow-container {
|
||||
padding-left: 18px !important;
|
||||
padding-right: 18px !important;
|
||||
padding-bottom: 4rem;
|
||||
/* Safe breathing room */
|
||||
}
|
||||
@@ -381,8 +388,14 @@
|
||||
padding: 0 1rem;
|
||||
z-index: 1000;
|
||||
box-sizing: border-box;
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.nexus-mobile-reader-header.immersive-zen-mode {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
|
||||
.theme-light .nexus-mobile-reader-header {
|
||||
background: rgba(249, 249, 249, 0.8);
|
||||
border-bottom-color: rgba(0, 0, 0, 0.08);
|
||||
|
||||
@@ -11,4 +11,7 @@ public interface IReaderStateService
|
||||
List<string> CurrentCheckpoints { get; set; }
|
||||
string CurrentBlockId { get; set; }
|
||||
MobileReaderTab ActiveTab { get; set; }
|
||||
bool IsBarsHidden { get; set; }
|
||||
event Func<Task>? OnBarsHiddenChanged;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ public sealed class ReaderStateService : IReaderStateService
|
||||
private List<string> _checkpoints = new();
|
||||
private string _blockId = string.Empty;
|
||||
private MobileReaderTab _activeTab = MobileReaderTab.Reader;
|
||||
private bool _barsHidden;
|
||||
|
||||
public event Func<Task>? OnBarsHiddenChanged;
|
||||
|
||||
public int CurrentScrollPercentage
|
||||
{
|
||||
@@ -38,4 +41,23 @@ public sealed class ReaderStateService : IReaderStateService
|
||||
get { lock (_lock) return _activeTab; }
|
||||
set { lock (_lock) _activeTab = value; }
|
||||
}
|
||||
|
||||
public bool IsBarsHidden
|
||||
{
|
||||
get { lock (_lock) return _barsHidden; }
|
||||
set
|
||||
{
|
||||
bool changed;
|
||||
lock (_lock)
|
||||
{
|
||||
changed = _barsHidden != value;
|
||||
_barsHidden = value;
|
||||
}
|
||||
if (changed && OnBarsHiddenChanged != null)
|
||||
{
|
||||
_ = OnBarsHiddenChanged.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export function initScrollListener(dotNetHelper, scrollContainerSelector) {
|
||||
if (!container) return null;
|
||||
|
||||
let isThrottled = false;
|
||||
let lastScrollTop = 0;
|
||||
|
||||
const onScroll = () => {
|
||||
if (isThrottled) return;
|
||||
@@ -44,6 +45,17 @@ export function initScrollListener(dotNetHelper, scrollContainerSelector) {
|
||||
// Ensure bounds
|
||||
percentage = Math.max(0, Math.min(100, percentage));
|
||||
|
||||
// Scroll delta detection:
|
||||
// Hide bars on scroll down, show on scroll up. Force show when close to top.
|
||||
const delta = scrollTop - lastScrollTop;
|
||||
if (scrollTop <= 10) {
|
||||
dotNetHelper.invokeMethodAsync('HandleScrollDelta', false);
|
||||
} else if (Math.abs(delta) > 5) {
|
||||
const hideBars = delta > 0;
|
||||
dotNetHelper.invokeMethodAsync('HandleScrollDelta', hideBars);
|
||||
}
|
||||
lastScrollTop = scrollTop;
|
||||
|
||||
dotNetHelper.invokeMethodAsync('HandleScrollPercentChanged', percentage);
|
||||
isThrottled = false;
|
||||
});
|
||||
@@ -60,3 +72,4 @@ export function initScrollListener(dotNetHelper, scrollContainerSelector) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user