Files
57ceffec56 Blankie 1.1 (Universal App w/ iPhone & iPad Support, Custom Sounds) (#61)
* fix: incorrect class status in AudioManager

* refactor: reduce AppDelegate, PresetManager and UITestingHelper function body length

* refactor: Split SoundSheet into components for better type body length

* refactor: Split AdaptiveContentView into PlaybackControlsView and SidebarContentView components for improved type body length

* style: fix opening braces not preceded by a single space on the same line as the declaration

* refactor: Split off PresetManager helper methods for smaller type body

* refactor: remove file_length, type_body_length, and function_body_length from disabled rules in SwiftLint configuration

* refactor: Introduce setter methods in PresetManager for better encapsulation and code clarity

* feat: Implement dropzone functionality for audio file import with associated manager

* refactor: Simplify icon search functionality

* feat: Add context menu for editing and deleting custom sounds in SoundIcon view
refactor: Remove original file name display in CustomSoundsView

* Refactor sound management features and enhance customization options

- Updated SoundIcon view to use gestures for toggling sound instead of a button.
- Introduced SoundDropDelegate for handling drag-and-drop functionality for sound reordering.
- Modified DropzoneModifier to ignore text-based drags for sound reordering.
- Enhanced SoundSheet to support sound customization mode.
- Added calculateDodgeOffset function for improved drag animations in AdaptiveContentView.
- Implemented drag-and-drop functionality for sound icons in AdaptiveContentView.
- Created SoundManagementView for managing both built-in and custom sounds.
- Added SoundCustomizationManagementView for managing sound customizations.
- Updated PreferencesView and SettingsView to include navigation to sound management.
- Refactored toolbar to open SoundManagementView instead of CustomSoundsView.

* fix: update to reccommended project settings

* refactor: update scenePhase change handling and adjust state variable visibility in AdaptiveContentView

* fix: remove unused Base localization from project settings

* fix: remove stale localization entries

* feat: enhance PlaybackControlsView with presets and menu buttons

* feat: implement DraggableSoundIcon for improved drag-and-drop functionality in sound grid

* feat: add solo mode for individual sound playback

- Long press on any sound to enter solo mode
- Solo mode plays only the selected sound at full volume
- Visual indicators show when solo mode is active
- Exit solo mode via the banner button or by toggling solo on the same sound
- Preserves original volume and selection state when exiting solo mode

* refactor: hide default preset from UI and show as "Blankie"

- Hide default preset from sidebar preset list
- Show default preset name as "Blankie" in preset picker
- Remove default preset section from preset picker view

* feat: add haptic feedback for drag and drop interactions

- Light haptic feedback when hovering over drop targets
- Success notification haptic when completing a drop
- Respects global haptic settings preference

* refactor: modularize SoundManagement

* refactor: extract AudioManager extensions to reduce file size

- Move solo mode functionality to AudioManager+SoloMode.swift
- Move sound management functions to AudioManager+SoundManagement.swift
- Reduce main AudioManager file from 489 to 323 lines
- Address SwiftLint file_length and type_body_length warnings

* fix: resolve compilation errors from refactoring by adjusting access levels and adding missing methods

* Refactor PlaybackControlsView and AdaptiveContentView for improved UI and functionality

- Removed the showingPresetPicker binding and integrated sound management directly into PlaybackControlsView.
- Added exit solo mode button and improved volume button handling in PlaybackControlsView.
- Enhanced the appearance and functionality of the menu button in PlaybackControlsView.
- Introduced theme picker and sound management sheets in PlaybackControlsView.
- Updated AdaptiveContentView to handle sound selection changes and added a no sounds selected indicator.
- Improved timer button and compact timer button styles.
- Refined the settings and preferences views to reflect changes in auto-play behavior.
- Added reset functionality for sound customizations and volume controls.
- Enhanced preset picker view with solo mode indication and improved editing capabilities.
- Updated volume controls view to include reset functionality and improved UI elements.

* componentize: split AudioManager with state management and persistence extensions, split GlobalSettings for better audio session handling, enhance some UI components

* Add ThemePickerSheet and AdaptiveContentView enhancements

- Implemented ThemePickerSheet for selecting appearance modes and accent colors.
- Refactored AdaptiveContentView to separate layout logic for large and small devices.
- Added menu options for managing presets, settings, and theme selection.
- Enhanced navigation header with status indicators and safe area handling.
- Introduced sound management features and improved UI responsiveness.

* refactor: adjust access levels of state properties in AdaptiveContentView for consistency

* feat: add custom color support for sounds

* fix: pause and deselect custom sounds before removal

* feat: implement cleanup of deleted custom sounds from presets

* feat: add toggle for showing/hiding sound names across the app

* feat: add list view option with drag-and-drop sound reordering

- Add new ListView layout option alongside existing grid view
- Implement drag-and-drop reordering of sounds in list view
- Add view toggle buttons in toolbar and menu bar
- Add moveVisibleSounds method to AudioManager for list reordering
- Maintain consistent spacing and layout across view modes

* feat: add configurable icon sizes with responsive design

- Add IconSize enum with small, medium, and large options
- Implement responsive icon sizing in SoundIcon and DraggableSoundIcon
- Add icon size picker in settings with real-time preview
- Add locale script detection for better font rendering
- Adjust spacing, padding, and font sizes based on icon size
- Improve text rendering for different script categories (CJK, etc.)

* feat: improve solo mode with persistence and better state management

- Add solo mode persistence across app restarts
- Improve solo mode state transitions and sound management
- Fix issue where solo sound would pause when entering/exiting solo mode
- Better handling of volume restoration when exiting solo mode
- Maintain playback state more accurately during solo mode transitions
- Save/restore solo mode sound in GlobalSettings

* feat: add randomize start position option for sounds

- Add randomizeStartPosition property to sound customization system
- Implement random start position logic in Sound.play() method
- Add toggle in sound editing sheets for custom sounds
- Support randomization for both built-in and custom sounds
- Default to enabled for new custom sounds
- Store randomization preference in sound customization data

* feat: add keyboard shortcut for toggling sound name visibility

- Add Ctrl+Cmd+N keyboard shortcut to toggle sound names
- Add menu item in View menu with dynamic title
- Add toolbar button for quick access to toggle
- Consistent naming and behavior across all interfaces

* chore: update localization strings and project configuration

- Add new localization keys for grid view, icon size, solo mode, and randomize start position
- Update existing localization comments for clarity
- Minor project configuration updates

* feat: add icon size menu with keyboard shortcuts on mac

* feat: implement list view toggle and reorder functionality with UI updates

* feat: update sound metadata display to include custom and built-in labels

* feat: enhance sound management view with expandable sections for built-in and custom sounds

* feat: convert audio files from MP3 to M4A format

- Replace all built-in sound files from MP3 to M4A for better quality and smaller file sizes
- Update sounds.json to reference new M4A file extensions
- Add convert-audio.sh script for future audio processing workflows

* feat: add audio analysis utilities for sound normalization

- Add AudioAnalyzer with LUFS loudness measurement capabilities
- Implement A-weighting filter for perceptual audio analysis
- Support for loudness normalization and audio level detection
- Foundation for consistent volume balancing across custom sounds

* refactor: reorganize Sound model with normalization and playback extensions

- Split Sound functionality into focused extensions
- Add Sound+Normalization for audio level analysis and volume balancing
- Add Sound+Playback for playback state management
- Remove deprecated CustomSound.swift in favor of unified Sound model
- Improve code organization and maintainability

* feat: enhance AudioManager with improved sound management capabilities

- Update AudioManager initialization for better custom sound support
- Improve sound loading with normalization integration
- Enhanced SwiftData integration for custom sound persistence
- Better notification handling for audio state changes
- Improved CustomSoundManager with metadata extraction
- Enhanced SoundCreditsManager for attribution tracking

* feat: add new UI components for enhanced sound customization

- Add ColorSelectionView for sound color theming
- Add IconPickerView for sound icon selection
- Add SoundSheetProcessingOverlay for import feedback
- Enhance existing components with better customization support
- Improve DraggableSoundIcon with enhanced interaction
- Update SoundIcon and SoundIconSelector for new features
- Enhanced SoundManagementRow with better organization

* feat: enhance sound sheet functionality with modular view architecture

- Split SoundSheet into focused extensions for better maintainability
- Add SoundSheet+Helpers for utility functions
- Add SoundSheet+Preview for audio preview functionality
- Add SoundSheetForm+Clean for form validation and cleanup
- Add SoundSheetForm+Helpers for form utilities
- Add SoundSheetView for improved sheet presentation
- Enhance existing sheet functionality with better organization

* feat: update data models and core managers for v1.1 features

- Enhance data models with better custom sound support
- Update CustomSoundData with improved metadata handling
- Enhance Preset and SoundData models with new capabilities
- Improve GlobalSettings with better audio session management
- Enhanced PresetManager persistence for custom sounds
- Update core views with improved sound management
- Remove deprecated CustomSoundsView in favor of unified approach
- Enhance CarPlay interface with new sound features

* chore: update project configuration and development tooling

- Update Xcode project with new files and build settings
- Enhance AppCommands with new functionality
- Update localization strings for v1.1 features
- Move blanki18n.swift to scripts directory for better organization
- Update development documentation and contributing guidelines
- Update credits documentation with new contributors
- Improve VS Code settings for better development experience

* chore: remove deprecated MP3 files and old script location

- Clean up old MP3 sound files after M4A conversion
- Remove old blanki18n.swift from root (moved to scripts/)

* feat: Implement audio normalization and playback profile management

- Added comprehensive audio analysis for sounds, including LUFS and true peak detection.
- Introduced PlaybackProfile to cache analysis results for efficient playback.
- Implemented batch analysis functionality to analyze all sounds and update profiles.
- Enhanced Sound class to include metadata properties such as channel count, duration, and file size.
- Updated SoundSheetForm to display sound information and normalization data.
- Added soft limiting functionality to prevent clipping during playback.
- Created documentation for sound normalization process and usage in the app.

* feat: Enhance audio management with playback profile caching and analysis for custom sounds

* feat: Add playback progress tracking and UI updates

- Introduced playback progress tracking in the Sound class with a new published property.
- Updated UI components (DraggableSoundIcon and SoundIcon) to display a circular progress indicator based on playback progress.
- Added a toggle in PreferencesView and SettingsView to allow users to show or hide the progress border.
- Created a new ColorPicker for selecting icon colors in SoundSheetColorPicker.
- Refactored SoundSheetForm to improve code organization and readability by separating helper functions into extensions.
- Enhanced audio analysis script to streamline sound re-analysis and output updated JSON.

* Improve CarPlay

- Add CarPlayInterfaceController for managing CarPlay interface lifecycle
- Implement PresetListTemplate for preset selection in CarPlay
- Add QuickMixGridTemplate for fast sound mixing on CarPlay
- Create SoundsListTemplate for browsing individual sounds
- Update CarPlaySceneDelegate to use new controller architecture
- Add AudioManager extension for CarPlay quick mix functionality

* feat: Add timer sheet and view settings sheet

- Add TimerSheetView for configurable sleep timer interface
- Add ViewSettingsSheet for managing view preferences
- Refactor TimerView to simplify implementation

* feat: Add AudioFileImporter utility

- Add comprehensive audio file import functionality
- Support for multiple audio formats (mp3, m4a, wav, aiff, caf)
- Include format conversion to M4A with progress tracking
- Add file validation and error handling

* refactor: Improve sound icon components and drag handling

- Refactor DraggableSoundIcon for better code organization
- Enhance SoundIcon with improved visual feedback
- Update SoundDropDelegate with better error handling
- Improve drag and drop interactions

* chore: Update app version and build numbers

- Update version to 1.1.0 across all Info.plist files
- Increment build number
- Update CarPlay plist configuration

* refactor: Major AdaptiveContentView improvements

- Add comprehensive state management and layout system
- Implement responsive grid and list layouts
- Add navigation and menu handling improvements
- Enhance CarPlay integration support
- Improve sheet presentation and management

* refactor: Streamline sound sheet architecture

- Consolidate sound sheet components for better maintainability
- Remove separate SoundSheetColorPicker in favor of inline implementation
- Improve form validation and state management
- Enhance preview functionality with better error handling
- Simplify sheet presentation logic

* feat: Enhance audio management system

- Add playback progress tracking to Sound class
- Improve NowPlayingManager with better metadata handling
- Add support for custom sound artwork in Now Playing
- Enhance sound loading with better error recovery
- Improve playback control reliability

* feat: Improve custom sound management

- Enhance CustomSoundManager with better file handling
- Add progress tracking support to CustomSoundData model
- Improve SoundCustomization with better default values
- Update SoundFileSelector with AudioFileImporter integration

* refactor: Consolidate sound management views

- Remove separate SoundCustomizationManagementView
- Integrate customization features into SoundManagementView
- Simplify SettingsView by removing redundant sections
- Improve overall settings organization

* feat: UI improvements across multiple views

- Update AboutView with better layout and spacing
- Add timer sheet support to ContentView
- Enhance PresetPickerView with improved selection handling
- Update SidebarContentView with better navigation
- Minor UI polish and consistency improvements

* feat: Enhance app lifecycle and settings management

- Add CarPlay configuration to BlankieApp
- Update AppDelegate with improved initialization
- Add view settings to GlobalSettings
- Improve logging configuration
- Add CarPlay support to PresetManager

* feat: Update localizations and utilities

- Add new localization strings for CarPlay and timer features
- Update Color extension with minor improvements
- Add translations for new UI elements

* chore: Update project configuration and resources

- Update project.pbxproj with new file references
- Replace NowPlaying.png with higher resolution version
- Update .gitignore with additional patterns

* docs: Add CarPlay documentation and update project docs

- Add CarPlayNotes.md with implementation details
- Add CarPlayRecc.md with recommendations
- Add CarPlayTodo.md with future enhancements
- Update DEVELOPMENT.md with CarPlay instructions
- Update README.md with CarPlay feature mention

* doc: fix missing header comments

* fix: remove unnecessary notes from sound entries

* chore: rename Blankie-Carplay plist to be more consistent

* docs: move/rename release process documentation, notes files

* lint: refactor for better type body management

* feat: refactor sound management and customization sections for improved organization and clarity

* refactor GlobalSettings into extensions

* chore: fix file lint issues

* feat: consolidate AdaptiveContentView functionality by integrating UI components and removing deprecated files

* chore: update deployment targets for macOS and iOS to latest versions

* feat: add option to lock portrait orientation on iOS in settings

* fix: update test files to use current GlobalSettings API

- Replace deprecated setAlwaysStartPaused() with setAutoPlayOnLaunch()
- Remove override keyword from MockSound init (not overriding designated initializer)
- Ensure tests use correct API for auto-play functionality

* feat: enhance timer with simple time addition controls

- Add addTime() method to TimerManager for adding 1 or 5 minutes during timer
- Add getEndTime() to show when timer will complete
- Redesign TimerSheetView with simple +1 min and +5 min buttons only
- Show completion time and improve layout with cleaner design
- Update presentation detents to fixed height for better UX
- Add localized strings for new timer interface elements

* feat: enhance preset management with new creation and editing features

* feat: enhance Now Playing info with full preset details and update progress handling. show cover art in carplay list. fix issues with cover art display.

* feat: Enhance Custom Sound Management and Metadata Handling

- Implement security-scoped resource access for importing audio files in CustomSoundManager.
- Extract and store ID3 metadata (title, artist, album, comment, URL) during audio file import.
- Update CustomSoundData model to include ID3 metadata and sharing permissions.
- Introduce new License cases for better license management.
- Refactor CreatePresetSheet and EditPresetSheet to improve sound selection and navigation.
- Add SoundAboutSheet for displaying and editing sound credits and permissions.
- Improve SoundSheet actions to integrate newly imported sounds into current presets.
- Enhance SoundManagementView to handle file imports more effectively.
- Introduce change tracking in SoundSheet to manage unsaved changes.

* Refactor SoundSheet and related views for improved modularity and readability

- Extracted editable credits view into a separate `EditableCreditsView` struct for better code organization.
- Created `BuiltInCreditsView` to handle displaying built-in sound credits.
- Introduced `SoundDetailsSection` to encapsulate sound detail display logic.
- Moved initialization logic for different modes of `SoundSheet` into a new extension for better separation of concerns.
- Simplified `SoundSheet` view by using dedicated layout views for macOS and iOS.
- Enhanced file import handling in `SoundSheet` for better error management.
- Updated `SoundSheet` to use new modular components, improving maintainability and clarity.

* Refactor Quick Mix functionality and UI components

- Updated localization for Quick Mix and Quick Mix Mode.
- Enhanced AudioManager to manage Quick Mix sounds, including filtering valid sounds and toggling selection.
- Modified Sound playback methods to ensure proper loading and pausing behavior.
- Removed deprecated SoundSheet initialization methods and replaced them with a new state management approach.
- Introduced QuickMixView for displaying Quick Mix sounds in a grid layout.
- Updated UI components to reflect Quick Mix state and actions, including buttons and indicators.
- Adjusted ViewSettingsSheet to hide certain options in Quick Mix mode.
- Improved overall code organization and readability.

* feat: Implement Quick Mix sound management with persistent settings and replacement options

* feat: Add delete and reset confirmation alerts in SoundSheet, enhance customization handling

* feat: Remove haptic feedback functionality and related settings from GlobalSettings

* feat: Enhance sensory feedback across UI components and remove unused PlaybackControlsView

* fix: Update popover presentation to use arrow edge for QuickMixSoundButton

* feat: Add GridSoundButton and GridSoundButtonWrapper components

- Introduced GridSoundButton for displaying sound controls with visual feedback and options.
- Implemented GridSoundButtonWrapper to manage drag-and-drop functionality for sound reordering.
- Enhanced SoundSheet actions to ensure customizations are applied and saved correctly.
- Updated preview handling in SoundSheet to improve user experience and maintain state.
- Refactored UI components to support adaptive layouts and improve responsiveness across devices.
- Added logging for debugging purposes to track state changes and actions within the SoundSheet.

* fix: Remove unnecessary whitespace in deleteSound function and ensure sound updates correctly

* feat: Implement waveform preview

* refactor: Rename CarPlay Quick Mix references to Quick Mix for consistency

* refactor: Remove hideInactiveSoundSliders functionality and related settings

* refactor: Simplify SoundManagementRow usage and update context menu actions

* fix: Ensure volume resets to 100% when disabling mix with others and apply new settings, remove unused audio ducking option

* feat: Implement shared progress tracking for improved CPU performanceaudio playback and update settings management

* refactor: modularize EditPresetSheet into focused sections

- Extract preset editing sections into dedicated EditPresetSections.swift
- Move image handling logic to PresetImageHandling.swift for reusability
- Simplify EditPresetSheet by delegating to specialized components
- Add icon property to Preset model for future customization

* refactor: simplify SoundSheet architecture and reduce code duplication

- Remove redundant preview functionality from SoundSheet extensions
- Consolidate state management and simplify sound sheet actions
- Clean up form helpers and improve code organization
- Remove unused icon property from SoundCustomization model
- Streamline audio processing UI and form layout

* feat: enhance sound loading with better error handling and icon support

- Add comprehensive error handling for sound file loading
- Support custom icons for sounds with fallback to category icons
- Improve debug logging for sound initialization
- Handle edge cases when sound files are missing or corrupted

* fix: improve UI consistency and preset management

- Fix preset icon display in GridSoundButton
- Update SoundIcon to properly handle preset icons
- Improve preset picker view deletion confirmation
- Clean up sound management view for better user experience
- Fix CarPlay preset list template icon handling

* chore: update localization strings

- Update localization strings to reflect UI changes
- Ensure all new UI elements have proper localization keys

* feat: reset Quick Mix volumes to 80% on activation and make changes temporary

- Quick Mix sounds now reset to 80% volume when entering Quick Mix mode
- Volume changes during Quick Mix are temporary and not persisted
- Prevents saving state or individual sound volumes while in Quick Mix mode
- Ensures consistent Quick Mix experience across sessions

* fix: enhance background material for improved visual clarity

* chore(deps): bump brace-expansion

Bumps the npm_and_yarn group with 1 update in the /docs directory: [brace-expansion](https://github.com/juliangruber/brace-expansion).

Updates `brace-expansion` from 2.0.1 to 2.0.2
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v2.0.1...v2.0.2)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 2.0.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: add background customization options and UI components for presets

* feat: Add PresetArtwork model and integrate artwork handling in presets

- Introduced `PresetArtwork` model to manage artwork data associated with presets.
- Updated `PresetStorage` to handle saving and loading of artwork IDs instead of raw data.
- Refactored `CreatePresetSheet` and `EditPresetSheet` to support artwork saving and loading using the new model.
- Enhanced image picking functionality with a new `ImagePicker` view for iOS.
- Removed deprecated `PresetImageHandling` file and consolidated image handling logic.
- Improved background image handling in `ContentView` and `AdaptiveContentView` to utilize artwork from `PresetArtwork`.
- Added color picker section in `ViewSettingsSheet` for customizing accent colors.
- Implemented sound selection logic to prevent deselecting the last sound.
- Added background settings management in `ViewSettingsSheet` for better user experience.

* fix: preset reordering and persistence

- Add order property to Preset model for custom reordering
- Implement transactional preset reordering in PresetPickerView with Cancel/Done semantics
- Fix order persistence by ensuring unique order values and preserving them during save
- Add proper edit mode handling with temporary state management
- Update preset creation to assign order values automatically

Fixes issue where preset reordering would reset after pressing Done.

* fix: Now Playing artist info persistence when paused

- Change artist info filter from isPlaying to isSelected in getArtistInfo
- Ensures Now Playing displays active sounds even when audio is paused
- Maintains consistency with playtime behavior that persists across pause/play

Fixes issue where artist information would disappear when pausing audio.

* refactor: media controls and fix SwiftLint warnings

- Refactor setupMediaControls into smaller focused functions for better maintainability
- Add proper preset navigation with order-based next/previous functionality
- Enhance media control configuration with organized command setup
- Fix SwiftLint function body length warning by breaking down large methods
- Improve code organization in AudioManager+MediaControls

Resolves SwiftLint warnings while maintaining all media control functionality.

* feat: add app setup infrastructure and modular architecture

- Add AppSetup for centralized app configuration and SwiftData setup
- Create SharedModifiers for reusable UI modifiers across the app
- Refactor BlankieApp to use modular setup patterns
- Add PresetArtwork SwiftData model with image type support (artwork/background)
- Enhance PresetArtworkManager with comprehensive artwork handling
- Add thread safety improvements with AudioManager+ThreadSafety
- Update PresetStorage to include artwork persistence capabilities

Establishes foundation for advanced preset customization features.

* feat: add background customization and UI enhancements

- Add comprehensive background customization options for presets
- Implement visual progress indicators and enhanced sound components
- Add draggable sound icons with improved interaction handling
- Enhance preset editing with background image/artwork options
- Update adaptive content view with background material support
- Improve settings sheets with new customization sections
- Add CarPlay interface improvements and audio manager optimizations
- Enhance visual components with consistent styling and animations

Enables users to fully customize preset appearance with backgrounds and artwork.

* fix: improve list and sound row backgrounds for better visibility

* feat: integrate ZIPFoundation for archive handling and add software dependencies section in credits

* fix: disable file_length rule in SwiftLint and add missing word to spell checker

* feat: add preset imports and exports

* fix: better sound order management and update handling in AudioManager and PresetManager

* fix: clean up lint issues

* feat: move preset settings button to be more accessible

* fix: update preset editing UI and fix artwork persistence

* fix: resize preset settings button icon

* feat: enhance timer functionality and UI updates

* refactor: update view settings to remove redundant background controls

* fix: update preset display name and improve selection handling in PresetPicker

* fix: simplify artist name display in NowPlayingManager and PresetListTemplate

* refactor: remove swipe actions from sound list for cleaner UI

* feat: Migrate data to app group and enhance CarPlay support

- Implemented data migration to app group for UserDefaults and SwiftData.
- Refactored CarPlay interface initialization to ensure proper app setup.
- Added functionality to load custom sounds by their IDs.
- Enhanced thumbnail caching for CarPlay presets.
- Introduced a new method for importing custom sounds with pre-analyzed metadata.
- Improved error handling and logging during data migration.
- Updated UserDefaults access to use shared instance for app group.
- Cleaned up orphaned sound entries in default sound order.
- Added support for caching artwork thumbnails for CarPlay.
- Refactored various audio and preset management functionalities for better performance and maintainability.

* feat: Cache thumbnails for CarPlay when artwork is added or updated

* feat: Enhance CarPlay support with improved initialization and loading mechanisms

* Refactor UserDefaults usage and implement unified data migration

- Introduced a shared UserDefaults instance to streamline access across app group and standard UserDefaults.
- Added AppDataMigrator to handle one-time migration of UserDefaults and SwiftData to app group.
- Updated various managers and models to utilize the new shared UserDefaults instance.
- Implemented debouncing for rapid updates in NowPlayingManager and SoundCustomizationManager to optimize performance.
- Enhanced logging and removed verbose print statements for better clarity during debugging.
- Consolidated SwiftData migration logic into AppDataMigrator, removing the old SwiftDataMigration utility.
- Cleaned up legacy code and ensured all relevant data is migrated to the new structure.

* Update project settings, localization, and migration logic

* fix: Update platforms in project settings

* fix: Correct extension name for Swift formatting in recommendations

* feat: Enhance CarPlay support with protected data handling and initialization improvements

* fix: Improve CarPlay file protection setup and ensure compatibility with existing databases

* feat: Implement CarPlay edit functionality

* feat: Enhance CarPlay functionality with improved sound management

* fix: New sounds not immediately visible

* refactor: Simplify sound filtering and improve UI labels in Sound Management

* Add new app icon and enhance AboutView with icon selection feature

- Introduced a new icon configuration file for the classic app icon.
- Updated AboutView to support alternate app icons, allowing users to choose between Default, Classic, and Beta icons.
- Improved icon loading logic to handle both display assets and legacy PNG sets.
- Added a tip for changing the app icon and refined the layout of the AboutView for better user experience.
- Implemented a new icon chooser sheet for iOS, enhancing accessibility and usability.

* feat: Implement animated artwork support for presets in iOS 26

- Added `AnimatedArtworkRef` and `staticArtworkPath` to the `Preset` model.
- Introduced `BlankieAnimatedArtwork.swift` for publishing animated artwork to Now Playing.
- Created `AnimatedArtworkFileStore.swift` for managing animated artwork files.
- Updated `CreatePresetSheet` and `EditPresetSheet` to include animated artwork selection.
- Added `AnimatedArtworkPicker` for user interface to choose animated artwork.
- Implemented `prepare_loop.sh` script for processing video files into required formats.
- Updated app settings to include a toggle for enabling/disabling animated backgrounds.
- Added migration notes and QA guidelines for the new animated artwork feature.

* feat: Add support for animated artwork files and configure LFS for .mov assets

* feat: Implement On-Demand Resource management for animated artwork videos and update related components

* refactor: Reorganize AnimatedArtwork resources

* Redesign main layout with simplified UI

* enhance UI with more liquid glass

* feat: Add spectrum-style color picker with iOS 26 native ticks

Replace grid-based accent color picker with a modern spectrum slider that features:
- Beautiful gradient spectrum background with liquid glass design
- iOS 26+ native tick marks using SliderTickContentForEach
- Colored chip label that appears during interaction
- Adaptive tick visibility (white dots for iOS 18, native ticks for iOS 26+)
- Haptic feedback on color selection
- Smooth animations and proper edge padding
- Custom color order: Green → Mint → Teal → Cyan → Blue → Indigo → Purple → Pink → Red → Orange → Yellow → Brown

Also includes ODR caching improvements for animated artwork and new ReorderableGrid component.

* chore: clean up .gitignore by removing specific notes directories

* feat: add interactive preset onboarding with custom SF Symbol

Add comprehensive onboarding flow for new users to create their first preset with:
- Custom blankie.symbol SF Symbol with layered breathing animation
- Interactive color picker step for accent color customization
- Sound selection with preview functionality (solo mode)
- Real-time UI theming that updates as user customizes
- TipKit for progressive feature discovery.

Closes #42

* Add library interface with navigation

* refactor: Modernize MixerView, preset editing, and UI components for v2.0

- Rename AdaptiveContentView to MixerView with modular file structure
- Add per-preset accent colors and moods picker
- Modernize glass effects using tinted .glassEffect API
- Move appearance settings (accent color, labels, progress borders) to Settings
- Improve Now Playing subtitle with creator name and timer status
- Fix race condition causing auto-play of deselected sounds
- Simplify SoundSheet with embedInNavigation support
- Require minimum 2 sounds in preset onboarding
- Remove hideInactiveSounds, edit mode, and empty state from MixerView
- Bump version to 2.0.0, iOS target to 26.1

* refactor: Revert tab navigation, rework Now Playing and mixer toolbar

Roll back HomeView tab-based navigation in favor of direct MixerView.
Delete PresetsLibraryView and SoundsLibraryView. Rework NowPlayingSheet
with Apple Music-inspired proportional layout using equal spacers.
Unify bottom toolbar as shared 3-button component that swaps icons
between mixer and now playing modes. Add top bar with timer, preset
title, and edit preset glass buttons. Replace overflow menu with
direct settings button.

* fix: Preset editor persistence, settings access, and Quick Mix improvements

- Fix sound additions not persisting in preset editor by calling
  applyChangesInstantly on sound selection sheet dismiss
- Stop removed sounds immediately but don't auto-play added sounds
- Fix missing navigationDestination for sound editing in preset editor
- Fix sound order init for default preset using defaultSoundOrder
- Wire settings button to full SettingsView (sounds, appearance,
  lock screen, about) instead of just SoundManagementView
- Extract SoundManagementView NavigationStack to call sites
- Collapse built-in sounds list with DisclosureGroup
- Add Quick Mix editor sheet with separate sound picker and reorder
- Respect preset accent color in mixer UI buttons
- Increase Now Playing info-to-volume spacing
- Add top padding to QuickMixView for top bar clearance

* fix: Icon contrast, Quick Mix visibility, and remove moods editor

- Improve icon contrast for all accent colors and appearances
- Add colorScheme environment for appearance change reactivity
- Make entire sound row card tappable in list view
- Reduce Quick Mix gradient and glass tint opacity for better visibility
- Show Quick Mix popovers below for top row items
- Remove moods picker from preset editor (data model preserved)

* fix: Now Playing background/artwork not updating on preset change

Task ID now includes both solo mode sound and current preset,
so artwork reloads when switching presets.

* refactor: Consolidate toolbar actions into ellipsis menu

- Move timer, edit preset, and settings into bottom toolbar menu
- Remove duplicate buttons from top bar and Now Playing
- Simplify Now Playing by removing info section (uses shared top bar)
- Show timer status text below preset title in top bar
- Top bar now visible in both mixer and Now Playing modes

* refactor: Remove per-sound custom colors

Sounds no longer have individual color customization. Preset and global
accent colors remain. Removes customColor from Sound model, color picker
from SoundSheet, and all sound.customColor fallback chains across UI
components, CarPlay templates, and importers.

* fix: Landscape Now Playing layout and toolbar positioning

- Add landscape layout with artwork left, controls right
- Move bottom toolbar into right column in landscape Now Playing
- Larger preset title in Now Playing mode
- Fix background image sizing for landscape
- Extend top bar material into horizontal safe areas

* fix: Landscape layout refinements for Now Playing and toolbar

- Use percentage-based artwork sizing for reliability
- Fix background image fill in landscape with explicit frame
- Right-align toolbar in landscape Now Playing via spacer split
- Add top padding to preset bar in landscape Now Playing

* fix: Update marketing version to 1.1.0

* refactor: Isolate CarPlay from audio core, deepen ODR, fix custom-sound normalization

- Move CarPlay connection state out of audio core into a new
  CarPlayAudioBridge; AudioManager gets an isCarPlayConnected flag
  that the bridge pushes into. Removes three #if CARPLAY_ENABLED
  leaks from AudioManager+QuickMix, +Notifications, +Initialization.
- OnDemandResourceManager now deduplicates concurrent requests for
  the same resource id via an in-flight task cache, eliminating the
  500ms sleep-and-check workaround and preventing accidental parallel
  NSBundleResourceRequests.
- Migrate three fire-and-forget Task launches to structured tasks
  with cancellation: FullScreenPreview.setupPlayer cancels on
  dismiss, NowPlayingManager coalesces duplicate artwork-download
  triggers per id, PresetManager cancels stale nearby-artwork
  prefetches on preset change.
- Fix bug where custom-sound normalization factor stayed stuck at
  1.0 for the session after deferred LUFS analysis completed.
  Sound.lufs and Sound.normalizationFactor are now @Published var
  and are updated in analyzeAndUpdateLUFS before updateVolume runs.
- Remove dead SoundSheetView.swift and SoundSheetForm.swift
  (superseded by SoundSheet + SoundSheetMacOSLayout +
  CleanSoundSheetForm).
- Collapse a no-op #if os(macOS)/#else branch in EditPresetSheet.

* chore: VS Code swift-format/SourceKit-LSP setup + i18n percentage fixes

Editor tooling (VS Code):
- Add .swift-format pinned to swift-format defaults; enable format-on-save
  via the Swift extension.
- Ignore xcode-build-server artifacts (buildServer.json, .compile).
- Disable XML auto-formatting so it stops mangling .xcscheme/.plist.
- Set swift.diagnosticsCollection=onlySwiftc to suppress unreliable
  cross-file SourceKit-LSP diagnostics; rely on Xcode for semantic errors.

Localization warnings (Localizable.xcstrings):
- Format volume/progress percentages with FormatStyle(.percent) for
  locale-correct % placement instead of hardcoded "%".
- Drop orphaned (%lld%%, -50%, +700%) and stale (Add Preset, Enter a name
  for this sound) string-catalog keys.

* feat: Long-press grid reorder + unified sound tiles, per-preset view mode

Replace the .onDrag/.onDrop tile reorder with a long-press SortableGridView
(ported from a reference implementation): the dragged tile lifts into an
overlay while cells swap live, committing once on release. The reorder
gesture ignores touches on UIControls so the inline volume slider stays
draggable. Measurements are taken in window space so multi-cell drags track
the finger, and tile positions are preserved across re-syncs so subsequent
drags keep working.

Unify the grid and Quick Mix tiles on a single GridSoundButton, add a
per-preset view-mode override (Preset.viewMode), and fold the old view
settings into the main Settings/Edit Preset sheets.

Remove ReorderableGrid, GridSoundButtonWrapper, and ViewSettingsSheet.

* feat(docs): migrate to Astro 6 and adopt shadcn/Base UI

Upgrade the docs site and adopt shadcn/ui (on Base UI) for interactive
and presentational components, themed with the existing Apple-style
palette and native system fonts.

Dependencies:
- Astro 5 -> 6 (Vite 7), Tailwind 4.1 -> 4.3, glob 10 -> 13,
  marked 15 -> 18, astro-seo 0.8 -> 1.1, @playform/* and others to latest
- Add @astrojs/react + @base-ui/react for shadcn islands

Components & theming:
- Add shadcn/Base UI primitives (Card, Badge, Accordion, Sheet, Table, Button)
- Map the Apple palette into shadcn CSS tokens (always-dark); native SF fonts
- FAQ -> Accordion island; mobile/tablet nav -> Sheet drawer
- i18n stats -> responsive table + mobile cards with a segmented progress bar
- Adopt Card across pages; marked-alert for GitHub-style markdown alerts

Fixes & cleanup:
- Replace hand-rolled alert regex with marked-alert (shared markdown util)
- Fix internal links missing trailing slashes (trailingSlash: always)
- Drop deprecated tsconfig baseUrl; remove dead experimental.contentIntellisense
- Associate *.css with the Tailwind CSS language mode in VS Code

* fix: Grid tile state, view-mode picker, light mode, and dead-code cleanup

Address review findings from the long-press grid rework:

- GridSoundButton.isActive is now computed from live sound.isSelected
  (Quick Mix still passes an explicit override) so grid tiles light up,
  scale, and enable their slider when toggled — the stored copy went stale
  because the tile's init doesn't re-run on a selection toggle.
- Restore the per-toggle selection haptic (trigger on sound.isSelected, not
  isLit): fixes missing feedback while paused and N simultaneous haptics on
  global play/pause.
- Volume slider disables on !isActive so a prior-preset-selected sound can't
  edit the base mix from the Quick Mix grid.
- SoundSurfaceBackground base stop adapts to colorScheme (was hardcoded
  near-black), fixing black-on-black tiles in Light appearance.
- SoundGridView reconciles tiles on drag end when membership changed
  mid-drag, so a dropped change no longer leaves stale/missing tiles.
- Settings View Mode picker reflects the effective mode and is disabled with
  a caption when the active preset overrides it.
- Restore the play/pause long-press → preset picker shortcut.
- Remove vestigial MixerView state (hideInactiveSounds, editMode,
  draggedIndex, hoveredIndex, dragResetTimer) and simplify filterSounds().

* feat: enhance UI with new icons and improve styling across pages

* feat: gate debug logs, add a11y labels, TestFlight credit link, import error alerts

- Route all 780 console prints through DebugLog (#if DEBUG + @autoclosure) so
  diagnostics and their string interpolation are stripped from release builds
- Add VoiceOver accessibility labels to 20 icon-only controls (play/pause,
  back, now-playing toggle, ellipsis menu, timer, preset edit/delete, exit
  solo, clear name, choose/remove artwork, close)
- Surface previously-silent .blankie preset and audio import failures via
  ErrorReporter's existing alert
- Remove dead onboarding state/task in MixerView (redundant with
  OnboardingPresentation; the removed check was pure)
- Add Bundle.isTestFlightOrDebug gate and beta-tester credit link in Settings
- Apply swift-format (reindent/reflow) across touched files

* ci: fix CodeQL Swift analysis

- Select newest installed Xcode on the runner so it can open the
  objectVersion=100 (Xcode 26) project format
- Build the iOS Simulator variant instead of the broken macOS build;
  CodeQL only needs the Swift sources compiled (no signing required)
- Add 'actions' to the analysis matrix (build-mode: none) now that
  default setup is disabled, keeping workflow scanning in this file
- Drop -quiet for legible build logs while iterating

* fix: update Quick Mix description for clarity and conciseness

* chore: update GitHub Actions to use latest versions of checkout, setup-node, and deploy-pages

* fix: update CodeQL workflow to use macos-26 and improve Xcode selection script

* fix: update CodeQL workflow to trigger only on main branch

* fix: refine Quick Mix description for clarity and conciseness

* feat: add CodeQL workflows for Actions and Swift analysis

* feat: Favorites system, iPad polish, and RC fixes

Add a starred-presets ("Favorites") system shared across the iPad sidebar,
CarPlay, and a future widget:
- Favorite/unfavorite presets and "All Blankie Sounds"; Quick Mix is always
  pinned (not favoritable) with its own CarPlay tab.
- iPad sidebar: Favorites with All Blankie Sounds fallback, pinned Quick Mix,
  "More" link, footer Settings, active-preset accent tint, star/now-playing
  indicators.
- CarPlay: Favorites section + Now Playing favorite toggle; leading
  now-playing indicator (isPlaying) instead of trailing checkmark.
- Preset picker: always-visible star toggle + Edit-mode reorder; "Close"
  dismiss.
- next/prev media controls follow favorites order, then remaining presets.

iPad/iPhone polish:
- Convert deprecated NavigationView to NavigationStack in sheets.
- Fix grid drag-reorder coordinate tracking (use .global converter).
- Shrink inactive grid tiles instead of growing active ones.
- Now Playing layout/contrast fixes for iPad landscape + dark backgrounds.

Fixes:
- Persist favorites on iPad (guard sidebar prune against launch race).
- Persist accent color (use setter, not direct @Published assignment).
- CarPlay: dedupe favorited presets out of All Presets/All Sounds.
- Gate solo progress TimelineView to only run while playing.
- toggleStarred mutates a local copy for a single publish.
- Sleep-timer expiry stops countdown + pauses atomically (no scrubber jump).
- Background-blur slider persists on each stepped change.
- Top-bar title single-line + inset so it can't run under the edit button.
- Artwork picker uses the active preset accent.

* chore: update CHANGELOG for version 1.1.0 release with new features and changes

* feat: macOS grid/UI overhaul and v1.1 release fixes

macOS UI:
- Rebuild the main grid on the shared long-press reorder engine
  (MacSoundGridView); filter to the active preset and persist reorders to
  the preset's sound order, mirroring iOS. Remove the legacy drag-icon path
  and the now-dead moveVisibleSounds.
- Replace Finder drag-and-drop import with a File > Import menu command;
  drop DropzoneManager/DropzoneModifier.
- Add VolumePopoverView and AccentColorCirclePicker; assorted sheet and
  window polish.

Audio / presets:
- Fix custom sounds not stopping between preset changes. Make custom-sound
  loading idempotent via hasLoadedCustomSounds so the CarPlay AppDelegate and
  AppSetup launch paths no longer re-instantiate live Sound objects and orphan
  their AVAudioPlayers; stop and deselect leaving instances unconditionally.

CarPlay / build:
- Gate all CarPlay code with `CARPLAY_ENABLED && canImport(CarPlay)` and scope
  -DCARPLAY_ENABLED to the iOS SDKs so the macOS build compiles.
- Rename scheme to "Blankie (Universal with CarPlay)".

* fix: align deployment-target floors and CHANGELOG for 1.1.0 RC

Test bundles drifted from the app target: BlankieTests was iOS 26.1 and
BlankieUITests was macOS 15.5, while the app ships iOS 26.0 / macOS 15.6.
Align both to the app's floor and correct the CHANGELOG (macOS 15.5 -> 15.6,
release date -> 2026-05-26) so the stated minimum matches what the binary
requires.

* fix: update copyright year to 2026 across various files

* feat: restore localization  for macOS "Preferences..." item

* feat(i18n): draft translations, catalog-only comments, localization cleanup

- Add machine-drafted needs_review translations for all 11 languages
  (de, en-GB, es, fr, hu, it, ja, ko, pt-PT, tr, zh-Hans); en-GB reviewed.
- Restore lost "Preferences..." translations; resolve the String Catalog
  symbol collision via an explicit "menu.preferences" key.
- Standardize descriptive comments on all 328 keys, living solely in the
  catalog; strip comment: args from code.
- Replace 5 NSLocalizedString calls with String(localized:) to match the
  codebase convention; drop ExpandableSection's unused comment parameter.
- Run swift-format across the project.

* feat(i18n): add Polish (community) + German/Spanish contributions

- Polish (Polski): new language from community contributor Kristopheros (#78)
  — 90 human strings + machine-drafted needs_review for full coverage;
  registered in knownRegions, FAQ, credits, docs, and cspell.
- German (Deutsch): integrate H. Rapp's translation (email) — 90 strings,
  promoting/finalizing his work and fixing prior weaker drafts.
- Spanish (Español): apply Dizz7's native-speaker fixes (#79).
- Fix 4 Polish typos in the contribution, validated against sjp.pl + Wiktionary
  (dźwięk, wstrzymaj, gwiazdkę).
- Credit H. Rapp, Dizz7, and Kristopheros; CHANGELOG updated (now 13 languages;
  UK English corrected to a localization, not a separate language).

Co-authored-by: Dizz7 <162482518+Dizz7@users.noreply.github.com>
Co-authored-by: Kristopheros <260881004+Kristopheros@users.noreply.github.com>

* feat: implement async TestFlight detection and update beta UI logic

* docs: update contributing guidelines, development setup, and FAQ for clarity and new features

* feat: enhance bug report template with platform selection and improved OS version description

* chore: remove unused SwiftLint configuration and clean up script headers

* fix: scope grid reorder gesture off volume sliders; refine Quick Mix

Grid volume sliders couldn't be dragged on iOS or macOS: the per-tile
long-press→drag reorder gesture covered the whole cell and won the gesture
arena, starving the slider's own drag — the knob would grab but not move,
and the tile never actually reordered. Attach the reorder gesture to just
the tile's icon/tap-zone via an environment-injected .reorderHandle(),
leaving the slider's drag uncontested.

Quick Mix refinements:
- Membership-only: drop the per-sound volume slider from its tiles.
- Use the Now Playing toggle instead of a Back button (leave Quick Mix via
  the preset picker / sidebar, which calls exitQuickMix()).
- Remove the stray 44pt top padding so tiles align with the normal grid.
- Update the editor blurb to note it has no customizable volume, and update
  all 12 localizations to match.

* feat: reorganize iOS settings — Playback up top, split Appearance defaults

Move the Playback section (Autoplay, Mix with Other Audio) out of Manage
Sounds to the top of the main iOS Settings, above Sounds. Split Appearance
into always-active settings and a new "Preset Defaults" section for the
values a preset can override (View Mode, Accent Color, Background Blur), and
fold the old "Lock Screen" section's toggle in as "Lock Screen Animations".
Localize the two new strings across all languages; drop the orphaned
"Animated Background" / "Lock Screen" keys.

* fix: retry AppTransaction probe so TestFlight detection survives cold-launch StoreKitError.unknown

AppTransaction.shared can throw StoreKitError.unknown ("Unable to
Complete Request") on the first call after a cold launch, before
StoreKit has the signed transaction cached — which left the beta-tester
gate reading false on TestFlight installs. Retry the probe a few times
with backoff to ride that out; return false if every attempt still
throws, preserving the "App Store never shows beta UI" invariant.

Does not call AppTransaction.refresh() (its App Store sign-in prompt
requires an explicit user action) and adds no persisted fallback (would
risk leaking beta UI into the App Store on the TestFlight-to-release
upgrade path).

* refactor: replace gesture-based reordering with a custom long-press implementation and improve accessibility traits for interactive components.

* UI refactor: refine Settings layout, fix scroll blocking on custom slider, and format codebase

- Refactored SettingsView: renamed Appearance to Display, split Customization Defaults to its own section, and consolidated Manage Sounds under Playback & Sounds.
- Added CarPlay state handling to disable 'Mix with Others' toggle appropriately.
- Replaced custom DragGesture with native onEditingChanged in SpectrumColorPicker to fix scroll blocking on the track.
- Used custom UIKit long-press for reordering in SortableGridView to preserve scrolling.
- Refined accessibility traits and tap gestures in SoundIcon.
- Added blankie.symbol to the About button.
- Ran swift-format across the repository to standardize code style.

* fix: update .gitignore to include scheduled_tasks.lock

* feat: add localizations for background blur and customization options in settings

* feat: add favoritable solo sounds to the iOS preset picker

Add a Sounds section to the preset picker (all sounds, alphabetical, tap to
solo) and make individual sounds favoritable via a new solo:<fileName> starred
token, which surfaces alongside preset favorites in the picker, iPad sidebar,
CarPlay, and lock-screen next/previous navigation.

Solo is now self-contained rather than inheriting the last preset:
- In-app Now Playing, the mixer background, and the system lock-screen artwork
  use the sound's own icon and the app accent instead of the preset's artwork.
- Tapping the solo icon (grid) or the Now Playing artwork toggles playback;
  the Now Playing / header edit control edits the sound; favorite the sound
  from Now Playing. Removed the dedicated exit-solo controls — you leave solo
  by choosing another sound or preset.
- The bottom-toolbar left button is always the Now Playing toggle; Quick Mix
  always activates the mixer.

Correctness:
- Robust solo state restoration so switching between solo sounds no longer
  leaks a previously-soloed sound into the mix.
- Launch restore: solo wins over the last preset, respects auto-play-on-launch,
  defers for not-yet-loaded custom sounds, and clears stale state if the saved
  sound was deleted. Solo restore is exclusive — preset application and the
  isSelected auto-play path no longer start other sounds while soloing.
- Orphaned solo favorite tokens are pruned at launch on every platform.

Cleanup: single sound(forSoloToken:) resolver, enterSoloMode owns the Quick Mix
exit, removed the dead Solo Mode - %@ string.

* feat: redesign iOS Now Playing sheet and render lock-screen artwork dynamically

- Rebuild Now Playing with transport controls (prev/play/next), an info row, the system volume slider (MPVolumeView), and drag-to-dismiss; size the sheet from the app window so it stays correct under iPad Split View / Stage Manager.

- Render the default lock-screen/CarPlay artwork from BrandedBlankieIcon via ImageRenderer instead of a bundled PNG; remove NowPlaying.png and point EditPresetSheet's share-preview fallback at the app-icon imageset.

- Fix artwork warnings: opaque renderers (drop the unnecessary alpha channel) and a nonisolated MPMediaItemArtwork wrapper so MediaPlayer's background request handler no longer forces an unsafeForcedSync hop.

- Limit the next/previous cycle to favorited items and add canNavigateNextPrevious.

- Extract a shared Sound.creditedAuthor helper, simplify the Mixer toolbar glyph, and add localizations.

* docs: note favoriting individual sounds and Now Playing next/previous in CHANGELOG

* docs: note single sound playback in CHANGELOG

* tooling: add blankifi audio toolkit, retire per-task scripts

Self-compiling Swift CLI (analyze/loop/fix/convert/reanalyze) that compiles
with the app's real AudioAnalyzer, so analysis matches the shipping app.
Removes convert-audio.sh and reanalyze-sounds.swift; ignores scripts/.build/.

* fix: remove fireplace loop pop

Trim edge silence + isolated crackles and crossfade the seam so the loop
wraps without the audible pop. (Metadata refreshed in the normalization commit.)

* feat: two-way K-weighted loudness normalization for built-ins

calculateLUFSNormalizationFactor now attenuates loud sounds as well as boosting
quiet ones, so built-ins match how custom sounds are analyzed. Built-ins carry
truePeakdBTP/needsLimiter so the soft limiter protects boosted sounds. Lowered
minimumLUFS to -60 to keep sounds off the un-normalize cliff. sounds.json
regenerated via 'blankifi reanalyze'.

* fix: load imported audio from an app-owned copy

A document picker returns a security-scoped URL that's only readable
while its scope is held. The custom-sound importers passed that raw URL
straight to the add-sound sheet, so previewing the not-yet-saved sound
failed to load once the picker callback returned ("audio file could not
be loaded").

Extract the copy-to-temp logic into AudioFileImporter.stagedTempCopy(of:)
and use it from the importers, so the sheet and its preview read a
stable, app-owned file.

* feat: redesign mixer header and Library

- Rename PresetPickerView to LibraryView. It now hosts an "Import" menu
  (New Preset / import an audio file or a .blankie preset) and shows
  CarPlay-style artwork thumbnails on rows.
- Decode those row thumbnails off the main actor in PresetArtworkManager
  (ImageIO downsample on a detached task, wrapped on the main actor).
- Replace the iPhone mixer's custom material header with a native inline
  navigation title: Library and Settings move into the top toolbar, and
  the Edit affordance moves into the bottom play controls.

* feat: mark preset-overridden Theme Defaults in Settings

Show an "Overridden by Preset" badge and disable the control for view
mode, accent color, and background blur when the active preset overrides
the app-wide default, replacing the verbose explanatory caption. Also
make the Settings sheet follow the appearance setting while it's open
via preferredColorScheme (with a new AppearanceMode.colorScheme helper).

* fix: align buttons, labels, and localization with the HIG

- Move sheet "Done" buttons to the trailing confirmation slot (Settings,
  Manage Sounds, Quick Mix, Edit/Create Preset). Use a leading Close
  (xmark) for the Timer, and "Cancel" on the add-sound sheet so it's
  clear it won't import. Edit-from-preset now keeps the standard Back.
- Add VoiceOver labels to the macOS transport buttons and to the sound
  tiles (so they're named even when "Show Sound Names" is off).
- CarPlay shows real sound titles instead of raw SF Symbol names, pushes
  Now Playing after selecting a preset, and localizes its strings.
- Add an AirPlay route picker to Now Playing and a Controls > Play/Pause
  command to the macOS menu bar.

* fix: stop solo mode inheriting the last preset's theme

Solo mode preserves currentPreset so the preset can be restored on exit,
but accent resolution and the Settings "Overridden by Preset" badges keyed
off it directly — so the last preset's accent leaked onto the solo play
button and the theme controls still read as overridden.

Centralize the rule in PresetManager.themingPreset (nil during solo and
Quick Mix) and route accent/override sites through it.

* fix: make Now Playing open reliably and fill the screen

The sheet could park partway open when reopened right after a
swipe-to-close — the freshly presented sheet's dismiss-drag picked up the
leftover touch as a phantom downward drag, leaving dragOffset stuck.
Mask the dismiss-drag until the sheet is fully presented so the open
window can't capture a stray touch, and reset dragOffset on appear and
on dismiss so no stale offset carries over. Open/close is now driven by
a value animation rather than a withAnimation inside the tap handler.

Also make the background full-bleed — it now covers the safe areas
(including behind the Dynamic Island) so the sheet reads as a true
full-screen surface, while the content and drag handle stay inset below
the Dynamic Island.

* refactor: regroup Edit Preset fields and tidy theme labels

- Move Favorite, Artwork, and Lock Screen Animation into the basic
  details group with Name and Creator
- Pin the Favorite row separator to the leading edge so it runs under
  the star icon
- Collapse the background blur toggle + picker into a single
  Default | None | Low | High selector
- Move the "Drag sounds to reorder" hint under the Sounds header
- Rename the Edit Preset section to "Theme Overrides", the Settings
  section to "Theme", and the override badge to "Changed by Preset"

* fix: show thumbnails for animated-only presets and refresh artwork cache on edit

* fix: remove leaked AVPlayer end-of-item observer in animated artwork picker

* refactor: use native pointerStyle(.link) for the hand cursor

* fix: compare archive versions numerically

* refactor: centralize m:ss formatting and fix sleep-timer end-time drift

Add TimeInterval.minuteSecondClock as the single source for the m:ss
format that was duplicated across the sound-info and timer labels, switch
date/time labels to the modern formatted() API, and anchor the timer end
time to start+duration so the 'Pausing at' label no longer flickers across
minute boundaries. Reword timer copy from 'Stops' to 'Pausing'.

* fix: make Now Playing view-only and hide the volume bar on CarPlay

Drop the edit (ellipsis) action and its presetToEdit/soundToEdit/
showingQuickMixEditor bindings — Now Playing is now view-only. Hide the
in-app volume row when CarPlay is connected (the car owns volume), center
the system volume slider, and show a repeat glyph beside the loop duration.

* fix: inherit the effective accent color in toolbar, settings, and sheets

Several surfaces fell back to the system accent or flipped color after an
interaction because they read Color.accentColor from an inconsistent
environment tint. Tint the Library/Settings toolbar buttons and the active
timer icon with the effective accent (preset override aware), and tint the
Settings form, About, Quick Mix, Sound, and Sound-Selection sheets once at
the container so accent-colored icons resolve from one stable environment.
Stop disabling preset-overridden Theme Defaults in Settings — the badge is
enough — and let the View Mode picker edit the app-wide default.

* refactor: remove unfinished Sharing Permissions section from Sound About

The permissions toggles were placeholders for an unimplemented sharing
feature. Remove the section, its unused state, and the orphaned
localization strings.

* refactor: remove solo-mode context menu and collapse DraggableSoundIcon

The long-press context menu lingered only on the large solo-mode icon; the
rest of the iOS app dropped it long ago. DraggableSoundIcon had a single
call site (the solo icon) where all its drag/drop/edit-mode/jiggle machinery
was inert, so replace it with a lean SoloSoundIcon and delete the now-dead
trio plus its exclusive SoundDropDelegate. Also remove the fully orphaned
SoundIconContextMenu and prune the six catalog strings those paths owned.

* Refactor UI components to unify button tinting and rename preset picker references

- Updated various sheets and views to apply a consistent primary tint color to buttons.
- Renamed `showingPresetPicker` to `showingLibrarySheet` across multiple files for clarity.
- Enhanced layout handling for iPad in `NowPlayingSheet` with a new `padNowPlaying` function.
- Improved `LibraryView` to manage dismiss behavior based on presentation type.
- Added accent color handling for library rows and indicators in `PresetPickerRow` and `SoloPickerRow`.
- Adjusted toolbar items in `MixerView` and `SettingsView` to use the primary tint.

* refactor: update cSpell words list by removing and reordering entries

* Remove Hide Inactive Sounds feature

Drop the toolbar/menu-bar toggle, both state stores (AppState and the
orphaned GlobalSettings copy), and the grid filter that hid unselected
sounds. AppDataMigrator now actively purges the stale hideInactiveSounds
UserDefaults key on launch instead of carrying it forward.

* refactor: replace string-based keyboard shortcuts with AppShortcut instances

* feat: comprehensive accessibility pass (VoiceOver, keyboard, Dynamic Type)

VoiceOver (#51, #52):
- Label every icon-only control; announce slider values and sound
  selection states; hide decorative artwork, rings, and separator glyphs
- Group sound credits, translations, and color pickers into named
  containers; make the Now Playing overlay modal so VoiceOver focus
  stays inside it
- Make the macOS sound grid keyboard-operable: Tab focuses tiles,
  Space/Return toggles, arrow keys adjust volume (no Full Keyboard
  Access required); volume remains a native slider stop so the rotor
  keeps working

Dynamic Type (#54):
- Convert fixed-point text fonts to semantic styles or @ScaledMetric
- Adapt layouts at accessibility sizes, enforce 44pt minimum touch
  targets, and expose grid tiles through the Large Content Viewer

Also:
- Rebuild macOS Preferences rows on native controls (Picker,
  LabeledContent, labeled Picker) so assistive tech handles them natively
- Convert Edit Preset sound rows to NavigationLink(value:)
- Delete ThemePickerSheet, unreachable since the AdaptiveContentView
  refactor, along with its dead MixerView wiring
- Record the release notes in CHANGELOG

* refactor: rework sound credits into inline sections, info popover, and editor

Replaces SoundAboutSheet/SoundDetailsSection/SoundSheetSoundInfo with
modular credits UI: SoundCreditInfoButton popover, SoundCreditsEditorView,
SoundDetailsRows, and SoundSheetCredits. Credits now resolve through a
cached CustomSoundData lookup instead of a per-render SwiftData fetch,
link rows get external-link indicators, and the popover is VoiceOver-ready.

* fix: update CarPlay thumbnail caching logic to regenerate or remove based on artwork changes

* feat: add Now Playing screen and Library features for iPhone and iPad; enhance audio normalization and fix audible pop in fireplace sound

* Add app identity header card to Settings

Replace the scattered About/beta/debug sections with a Drafts-style
card at the top: app icon, name, version, and a feedback link, with
the About, Show Onboarding (debug), and beta tester credits rows
grouped beneath it.

* feat: enhance visuals section with segmented picker for view mode and adjust list row separator for accent color

* feat: make Library the iPhone navigation root with Now Playing backdrop

- Library is now the stack root to the LEFT of the mixer; the app
  launches with the mixer pushed, so its back button leads to the Library
- Selecting a preset, Quick Mix, or solo sound navigates forward to the mixer
- Settings moves off the mixer toolbar into the Library's leading gear
  (still presented as a sheet, matching the iPad sidebar)
- Library page inherits Now Playing's blurred-artwork/accent backdrop,
  always-dark reading, and glassy material row cards

* feat: condense background blur to a single on/off setting

- Settings: replace None/Low/High picker with a Blur Background switch
  (on = the old Low radius, 7.5pt)
- Edit Preset: condense per-preset override to Default/Off/On segments,
  and merge the accent color toggle + spectrum picker into one list row
  (removes the separator hack and extra row padding)
- Migrate stored values: app-wide UserDefaults radius and saved preset
  overrides normalize to 7.5/0 on load
- Localization: add Blur Background/Off/On keys with needs_review
  backfill across all locales

* feat: replace bottom toolbar with Apple Music-style Now Playing bar

Add a liquid glass mini player (NowPlayingBar) pinned via safeAreaBar that
persists across Library and mixer on iPhone and iPad, with inline AirPlay
and a standalone play/pause circle. Now Playing becomes a real
fullScreenCover with a zoom transition from the bar, replacing the custom
zIndex overlay and its drag-dismiss machinery. Edit Preset/Quick Mix/Sound
moves to a top-trailing nav toolbar item, and the player's bottom actions
(close chevron, AirPlay, timer) get a liquid glass toolbar.

* refactor: remove timer management from MixerView and clean up related code

* feat: enhance TestFlight detection by integrating MarketplaceKit for install source verification

* fix: update sound icon colors to use AnyShapeStyle for better consistency

* feat: add drag-to-reorder to Quick Mix grid

Parameterize SoundGridView with a tile closure (default keeps the mixer
grid unchanged) and host Quick Mix tiles in it, so the long-press
reorder gesture already on GridSoundButton works there too. Drops
commit the new order to quickMixSoundFileNames, carrying over any
stored names that didn't resolve to a displayed sound.

* feat: refresh Quick Mix grid on membership or order changes

* feat: add press kit page with fact sheet, coverage, and downloadable assets

* feat: update app description and statistics for press kit and homepage

* refactor: polish Library toolbar and remove legacy CarPlay interface

- Settings gear moves to the leading edge in the iPad sidebar, matching iPhone
- "All Presets" section header renamed to "Presets" (Library + CarPlay)
- Delete unused CarPlayInterface/CarPlayStatusView; Now Playing reads
  AudioManager.isCarPlayConnected (fed by CarPlayAudioBridge) instead

* feat: prune stale strings and backfill missing translations for all locales

- Remove 17 stale keys with no remaining source references
- Backfill 575 missing entries across 12 locales, all marked needs_review
- Fix Control key spoken-name translations (wrong auto-comment had steered
  them to a color-control sense) and correct the comment
- Mark 5 keys identical across every locale as shouldTranslate=false
  (%@ %@, %lld, AirPlay, https://..., LUFS)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Cody Bromley <codybrom@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dizz7 <162482518+Dizz7@users.noreply.github.com>
Co-authored-by: Kristopheros <260881004+Kristopheros@users.noreply.github.com>
2026-06-04 09:38:45 -05:00

244 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage: prepare_loop.sh [input-file] [options]
If no input file is provided, processes all video files in Blankie/Assets/AnimatedArtwork
that don't already have .mov and .jpg versions.
Options:
-o, --output-dir DIR Directory for the generated files (default: same as input)
-n, --name NAME Base name for outputs (default: input file name without extension)
-w, --width PIXELS Output width in pixels (default: 1080)
-h, --height PIXELS Output height in pixels (default: 1440, 3:4 aspect ratio)
--crf VALUE Constant Rate Factor (default: 20)
--anchor POS Crop anchor: center, top, bottom, left, right,
bottom-center, top-center (default: center)
--help Show this help and exit
The script crops the input to a 3:4 portrait aspect ratio (for iPhone lock screens),
scales to the requested size, encodes to the chosen codec, and writes:
<NAME>.mov (loop video)
<NAME>.jpg (first-frame preview, 3:4 portrait)
<NAME>Square.jpg (first-frame preview, 1:1 square)
USAGE
}
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "❌ ffmpeg is required but not found in PATH" >&2
exit 1
fi
to_lower() {
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}
INPUT=""
OUTPUT_DIR=""
NAME=""
WIDTH=1080
HEIGHT=1440
CRF="20"
ANCHOR="center"
while [[ $# -gt 0 ]]; do
case "$1" in
-o|--output-dir)
OUTPUT_DIR="$2"; shift 2;;
-n|--name)
NAME="$2"; shift 2;;
-w|--width)
WIDTH="$2"; shift 2;;
-h|--height)
HEIGHT="$2"; shift 2;;
--crf)
CRF="$2"; shift 2;;
--anchor)
ANCHOR="$(to_lower "$2")"; shift 2;;
--help)
usage; exit 0;;
-*)
echo "Unknown option: $1" >&2
usage; exit 1;;
*)
if [[ -z "$INPUT" ]]; then
INPUT="$1"; shift
else
echo "Only one input file may be specified." >&2
usage; exit 1
fi;;
esac
done
# If no input provided, process all unprocessed files in AnimatedArtwork
if [[ -z "$INPUT" ]]; then
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROJECT_DIR=$(dirname "$SCRIPT_DIR")
ARTWORK_DIR="$PROJECT_DIR/Blankie/Assets/AnimatedArtwork"
if [[ ! -d "$ARTWORK_DIR" ]]; then
echo "❌ AnimatedArtwork directory not found: $ARTWORK_DIR" >&2
exit 1
fi
# Find all video files that don't have all three outputs (.mov, .jpg, and Square.jpg)
# Only use .mp4 files as source (skip .mov files - they are outputs)
FILES_TO_PROCESS=()
while IFS= read -r -d '' file; do
basename=$(basename "$file")
name="${basename%.*}"
ext="${basename##*.}"
# Only process .mp4 files (and other source formats), skip .mov files
if [[ "$ext" == "mov" ]]; then
continue
fi
# Check if all three outputs exist
if [[ ! -f "$ARTWORK_DIR/$name.mov" ]] || [[ ! -f "$ARTWORK_DIR/$name.jpg" ]] || [[ ! -f "$ARTWORK_DIR/${name}Square.jpg" ]]; then
FILES_TO_PROCESS+=("$file")
fi
done < <(find "$ARTWORK_DIR" -type f \( -name "*.mp4" -o -name "*.avi" -o -name "*.mkv" \) ! -name ".*" -print0 | sort -z)
if [[ ${#FILES_TO_PROCESS[@]} -eq 0 ]]; then
echo "✅ All files in $ARTWORK_DIR already have .mov, .jpg, and Square.jpg versions"
exit 0
fi
echo "🎬 Found ${#FILES_TO_PROCESS[@]} file(s) to process:"
for f in "${FILES_TO_PROCESS[@]}"; do
echo " - $(basename "$f")"
done
echo ""
# Process each file
for file in "${FILES_TO_PROCESS[@]}"; do
basename=$(basename "$file")
name="${basename%.*}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📹 Processing: $basename"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Recursively call this script with the file as input
"$0" "$file" -w "$WIDTH" -h "$HEIGHT" --crf "$CRF" --anchor "$ANCHOR"
echo ""
done
echo "🎉 Batch processing complete!"
exit 0
fi
if [[ ! -f "$INPUT" ]]; then
echo "❌ Input file not found: $INPUT" >&2
exit 1
fi
INPUT=$(realpath "$INPUT")
INPUT_DIR=$(dirname "$INPUT")
if [[ -z "$OUTPUT_DIR" ]]; then
OUTPUT_DIR="$INPUT_DIR"
else
mkdir -p "$OUTPUT_DIR"
fi
OUTPUT_DIR=$(realpath "$OUTPUT_DIR")
if [[ -z "$NAME" ]]; then
NAME=$(basename "$INPUT")
NAME="${NAME%.*}"
fi
# H.264 only for maximum compatibility
VCODEC="libx264"
TAG="avc1"
CODEC_PARAMS=(-x264-params "keyint=60:min-keyint=60:scenecut=0")
case "$ANCHOR" in
center|top|bottom|left|right|top-center|bottom-center) ;;
*)
echo "❌ Unsupported anchor: $ANCHOR (use center, top, bottom, left, right)" >&2
exit 1
;;
esac
# Calculate 3:4 crop dimensions (portrait)
# crop_w = min(iw, ih * 3/4)
# crop_h = crop_w * 4/3
CROP_W="min(iw\\,ih*3/4)"
CROP_H="(${CROP_W})*4/3"
# Default center positioning
base_x="((iw-${CROP_W})/2)"
base_y="((ih-${CROP_H})/2)"
anchor_x="$base_x"
anchor_y="$base_y"
case "$ANCHOR" in
left)
anchor_x="0" ;;
right)
anchor_x="(iw-${CROP_W})" ;;
top)
anchor_y="0" ;;
bottom)
anchor_y="(ih-${CROP_H})" ;;
top-center)
anchor_y="((ih-${CROP_H})/4)" ;;
bottom-center)
anchor_y="(3*(ih-${CROP_H})/4)" ;;
esac
LOOP_PATH="$OUTPUT_DIR/${NAME}.mov"
PREVIEW_PATH="$OUTPUT_DIR/${NAME}.jpg"
SQUARE_PREVIEW_PATH="$OUTPUT_DIR/${NAME}Square.jpg"
FILTER="crop=${CROP_W}:${CROP_H}:${anchor_x}:${anchor_y},scale=${WIDTH}:${HEIGHT},setsar=1"
echo "🎞️ Encoding loop → $LOOP_PATH"
ffmpeg -hide_banner -y \
-i "$INPUT" \
-vf "$FILTER" \
-an \
-c:v "$VCODEC" \
-preset medium \
-crf "$CRF" \
"${CODEC_PARAMS[@]}" \
-pix_fmt yuv420p \
-tag:v "$TAG" \
-movflags +faststart \
"$LOOP_PATH"
echo "🖼️ Extracting preview → $PREVIEW_PATH"
ffmpeg -hide_banner -y \
-i "$LOOP_PATH" \
-vf "select=eq(n\\,0)" \
-frames:v 1 \
-update 1 \
"$PREVIEW_PATH"
# Calculate square crop dimensions (1:1 aspect ratio)
SQUARE_SIZE="min(iw\\,ih)"
SQUARE_X="((iw-${SQUARE_SIZE})/2)"
SQUARE_Y="((ih-${SQUARE_SIZE})/2)"
SQUARE_FILTER="crop=${SQUARE_SIZE}:${SQUARE_SIZE}:${SQUARE_X}:${SQUARE_Y},scale=${WIDTH}:${WIDTH},setsar=1"
echo "🔲 Extracting square preview → $SQUARE_PREVIEW_PATH"
ffmpeg -hide_banner -y \
-i "$INPUT" \
-vf "${SQUARE_FILTER},select=eq(n\\,0)" \
-frames:v 1 \
-update 1 \
"$SQUARE_PREVIEW_PATH"
echo "✅ Done. Generated:"
echo " $LOOP_PATH"
echo " $PREVIEW_PATH"
echo " $SQUARE_PREVIEW_PATH"