diff --git a/src/TafsirQuran.UI/Components/AudioPlayer.razor b/src/TafsirQuran.UI/Components/AudioPlayer.razor index 28c17d7..84fb15c 100644 --- a/src/TafsirQuran.UI/Components/AudioPlayer.razor +++ b/src/TafsirQuran.UI/Components/AudioPlayer.razor @@ -2,35 +2,38 @@ @inject ApiOptions ApiOptions @implements IAsyncDisposable -
-
@Title
-
- @if (ShowFavorite) - { - - } - @if (ShowBookmark) - { - - } +
+
+ +
+ @if (ShowFavorite) + { + + } + @if (ShowBookmark) + { + + } +
@if (!string.IsNullOrWhiteSpace(AudioUrl)) { - -
- +
+
@Format(current) / @Format(duration)
-
@Format(current) / @Format(duration)
+
} else { -

صوت این آیه در دسترس نیست

+

صوت این آیه در دسترس نیست

}
@@ -46,6 +49,7 @@ [Parameter] public EventCallback OnFavoriteToggle { get; set; } [Parameter] public EventCallback OnBookmarkToggle { get; set; } [Parameter] public EventCallback OnProgress { get; set; } + [Parameter] public EventCallback OnTitleClick { get; set; } private ElementReference audioEl; private ElementReference root; @@ -54,6 +58,7 @@ private double current; private double duration; private string boundKey = ""; + private bool bound; private string AudioSrc => ResolveAudioUrl(AudioUrl ?? ""); @@ -61,10 +66,11 @@ { if (string.IsNullOrWhiteSpace(AudioUrl)) return; - if (firstRender) + if (!bound) { self = DotNetObjectReference.Create(this); await Js.InvokeVoidAsync("tqAudio.bind", audioEl, self); + bound = true; } var key = string.IsNullOrWhiteSpace(AudioKey) ? AudioSrc : AudioKey; @@ -131,7 +137,8 @@ { try { - await Js.InvokeVoidAsync("tqAudio.unbind", audioEl); + if (bound) + await Js.InvokeVoidAsync("tqAudio.unbind", audioEl); } catch { /* ignore */ } self?.Dispose(); diff --git a/src/TafsirQuran.UI/Components/PlayerDock.razor b/src/TafsirQuran.UI/Components/PlayerDock.razor new file mode 100644 index 0000000..e9dd401 --- /dev/null +++ b/src/TafsirQuran.UI/Components/PlayerDock.razor @@ -0,0 +1,42 @@ +@inject PlayerSessionService Session +@inject NavigationManager Nav +@implements IDisposable + +@if (Session.HasTrack) +{ +
+ +
+} + +@code { + protected override void OnInitialized() => Session.Changed += OnChanged; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) return; + await Session.EnsureLoadedAsync(); + } + + private async Task OnProgress(double position) => await Session.ReportPositionAsync(position); + + private void GoToTrack() + { + if (Session.HasTrack) + Nav.NavigateTo(Session.ResumeHref); + } + + private void OnChanged() => InvokeAsync(StateHasChanged); + + public void Dispose() => Session.Changed -= OnChanged; +} diff --git a/src/TafsirQuran.UI/Components/SkeletonBlock.razor b/src/TafsirQuran.UI/Components/SkeletonBlock.razor new file mode 100644 index 0000000..f455408 --- /dev/null +++ b/src/TafsirQuran.UI/Components/SkeletonBlock.razor @@ -0,0 +1,27 @@ +@if (Variant == "lines") +{ + +} +else if (Variant == "cards") +{ + +} +else +{ + +} + +@code { + [Parameter] public string Variant { get; set; } = "block"; + [Parameter] public int Lines { get; set; } = 4; +} diff --git a/src/TafsirQuran.UI/Layout/AppLayout.razor b/src/TafsirQuran.UI/Layout/AppLayout.razor index d3b3218..b8129b6 100644 --- a/src/TafsirQuran.UI/Layout/AppLayout.razor +++ b/src/TafsirQuran.UI/Layout/AppLayout.razor @@ -2,7 +2,7 @@ @implements IDisposable @inject AuthStateService Auth -
+
@code { + [Inject] private PlayerSessionService Session { get; set; } = default!; private bool sidebarOpen; + private bool hasDock; - protected override void OnInitialized() => Auth.Changed += OnAuthChanged; + protected override void OnInitialized() + { + Auth.Changed += OnAuthChanged; + Session.Changed += OnSessionChanged; + hasDock = Session.HasTrack; + } protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; await Auth.RefreshAsync(); + await Session.EnsureLoadedAsync(); + hasDock = Session.HasTrack; await InvokeAsync(StateHasChanged); } private void OnAuthChanged() => InvokeAsync(StateHasChanged); + private void OnSessionChanged() + { + hasDock = Session.HasTrack; + InvokeAsync(StateHasChanged); + } private void ToggleSidebar() => sidebarOpen = !sidebarOpen; private void CloseSidebar() => sidebarOpen = false; - public void Dispose() => Auth.Changed -= OnAuthChanged; + public void Dispose() + { + Auth.Changed -= OnAuthChanged; + Session.Changed -= OnSessionChanged; + } } diff --git a/src/TafsirQuran.UI/Layout/Sidebar.razor b/src/TafsirQuran.UI/Layout/Sidebar.razor index 4058335..492412b 100644 --- a/src/TafsirQuran.UI/Layout/Sidebar.razor +++ b/src/TafsirQuran.UI/Layout/Sidebar.razor @@ -16,6 +16,7 @@
} + فهرست سوره‌ها علاقه‌مندی‌ها نشان‌شده‌ها درباره ما diff --git a/src/TafsirQuran.UI/Pages/HomePage.razor b/src/TafsirQuran.UI/Pages/HomePage.razor index 37376c5..3645221 100644 --- a/src/TafsirQuran.UI/Pages/HomePage.razor +++ b/src/TafsirQuran.UI/Pages/HomePage.razor @@ -1,12 +1,23 @@ @page "/" @inject TafsirApiClient Api +@inject PlayerSessionService Player +@inject NavigationManager Nav +@implements IDisposable

تفسیر قرآن

+ @if (Player.HasTrack) + { + + } + @if (loading) { -

در حال بارگذاری...

+ } else if (tafsirs.Count == 0) { @@ -33,11 +44,14 @@ private List tafsirs = []; private bool loading = true; + protected override void OnInitialized() => Player.Changed += OnPlayerChanged; + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; try { + await Player.EnsureLoadedAsync(); tafsirs = (await Api.GetTafsirsAsync()).ToList(); } catch @@ -50,4 +64,10 @@ await InvokeAsync(StateHasChanged); } } + + private void Resume() => Nav.NavigateTo(Player.ResumeHref); + + private void OnPlayerChanged() => InvokeAsync(StateHasChanged); + + public void Dispose() => Player.Changed -= OnPlayerChanged; } diff --git a/src/TafsirQuran.UI/Pages/TafsirPage.razor b/src/TafsirQuran.UI/Pages/TafsirPage.razor index 1436b19..244157d 100644 --- a/src/TafsirQuran.UI/Pages/TafsirPage.razor +++ b/src/TafsirQuran.UI/Pages/TafsirPage.razor @@ -4,111 +4,128 @@ @inject TafsirApiClient Api @inject IDeviceIdStore DeviceStore @inject NavigationManager Nav +@inject PlayerSessionService Player
-