Skip to content

Commit fd1375c

Browse files
committed
Release v1.0.8
- Smart Transcoding: auto-switches bitrate based on WiFi/mobile (connectivity_plus) - Dynamic accent color: Material You on Android 12+, custom picker everywhere else - iOS Control Center: fixed player widget (restored prev/next buttons, media type metadata, artwork cache race fix) - Loading screen: spinner shown during server check instead of login flash - Server retry: up to 3 ping attempts on launch + Retry button on error screen - Desktop home layout: wider padding, larger cards/fonts, table-style song rows - Settings: localized all hardcoded strings (~100 new ARB keys) - Removed all Dart source comments - Bump version to 1.0.8
1 parent 65d5229 commit fd1375c

141 files changed

Lines changed: 32948 additions & 27863 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,46 @@ All notable changes to Musly will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.8] - 2026-03-08
9+
10+
### Added
11+
- **Smart Transcoding**: New automatic quality mode that switches bitrate in real time based on active network
12+
- Detects WiFi vs mobile data via `connectivity_plus`
13+
- Configure separate bitrates for WiFi and mobile; the app picks the right one automatically
14+
- Live connection badge (WiFi / Mobile pill) in Settings → Playback while smart mode is active
15+
- Smart mode toggle persists across restarts
16+
- **Dynamic & Custom Accent Colors**: The accent color now propagates everywhere in the app
17+
- On Android 12+ the wallpaper-derived Material You palette is used automatically (via `dynamic_color`)
18+
- On all other platforms any color picked in Settings → Display is applied to every widget
19+
- Eliminated all hardcoded `AppTheme.appleMusicRed` references in settings tabs, mini player, song tiles, cast button, and album artwork shadow
20+
- **iOS Control Center player**: Fixed the player widget shown in the iOS Control Center and Lock Screen
21+
- Disabled the podcast-style ±15 s skip buttons that were hiding the standard ⏮ ▶/⏸ ⏭ controls
22+
- Added `MPNowPlayingInfoPropertyMediaType = .audio` and `MPNowPlayingInfoPropertyDefaultPlaybackRate` for correct system content categorization
23+
- Fixed an artwork caching race condition: concurrent 1-second position updates no longer restart artwork downloads already in progress
24+
- **Server connection retry**: `AuthProvider._verifyConnection()` now retries the ping up to 3 times (2 s backoff) before declaring the server unreachable — handles slow mobile network initialization on launch
25+
- **Retry button on the server-unreachable screen**: A "Retry" button lets users re-attempt the connection without restarting the app (`AuthProvider.retryConnection()`)
26+
- **Localization — Settings strings**: All hardcoded strings in the five Settings tabs are now in `app_en.arb` (~100 new keys covering Playback, Storage, About, Display, and Server sections)
27+
28+
### Changed
29+
- **Loading screen**: The app no longer flashes the login screen while checking the server on startup; `AuthState.authenticating` now shows a centered `CircularProgressIndicator` on a black background
30+
- **Home screen desktop layout**: Improved density and alignment for macOS/Windows/Linux
31+
- Wider horizontal padding (32 px), larger section headers and album cards (180 px)
32+
- Song lists render as a compact table (`_DesktopSongRow`) instead of full `SongTile` cards
33+
- Recent albums (6) and playlists (3) shown instead of 4 and 2
34+
- **Error messages**: Improved error string formatting in `AuthProvider._formatError()` — strips `Exception:`, `Network error:`, and verbose library boilerplate for cleaner display
35+
- **Connection timeout**: Server ping timeout increased from 6 s to 10 s
36+
- **Now Playing screen**: Matrix transforms updated to Flutter 3.41-compatible `scaleByDouble`/`translateByDouble` signatures; `.withOpacity()` replaced with `.withValues(alpha:)` throughout
37+
38+
### Fixed
39+
- **`seekForward`/`seekBackward` events from iOS Control Center**: Added `onSeekForward`/`onSeekBackward` callbacks to `AndroidSystemService` and registered handlers in `PlayerProvider` (clamping backward seeks to `Duration.zero`)
40+
- **Settings tab indicator color hardcoded**: `indicatorColor` and `labelColor` in `settings_screen.dart` now use `Theme.of(context).colorScheme.primary`
41+
- **`DjMixerService` removed**: Deleted unused `dj_mixer_service.dart` that was included by mistake; `services.dart` barrel still intact
42+
43+
### Dependencies
44+
- Added `connectivity_plus: ^7.0.0` — network type detection for Smart Transcoding
45+
- Added `dynamic_color: ^1.7.0` — Material You wallpaper color extraction on Android 12+
46+
- Added `path: ^1.9.0`
47+
848
## [1.0.7] - 2026-02-22
949

1050
### Added

installer.nsi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
; Version (passed via /DMUSLY_VERSION=x.x.x from CI, or fallback)
55

66
!ifndef MUSLY_VERSION
7-
!define MUSLY_VERSION "1.0.7"
7+
!define MUSLY_VERSION "1.0.8"
88
!endif
99

1010
;--------------------------------
@@ -106,7 +106,7 @@ Section "Musly" SecMain
106106
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Musly" \
107107
"Publisher" "dddevid"
108108
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Musly" \
109-
"DisplayVersion" "1.0.7"
109+
"DisplayVersion" "1.0.8"
110110
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Musly" \
111111
"URLInfoAbout" "https://github.com/dddevid/Musly"
112112

ios/Podfile.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ PODS:
66
- Flutter
77
- battery_plus (1.0.0):
88
- Flutter
9+
- connectivity_plus (0.0.1):
10+
- Flutter
911
- device_info_plus (0.0.1):
1012
- Flutter
1113
- DKImagePickerController/Core (4.3.9):
@@ -76,6 +78,7 @@ DEPENDENCIES:
7678
- audio_service (from `.symlinks/plugins/audio_service/darwin`)
7779
- audio_session (from `.symlinks/plugins/audio_session/ios`)
7880
- battery_plus (from `.symlinks/plugins/battery_plus/ios`)
81+
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
7982
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
8083
- file_picker (from `.symlinks/plugins/file_picker/ios`)
8184
- Flutter (from `Flutter`)
@@ -104,6 +107,8 @@ EXTERNAL SOURCES:
104107
:path: ".symlinks/plugins/audio_session/ios"
105108
battery_plus:
106109
:path: ".symlinks/plugins/battery_plus/ios"
110+
connectivity_plus:
111+
:path: ".symlinks/plugins/connectivity_plus/ios"
107112
device_info_plus:
108113
:path: ".symlinks/plugins/device_info_plus/ios"
109114
file_picker:
@@ -131,6 +136,7 @@ SPEC CHECKSUMS:
131136
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
132137
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
133138
battery_plus: ae71a65eceb5680dfc7a1d926f6cde59a1b219d0
139+
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
134140
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
135141
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
136142
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60

ios/Runner/iOSSystemPlugin.swift

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -184,25 +184,11 @@ public class iOSSystemPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
184184
return .success
185185
}
186186

187-
commandCenter.skipForwardCommand.isEnabled = true
188-
commandCenter.skipForwardCommand.preferredIntervals = [15]
189-
commandCenter.skipForwardCommand.addTarget { [weak self] event in
190-
if let event = event as? MPSkipIntervalCommandEvent {
191-
let interval = Int(event.interval * 1000)
192-
self?.sendEvent("seekForward", data: ["interval": interval])
193-
}
194-
return .success
195-
}
196-
197-
commandCenter.skipBackwardCommand.isEnabled = true
198-
commandCenter.skipBackwardCommand.preferredIntervals = [15]
199-
commandCenter.skipBackwardCommand.addTarget { [weak self] event in
200-
if let event = event as? MPSkipIntervalCommandEvent {
201-
let interval = Int(event.interval * 1000)
202-
self?.sendEvent("seekBackward", data: ["interval": interval])
203-
}
204-
return .success
205-
}
187+
// Skip forward/backward (podcast-style ±15s) are intentionally disabled
188+
// for this music player so that the Control Center compact widget shows the
189+
// standard prev-track / play-pause / next-track buttons instead.
190+
commandCenter.skipForwardCommand.isEnabled = false
191+
commandCenter.skipBackwardCommand.isEnabled = false
206192

207193
print("Remote command center configured")
208194
}
@@ -306,6 +292,8 @@ public class iOSSystemPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
306292
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = Double(duration) / 1000.0
307293
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = Double(position) / 1000.0
308294
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = playing ? 1.0 : 0.0
295+
nowPlayingInfo[MPNowPlayingInfoPropertyDefaultPlaybackRate] = 1.0
296+
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = MPNowPlayingInfoMediaType.audio.rawValue
309297

310298
if let trackNumber = trackNumber {
311299
nowPlayingInfo[MPMediaItemPropertyAlbumTrackNumber] = trackNumber
@@ -320,6 +308,10 @@ public class iOSSystemPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
320308
// Same URL: reuse cached artwork — avoids re-fetching on every 1-second position tick
321309
nowPlayingInfo[MPMediaItemPropertyArtwork] = cached
322310
nowPlayingCenter.nowPlayingInfo = nowPlayingInfo
311+
} else if artworkUrl == lastLoadedArtworkUrl {
312+
// Same URL but artwork is still loading asynchronously — update text
313+
// metadata now; the completion handler will add the artwork when ready.
314+
nowPlayingCenter.nowPlayingInfo = nowPlayingInfo
323315
} else {
324316
// New artwork URL: set text metadata immediately, then load artwork async
325317
lastLoadedArtworkUrl = artworkUrl

lib/l10n/app_en.arb

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1463,5 +1463,212 @@
14631463
"@openOfflineMode": { "description": "Button to enter offline mode from the server-unreachable screen" },
14641464

14651465
"disconnect": "Disconnect",
1466-
"@disconnect": { "description": "Button to clear server credentials and go back to login" }
1466+
"@disconnect": { "description": "Button to clear server credentials and go back to login" },
1467+
1468+
"retry": "Retry",
1469+
"@retry": { "description": "Button to retry a failed server connection" },
1470+
1471+
"appearanceSection": "Appearance",
1472+
"@appearanceSection": { "description": "Display settings section header for theme / appearance" },
1473+
1474+
"themeLabel": "Theme",
1475+
"@themeLabel": { "description": "Label for the theme mode selector (System / Light / Dark)" },
1476+
1477+
"accentColorLabel": "Accent color",
1478+
"@accentColorLabel": { "description": "Label for the accent color picker" },
1479+
1480+
"circularDesignLabel": "Circular Design",
1481+
"@circularDesignLabel": { "description": "Label for the Circular Design (glass-blur UI) toggle" },
1482+
1483+
"circularDesignSubtitle": "Floating, rounded UI with translucent panels and glass-blur effect on the player and navigation bar.",
1484+
"@circularDesignSubtitle": { "description": "Subtitle describing the Circular Design visual style" },
1485+
1486+
"themeModeSystem": "System",
1487+
"@themeModeSystem": { "description": "Theme mode option that follows the OS setting" },
1488+
1489+
"themeModeLight": "Light",
1490+
"@themeModeLight": { "description": "Light theme mode option" },
1491+
1492+
"themeModeDark": "Dark",
1493+
"@themeModeDark": { "description": "Dark theme mode option" },
1494+
1495+
"internetRadio": "Internet Radio",
1496+
"@internetRadio": { "description": "Subtitle shown in the mini player and player bar when a radio station is playing" },
1497+
1498+
"liveLabel": "LIVE",
1499+
"@liveLabel": { "description": "Badge shown next to a live radio stream" },
1500+
1501+
"discordStatusText": "Discord status text",
1502+
"@discordStatusText": { "description": "Settings label for the Discord Rich Presence second-line style" },
1503+
1504+
"discordStatusTextSubtitle": "Second line shown in Discord activity",
1505+
"@discordStatusTextSubtitle": { "description": "Subtitle for the Discord status text setting" },
1506+
1507+
"discordRpcStyleArtist": "Artist name",
1508+
"@discordRpcStyleArtist": { "description": "Discord RPC state style option: show artist name" },
1509+
1510+
"discordRpcStyleSong": "Song title",
1511+
"@discordRpcStyleSong": { "description": "Discord RPC state style option: show song title" },
1512+
1513+
"discordRpcStyleApp": "App name (Musly)",
1514+
"@discordRpcStyleApp": { "description": "Discord RPC state style option: show app name" },
1515+
1516+
"sectionAutoDj": "AUTO DJ",
1517+
"@sectionAutoDj": { "description": "Playback settings section header for Auto DJ" },
1518+
1519+
"sectionVolumeNormalization": "VOLUME NORMALIZATION (REPLAYGAIN)",
1520+
"@sectionVolumeNormalization": { "description": "Playback settings section header for ReplayGain" },
1521+
1522+
"sectionStreamingQuality": "STREAMING QUALITY",
1523+
"@sectionStreamingQuality": { "description": "Playback settings section header for transcoding / streaming quality" },
1524+
1525+
"replayGainMode": "Mode",
1526+
"@replayGainMode": { "description": "Label for the ReplayGain mode selector" },
1527+
1528+
"replayGainModeOff": "Off",
1529+
"@replayGainModeOff": { "description": "ReplayGain mode: disabled" },
1530+
1531+
"replayGainModeTrack": "Track",
1532+
"@replayGainModeTrack": { "description": "ReplayGain mode: per-track normalization" },
1533+
1534+
"replayGainModeAlbum": "Album",
1535+
"@replayGainModeAlbum": { "description": "ReplayGain mode: album-level normalization" },
1536+
1537+
"replayGainPreamp": "Preamp: {value} dB",
1538+
"@replayGainPreamp": {
1539+
"description": "ReplayGain preamp slider label",
1540+
"placeholders": { "value": { "type": "String" } }
1541+
},
1542+
1543+
"replayGainPreventClipping": "Prevent Clipping",
1544+
"@replayGainPreventClipping": { "description": "Toggle label for ReplayGain prevent-clipping option" },
1545+
1546+
"replayGainFallbackGain": "Fallback Gain: {value} dB",
1547+
"@replayGainFallbackGain": {
1548+
"description": "ReplayGain fallback gain slider label",
1549+
"placeholders": { "value": { "type": "String" } }
1550+
},
1551+
1552+
"autoDjSongsToAdd": "Songs to Add: {count}",
1553+
"@autoDjSongsToAdd": {
1554+
"description": "Auto DJ slider label showing how many songs to add",
1555+
"placeholders": { "count": { "type": "int" } }
1556+
},
1557+
1558+
"transcodingEnable": "Enable Transcoding",
1559+
"@transcodingEnable": { "description": "Toggle label to enable transcoding" },
1560+
1561+
"transcodingEnableSubtitle": "Reduce data usage with lower quality",
1562+
"@transcodingEnableSubtitle": { "description": "Subtitle for the enable transcoding toggle" },
1563+
1564+
"smartTranscoding": "Smart Transcoding",
1565+
"@smartTranscoding": { "description": "Toggle label for smart (auto) transcoding mode" },
1566+
1567+
"smartTranscodingSubtitle": "Automatically adjusts quality based on your connection (WiFi vs mobile data)",
1568+
"@smartTranscodingSubtitle": { "description": "Subtitle for the smart transcoding toggle" },
1569+
1570+
"smartTranscodingDetectedNetwork": "Detected network: ",
1571+
"@smartTranscodingDetectedNetwork": { "description": "Label shown before the live network type badge" },
1572+
1573+
"smartTranscodingActiveBitrate": "Active bitrate: {bitrate}",
1574+
"@smartTranscodingActiveBitrate": {
1575+
"description": "Shows the currently active transcoding bitrate",
1576+
"placeholders": { "bitrate": { "type": "String" } }
1577+
},
1578+
1579+
"transcodingWifiQuality": "WiFi Quality",
1580+
"@transcodingWifiQuality": { "description": "Label for WiFi bitrate selector" },
1581+
1582+
"transcodingWifiQualitySubtitleSmart": "Used automatically on WiFi",
1583+
"@transcodingWifiQualitySubtitleSmart": { "description": "WiFi quality subtitle when smart mode is on" },
1584+
1585+
"transcodingWifiQualitySubtitle": "Bitrate when on WiFi",
1586+
"@transcodingWifiQualitySubtitle": { "description": "WiFi quality subtitle when smart mode is off" },
1587+
1588+
"transcodingMobileQuality": "Mobile Quality",
1589+
"@transcodingMobileQuality": { "description": "Label for mobile data bitrate selector" },
1590+
1591+
"transcodingMobileQualitySubtitleSmart": "Used automatically on cellular data",
1592+
"@transcodingMobileQualitySubtitleSmart": { "description": "Mobile quality subtitle when smart mode is on" },
1593+
1594+
"transcodingMobileQualitySubtitle": "Bitrate when on mobile data",
1595+
"@transcodingMobileQualitySubtitle": { "description": "Mobile quality subtitle when smart mode is off" },
1596+
1597+
"transcodingFormat": "Format",
1598+
"@transcodingFormat": { "description": "Label for the transcoding format selector" },
1599+
1600+
"transcodingFormatSubtitle": "Audio codec used for streaming",
1601+
"@transcodingFormatSubtitle": { "description": "Subtitle for the transcoding format selector" },
1602+
1603+
"transcodingBitrateOriginal": "Original (No Transcoding)",
1604+
"@transcodingBitrateOriginal": { "description": "Transcoding bitrate option: no transcoding, use original" },
1605+
1606+
"transcodingFormatOriginal": "Original",
1607+
"@transcodingFormatOriginal": { "description": "Transcoding format option: original (no conversion)" },
1608+
1609+
"sectionCacheSettings": "CACHE SETTINGS",
1610+
"@sectionCacheSettings": { "description": "Storage settings section header" },
1611+
1612+
"sectionCacheCleanup": "CACHE CLEANUP",
1613+
"@sectionCacheCleanup": { "description": "Storage settings section header for cache cleanup" },
1614+
1615+
"sectionOfflineDownloads": "OFFLINE DOWNLOADS",
1616+
"@sectionOfflineDownloads": { "description": "Storage settings section header for offline downloads" },
1617+
1618+
"sectionBpmAnalysis": "BPM ANALYSIS",
1619+
"@sectionBpmAnalysis": { "description": "Storage settings section header for BPM analysis" },
1620+
1621+
"imageCacheTitle": "Image Cache",
1622+
"@imageCacheTitle": { "description": "Toggle title for image (album art) cache" },
1623+
1624+
"imageCacheSubtitle": "Save album covers locally",
1625+
"@imageCacheSubtitle": { "description": "Subtitle for image cache toggle" },
1626+
1627+
"musicCacheTitle": "Music Cache",
1628+
"@musicCacheTitle": { "description": "Toggle title for music metadata cache" },
1629+
1630+
"musicCacheSubtitle": "Save song metadata locally",
1631+
"@musicCacheSubtitle": { "description": "Subtitle for music cache toggle" },
1632+
1633+
"bpmCacheTitle": "BPM Cache",
1634+
"@bpmCacheTitle": { "description": "Toggle title for BPM analysis cache" },
1635+
1636+
"bpmCacheSubtitle": "Save BPM analysis locally",
1637+
"@bpmCacheSubtitle": { "description": "Subtitle for BPM cache toggle" },
1638+
1639+
"clearAllCache": "Clear All Cache",
1640+
"@clearAllCache": { "description": "Button to clear all cached data" },
1641+
1642+
"sectionAboutInformation": "INFORMATION",
1643+
"@sectionAboutInformation": { "description": "About screen section header" },
1644+
1645+
"sectionAboutDeveloper": "DEVELOPER",
1646+
"@sectionAboutDeveloper": { "description": "About screen developer section header" },
1647+
1648+
"sectionAboutLinks": "LINKS",
1649+
"@sectionAboutLinks": { "description": "About screen links section header" },
1650+
1651+
"aboutVersion": "Version",
1652+
"@aboutVersion": { "description": "About screen version row title" },
1653+
1654+
"aboutPlatform": "Platform",
1655+
"@aboutPlatform": { "description": "About screen platform row title" },
1656+
1657+
"aboutMadeBy": "Made by dddevid",
1658+
"@aboutMadeBy": { "description": "Developer credit text in the about tab" },
1659+
1660+
"aboutGitHub": "github.com/dddevid",
1661+
"@aboutGitHub": { "description": "Developer GitHub handle shown as subtitle" },
1662+
1663+
"aboutLinkGitHub": "GitHub Repository",
1664+
"@aboutLinkGitHub": { "description": "Link tile title for the GitHub repo" },
1665+
1666+
"aboutLinkChangelog": "Changelog",
1667+
"@aboutLinkChangelog": { "description": "Link tile title for the app changelog" },
1668+
1669+
"aboutLinkReportIssue": "Report Issue",
1670+
"@aboutLinkReportIssue": { "description": "Link tile title for reporting a bug" },
1671+
1672+
"aboutLinkDiscord": "Join Discord Community",
1673+
"@aboutLinkDiscord": { "description": "Link tile title for the Discord server" }
14671674
}

0 commit comments

Comments
 (0)