Compare commits
10 Commits
09e31cafa9
...
f611e719db
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f611e719db | ||
![]() |
4ed40d95b2 | ||
![]() |
b24dcd2951 | ||
![]() |
8ff90bafd7 | ||
![]() |
9190af92ef | ||
![]() |
151a440e7e | ||
![]() |
9471e008e3 | ||
![]() |
ecc0bdcc42 | ||
![]() |
49c04af302 | ||
![]() |
afe0bea306 |
63
CHANGELOG.md
@ -1,5 +1,68 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [5.0.0](https://github.com/KRTirtho/spotube/compare/v4.0.2...v5.0.0) (2025-09-08)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Add ISRC track search for YouTube ([#2594](https://github.com/KRTirtho/spotube/issues/2594))
|
||||||
|
- Add new icons #2676 by @alexio-dev ([#2678](https://github.com/KRTirtho/spotube/issues/2678))
|
||||||
|
- Add connect confirmation dialog
|
||||||
|
- Add metadata api service and models
|
||||||
|
- **metadata-plugin**: Add pagination support, feed and playlist CRUD endpoints
|
||||||
|
- **metadata-plugin**: Add local storage api
|
||||||
|
- Add webview, totp and setInterval apis for plugins
|
||||||
|
- Enhance local storage and webview APIs with improved error handling and resource management
|
||||||
|
- **metadata_plugin**: Add logout method
|
||||||
|
- Update plugin configuration with more fields
|
||||||
|
- Implement metadata plugins based on hetu
|
||||||
|
- Update models to match hetu_spotube_plugin signature
|
||||||
|
- Add user endpoint calls in metadata and paginated async notifiers
|
||||||
|
- Add playlist endpoint and providers
|
||||||
|
- Add albums metadata endpoint and provider
|
||||||
|
- Add artist and album providers
|
||||||
|
- Add track endpoint for metadata service
|
||||||
|
- Remove green corp names formally
|
||||||
|
- **metadata**: Add plugin form
|
||||||
|
- Add support for entity specific search
|
||||||
|
- Enhance image handling
|
||||||
|
- Add support for automatic plugin repository from github and codeberg
|
||||||
|
- Use isolate for youtube_explode engine
|
||||||
|
- Add repository and plugin API version fields to metadata plugins
|
||||||
|
- Update new pipe version
|
||||||
|
- **metadata**: Add plugin update checker and dialog for available updates
|
||||||
|
- Optimize track options and related artists
|
||||||
|
- Add plugin scrobbling support and support button
|
||||||
|
- Add ErrorBox and NoDefaultMetadataPlugin components
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Calling /track/:streamId endpoint causes active sourced track to be anything
|
||||||
|
- **mobile**: Dialogs in bottom sheet are not opening
|
||||||
|
- Default accent color is orange but it shows blue in settings
|
||||||
|
- Artist images are not loading up
|
||||||
|
- CVE: Remote path traversal through websocket when devices are on same network
|
||||||
|
- Endless playback not working
|
||||||
|
- **android**: NewPipe invalid search content filters
|
||||||
|
- Make YoutubeExplode engine faster
|
||||||
|
- Create and delete playlist not working
|
||||||
|
- Local track not working and images of local not showing up
|
||||||
|
- Local playback not working for tracks with special # (hashtag) characters
|
||||||
|
- Inaccessible streaming url causing rapid skips
|
||||||
|
- **yt**: Fallback to different search result if all streaming url is inaccessible
|
||||||
|
- **playback**: Skip network requests if cached file already exists
|
||||||
|
- Yt-dlp playback not working and add partial support for HLS streaming
|
||||||
|
- Windows webview2 environment permission issue
|
||||||
|
- **playback**: Play not fetching full playlist if playlist is too long
|
||||||
|
- **track_options**: Tapping on option doesn't close the menu
|
||||||
|
- **playback**: Alternative track sources switch not working
|
||||||
|
- **ui**: Lyrics white text in white background and small player buttons
|
||||||
|
|
||||||
|
### Translation
|
||||||
|
|
||||||
|
- Add Traditional Chinese translation ([#2762](https://github.com/KRTirtho/spotube/issues/2762))
|
||||||
|
- Fix Japanese translations ([#2732](https://github.com/KRTirtho/spotube/issues/2732))
|
||||||
|
- Correction of the dutch language ([#1306](https://github.com/KRTirtho/spotube/issues/1306))
|
||||||
|
|
||||||
## [4.0.2](https://github.com/krtirtho/spotube/compare/v4.0.1...v4.0.2) (2025-03-16)
|
## [4.0.2](https://github.com/krtirtho/spotube/compare/v4.0.1...v4.0.2) (2025-03-16)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
@ -18,6 +18,10 @@ Btw it's not just another Electron app 😉
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 🌃 Features
|
## 🌃 Features
|
||||||
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 38 KiB |
BIN
assets/branding/mobile-screenshots/combined.jpg
Normal file
After Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 790 KiB |
Before Width: | Height: | Size: 1006 KiB After Width: | Height: | Size: 771 KiB |
@ -61,7 +61,7 @@ protect_breaking_commits = false
|
|||||||
commit_parsers = [
|
commit_parsers = [
|
||||||
{ message = "^feat", group = "<!-- 0 -->Features" },
|
{ message = "^feat", group = "<!-- 0 -->Features" },
|
||||||
{ message = "^fix", group = "<!-- 1 -->Bug Fixes" },
|
{ message = "^fix", group = "<!-- 1 -->Bug Fixes" },
|
||||||
# { message = "^doc", group = "<!-- 3 -->📚 Documentation" },
|
{ message = "^translation", group = "<!-- 3 --> Translation" },
|
||||||
# { message = "^perf", group = "<!-- 4 -->⚡ Performance" },
|
# { message = "^perf", group = "<!-- 4 -->⚡ Performance" },
|
||||||
# { message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
# { message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
|
||||||
# { message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
# { message = "^style", group = "<!-- 5 -->🎨 Styling" },
|
||||||
|
@ -13,7 +13,7 @@ class BackButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IconButton.ghost(
|
return IconButton.ghost(
|
||||||
size: const ButtonSize(.9),
|
size: const ButtonSize(1.2),
|
||||||
icon: Icon(icon, color: color),
|
icon: Icon(icon, color: color),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
);
|
);
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
"sort_z_a": "Sorteren op Z-A",
|
"sort_z_a": "Sorteren op Z-A",
|
||||||
"sort_artist": "Sorteren op artiest",
|
"sort_artist": "Sorteren op artiest",
|
||||||
"sort_album": "Sorteren op album",
|
"sort_album": "Sorteren op album",
|
||||||
"sort_duration": "Sorteren op lengte",
|
"sort_duration": "Sorteren op lengte",
|
||||||
"sort_tracks": "Nummers sorteren",
|
"sort_tracks": "Nummers sorteren",
|
||||||
"currently_downloading": "Momenteel aan het downloaden ({tracks_length})",
|
"currently_downloading": "Momenteel aan het downloaden ({tracks_length})",
|
||||||
"cancel_all": "Alles annuleren",
|
"cancel_all": "Alles annuleren",
|
||||||
@ -287,7 +287,7 @@
|
|||||||
"genres": "Genres",
|
"genres": "Genres",
|
||||||
"explore_genres": "Genres verkennen",
|
"explore_genres": "Genres verkennen",
|
||||||
"friends": "Vrienden",
|
"friends": "Vrienden",
|
||||||
"no_lyrics_available": "Sorry, geen teksten gevonden voor dit nummer"
|
"no_lyrics_available": "Sorry, geen teksten gevonden voor dit nummer",
|
||||||
"start_a_radio": "Een radio starten",
|
"start_a_radio": "Een radio starten",
|
||||||
"how_to_start_radio": "Hoe wil je de radio starten?",
|
"how_to_start_radio": "Hoe wil je de radio starten?",
|
||||||
"replace_queue_question": "Wil je de huidige wachtrij vervangen of eraan toevoegen?",
|
"replace_queue_question": "Wil je de huidige wachtrij vervangen of eraan toevoegen?",
|
||||||
|
@ -39,7 +39,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get featured => 'Aanbevolen';
|
String get featured => 'Aanbevolen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get new_releases => 'Nieuwe uitgaves';
|
String get new_releases => 'Nieuwe uitgaven';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get songs => 'Liedjes';
|
String get songs => 'Liedjes';
|
||||||
@ -139,7 +139,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get sort_album => 'Sorteren op album';
|
String get sort_album => 'Sorteren op album';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sort_duration => 'Sorteer op Duur';
|
String get sort_duration => 'Sorteren op lengte';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sort_tracks => 'Nummers sorteren';
|
String get sort_tracks => 'Nummers sorteren';
|
||||||
@ -150,7 +150,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get cancel_all => 'Alle annuleren';
|
String get cancel_all => 'Alles annuleren';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get filter_artist => 'Artiesten filteren…';
|
String get filter_artist => 'Artiesten filteren…';
|
||||||
@ -164,10 +164,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get add_artist_to_blacklist => 'Artiest toevoegen aan zwarte lijst';
|
String get add_artist_to_blacklist => 'Artiest toevoegen aan zwarte lijst';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get top_tracks => 'Topsporen';
|
String get top_tracks => 'Topnummers';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fans_also_like => 'Liefhebbers willen ook';
|
String get fans_also_like => 'Fans luisteren ook';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loading => 'Laden…';
|
String get loading => 'Laden…';
|
||||||
@ -312,10 +312,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get slide_to_seek => 'Schuiven om vooruit of achteruit te zoeken';
|
String get slide_to_seek => 'Schuiven om vooruit of achteruit te zoeken';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get shuffle_playlist => 'Afspeellijst schuifelen';
|
String get shuffle_playlist => 'Afspeellijst willekeurig';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get unshuffle_playlist => 'Afspeellijst onschuifelen';
|
String get unshuffle_playlist => 'Afspeellijst op volgorde';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get previous_track => 'Vorige nummer';
|
String get previous_track => 'Vorige nummer';
|
||||||
@ -342,7 +342,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get queue => 'Wachtrij';
|
String get queue => 'Wachtrij';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get alternative_track_sources => 'Alternatieve nummerbronnen';
|
String get alternative_track_sources => 'Alternatieve bronnen voor nummers';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get download_track => 'Nummer downloaden';
|
String get download_track => 'Nummer downloaden';
|
||||||
@ -776,10 +776,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get stop => 'Stoppen';
|
String get stop => 'Stoppen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sort_newest => 'Sorteren op nieuwste toegevoegd';
|
String get sort_newest => 'Sorteren op recent toegevoegd';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sort_oldest => 'Sorteren op oudste toegevoegd';
|
String get sort_oldest => 'Sorteren op langst toegevoegd';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get sleep_timer => 'Slaaptimer';
|
String get sleep_timer => 'Slaaptimer';
|
||||||
@ -815,7 +815,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get search_mode => 'Zoekmodus';
|
String get search_mode => 'Zoekmodus';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get audio_source => 'Audiobron';
|
String get audio_source => 'Audio Source';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get ok => 'Oké';
|
String get ok => 'Oké';
|
||||||
@ -927,57 +927,56 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
'Sorry, geen teksten gevonden voor dit nummer';
|
'Sorry, geen teksten gevonden voor dit nummer';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get start_a_radio => 'Start een Radio';
|
String get start_a_radio => 'Een radio starten';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get how_to_start_radio => 'Hoe wilt u de radio starten?';
|
String get how_to_start_radio => 'Hoe wil je de radio starten?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get replace_queue_question =>
|
String get replace_queue_question =>
|
||||||
'Wilt u de huidige wachtrij vervangen of eraan toevoegen?';
|
'Wil je de huidige wachtrij vervangen of eraan toevoegen?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get endless_playback => 'Eindeloze Afspelen';
|
String get endless_playback => 'Oneindig afspelen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete_playlist => 'Verwijder Afspeellijst';
|
String get delete_playlist => 'Afspeellijst verwijderen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get delete_playlist_confirmation =>
|
String get delete_playlist_confirmation =>
|
||||||
'Weet u zeker dat u deze afspeellijst wilt verwijderen?';
|
'Weet je zeker dat je deze afspeellijst wilt verwijderen?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get local_tracks => 'Lokale Nummers';
|
String get local_tracks => 'Lokale nummers';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get local_tab => 'Lokaal';
|
String get local_tab => 'Lokaal';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get song_link => 'Nummer Link';
|
String get song_link => 'Song-link';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get skip_this_nonsense => 'Sla deze onzin over';
|
String get skip_this_nonsense => 'Deze onzin overslaan';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get freedom_of_music => '“Vrijheid van Muziek”';
|
String get freedom_of_music => '“Vrijheid van muziek”';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get freedom_of_music_palm =>
|
String get freedom_of_music_palm => '“Vrijheid van muziek in je hand”';
|
||||||
'“Vrijheid van Muziek in de palm van je hand”';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get get_started => 'Laten we beginnen';
|
String get get_started => 'Laten we beginnen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get youtube_source_description => 'Aanbevolen en werkt het beste.';
|
String get youtube_source_description => 'Aangeraden en werkt het best.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get piped_source_description =>
|
String get piped_source_description =>
|
||||||
'Voel je vrij? Hetzelfde als YouTube maar veel gratis.';
|
'Voel je je vrij? Net als YouTube, maar meer vrij.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get jiosaavn_source_description =>
|
String get jiosaavn_source_description =>
|
||||||
'Het beste voor de Zuid-Aziatische regio.';
|
'Het beste voor de regio Zuid-Azië.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get invidious_source_description =>
|
String get invidious_source_description =>
|
||||||
@ -985,41 +984,41 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String highest_quality(Object quality) {
|
String highest_quality(Object quality) {
|
||||||
return 'Hoogste Kwaliteit: $quality';
|
return 'Hoogste kwaliteit: $quality';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get select_audio_source => 'Selecteer Audiobron';
|
String get select_audio_source => 'Audiobron kiezen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get endless_playback_description =>
|
String get endless_playback_description =>
|
||||||
'Voeg automatisch nieuwe nummers toe aan het einde van de wachtrij';
|
'Nieuwe nummers automatisch achteraan de wachtrij toevoegen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get choose_your_region => 'Kies uw regio';
|
String get choose_your_region => 'Kies je regio';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get choose_your_region_description =>
|
String get choose_your_region_description =>
|
||||||
'Dit zal Spotube helpen om de juiste inhoud voor uw locatie te tonen.';
|
'Dit helpt Spotube om de juiste inhoud\nvoor jouw locatie te tonen.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get choose_your_language => 'Kies uw taal';
|
String get choose_your_language => 'Kies je taal';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get help_project_grow => 'Help dit project groeien';
|
String get help_project_grow => 'Help dit project met groeien';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get help_project_grow_description =>
|
String get help_project_grow_description =>
|
||||||
'Spotube is een open-source project. U kunt dit project helpen groeien door bij te dragen aan het project, bugs te melden of nieuwe functies voor te stellen.';
|
'Spotube is een open-source project. Je kunt dit project helpen groeien door eraan bij te dragen, problemen te melden of nieuwe functies voor te stellen.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contribute_on_github => 'Bijdragen op GitHub';
|
String get contribute_on_github => 'Bijdragen on GitHub';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get donate_on_open_collective => 'Doneren op Open Collective';
|
String get donate_on_open_collective => 'Doneren on Open Collective';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get browse_anonymously => 'Anoniem Bladeren';
|
String get browse_anonymously => 'Anoniem browsen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get enable_connect => 'Verbinding inschakelen';
|
String get enable_connect => 'Verbinding inschakelen';
|
||||||
|
@ -217,7 +217,7 @@ class Spotube extends HookConsumerWidget {
|
|||||||
iconTheme: const IconThemeProperties(),
|
iconTheme: const IconThemeProperties(),
|
||||||
colorScheme:
|
colorScheme:
|
||||||
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.light) ??
|
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.light) ??
|
||||||
LegacyColorSchemes.lightOrange(),
|
LegacyColorSchemes.lightSlate(),
|
||||||
surfaceOpacity: .8,
|
surfaceOpacity: .8,
|
||||||
surfaceBlur: 10,
|
surfaceBlur: 10,
|
||||||
),
|
),
|
||||||
@ -226,7 +226,7 @@ class Spotube extends HookConsumerWidget {
|
|||||||
iconTheme: const IconThemeProperties(),
|
iconTheme: const IconThemeProperties(),
|
||||||
colorScheme:
|
colorScheme:
|
||||||
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.dark) ??
|
colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.dark) ??
|
||||||
LegacyColorSchemes.darkOrange(),
|
LegacyColorSchemes.darkSlate(),
|
||||||
surfaceOpacity: .8,
|
surfaceOpacity: .8,
|
||||||
surfaceBlur: 10,
|
surfaceBlur: 10,
|
||||||
),
|
),
|
||||||
|
@ -102,7 +102,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
);
|
);
|
||||||
await customStatement(
|
await customStatement(
|
||||||
"ALTER TABLE $tableName "
|
"ALTER TABLE $tableName "
|
||||||
"ADD COLUMN $columnName TEXT NOT NULL DEFAULT 'Orange:0xFFf97315'",
|
"ADD COLUMN $columnName TEXT NOT NULL DEFAULT 'Slate:0xff64748b'",
|
||||||
);
|
);
|
||||||
await customStatement(
|
await customStatement(
|
||||||
"UPDATE $tableName "
|
"UPDATE $tableName "
|
||||||
@ -114,7 +114,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
);
|
);
|
||||||
await customStatement(
|
await customStatement(
|
||||||
"UPDATE $tableName "
|
"UPDATE $tableName "
|
||||||
"SET $columnName = 'Orange:0xFFf97315' WHERE $columnName = 'Blue:0xFF2196F3'",
|
"SET $columnName = 'Slate:0xff64748b' WHERE $columnName = 'Blue:0xFF2196F3'",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
from5To6: (m, schema) async {
|
from5To6: (m, schema) async {
|
||||||
|
@ -666,7 +666,7 @@ class $PreferencesTableTable extends PreferencesTable
|
|||||||
'accent_color_scheme', aliasedName, false,
|
'accent_color_scheme', aliasedName, false,
|
||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: const Constant("Orange:0xFFf97315"))
|
defaultValue: const Constant("Slate:0xff64748b"))
|
||||||
.withConverter<SpotubeColor>(
|
.withConverter<SpotubeColor>(
|
||||||
$PreferencesTableTable.$converteraccentColorScheme);
|
$PreferencesTableTable.$converteraccentColorScheme);
|
||||||
static const VerificationMeta _layoutModeMeta =
|
static const VerificationMeta _layoutModeMeta =
|
||||||
|
@ -1407,7 +1407,7 @@ final class Schema5 extends i0.VersionedSchema {
|
|||||||
i1.GeneratedColumn<String> _column_55(String aliasedName) =>
|
i1.GeneratedColumn<String> _column_55(String aliasedName) =>
|
||||||
i1.GeneratedColumn<String>('accent_color_scheme', aliasedName, false,
|
i1.GeneratedColumn<String>('accent_color_scheme', aliasedName, false,
|
||||||
type: i1.DriftSqlType.string,
|
type: i1.DriftSqlType.string,
|
||||||
defaultValue: const Constant("Orange:0xFFf97315"));
|
defaultValue: const Constant("Slate:0xff64748b"));
|
||||||
|
|
||||||
final class Schema6 extends i0.VersionedSchema {
|
final class Schema6 extends i0.VersionedSchema {
|
||||||
Schema6({required super.database}) : super(version: 6);
|
Schema6({required super.database}) : super(version: 6);
|
||||||
|
@ -79,7 +79,7 @@ class PreferencesTable extends Table {
|
|||||||
TextColumn get closeBehavior => textEnum<CloseBehavior>()
|
TextColumn get closeBehavior => textEnum<CloseBehavior>()
|
||||||
.withDefault(Constant(CloseBehavior.close.name))();
|
.withDefault(Constant(CloseBehavior.close.name))();
|
||||||
TextColumn get accentColorScheme => text()
|
TextColumn get accentColorScheme => text()
|
||||||
.withDefault(const Constant("Orange:0xFFf97315"))
|
.withDefault(const Constant("Slate:0xff64748b"))
|
||||||
.map(const SpotubeColorConverter())();
|
.map(const SpotubeColorConverter())();
|
||||||
TextColumn get layoutMode =>
|
TextColumn get layoutMode =>
|
||||||
textEnum<LayoutMode>().withDefault(Constant(LayoutMode.adaptive.name))();
|
textEnum<LayoutMode>().withDefault(Constant(LayoutMode.adaptive.name))();
|
||||||
@ -131,7 +131,7 @@ class PreferencesTable extends Table {
|
|||||||
systemTitleBar: false,
|
systemTitleBar: false,
|
||||||
skipNonMusic: false,
|
skipNonMusic: false,
|
||||||
closeBehavior: CloseBehavior.close,
|
closeBehavior: CloseBehavior.close,
|
||||||
accentColorScheme: SpotubeColor(Colors.orange.value, name: "Orange"),
|
accentColorScheme: SpotubeColor(Colors.slate.value, name: "Slate"),
|
||||||
layoutMode: LayoutMode.adaptive,
|
layoutMode: LayoutMode.adaptive,
|
||||||
locale: const Locale("system", "system"),
|
locale: const Locale("system", "system"),
|
||||||
market: Market.US,
|
market: Market.US,
|
||||||
|
@ -21,7 +21,6 @@ import 'package:spotube/extensions/constrains.dart';
|
|||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/modules/root/spotube_navigation_bar.dart';
|
import 'package:spotube/modules/root/spotube_navigation_bar.dart';
|
||||||
import 'package:spotube/provider/audio_player/audio_player.dart';
|
import 'package:spotube/provider/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/core/auth.dart';
|
|
||||||
import 'package:spotube/provider/server/active_track_sources.dart';
|
import 'package:spotube/provider/server/active_track_sources.dart';
|
||||||
import 'package:spotube/provider/volume_provider.dart';
|
import 'package:spotube/provider/volume_provider.dart';
|
||||||
import 'package:spotube/services/sourced_track/sources/youtube.dart';
|
import 'package:spotube/services/sourced_track/sources/youtube.dart';
|
||||||
@ -40,7 +39,6 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final authenticated = ref.watch(metadataPluginAuthenticatedProvider);
|
|
||||||
final sourcedCurrentTrack = ref.watch(activeTrackSourcesProvider);
|
final sourcedCurrentTrack = ref.watch(activeTrackSourcesProvider);
|
||||||
final currentActiveTrack =
|
final currentActiveTrack =
|
||||||
ref.watch(audioPlayerProvider.select((s) => s.activeTrack));
|
ref.watch(audioPlayerProvider.select((s) => s.activeTrack));
|
||||||
@ -105,13 +103,15 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
surfaceBlur: 0,
|
surfaceBlur: 0,
|
||||||
leading: [
|
leading: [
|
||||||
IconButton.ghost(
|
IconButton.ghost(
|
||||||
icon: const Icon(SpotubeIcons.angleDown, size: 18),
|
size: const ButtonSize(1.2),
|
||||||
|
icon: const Icon(SpotubeIcons.angleDown),
|
||||||
onPressed: panelController.close,
|
onPressed: panelController.close,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
trailing: [
|
trailing: [
|
||||||
if (currentActiveTrackSource is YoutubeSourcedTrack)
|
if (currentActiveTrackSource is YoutubeSourcedTrack)
|
||||||
TextButton(
|
TextButton(
|
||||||
|
size: const ButtonSize(1.2),
|
||||||
leading: Assets.images.logos.songlinkTransparent.image(
|
leading: Assets.images.logos.songlinkTransparent.image(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
@ -131,7 +131,8 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
child: Text(context.l10n.details),
|
child: Text(context.l10n.details),
|
||||||
).call,
|
).call,
|
||||||
child: IconButton.ghost(
|
child: IconButton.ghost(
|
||||||
icon: const Icon(SpotubeIcons.info, size: 18),
|
size: const ButtonSize(1.2),
|
||||||
|
icon: const Icon(SpotubeIcons.info),
|
||||||
onPressed: currentActiveTrackSource == null
|
onPressed: currentActiveTrackSource == null
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
@ -239,18 +240,16 @@ class PlayerView extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (authenticated.asData?.value == true)
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
Expanded(
|
||||||
if (authenticated.asData?.value == true)
|
child: OutlineButton(
|
||||||
Expanded(
|
leading: const Icon(SpotubeIcons.music),
|
||||||
child: OutlineButton(
|
child: Text(context.l10n.lyrics),
|
||||||
leading: const Icon(SpotubeIcons.music),
|
onPressed: () {
|
||||||
child: Text(context.l10n.lyrics),
|
context.pushRoute(const PlayerLyricsRoute());
|
||||||
onPressed: () {
|
},
|
||||||
context.pushRoute(const PlayerLyricsRoute());
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer;
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/collections/intents.dart';
|
import 'package:spotube/collections/intents.dart';
|
||||||
@ -14,6 +14,7 @@ import 'package:spotube/modules/player/use_progress.dart';
|
|||||||
import 'package:spotube/provider/audio_player/audio_player.dart';
|
import 'package:spotube/provider/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/provider/audio_player/querying_track_info.dart';
|
import 'package:spotube/provider/audio_player/querying_track_info.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class PlayerControls extends HookConsumerWidget {
|
class PlayerControls extends HookConsumerWidget {
|
||||||
final PaletteGenerator? palette;
|
final PaletteGenerator? palette;
|
||||||
@ -48,6 +49,9 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
|
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
final buttonSize =
|
||||||
|
kIsMobile ? const ButtonSize(1.5) : const ButtonSize(1.2);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -149,9 +153,11 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
).call,
|
).call,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
size: buttonSize,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
SpotubeIcons.shuffle,
|
SpotubeIcons.shuffle,
|
||||||
color: shuffled ? theme.colorScheme.primary : null,
|
color: shuffled ? theme.colorScheme.primary : null,
|
||||||
|
size: 22,
|
||||||
),
|
),
|
||||||
variance: shuffled
|
variance: shuffled
|
||||||
? ButtonVariance.secondary
|
? ButtonVariance.secondary
|
||||||
@ -170,8 +176,10 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
}),
|
}),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
tooltip: TooltipContainer(
|
tooltip: TooltipContainer(
|
||||||
child: Text(context.l10n.previous_track)).call,
|
child: Text(context.l10n.previous_track),
|
||||||
|
).call,
|
||||||
child: IconButton.ghost(
|
child: IconButton.ghost(
|
||||||
|
size: buttonSize,
|
||||||
enabled: !isFetchingActiveTrack,
|
enabled: !isFetchingActiveTrack,
|
||||||
icon: const Icon(SpotubeIcons.skipBack),
|
icon: const Icon(SpotubeIcons.skipBack),
|
||||||
onPressed: audioPlayer.skipToPrevious,
|
onPressed: audioPlayer.skipToPrevious,
|
||||||
@ -186,6 +194,7 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
).call,
|
).call,
|
||||||
child: IconButton.primary(
|
child: IconButton.primary(
|
||||||
|
size: buttonSize,
|
||||||
shape: ButtonShape.circle,
|
shape: ButtonShape.circle,
|
||||||
icon: isFetchingActiveTrack
|
icon: isFetchingActiveTrack
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
@ -206,8 +215,10 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
tooltip:
|
tooltip:
|
||||||
TooltipContainer(child: Text(context.l10n.next_track)).call,
|
TooltipContainer(child: Text(context.l10n.next_track))
|
||||||
|
.call,
|
||||||
child: IconButton.ghost(
|
child: IconButton.ghost(
|
||||||
|
size: buttonSize,
|
||||||
icon: const Icon(SpotubeIcons.skipForward),
|
icon: const Icon(SpotubeIcons.skipForward),
|
||||||
onPressed:
|
onPressed:
|
||||||
isFetchingActiveTrack ? null : audioPlayer.skipToNext,
|
isFetchingActiveTrack ? null : audioPlayer.skipToNext,
|
||||||
@ -228,6 +239,7 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
).call,
|
).call,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
size: buttonSize,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
loopMode == PlaylistMode.single
|
loopMode == PlaylistMode.single
|
||||||
? SpotubeIcons.repeatOne
|
? SpotubeIcons.repeatOne
|
||||||
|
@ -47,10 +47,8 @@ class PlayerTrackDetails extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
LinkText(
|
Text(
|
||||||
playback.activeTrack?.name ?? "",
|
playback.activeTrack?.name ?? "",
|
||||||
TrackRoute(trackId: playback.activeTrack?.id ?? ""),
|
|
||||||
push: true,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: theme.typography.normal.copyWith(
|
style: theme.typography.normal.copyWith(
|
||||||
color: color,
|
color: color,
|
||||||
|
@ -70,6 +70,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
final preferences = ref.watch(userPreferencesProvider);
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
final youtubeEngine = ref.watch(youtubeEngineProvider);
|
final youtubeEngine = ref.watch(youtubeEngineProvider);
|
||||||
|
|
||||||
|
final isLoading = useState(false);
|
||||||
final isSearching = useState(false);
|
final isSearching = useState(false);
|
||||||
final searchMode = useState(preferences.searchMode);
|
final searchMode = useState(preferences.searchMode);
|
||||||
final activeTrackSources = ref.watch(activeTrackSourcesProvider);
|
final activeTrackSources = ref.watch(activeTrackSourcesProvider);
|
||||||
@ -195,27 +196,40 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
enabled: !isFetchingActiveTrack,
|
enabled: !isFetchingActiveTrack && !isLoading.value,
|
||||||
selected: !isFetchingActiveTrack &&
|
selected: !isFetchingActiveTrack &&
|
||||||
sourceInfo.id == activeTrackSource?.info.id,
|
sourceInfo.id == activeTrackSource?.info.id,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (!isFetchingActiveTrack &&
|
if (!isFetchingActiveTrack &&
|
||||||
sourceInfo.id != activeTrackSource?.info.id) {
|
sourceInfo.id != activeTrackSource?.info.id) {
|
||||||
await activeTrackNotifier?.swapWithSibling(sourceInfo);
|
try {
|
||||||
await ref.read(audioPlayerProvider.notifier).swapActiveSource();
|
isLoading.value = true;
|
||||||
|
await activeTrackNotifier?.swapWithSibling(sourceInfo);
|
||||||
|
await ref.read(audioPlayerProvider.notifier).swapActiveSource();
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
if (MediaQuery.sizeOf(context).mdAndUp) {
|
if (MediaQuery.sizeOf(context).mdAndUp) {
|
||||||
closeOverlay(context);
|
closeOverlay(context);
|
||||||
} else {
|
} else {
|
||||||
closeDrawer(context);
|
closeDrawer(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) {
|
||||||
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[activeTrackSource, activeTrackNotifier, siblings],
|
[
|
||||||
|
activeTrackSource,
|
||||||
|
activeTrackNotifier,
|
||||||
|
siblings,
|
||||||
|
isFetchingActiveTrack,
|
||||||
|
isLoading.value,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
final scale = context.theme.scaling;
|
final scale = context.theme.scaling;
|
||||||
@ -293,6 +307,15 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: isLoading.value
|
||||||
|
? const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: LinearProgressIndicator(),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
@ -307,7 +330,9 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
itemCount: siblings.length,
|
itemCount: siblings.length,
|
||||||
separatorBuilder: (context, index) => const Gap(8),
|
separatorBuilder: (context, index) => const Gap(8),
|
||||||
itemBuilder: (context, index) => itemBuilder(
|
itemBuilder: (context, index) => itemBuilder(
|
||||||
siblings[index], activeTrackSource!.source),
|
siblings[index],
|
||||||
|
activeTrackSource!.source,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
true => FutureBuilder(
|
true => FutureBuilder(
|
||||||
future: searchRequest,
|
future: searchRequest,
|
||||||
|
@ -3,7 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
|
||||||
import 'package:spotube/collections/side_bar_tiles.dart';
|
import 'package:spotube/collections/side_bar_tiles.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
@ -20,19 +19,6 @@ class Sidebar extends HookConsumerWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
static Widget brandLogo(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black,
|
|
||||||
borderRadius: BorderRadius.circular(50),
|
|
||||||
),
|
|
||||||
child: Assets.branding.spotubeLogoPng.image(
|
|
||||||
height: 50,
|
|
||||||
cacheHeight: (100 * MediaQuery.devicePixelRatioOf(context)).toInt(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final ThemeData(:colorScheme) = Theme.of(context);
|
final ThemeData(:colorScheme) = Theme.of(context);
|
||||||
|
@ -3,8 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
|
||||||
import 'package:spotube/collections/routes.gr.dart';
|
import 'package:spotube/collections/routes.gr.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
@ -43,19 +41,17 @@ class HomePage extends HookConsumerWidget {
|
|||||||
if (mediaQuery.smAndDown || layoutMode == LayoutMode.compact)
|
if (mediaQuery.smAndDown || layoutMode == LayoutMode.compact)
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
floating: true,
|
floating: true,
|
||||||
title: Image.asset(
|
title: DefaultTextStyle(
|
||||||
theme.brightness == Brightness.dark
|
style: TextStyle(
|
||||||
? Assets.branding.spotubeLogoPng.path
|
fontFamily: "Cookie",
|
||||||
: Assets.branding.spotubeLogoLight.path,
|
fontSize: 30,
|
||||||
height: 45,
|
letterSpacing: 1.8,
|
||||||
width: 45,
|
color: theme.colorScheme.foreground,
|
||||||
color: theme.colorScheme.background,
|
),
|
||||||
colorBlendMode: BlendMode.saturation,
|
child: const Text("Spotube"),
|
||||||
cacheHeight:
|
|
||||||
(100 * MediaQuery.devicePixelRatioOf(context)).toInt(),
|
|
||||||
),
|
),
|
||||||
backgroundColor: context.theme.colorScheme.background,
|
backgroundColor: theme.colorScheme.background,
|
||||||
foregroundColor: context.theme.colorScheme.foreground,
|
foregroundColor: theme.colorScheme.foreground,
|
||||||
actions: [
|
actions: [
|
||||||
const ConnectDeviceButton(),
|
const ConnectDeviceButton(),
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
|
@ -18,8 +18,6 @@ import 'package:spotube/provider/lyrics/synced.dart';
|
|||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/logger/logger.dart';
|
import 'package:spotube/services/logger/logger.dart';
|
||||||
|
|
||||||
import 'package:stroke_text/stroke_text.dart';
|
|
||||||
|
|
||||||
class SyncedLyrics extends HookConsumerWidget {
|
class SyncedLyrics extends HookConsumerWidget {
|
||||||
final PaletteColor palette;
|
final PaletteColor palette;
|
||||||
final bool? isModal;
|
final bool? isModal;
|
||||||
@ -160,6 +158,9 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
child: AnimatedDefaultTextStyle(
|
child: AnimatedDefaultTextStyle(
|
||||||
duration: const Duration(milliseconds: 250),
|
duration: const Duration(milliseconds: 250),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
color: isActive
|
||||||
|
? theme.colorScheme.foreground
|
||||||
|
: theme.colorScheme.mutedForeground,
|
||||||
fontWeight: isActive
|
fontWeight: isActive
|
||||||
? FontWeight.w500
|
? FontWeight.w500
|
||||||
: FontWeight.normal,
|
: FontWeight.normal,
|
||||||
@ -181,25 +182,7 @@ class SyncedLyrics extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
audioPlayer.seek(time);
|
audioPlayer.seek(time);
|
||||||
},
|
},
|
||||||
child: Builder(builder: (context) {
|
child: Text(lyricSlice.text),
|
||||||
return StrokeText(
|
|
||||||
text: lyricSlice.text,
|
|
||||||
textStyle:
|
|
||||||
DefaultTextStyle.of(context).style,
|
|
||||||
textColor: switch ((
|
|
||||||
isActive,
|
|
||||||
isModal == true
|
|
||||||
)) {
|
|
||||||
(true, _) => Colors.white,
|
|
||||||
(_, true) =>
|
|
||||||
theme.colorScheme.mutedForeground,
|
|
||||||
(_, _) => palette.bodyTextColor,
|
|
||||||
},
|
|
||||||
strokeColor: isActive
|
|
||||||
? Colors.black
|
|
||||||
: Colors.transparent,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -28,20 +28,18 @@ class PlayerLyricsPage extends HookConsumerWidget {
|
|||||||
final selectedIndex = useState(0);
|
final selectedIndex = useState(0);
|
||||||
final palette = usePaletteColor(albumArt, ref);
|
final palette = usePaletteColor(albumArt, ref);
|
||||||
|
|
||||||
final tabbar = Padding(
|
final tabbar = TabList(
|
||||||
padding: const EdgeInsets.all(10),
|
index: selectedIndex.value,
|
||||||
child: TabList(
|
onChanged: (index) => selectedIndex.value = index,
|
||||||
index: selectedIndex.value,
|
children: [
|
||||||
onChanged: (index) => selectedIndex.value = index,
|
TabItem(
|
||||||
children: [
|
child: Text(context.l10n.synced),
|
||||||
TabItem(
|
),
|
||||||
child: Text(context.l10n.synced),
|
TabItem(
|
||||||
),
|
child: Text(context.l10n.plain),
|
||||||
TabItem(
|
),
|
||||||
child: Text(context.l10n.plain),
|
],
|
||||||
),
|
);
|
||||||
],
|
|
||||||
));
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
headers: [
|
headers: [
|
||||||
|
@ -13,7 +13,6 @@ import 'package:spotube/l10n/l10n.dart';
|
|||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
|
||||||
final localWithName = L10n.all.map((e) {
|
final localWithName = L10n.all.map((e) {
|
||||||
print(e);
|
|
||||||
final isoCodeName =
|
final isoCodeName =
|
||||||
LanguageLocals.getDisplayLanguage(e.languageCode, e.countryCode);
|
LanguageLocals.getDisplayLanguage(e.languageCode, e.countryCode);
|
||||||
return (
|
return (
|
||||||
|
@ -147,21 +147,29 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
|||||||
try {
|
try {
|
||||||
// Playlist and state has to be in sync. This is only meant for
|
// Playlist and state has to be in sync. This is only meant for
|
||||||
// the shuffle/re-ordering indices to be in sync
|
// the shuffle/re-ordering indices to be in sync
|
||||||
if (playlist.medias.length != state.tracks.length) return;
|
if (playlist.medias.length != state.tracks.length) {
|
||||||
|
AppLogger.log.w(
|
||||||
final queries = playlist.medias
|
"Playlist length does not match state tracks length. Ignoring... "
|
||||||
.map((media) => TrackSourceQuery.parseUri(media.uri))
|
"Playlist length: ${playlist.medias.length}, "
|
||||||
.toList();
|
"State tracks length: ${state.tracks.length}",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final trackGroupedById = groupBy(
|
final trackGroupedById = groupBy(
|
||||||
state.tracks,
|
state.tracks,
|
||||||
(query) => query.id,
|
(query) => query.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
final tracks = queries
|
final tracks = <SpotubeTrackObject>[];
|
||||||
.map((query) => trackGroupedById[query.id]?.firstOrNull)
|
|
||||||
.nonNulls
|
for (final media in playlist.medias) {
|
||||||
.toList();
|
final trackQuery = TrackSourceQuery.parseUri(media.uri);
|
||||||
|
final track = trackGroupedById[trackQuery.id]?.firstOrNull;
|
||||||
|
if (track != null) {
|
||||||
|
tracks.add(track);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tracks.length != state.tracks.length) {
|
if (tracks.length != state.tracks.length) {
|
||||||
AppLogger.log.w("Mismatch in tracks after reordering/shuffling.");
|
AppLogger.log.w("Mismatch in tracks after reordering/shuffling.");
|
||||||
|
@ -146,7 +146,5 @@ mixin SpotubeAudioPlayersStreams on AudioPlayerInterface {
|
|||||||
|
|
||||||
Stream<String> get errorStream => _mkPlayer.stream.error;
|
Stream<String> get errorStream => _mkPlayer.stream.error;
|
||||||
|
|
||||||
Stream<mk.Playlist> get playlistStream => _mkPlayer.stream.playlist.map((s) {
|
Stream<mk.Playlist> get playlistStream => _mkPlayer.stream.playlist;
|
||||||
return s;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -12,19 +12,15 @@ import 'package:spotube/utils/platform.dart';
|
|||||||
/// This class adds a state stream to the [Player] class.
|
/// This class adds a state stream to the [Player] class.
|
||||||
class CustomPlayer extends Player {
|
class CustomPlayer extends Player {
|
||||||
final StreamController<AudioPlaybackState> _playerStateStream;
|
final StreamController<AudioPlaybackState> _playerStateStream;
|
||||||
final StreamController<bool> _shuffleStream;
|
|
||||||
|
|
||||||
late final List<StreamSubscription> _subscriptions;
|
late final List<StreamSubscription> _subscriptions;
|
||||||
|
|
||||||
bool _shuffled;
|
|
||||||
int _androidAudioSessionId = 0;
|
int _androidAudioSessionId = 0;
|
||||||
String _packageName = "";
|
String _packageName = "";
|
||||||
AndroidAudioManager? _androidAudioManager;
|
AndroidAudioManager? _androidAudioManager;
|
||||||
|
|
||||||
CustomPlayer({super.configuration})
|
CustomPlayer({super.configuration})
|
||||||
: _playerStateStream = StreamController.broadcast(),
|
: _playerStateStream = StreamController.broadcast() {
|
||||||
_shuffleStream = StreamController.broadcast(),
|
|
||||||
_shuffled = false {
|
|
||||||
nativePlayer.setProperty("network-timeout", "120");
|
nativePlayer.setProperty("network-timeout", "120");
|
||||||
|
|
||||||
_subscriptions = [
|
_subscriptions = [
|
||||||
@ -86,10 +82,10 @@ class CustomPlayer extends Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get shuffled => _shuffled;
|
bool get shuffled => state.shuffle;
|
||||||
|
|
||||||
Stream<AudioPlaybackState> get playerStateStream => _playerStateStream.stream;
|
Stream<AudioPlaybackState> get playerStateStream => _playerStateStream.stream;
|
||||||
Stream<bool> get shuffleStream => _shuffleStream.stream;
|
Stream<bool> get shuffleStream => stream.shuffle;
|
||||||
Stream<int> get indexChangeStream {
|
Stream<int> get indexChangeStream {
|
||||||
int oldIndex = state.playlist.index;
|
int oldIndex = state.playlist.index;
|
||||||
return stream.playlist.map((event) => event.index).where((newIndex) {
|
return stream.playlist.map((event) => event.index).where((newIndex) {
|
||||||
@ -103,22 +99,14 @@ class CustomPlayer extends Player {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> setShuffle(bool shuffle) async {
|
Future<void> setShuffle(bool shuffle) async {
|
||||||
_shuffled = shuffle;
|
|
||||||
await super.setShuffle(shuffle);
|
await super.setShuffle(shuffle);
|
||||||
_shuffleStream.add(shuffle);
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
if (shuffle) {
|
|
||||||
await move(state.playlist.index, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> stop() async {
|
Future<void> stop() async {
|
||||||
await super.stop();
|
await super.stop();
|
||||||
|
|
||||||
_shuffled = false;
|
|
||||||
_playerStateStream.add(AudioPlaybackState.stopped);
|
_playerStateStream.add(AudioPlaybackState.stopped);
|
||||||
_shuffleStream.add(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -134,7 +122,8 @@ class CustomPlayer extends Player {
|
|||||||
|
|
||||||
Future<void> insert(int index, Media media) async {
|
Future<void> insert(int index, Media media) async {
|
||||||
await add(media);
|
await add(media);
|
||||||
await move(state.playlist.medias.length, index);
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
await move(state.playlist.medias.length - 1, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setAudioNormalization(bool normalize) async {
|
Future<void> setAudioNormalization(bool normalize) async {
|
||||||
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 38 KiB |
73
pubspec.lock
@ -1570,58 +1570,65 @@ packages:
|
|||||||
media_kit:
|
media_kit:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit
|
path: media_kit
|
||||||
sha256: "48c10c3785df5d88f0eef970743f8c99b2e5da2b34b9d8f9876e598f62d9e776"
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||||
source: hosted
|
url: "https://github.com/media-kit/media-kit"
|
||||||
|
source: git
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
media_kit_libs_android_audio:
|
media_kit_libs_android_audio:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_android_audio
|
path: "libs/android/media_kit_libs_android_audio"
|
||||||
sha256: d0923d251c1232880e755b44555ce31b9e7068a29f05e01ed2fb510956bba851
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||||
source: hosted
|
url: "https://github.com/media-kit/media-kit"
|
||||||
|
source: git
|
||||||
version: "1.3.7"
|
version: "1.3.7"
|
||||||
media_kit_libs_audio:
|
media_kit_libs_audio:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_audio
|
path: "libs/universal/media_kit_libs_audio"
|
||||||
sha256: f5ea1eb414c4ee4994f36de36c7ce11577a249e058e907e7eb6e656513bccbfb
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||||
source: hosted
|
url: "https://github.com/media-kit/media-kit"
|
||||||
|
source: git
|
||||||
version: "1.0.6"
|
version: "1.0.6"
|
||||||
media_kit_libs_ios_audio:
|
media_kit_libs_ios_audio:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_ios_audio
|
path: "libs/ios/media_kit_libs_ios_audio"
|
||||||
sha256: "78ccf04e27d6b4ba00a355578ccb39b772f00d48269a6ac3db076edf2d51934f"
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||||
source: hosted
|
url: "https://github.com/media-kit/media-kit"
|
||||||
|
source: git
|
||||||
version: "1.1.4"
|
version: "1.1.4"
|
||||||
media_kit_libs_linux:
|
media_kit_libs_linux:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_linux
|
path: "libs/linux/media_kit_libs_linux"
|
||||||
sha256: "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf"
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||||
source: hosted
|
url: "https://github.com/media-kit/media-kit"
|
||||||
|
source: git
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
media_kit_libs_macos_audio:
|
media_kit_libs_macos_audio:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_macos_audio
|
path: "libs/macos/media_kit_libs_macos_audio"
|
||||||
sha256: "3be21844df98f286de32808592835073cdef2c1a10078bac135da790badca950"
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||||
source: hosted
|
url: "https://github.com/media-kit/media-kit"
|
||||||
|
source: git
|
||||||
version: "1.1.4"
|
version: "1.1.4"
|
||||||
media_kit_libs_windows_audio:
|
media_kit_libs_windows_audio:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_windows_audio
|
path: "libs/windows/media_kit_libs_windows_audio"
|
||||||
sha256: c2fd558cc87b9d89a801141fcdffe02e338a3b21a41a18fbd63d5b221a1b8e53
|
ref: HEAD
|
||||||
url: "https://pub.dev"
|
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||||
source: hosted
|
url: "https://github.com/media-kit/media-kit"
|
||||||
|
source: git
|
||||||
version: "1.0.9"
|
version: "1.0.9"
|
||||||
menu_base:
|
menu_base:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
|
32
pubspec.yaml
@ -3,7 +3,7 @@ description: Open source extensible music streaming platform and app, based on B
|
|||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 4.0.2+41
|
version: 5.0.0+42
|
||||||
|
|
||||||
homepage: https://spotube.krtirtho.dev
|
homepage: https://spotube.krtirtho.dev
|
||||||
repository: https://github.com/KRTirtho/spotube
|
repository: https://github.com/KRTirtho/spotube
|
||||||
@ -84,8 +84,14 @@ dependencies:
|
|||||||
logger: ^2.0.2
|
logger: ^2.0.2
|
||||||
logging: ^1.3.0
|
logging: ^1.3.0
|
||||||
lrc: ^1.0.2
|
lrc: ^1.0.2
|
||||||
media_kit: ^1.1.10+1
|
media_kit:
|
||||||
media_kit_libs_audio: ^1.0.4
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: media_kit
|
||||||
|
media_kit_libs_audio:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/universal/media_kit_libs_audio
|
||||||
metadata_god: ^1.1.0
|
metadata_god: ^1.1.0
|
||||||
mime: ^2.0.0
|
mime: ^2.0.0
|
||||||
open_file: ^3.5.10
|
open_file: ^3.5.10
|
||||||
@ -194,6 +200,26 @@ dependency_overrides:
|
|||||||
ref: patch-2
|
ref: patch-2
|
||||||
path: flutter_secure_storage_linux
|
path: flutter_secure_storage_linux
|
||||||
flutter_secure_storage_platform_interface: 2.0.0
|
flutter_secure_storage_platform_interface: 2.0.0
|
||||||
|
media_kit_libs_android_audio:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/android/media_kit_libs_android_audio
|
||||||
|
media_kit_libs_ios_audio:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/ios/media_kit_libs_ios_audio
|
||||||
|
media_kit_libs_macos_audio:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/macos/media_kit_libs_macos_audio
|
||||||
|
media_kit_libs_windows_audio:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/windows/media_kit_libs_windows_audio
|
||||||
|
media_kit_libs_linux:
|
||||||
|
git:
|
||||||
|
url: https://github.com/media-kit/media-kit
|
||||||
|
path: libs/linux/media_kit_libs_linux
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
generate: true
|
generate: true
|
||||||
|
@ -525,7 +525,7 @@ class PreferencesTable extends Table
|
|||||||
GeneratedColumn<String>('accent_color_scheme', aliasedName, false,
|
GeneratedColumn<String>('accent_color_scheme', aliasedName, false,
|
||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: const Constant("Orange:0xFFf97315"));
|
defaultValue: const Constant("Slate:0xff64748b"));
|
||||||
late final GeneratedColumn<String> layoutMode = GeneratedColumn<String>(
|
late final GeneratedColumn<String> layoutMode = GeneratedColumn<String>(
|
||||||
'layout_mode', aliasedName, false,
|
'layout_mode', aliasedName, false,
|
||||||
type: DriftSqlType.string,
|
type: DriftSqlType.string,
|
||||||
|
@ -1 +1,5 @@
|
|||||||
{}
|
{
|
||||||
|
"nl": [
|
||||||
|
"audio_source"
|
||||||
|
]
|
||||||
|
}
|
||||||
|