Skip to content

Commit 1fcffaf

Browse files
committed
feat: introduce interactive update prompt and enhance audio analysis visualization
1 parent b153a18 commit 1fcffaf

12 files changed

Lines changed: 139 additions & 69 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## [0.30.0] - 2025-12-07
4+
5+
### Added
6+
7+
- **Unskippable Update Prompt**: When a new version is available, users are shown a mandatory modal that must be acknowledged before using the app
8+
- Displays current and latest version with update instructions
9+
- Press Enter, Esc, q, or Space to dismiss
10+
- Replaces the old auto-dismissing notification banner
11+
12+
### Changed
13+
14+
- **Audio Visualization Improvements**:
15+
- Added noise gate to filter out background noise when no audio is playing
16+
- Boosted high frequency bands (Air, Ultra) for better visibility
17+
- Added gradient colors to spectrum bars based on bar height (green → yellow → orange → red)
18+
319
## [0.29.0] - 2025-12-07
420

521
### Added

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ documentation = "https://github.com/LargeModGames/spotatui"
66
repository = "https://github.com/LargeModGames/spotatui"
77
keywords = ["spotify", "tui", "cli", "terminal"]
88
categories = ["command-line-utilities"]
9-
version = "0.29.3"
9+
version = "0.30.0"
1010
authors = ["LargeModGames <LargeModGames@gmail.com>"]
1111
edition = "2021"
1212
license = "MIT"

‎src/app.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ pub enum ActiveBlock {
139139
Artists,
140140
BasicView,
141141
Dialog(DialogContext),
142+
UpdatePrompt,
142143
}
143144

144145
#[derive(Clone, PartialEq, Debug)]
@@ -160,6 +161,7 @@ pub enum RouteId {
160161
PodcastEpisodes,
161162
Recommendations,
162163
Dialog,
164+
UpdatePrompt,
163165
}
164166

165167
#[derive(Debug)]
@@ -336,7 +338,7 @@ pub struct App {
336338
pub dialog: Option<String>,
337339
pub confirm: bool,
338340
pub update_available: Option<UpdateInfo>,
339-
pub update_notification_shown_at: Option<Instant>,
341+
pub update_prompt_acknowledged: bool,
340342
}
341343

342344
impl Default for App {
@@ -427,7 +429,7 @@ impl Default for App {
427429
dialog: None,
428430
confirm: false,
429431
update_available: None,
430-
update_notification_shown_at: None,
432+
update_prompt_acknowledged: false,
431433
}
432434
}
433435
}

‎src/audio/analyzer.rs‎

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,21 +127,20 @@ impl AudioAnalyzer {
127127
}
128128
}
129129

130-
// Per-band gain for a pleasing visual curve
131-
// Creates a natural "smile curve" - moderate lows, full mids, moderate highs
130+
// Per-band gain for visualization - boosted highs to compensate for low energy
132131
const BAND_GAINS: [f32; NUM_BANDS] = [
133-
0.5, // Sub - reduce sub rumble
134-
0.65, // Bass - visible but not overwhelming
135-
0.8, // Low - building up
136-
0.9, // LMid - almost full
137-
1.0, // Mid - peak visibility (voice frequencies)
138-
1.0, // UMid - peak visibility
139-
0.95, // High - slightly reduce
140-
0.85, // HiMid - gentle rolloff
141-
0.75, // Pres - continuing rolloff
142-
0.65, // Bril - less harsh highs
143-
0.55, // Air - subtle
144-
0.45, // Ultra - very subtle
132+
0.7, // Sub - reduce sub rumble
133+
0.8, // Bass - visible but not overwhelming
134+
0.9, // Low - building up
135+
1.0, // LMid - full
136+
1.0, // Mid - peak visibility (voice frequencies)
137+
1.0, // UMid - peak visibility
138+
1.1, // High - boost slightly
139+
1.2, // HiMd - boost more
140+
1.3, // Pres - boost more
141+
1.4, // Bril - boost more
142+
1.6, // Air - significant boost
143+
2.0, // Ultra - major boost (very little energy here)
145144
];
146145

147146
// Normalize and apply pleasing visual curve
@@ -160,11 +159,18 @@ impl AudioAnalyzer {
160159
for i in 0..NUM_BANDS {
161160
self.spectrum.bands[i] =
162161
self.spectrum.bands[i] * SMOOTHING + new_bands[i] * (1.0 - SMOOTHING);
162+
// Noise gate: treat very low values as zero
163+
if self.spectrum.bands[i] < 0.005 {
164+
self.spectrum.bands[i] = 0.0;
165+
}
163166
}
164167

165-
// Update peak with smoothing
168+
// Update peak with smoothing and noise gate
166169
let current_peak = new_bands.iter().cloned().fold(0.0f32, f32::max);
167170
self.spectrum.peak = self.spectrum.peak * SMOOTHING + current_peak * (1.0 - SMOOTHING);
171+
if self.spectrum.peak < 0.005 {
172+
self.spectrum.peak = 0.0;
173+
}
168174
}
169175
}
170176

‎src/cli/update.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ pub struct UpdateInfo {
1111
/// Check for updates in the background (non-blocking)
1212
/// Returns Some(UpdateInfo) if an update is available, None if up to date
1313
pub fn check_for_update_silent() -> Option<UpdateInfo> {
14+
// ============ TESTING: Uncomment below to simulate update ============
15+
// return Some(UpdateInfo {
16+
// current_version: env!("CARGO_PKG_VERSION").to_string(),
17+
// latest_version: "99.0.0".to_string(),
18+
// });
19+
// =====================================================================
20+
1421
let current_version = cargo_crate_version!();
1522

1623
let status = self_update::backends::github::Update::configure()

‎src/handlers/common_key_events.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ pub fn handle_right_event(app: &mut App) {
135135
RouteId::Analysis => {}
136136
RouteId::BasicView => {}
137137
RouteId::Dialog => {}
138+
RouteId::UpdatePrompt => {}
138139
},
139140
_ => {}
140141
};

‎src/handlers/mod.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod recently_played;
2121
mod search_results;
2222
mod select_device;
2323
mod track_table;
24+
mod update_prompt;
2425

2526
use super::app::{ActiveBlock, App, ArtistBlock, RouteId, SearchResultBlock};
2627
use crate::event::Key;
@@ -170,6 +171,9 @@ fn handle_block_events(key: Key, app: &mut App) {
170171
ActiveBlock::Dialog(_) => {
171172
dialog::handler(key, app);
172173
}
174+
ActiveBlock::UpdatePrompt => {
175+
update_prompt::handler(key, app);
176+
}
173177
}
174178
}
175179

@@ -191,6 +195,8 @@ fn handle_escape(app: &mut App) {
191195
}
192196
// These are global views that have no active/inactive distinction so do nothing
193197
ActiveBlock::SelectDevice | ActiveBlock::Analysis => {}
198+
// Update prompt must be dismissed with Enter/Esc, not global escape
199+
ActiveBlock::UpdatePrompt => {}
194200
_ => {
195201
app.set_current_route_state(Some(ActiveBlock::Empty), None);
196202
}

‎src/handlers/update_prompt.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use super::super::app::App;
2+
use crate::event::Key;
3+
4+
pub fn handler(key: Key, app: &mut App) {
5+
match key {
6+
Key::Enter | Key::Esc | Key::Char('q') | Key::Char(' ') => {
7+
app.update_prompt_acknowledged = true;
8+
app.pop_navigation_stack();
9+
}
10+
_ => {}
11+
}
12+
}

‎src/main.rs‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -467,21 +467,20 @@ async fn start_ui(user_config: UserConfig, app: &Arc<Mutex<App>>) -> Result<()>
467467
#[cfg(any(feature = "audio-viz", feature = "audio-viz-cpal"))]
468468
let mut audio_capture: Option<audio::AudioCaptureManager> = None;
469469

470-
// Check for updates in background (non-blocking)
471-
let app_clone = Arc::clone(app);
472-
tokio::spawn(async move {
473-
// Run update check in a blocking thread pool since self_update uses blocking I/O
470+
// Check for updates SYNCHRONOUSLY before starting the event loop
471+
// This ensures the update prompt appears before any user interaction
472+
{
474473
let update_info = tokio::task::spawn_blocking(cli::check_for_update_silent)
475474
.await
476475
.ok()
477476
.flatten();
478477
if let Some(info) = update_info {
479-
if let Ok(mut app) = app_clone.try_lock() {
480-
app.update_available = Some(info);
481-
app.update_notification_shown_at = Some(std::time::Instant::now());
482-
}
478+
let mut app = app.lock().await;
479+
app.update_available = Some(info);
480+
// Push the mandatory update prompt modal onto navigation stack
481+
app.push_navigation_stack(RouteId::UpdatePrompt, ActiveBlock::UpdatePrompt);
483482
}
484-
});
483+
}
485484

486485
// play music on, if not send them to the device selection view
487486

@@ -537,6 +536,9 @@ async fn start_ui(user_config: UserConfig, app: &Arc<Mutex<App>>) -> Result<()>
537536
ActiveBlock::BasicView => {
538537
ui::draw_basic_view(f, &app);
539538
}
539+
ActiveBlock::UpdatePrompt => {
540+
ui::draw_update_prompt(f, &app);
541+
}
540542
_ => {
541543
ui::draw_main_layout(f, &app);
542544
}

0 commit comments

Comments
 (0)