Add Flatpak welcome modal and sequential modal priority system
- Add FlatpakWelcomeModal component shown once for Flatpak users - Add persistence for Flatpak welcome shown state (updates.db) - Implement sequential modal priority: Flatpak > What's new > Update available - Add Flatpak section to Settings navigation (only when running in Flatpak) - Clean up duplicate Flatpak section and invalid style block in SettingsView - Add copyable command styling for Flatpak section
This commit is contained in:
1 parent
4ff21312b2
commit
561f7487d0
6 files changed
+353
-144
No files matched your search
@@ -771,6 +771,8 @@ pub fn run() {
|
||||
updates::is_release_ignored,
|
||||
updates::has_whats_new_been_shown,
|
||||
updates::mark_whats_new_shown,
|
||||
updates::has_flatpak_welcome_been_shown,
|
||||
updates::mark_flatpak_welcome_shown,
|
||||
updates::get_current_version,
|
||||
updates::check_for_updates,
|
||||
updates::fetch_release_for_version,
|
||||
|
||||
@@ -123,6 +123,11 @@ impl UpdatesStore {
|
||||
CREATE TABLE IF NOT EXISTS whats_new_shown (
|
||||
version TEXT PRIMARY KEY,
|
||||
shown_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS flatpak_welcome_shown (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
shown_at INTEGER NOT NULL
|
||||
);",
|
||||
)
|
||||
.map_err(|e| format!("Failed to create updates tables: {}", e))?;
|
||||
@@ -233,6 +238,26 @@ impl UpdatesStore {
|
||||
.map_err(|e| format!("Failed to mark whats_new_shown: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_flatpak_welcome_been_shown(&self) -> bool {
|
||||
self.conn
|
||||
.query_row(
|
||||
"SELECT 1 FROM flatpak_welcome_shown WHERE id = 1 LIMIT 1",
|
||||
[],
|
||||
|_row| Ok(()),
|
||||
)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn mark_flatpak_welcome_shown(&self) -> Result<(), String> {
|
||||
self.conn
|
||||
.execute(
|
||||
"INSERT OR REPLACE INTO flatpak_welcome_shown (id, shown_at) VALUES (1, ?1)",
|
||||
params![now_epoch_seconds()],
|
||||
)
|
||||
.map_err(|e| format!("Failed to mark flatpak_welcome_shown: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe state wrapper
|
||||
@@ -448,6 +473,16 @@ pub fn mark_whats_new_shown(version: String, state: tauri::State<UpdatesState>)
|
||||
state.with_store(|store| store.mark_whats_new_shown(&version))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn has_flatpak_welcome_been_shown(state: tauri::State<UpdatesState>) -> bool {
|
||||
state.with_store(|store| store.has_flatpak_welcome_been_shown())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn mark_flatpak_welcome_shown(state: tauri::State<UpdatesState>) -> Result<(), String> {
|
||||
state.with_store(|store| store.mark_flatpak_welcome_shown())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_current_version(state: tauri::State<UpdatesState>) -> String {
|
||||
state.current_version()
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<script lang="ts">
|
||||
import Modal from '../Modal.svelte';
|
||||
import { Package } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
let { isOpen, onClose }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flatpak-welcome-modal">
|
||||
<Modal
|
||||
{isOpen}
|
||||
onClose={onClose}
|
||||
title="Running in Flatpak"
|
||||
maxWidth="520px"
|
||||
>
|
||||
<div class="modal-content">
|
||||
<div class="icon-container">
|
||||
<Package size={48} strokeWidth={1.5} />
|
||||
</div>
|
||||
|
||||
<p class="intro">
|
||||
QBZ detected it's running inside a <strong>Flatpak sandbox</strong>.
|
||||
There are some considerations to ensure the best experience.
|
||||
</p>
|
||||
|
||||
<div class="info-box">
|
||||
<h4>What you should know:</h4>
|
||||
<ul>
|
||||
<li>Access to external music folders (NAS, network mounts) requires manual permission grants</li>
|
||||
<li>Some audio features may behave differently due to sandbox restrictions</li>
|
||||
<li>The <strong>Settings</strong> page includes Flatpak-specific commands you can copy and run</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p class="note">
|
||||
Check the <strong>Flatpak Sandbox</strong> section in Settings for detailed instructions and copy-paste commands.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#snippet footer()}
|
||||
<div class="footer-actions">
|
||||
<button class="btn btn-primary" type="button" onclick={onClose}>
|
||||
Got it
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--accent-primary, #4285f4);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-primary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.info-box h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.info-box ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.info-box li {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.note {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
@@ -162,10 +162,11 @@
|
||||
let integrationsSection: HTMLElement;
|
||||
let updatesSection: HTMLElement;
|
||||
let storageSection: HTMLElement;
|
||||
let flatpakSection: HTMLElement;
|
||||
let activeSection = $state('audio');
|
||||
|
||||
// Navigation section definitions (static, refs resolved at click/scroll time)
|
||||
const navSectionDefs = [
|
||||
// Base navigation sections (always present)
|
||||
const baseNavSectionDefs = [
|
||||
{ id: 'audio', label: 'Audio' },
|
||||
{ id: 'playback', label: 'Playback' },
|
||||
{ id: 'offline', label: 'Offline' },
|
||||
@@ -177,6 +178,14 @@
|
||||
{ id: 'storage', label: 'Storage' },
|
||||
];
|
||||
|
||||
// Navigation section definitions (dynamic: includes Flatpak only when running in Flatpak)
|
||||
// NOTE: If adding new sections, add them BEFORE Flatpak. Flatpak must always be last.
|
||||
const navSectionDefs = $derived(
|
||||
isFlatpak
|
||||
? [...baseNavSectionDefs, { id: 'flatpak', label: 'Flatpak' }]
|
||||
: baseNavSectionDefs
|
||||
);
|
||||
|
||||
// Get section element by id (resolved at call time, not definition time)
|
||||
function getSectionEl(id: string): HTMLElement | undefined {
|
||||
switch (id) {
|
||||
@@ -189,6 +198,7 @@
|
||||
case 'integrations': return integrationsSection;
|
||||
case 'updates': return updatesSection;
|
||||
case 'storage': return storageSection;
|
||||
case 'flatpak': return flatpakSection;
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
@@ -2081,96 +2091,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Flatpak Section (only shown when running in Flatpak) -->
|
||||
{#if isFlatpak}
|
||||
<section class="section flatpak-section">
|
||||
<h3 class="section-title">Flatpak Sandbox</h3>
|
||||
<div class="flatpak-info">
|
||||
<p class="flatpak-intro">
|
||||
QBZ is running inside a Flatpak sandbox. For offline libraries on NAS, network mounts, or external disks, direct filesystem access is required.
|
||||
</p>
|
||||
<div class="flatpak-guide">
|
||||
<h4>Grant Filesystem Access</h4>
|
||||
<p>Use <strong>Flatseal</strong> (GUI) or run this command for each folder you want to add:</p>
|
||||
<div class="copyable-command">
|
||||
<pre class="code-block">flatpak override --user --filesystem=/path/to/your/music com.blitzfc.qbz</pre>
|
||||
<button class="copy-btn" onclick={() => copyCommand('fs-basic', 'flatpak override --user --filesystem=/path/to/your/music com.blitzfc.qbz')}>
|
||||
{copiedCommands['fs-basic'] ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<h4>Examples</h4>
|
||||
<div class="copyable-command">
|
||||
<pre class="code-block"># CIFS / Samba mount
|
||||
flatpak override --user --filesystem=/mnt/nas com.blitzfc.qbz
|
||||
|
||||
# SSHFS mount
|
||||
flatpak override --user --filesystem=$HOME/music-nas com.blitzfc.qbz
|
||||
|
||||
# Custom folder (edit as needed)
|
||||
flatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz</pre>
|
||||
<button class="copy-btn" onclick={() => copyCommand('fs-examples', `# CIFS / Samba mount\nflatpak override --user --filesystem=/mnt/nas com.blitzfc.qbz\n\n# SSHFS mount\nflatpak override --user --filesystem=$HOME/music-nas com.blitzfc.qbz\n\n# Custom folder (edit as needed)\nflatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz`)}>
|
||||
{copiedCommands['fs-examples'] ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<p class="flatpak-note">
|
||||
<strong>Note:</strong> This setting is persistent and survives reboots and updates.<br />
|
||||
<strong>Tip:</strong> You can repeat the command for as many folders as you need.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flatpak-guide" style="margin-top:2em;">
|
||||
<h4>Chromecast & DLNA Device Discovery</h4>
|
||||
<p>
|
||||
To detect Chromecast and DLNA devices on your network, you must grant network sharing permissions to the app:
|
||||
</p>
|
||||
<div class="copyable-command">
|
||||
<pre class="code-block">flatpak override --user --share=network com.blitzfc.qbz</pre>
|
||||
<button class="copy-btn" onclick={() => copyCommand('network', 'flatpak override --user --share=network com.blitzfc.qbz')}>
|
||||
{copiedCommands['network'] ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<p class="flatpak-note">
|
||||
<strong>Note:</strong> Without this, device discovery will not work.<br />
|
||||
You only need to do this once.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.copyable-command {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.copyable-command .code-block {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
user-select: all;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
.copy-btn {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.copy-btn:hover {
|
||||
background: var(--accent-secondary);
|
||||
}
|
||||
</style>
|
||||
<!-- Library Section -->
|
||||
<section class="section" bind:this={librarySection}>
|
||||
<h3 class="section-title">{$t('settings.library.title')}</h3>
|
||||
@@ -2501,8 +2421,9 @@ flatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz</pre>
|
||||
</section>
|
||||
|
||||
<!-- Flatpak Section (only shown when running in Flatpak) -->
|
||||
<!-- NOTE: Keep this section LAST. If adding new settings sections, add them BEFORE this one. -->
|
||||
{#if isFlatpak}
|
||||
<section class="section flatpak-section">
|
||||
<section class="section flatpak-section" id="flatpak" bind:this={flatpakSection}>
|
||||
<h3 class="section-title">Flatpak Sandbox</h3>
|
||||
<div class="flatpak-info">
|
||||
<p class="flatpak-intro">
|
||||
@@ -2510,16 +2431,46 @@ flatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz</pre>
|
||||
</p>
|
||||
<div class="flatpak-guide">
|
||||
<h4>Grant Filesystem Access</h4>
|
||||
<p>Use <strong>Flatseal</strong> (GUI) or run this command:</p>
|
||||
<pre class="code-block">flatpak override --user --filesystem=/path/to/music com.blitzfc.qbz</pre>
|
||||
<p>Use <strong>Flatseal</strong> (GUI) or run this command for each folder you want to add:</p>
|
||||
<div class="copyable-command">
|
||||
<pre class="code-block">flatpak override --user --filesystem=/path/to/your/music com.blitzfc.qbz</pre>
|
||||
<button class="copy-btn" onclick={() => copyCommand('fs-basic', 'flatpak override --user --filesystem=/path/to/your/music com.blitzfc.qbz')}>
|
||||
{copiedCommands['fs-basic'] ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<h4>Examples</h4>
|
||||
<pre class="code-block"># CIFS / Samba mount
|
||||
<div class="copyable-command">
|
||||
<pre class="code-block"># CIFS / Samba mount
|
||||
flatpak override --user --filesystem=/mnt/nas com.blitzfc.qbz
|
||||
|
||||
# SSHFS mount
|
||||
flatpak override --user --filesystem=$HOME/music-nas com.blitzfc.qbz</pre>
|
||||
flatpak override --user --filesystem=$HOME/music-nas com.blitzfc.qbz
|
||||
|
||||
# Custom folder (edit as needed)
|
||||
flatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz</pre>
|
||||
<button class="copy-btn" onclick={() => copyCommand('fs-examples', `# CIFS / Samba mount\nflatpak override --user --filesystem=/mnt/nas com.blitzfc.qbz\n\n# SSHFS mount\nflatpak override --user --filesystem=$HOME/music-nas com.blitzfc.qbz\n\n# Custom folder (edit as needed)\nflatpak override --user --filesystem=/home/USUARIO/Música com.blitzfc.qbz`)}>
|
||||
{copiedCommands['fs-examples'] ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<p class="flatpak-note">
|
||||
<strong>Note:</strong> This setting is persistent and survives reboots and updates.
|
||||
<strong>Note:</strong> This setting is persistent and survives reboots and updates.<br />
|
||||
<strong>Tip:</strong> You can repeat the command for as many folders as you need.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flatpak-guide" style="margin-top:2em;">
|
||||
<h4>Chromecast & DLNA Device Discovery</h4>
|
||||
<p>
|
||||
To detect Chromecast and DLNA devices on your network, you must grant network sharing permissions to the app:
|
||||
</p>
|
||||
<div class="copyable-command">
|
||||
<pre class="code-block">flatpak override --user --share=network com.blitzfc.qbz</pre>
|
||||
<button class="copy-btn" onclick={() => copyCommand('network', 'flatpak override --user --share=network com.blitzfc.qbz')}>
|
||||
{copiedCommands['network'] ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<p class="flatpak-note">
|
||||
<strong>Note:</strong> Without this, device discovery will not work.<br />
|
||||
You only need to do this once.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3312,6 +3263,44 @@ flatpak override --user --filesystem=$HOME/music-nas com.blitzfc.qbz</pre>
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Flatpak copyable command styling */
|
||||
.copyable-command {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.copyable-command .code-block {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
user-select: all;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: var(--accent-secondary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<MigrationModal
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import {
|
||||
acknowledgeRelease,
|
||||
@@ -122,23 +123,51 @@ function getDevFakeRelease(currentVersion: string): ReleaseInfo | null {
|
||||
|
||||
export interface LaunchUpdateDecision {
|
||||
currentVersion: string;
|
||||
updateRelease: ReleaseInfo | null;
|
||||
showFlatpakWelcome: boolean;
|
||||
whatsNewRelease: ReleaseInfo | null;
|
||||
updateRelease: ReleaseInfo | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide which modal (if any) should be shown on launch.
|
||||
* Check if running in Flatpak and if welcome modal has been shown.
|
||||
*/
|
||||
async function shouldShowFlatpakWelcome(): Promise<boolean> {
|
||||
try {
|
||||
const isFlatpak = await invoke<boolean>('is_running_in_flatpak');
|
||||
if (!isFlatpak) return false;
|
||||
const alreadyShown = await invoke<boolean>('has_flatpak_welcome_been_shown');
|
||||
return !alreadyShown;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark Flatpak welcome modal as shown.
|
||||
*/
|
||||
export async function markFlatpakWelcomeShown(): Promise<void> {
|
||||
try {
|
||||
await invoke('mark_flatpak_welcome_shown');
|
||||
} catch (error) {
|
||||
console.debug('[Updates] Failed to mark flatpak welcome shown:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide which modals should be shown on launch.
|
||||
*
|
||||
* Priority:
|
||||
* 1) Update available modal
|
||||
* 2) What's New modal
|
||||
* Priority (shown sequentially, one after another):
|
||||
* 1) Flatpak welcome modal (only for Flatpak users, once ever)
|
||||
* 2) What's New modal (once per version)
|
||||
* 3) Update available modal (when newer version exists)
|
||||
*/
|
||||
export async function decideLaunchModals(): Promise<LaunchUpdateDecision> {
|
||||
if (launchFlowStarted) {
|
||||
return {
|
||||
currentVersion: getCurrentVersion(),
|
||||
updateRelease: null,
|
||||
showFlatpakWelcome: false,
|
||||
whatsNewRelease: null,
|
||||
updateRelease: null,
|
||||
};
|
||||
}
|
||||
launchFlowStarted = true;
|
||||
@@ -150,38 +179,17 @@ export async function decideLaunchModals(): Promise<LaunchUpdateDecision> {
|
||||
const currentVersion = getCurrentVersion();
|
||||
|
||||
if (getSessionFlag(SESSION_CHECK_DONE_KEY)) {
|
||||
return { currentVersion, updateRelease: null, whatsNewRelease: null };
|
||||
return { currentVersion, showFlatpakWelcome: false, whatsNewRelease: null, updateRelease: null };
|
||||
}
|
||||
setSessionFlag(SESSION_CHECK_DONE_KEY);
|
||||
|
||||
// Phase 1: update available
|
||||
if (prefs.checkOnLaunch) {
|
||||
const devFake = getDevFakeRelease(currentVersion);
|
||||
if (devFake) {
|
||||
const devScopedKey = `${SESSION_UPDATE_SHOWN_KEY}:${devFake.version}`;
|
||||
if (!getSessionFlag(devScopedKey)) {
|
||||
setSessionFlag(devScopedKey);
|
||||
return {
|
||||
currentVersion,
|
||||
updateRelease: devFake,
|
||||
whatsNewRelease: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
// Collect all modals that need to be shown
|
||||
let showFlatpakWelcome = false;
|
||||
let whatsNewRelease: ReleaseInfo | null = null;
|
||||
let updateRelease: ReleaseInfo | null = null;
|
||||
|
||||
const result = await checkForUpdates('launch');
|
||||
if (result.status === 'update_available' && result.release) {
|
||||
const scopedUpdateKey = `${SESSION_UPDATE_SHOWN_KEY}:${result.release.version}`;
|
||||
if (!getSessionFlag(scopedUpdateKey)) {
|
||||
setSessionFlag(scopedUpdateKey);
|
||||
return {
|
||||
currentVersion,
|
||||
updateRelease: result.release,
|
||||
whatsNewRelease: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Phase 1: Flatpak welcome (highest priority, shown first)
|
||||
showFlatpakWelcome = await shouldShowFlatpakWelcome();
|
||||
|
||||
// Phase 2: What's New for current version
|
||||
if (prefs.showWhatsNewOnLaunch && currentVersion) {
|
||||
@@ -192,19 +200,37 @@ export async function decideLaunchModals(): Promise<LaunchUpdateDecision> {
|
||||
const release = await fetchReleaseForVersion(currentVersion);
|
||||
if (release) {
|
||||
setSessionFlag(scopedWhatsNewKey);
|
||||
// Persist "shown once per version" at the time we decide to show it.
|
||||
await markWhatsNewShown(currentVersion);
|
||||
return {
|
||||
currentVersion,
|
||||
updateRelease: null,
|
||||
whatsNewRelease: release,
|
||||
};
|
||||
whatsNewRelease = release;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { currentVersion, updateRelease: null, whatsNewRelease: null };
|
||||
// Phase 3: Update available (lowest priority, shown last)
|
||||
if (prefs.checkOnLaunch) {
|
||||
const devFake = getDevFakeRelease(currentVersion);
|
||||
if (devFake) {
|
||||
const devScopedKey = `${SESSION_UPDATE_SHOWN_KEY}:${devFake.version}`;
|
||||
if (!getSessionFlag(devScopedKey)) {
|
||||
setSessionFlag(devScopedKey);
|
||||
updateRelease = devFake;
|
||||
}
|
||||
}
|
||||
|
||||
if (!updateRelease) {
|
||||
const result = await checkForUpdates('launch');
|
||||
if (result.status === 'update_available' && result.release) {
|
||||
const scopedUpdateKey = `${SESSION_UPDATE_SHOWN_KEY}:${result.release.version}`;
|
||||
if (!getSessionFlag(scopedUpdateKey)) {
|
||||
setSessionFlag(scopedUpdateKey);
|
||||
updateRelease = result.release;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { currentVersion, showFlatpakWelcome, whatsNewRelease, updateRelease };
|
||||
}
|
||||
|
||||
export async function openReleasePageAndAcknowledge(release: ReleaseInfo): Promise<void> {
|
||||
|
||||
+46
-5
@@ -301,11 +301,13 @@
|
||||
import UpdateAvailableModal from '$lib/components/updates/UpdateAvailableModal.svelte';
|
||||
import UpdateReminderModal from '$lib/components/updates/UpdateReminderModal.svelte';
|
||||
import WhatsNewModal from '$lib/components/updates/WhatsNewModal.svelte';
|
||||
import FlatpakWelcomeModal from '$lib/components/updates/FlatpakWelcomeModal.svelte';
|
||||
import type { ReleaseInfo } from '$lib/stores/updatesStore';
|
||||
import {
|
||||
decideLaunchModals,
|
||||
disableUpdateChecks,
|
||||
ignoreReleaseVersion,
|
||||
markFlatpakWelcomeShown,
|
||||
openReleasePageAndAcknowledge,
|
||||
} from '$lib/services/updatesService';
|
||||
|
||||
@@ -342,8 +344,13 @@
|
||||
let isUpdateModalOpen = $state(false);
|
||||
let isReminderModalOpen = $state(false);
|
||||
let isWhatsNewModalOpen = $state(false);
|
||||
let isFlatpakWelcomeOpen = $state(false);
|
||||
let updatesLaunchTriggered = $state(false);
|
||||
|
||||
// Sequential modal queue: Flatpak → What's new → Update available
|
||||
let pendingWhatsNewRelease = $state<ReleaseInfo | null>(null);
|
||||
let pendingUpdateRelease = $state<ReleaseInfo | null>(null);
|
||||
|
||||
// Album, Artist and Label data are fetched, so kept local
|
||||
let selectedAlbum = $state<AlbumDetail | null>(null);
|
||||
let selectedArtist = $state<ArtistDetail | null>(null);
|
||||
@@ -370,15 +377,35 @@
|
||||
const decision = await decideLaunchModals();
|
||||
updatesCurrentVersion = decision.currentVersion;
|
||||
|
||||
if (decision.updateRelease) {
|
||||
updateRelease = decision.updateRelease;
|
||||
isUpdateModalOpen = true;
|
||||
// Store pending modals for sequential display
|
||||
// Order: Flatpak → What's new → Update available
|
||||
pendingWhatsNewRelease = decision.whatsNewRelease;
|
||||
pendingUpdateRelease = decision.updateRelease;
|
||||
|
||||
// Show first modal in queue (Flatpak has highest priority)
|
||||
if (decision.showFlatpakWelcome) {
|
||||
isFlatpakWelcomeOpen = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (decision.whatsNewRelease) {
|
||||
whatsNewRelease = decision.whatsNewRelease;
|
||||
// No Flatpak modal, try What's New
|
||||
showNextModalInQueue();
|
||||
}
|
||||
|
||||
function showNextModalInQueue(): void {
|
||||
// What's New has second priority
|
||||
if (pendingWhatsNewRelease) {
|
||||
whatsNewRelease = pendingWhatsNewRelease;
|
||||
pendingWhatsNewRelease = null;
|
||||
isWhatsNewModalOpen = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update Available has lowest priority
|
||||
if (pendingUpdateRelease) {
|
||||
updateRelease = pendingUpdateRelease;
|
||||
pendingUpdateRelease = null;
|
||||
isUpdateModalOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,9 +441,18 @@
|
||||
void disableUpdateChecks();
|
||||
}
|
||||
|
||||
function handleFlatpakWelcomeClose(): void {
|
||||
isFlatpakWelcomeOpen = false;
|
||||
void markFlatpakWelcomeShown();
|
||||
// Show next modal in queue
|
||||
showNextModalInQueue();
|
||||
}
|
||||
|
||||
function handleWhatsNewClose(): void {
|
||||
isWhatsNewModalOpen = false;
|
||||
whatsNewRelease = null;
|
||||
// Show next modal in queue
|
||||
showNextModalInQueue();
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
@@ -3119,6 +3155,11 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<FlatpakWelcomeModal
|
||||
isOpen={isFlatpakWelcomeOpen}
|
||||
onClose={handleFlatpakWelcomeClose}
|
||||
/>
|
||||
|
||||
<!-- Track Info Modal -->
|
||||
<TrackInfoModal
|
||||
isOpen={isTrackInfoOpen}
|
||||
|
||||
Reference in new issue
Block a user