Compare commits

...

46 Commits

Author SHA1 Message Date
neonItem
189aa08aa1
Merge 729bdd43b9 into 719229b2b7 2025-09-08 16:30:20 +06:00
Kingkor Roy Tirtho
719229b2b7 chore: add gap between metadata plugin 2025-09-08 16:30:10 +06:00
Kingkor Roy Tirtho
49db82083c chore: add support for language dialects 2025-09-08 16:29:05 +06:00
Kingkor Roy Tirtho
729bdd43b9
Merge branch 'dev' into libjsoncpp26-support 2025-09-08 16:15:47 +06:00
maboroshin
84f119e482
translation: fix Japanese translations (#2732)
Co-authored-by: Kingkor Roy Tirtho <krtirtho@gmail.com>
2025-09-08 16:15:07 +06:00
SamHacker
aeb8caf059
translation: add Traditional Chinese translation (#2762)
* add: Add the translation file for Chinese Traditional

* i18n: fix wrong translation in Chinese Simpfied

* i18n: Finish the translation in Chinese Traditional file

* i18n: Add Traditional Chinese locale support

* i18n: Add Traditional Chinese language code

---------

Co-authored-by: Kingkor Roy Tirtho <krtirtho@gmail.com>
2025-09-08 16:11:54 +06:00
Kingkor Roy Tirtho
7c4956153a chore: allow for any libjsoncpp 2025-09-08 16:04:23 +06:00
Kingkor Roy Tirtho
58dc80aa09 fix(playback): alternative track sources switch not working 2025-09-08 15:31:29 +06:00
Kingkor Roy Tirtho
4a07945214 fix(track_options): tapping on option doesn't close the menu 2025-09-08 14:27:29 +06:00
Kingkor Roy Tirtho
7b21eca37b fix(playback): play not fetching full playlist if playlist is too long 2025-09-08 14:09:16 +06:00
Kingkor Roy Tirtho
43ddf90c48 docs: remove legal notice warning 2025-09-07 20:36:32 +06:00
Kingkor Roy Tirtho
878a441a9f fix: windows webview2 environment permission issue 2025-09-06 22:32:41 +06:00
Kingkor Roy Tirtho
90493f0ea3 chore: add changelog generator config 2025-09-06 00:40:33 +06:00
Kingkor Roy Tirtho
db22b4fcce chore: syntax errors 2025-09-05 23:39:07 +06:00
Kingkor Roy Tirtho
0d6d482630 chore: build runner error 2025-09-05 23:33:55 +06:00
Kingkor Roy Tirtho
a4162dc2ad chore: upgrade drift schema for new plugin metadata fields 2025-09-05 22:52:06 +06:00
Kingkor Roy Tirtho
469a76dbd6 fix: yt-dlp playback not working and add partial support for HLS streaming 2025-09-05 22:40:18 +06:00
Kingkor Roy Tirtho
6940e92142 chore: system tray icon and pagination not working 2025-09-05 21:16:24 +06:00
Kingkor Roy Tirtho
4d57b134a3 cd: use xcode 16.2 2025-09-05 20:01:33 +06:00
Kingkor Roy Tirtho
a370166576 chore: add metadata plugin version 2025-09-05 18:47:46 +06:00
Kingkor Roy Tirtho
69d50eec35 chore: fix artist top tracks play button not showing loading indicator 2025-09-05 17:53:06 +06:00
Kingkor Roy Tirtho
2e48ac380b chore(translation): update missing translations 2025-09-05 17:31:09 +06:00
Kingkor Roy Tirtho
d22b5349a3 chore: fix pagination not working 2025-09-05 17:06:12 +06:00
Kingkor Roy Tirtho
83172f198c chore: fix saving/removing tracks, albums, artists and playlists from library not working 2025-09-05 16:57:29 +06:00
Kingkor Roy Tirtho
f870e12011 fix(playback): skip network requests if cached file already exists 2025-09-05 11:03:56 +06:00
Kingkor Roy Tirtho
345c6ac714 chore: fix pagination for liked tracks and saved albums not working 2025-09-05 10:50:14 +06:00
Kingkor Roy Tirtho
005355e267 chore(translation): add l10n support for new metadata plugin pages 2025-09-03 18:45:38 +06:00
Kingkor Roy Tirtho
aee2c9282d chore: avoid checking stream accessibility all the time
Only check stream accessible or not when first time it fails or gets blocked
2025-09-03 17:31:38 +06:00
Kingkor Roy Tirtho
ea329f40e8 fix(yt): fallback to different search result if all streaming url is inaccessible 2025-09-02 20:56:28 +06:00
Kingkor Roy Tirtho
b248f90403 chore: remove log in TrackSourceQuery.parseUri 2025-09-02 20:10:43 +06:00
Kingkor Roy Tirtho
e2c0ddef24 fix: inaccessible streaming url causing rapid skips
Now it filters the inaccessible streaming urls out so weird stuff won't happen
2025-09-02 20:10:02 +06:00
Kingkor Roy Tirtho
2a0853026a fix: local playback not working for tracks with special # (hashtag) characters 2025-08-31 00:45:29 +06:00
Kingkor Roy Tirtho
dddaa5a964 chore: upgrade new pipe extractor 2025-08-30 23:40:48 +06:00
Kingkor Roy Tirtho
412f3dd81c chore: optimize assets and app size 2025-08-29 17:33:54 +06:00
Kingkor Roy Tirtho
7f30ae8d31 chore: disable caching of plugin download file 2025-08-29 15:05:29 +06:00
Kingkor Roy Tirtho
c0d50d441e fix: use plugin author and name as slug 2025-08-29 14:56:24 +06:00
Kingkor Roy Tirtho
66cae6c7ac chore: upgrade Flutter version 2025-08-29 13:08:28 +06:00
neonItem
21afee3cfb
Fix dependencies on Debian Testing/Unstable (rolling release) systems 2025-03-22 23:54:28 +02:00
Kingkor Roy Tirtho
ba27dc70e4
Merge pull request #2550 from KRTirtho/dev
Release 4.0.2
2025-03-16 23:57:54 +06:00
Kingkor Roy Tirtho
723b6b1f38
Merge pull request #2524 from KRTirtho/dev
Release 4.0.1
2025-03-15 17:21:27 +06:00
Kingkor Roy Tirtho
464666c01a
Merge pull request #2410 from KRTirtho/dev
chore: update linux appdata screenshot
2025-03-07 20:22:32 +06:00
Kingkor Roy Tirtho
0e58cd0e99
Merge pull request #2408 from KRTirtho/dev
chore: add new images
2025-03-07 20:18:03 +06:00
Kingkor Roy Tirtho
d4f70f56e4
Merge pull request #2405 from KRTirtho/dev
Release 4.0.0
2025-03-07 18:05:55 +06:00
Kingkor Roy Tirtho
8c1337d1fc
Merge pull request #2118 from KRTirtho/dev
chore: release 3.9.0
2024-12-09 00:04:29 +06:00
Kingkor Roy Tirtho
94e704087f Merge branch 'dev' 2024-10-09 16:38:23 +06:00
Kingkor Roy Tirtho
8e287ab1e5
Merge pull request #1981 from KRTirtho/dev
Release 3.8.3
2024-10-09 15:39:31 +06:00
247 changed files with 14443 additions and 2121 deletions

View File

@ -1,3 +1,3 @@
{
"flutterSdkVersion": "3.32.7"
"flutterSdkVersion": "3.35.2"
}

2
.fvmrc
View File

@ -1,4 +1,4 @@
{
"flutter": "3.32.7",
"flutter": "3.35.2",
"flavors": {}
}

View File

@ -4,7 +4,7 @@ on:
pull_request:
env:
FLUTTER_VERSION: 3.32.7
FLUTTER_VERSION: 3.35.2
jobs:
lint:

View File

@ -20,7 +20,7 @@ on:
description: Dry run without uploading to release
env:
FLUTTER_VERSION: 3.32.7
FLUTTER_VERSION: 3.35.2
FLUTTER_CHANNEL: master
permissions:
@ -95,7 +95,7 @@ jobs:
if: ${{matrix.platform == 'ios'}}
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.1"
xcode-version: "16.2"
- name: Install ${{matrix.platform}} dependencies
run: |

View File

@ -30,5 +30,5 @@
"README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md",
"*.dart": "${capture}.g.dart,${capture}.freezed.dart"
},
"dart.flutterSdkPath": ".fvm/versions/3.32.7"
"dart.flutterSdkPath": ".fvm/versions/3.35.2"
}

View File

@ -8,7 +8,7 @@ tar:
mkdir -p $(TEMP_DIR)\
&& cp -r $(BUNDLE_DIR)/* $(TEMP_DIR)\
&& cp linux/spotube.desktop $(TEMP_DIR)\
&& cp assets/spotube-logo.png $(TEMP_DIR)\
&& cp assets/branding/spotube-logo.png $(TEMP_DIR)\
&& cp linux/com.github.KRTirtho.Spotube.appdata.xml $(TEMP_DIR)\
&& tar -cJf build/spotube-linux-${VERSION}-${PKG_ARCH}.tar.xz -C $(TEMP_DIR) .\
&& rm -rf $(TEMP_DIR)
@ -52,4 +52,7 @@ dmg:
if [ -f dist/Spotube-macos-universal.dmg ];\
then rm dist/Spotube-macos-universal.dmg;\
fi &&\
appdmg appdmg.json dist/Spotube-macos-universal.dmg
appdmg appdmg.json dist/Spotube-macos-universal.dmg
changelog:
git-cliff --unreleased

361
README.md
View File

@ -1,47 +1,8 @@
# 🚨 Spotube is banned from using "Spotify™ API" 🚨
### The developer of Spotube has received a cease and desist letter from Spotify USA Inc. and Spotify AB, asserting a legal threat concerning the distribution and development of any application that utilizes Spotifys data API in conjunction with content from YouTube® to facilitate ad-free playback of music tracks. The letter contends that this specific use of the Spotify™ APIs contravenes the Spotify™ Agreements and may also infringe upon the rights of music rights holders.
### Consequently, as the official maintainer of Spotube, I will immediately cease all forms of official distribution and development of Spotube that continue to employ the aforementioned 'Spotify™ APIs'
### <ins>Their exact reasoning</ins>: (any) "uses of Spotifys data API in connection with content from YouTube to provide ad-free playback of music tracks. The use of the Spotify APIs in this manner violates the Spotify Agreements and may also violate the rights of music rights holders."
## So what's now?
> In short, we are cooked (legally)
For now, I've to:
1. Stop distributing/developing Spotube/any app that uses "Spotify™ APIs"
That means, I can no longer distribute Spotube through the website, GitHub, any app store and immediately have to take down the versions that uses Spotify™ APIs.
1. Stop using their logo/image/name/intellectual property in a manner that "seems infringement"
1. Forever desist from aiding or assisting any other person or entity in the activities described above
---
**For the users of Spotube:**
Don't worry, Spotube is banned only from (or assisting other) using those APIs. As long as the app isn't using them or no way helps anyone else to use them, it's ok.
In future, I'll try to rewrite Spotube to ensure it operates within the bounds of copyright law and platform policies. And give ways for the users to extend the app to their use cases. Work is already in progress to implement this! So expect some big updates soon!
But for eternity, you can't download versions of Spotube that still uses "Spotify™ APIs" from official means (website/Github/app stores). Those will be taken down.
**But newer version of Spotube that _doesn't_ use "Spotify™ APIs" will be available to replace those.**
That means, in the upcoming new versions, you will no longer be able to login with your "Spotify™ Account", access your saved playlists, albums, tracks, followed artists or perform any action on that account or anything that is from "Spotify™" or owned by "Spotify™" (yes the API public data (e.g. track metadata) as well) through Spotube.
**Conclusion:** I'm extremely sorry for this disruption to your day to day music listening experience. Spotube existed and it used by a large number of users because they find it better. And we'll continue to be better than others but legally\* from now on
> Spotube has no affiliation with Spotify™ or any of its subsidiaries.
<div align="center">
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">
<img width="600" src="assets/branding/spotube_banner.png" alt="Spotube Logo">
An open source, cross-platform music client<br />
utilizing selected music provider API and YouTube®, Piped.video or JioSaavn as an audio source
A cross-platform extensible open-source music streaming platform.<br>
Bring your own music metadata/playlist with plugins created by community or by yourself. A small step towards the decentralized music streaming era!
Btw it's not just another Electron app 😉
@ -61,30 +22,316 @@ Btw it's not just another Electron app 😉
## 🌃 Features
- 🚫 No ads, thanks to the use of public & free music metadata providers and YT Music APIs¹
- ⬇️ Freely downloadable tracks
- 🖥️ 📱 Cross-platform support
- 🪶 Small size & less data usage
- 🕵️ Anonymous/guest login
- 🕒 Time synced lyrics
- ✋ No telemetry, diagnostics or user data collection
- 🚀 Native performance
- 📖 Open source/libre software
- 🔉 Playback control is done locally, not on the server
- 🧩 Plugin powered, supports any platform or custom music service through plugins.
- 🗺️ Community driven plugins for popular platforms or create your own.
- ⬇️ Freely downloadable tracks with tagged metadata.
- 🖥️ 📱 Cross-platform support.
- 🪶 Small size & less data usage.
- 🕒 Time synced lyrics regardless of the plugin support.
- ✋ No telemetry, diagnostics or user data collection.
- 🚀 Native performance.
- 📖 Open source/libre software.
- 🔉 Playback control is done locally, not on the server.
**¹** It is still **recommended** to support creators by engaging with their YouTube channels/tracks in music platforms (or preferably by buying their merch/concert tickets/physical media).
## 📜 ⬇️ Installation guide
### ❌ Unsupported features
New versions usually release every 3-4 months.<br />
This handy table lists all the methods you can use to install Spotube:
- 🗣️ **Shows & Podcasts:** Shows and Podcasts will <ins>**never be supported**</ins> because the audio tracks are <ins>_only_</ins> available on music providers and accessing them would require premium.
- 🎧 **Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8)
<table>
<tr>
<th>Platform</th>
<th>Package/Installation Method</th>
</tr>
<tr>
<td>Windows</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-windows-x86_64-setup.exe">
<img width="220" alt="Windows Download" src="https://get.todoist.help/hc/article_attachments/4403191721234/WindowsButton.svg">
</a>
</tr>
<tr>
<td>MacOS</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-macos-universal.dmg">
<img width="220" alt="MacOS Download" src="https://memory-map.com/wp-content/uploads/download-mac-OS-01.svg">
</a>
</td>
</tr>
<tr>
<td>Android</td>
<td>
<a href="https://play.google.com/store/apps/details?id=oss.krtirtho.spotube">
<img width="220" alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png">
</a>
<br>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-android-all-arch.apk">
<img width="220" alt="APK download" src="https://user-images.githubusercontent.com/114044633/223920025-83687de0-e463-4c5d-8122-e06e4bb7d40c.png">
</a>
<br/>
<a href="https://f-droid.org/packages/oss.krtirtho.spotube">
<img width="220" alt="Download from F-Droid" src="https://user-images.githubusercontent.com/61944859/174589876-bace24c0-b3fd-4c4a-bdb4-6fa82b5853ec.png">
</a>
</td>
</tr>
<tr>
<tr>
<td>iOS</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-iOS.ipa">
<img width="220" alt="Download iOS IPA" src="https://github.com/user-attachments/assets/3e50d93d-fb39-435c-be6b-337745f7c423">
</a>
<br/>
<blockquote style="color:red">
*iPA file only. Requires sideloading with <a href="https://altstore.io/">AltStore</a> or similar tools.
</blockquote>
</td>
</tr>
<tr>
<td>Flatpak</td>
<td>
<p><code>flatpak install com.github.KRTirtho.Spotube</code></p>
<a href="https://flathub.org/apps/details/com.github.KRTirtho.Spotube">
<img width="220" alt="Download on Flathub" src="https://flathub.org/assets/badges/flathub-badge-en.png">
</a>
</td>
</tr>
<tr>
<td>AppImage</td>
<td>AppImage's lacking stability led to it's temporary removal. More information at https://github.com/KRTirtho/spotube/issues/1082</td>
</tr>
<tr>
<td>Debian/Ubuntu</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.deb">
<img width="220" alt="Debian/Ubuntu Download" src="https://user-images.githubusercontent.com/61944859/169097994-e92aff78-fd75-4c93-b6e4-f072a4b5a7ed.png">
</a>
<p>Then run: <code>sudo apt install ./Spotube-linux-x86_64.deb</code></p>
</td>
</tr>
<tr>
<td>Arch/Manjaro</td>
<td>
<p>With pamac: <code>sudo pamac install spotube-bin</code></p>
<p>With yay: <code>yay -Sy spotube-bin</code></p>
</td>
</tr>
<tr>
<td>Fedora/OpenSuse</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.rpm">
<img width="220" alt="Fedora/OpenSuse Download" src="https://user-images.githubusercontent.com/61944859/223638350-5926b9da-04d6-4edd-931d-ad533e4ff058.png">
</a>
<p>For Fedora: <code>sudo dnf install ./Spotube-linux-x86_64.rpm</code></p>
<p>For OpenSuse: <code>sudo zypper in ./Spotube-linux-x86_64.rpm</code></p>
</td>
</tr>
<tr>
<td>Linux (tarball)</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest">
<img width="220" alt="Tarball Download" src="https://user-images.githubusercontent.com/61944859/169456985-e0ba1fd4-10e8-4cc0-ab94-337acc6e0295.png">
</a>
</td>
</tr>
<tr>
<td>Macos - <a href="https://brew.sh">Homebrew</a></td>
<td>
<pre lang="bash">
brew tap krtirtho/apps
brew install --cask spotube
</pre>
</td>
</tr>
<tr>
<td>Windows - <a href="https://chocolatey.org">Chocolatey</a></td>
<td>
<p><code>choco install spotube</code></p>
</td>
</tr>
<tr>
<td>Windows - <a href="https://scoop.sh">Scoop</a></td>
<td>
<p><code>scoop bucket add extras</code></p>
<p><code>scoop install spotube</code></p>
</td>
</tr>
<tr>
<td>Windows - <a href="https://github.com/microsoft/winget-cli">WinGet</a></td>
<td>
<p><code>winget install --id KRTirtho.Spotube</code></p>
</td>
</tr>
</table>
### 🔄 Nightly Builds
Grab the latest nightly builds of Spotube [from the GitHub Releases](https://github.com/KRTirtho/spotube/releases/tag/nightly).
## 🕳️ Building from source
<a href="https://github.com/KRTirtho/spotube/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/KRTirtho/spotube/spotube-release-binary.yml?+label=Build%20Status"></a>
You can compile Spotube's source code by [following these instructions](CONTRIBUTION.md#your-first-code-contribution).
## 👥 The Spotube team
- [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer
- [Owen Connor](https://github.com/owencz1998) - The Cool Discord Moderator
- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer
- [Rusty Apple](https://github.com/RustyApple) - The Mysterious Unknown Guy
## 💼 License
Spotube is open source and licensed under the [BSD-4-Clause](/LICENSE) License.
If you are concerned, you can [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
If you are curious, you can [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
<details>
<summary>
<h2><code>[Click to show]</code> 🙏 Services/Package/Plugin Credits</h2>
</summary>
### Services
1. [Flutter](https://flutter.dev) - Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase
1. [MPV](https://mpv.io) - mpv is a free (as in freedom) media player for the command line. It supports a wide variety of media file formats, audio and video codecs, and subtitle types.
1. [Musicbrainz](https://musicbrainz.org) - MusicBrainz is a MetaBrainz project that aims to create a collaborative music database that is similar to the freedb project.
1. [Listenbrainz](https://listenbrainz.org) - ListenBrainz is a open-source project by the MetaBrainz Foundation that allows users to crowdsource and publicly store their digital music listening data.
1. [Piped](https://piped-docs.kavin.rocks/) - Piped is a privacy friendly alternative YouTube frontend, which is efficient and scalable by design.
1. [Invidious](https://invidious.io/) - Invidious is an open source alternative front-end to YouTube.
1. [yt-dlp](https://github.com/yt-dlp/yt-dlp) - A feature-rich command-line audio/video downloader.
1. [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor) - NewPipe's core library for extracting data from streaming sites.
1. [SongLink](https://song.link) - SongLink is a free smart link service that helps you share music with your audience. It's a one-stop-shop for creating smart links for music, podcasts, and other audio content
1. [LRCLib](https://lrclib.net/) - A public synced lyric API.
1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users
1. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux
1. [SponsorBlock](https://sponsor.ajay.app) - SponsorBlock is an open-source crowdsourced browser extension and open API for skipping sponsor segments in YouTube videos.
1. [Inno Setup](https://jrsoftware.org/isinfo.php) - Inno Setup is a free installer for Windows programs by Jordan Russell and Martijn Laan
1. [F-Droid](https://f-droid.org) - F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device
1. [LastFM](https://last.fm) - Last.fm is a music streaming and discovery platform that helps users discover and share new music. It tracks users' music listening habits across many devices and platforms.
### Dependencies
1. [app_links](https://github.com/llfbandit/app_links) - Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).
1. [args](https://pub.dev/packages/args) - Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.
1. [async](https://pub.dev/packages/async) - Utility functions and classes related to the 'dart:async' library.
1. [audio_service](https://pub.dev/packages/audio_service) - Flutter plugin to play audio in the background while the screen is off.
1. [audio_service_mpris](https://github.com/bdrazhzhov/audio-service-mpris) - audio_service platform interface supporting Media Player Remote Interfacing Specification.
1. [audio_session](https://github.com/ryanheise/audio_session) - Sets the iOS audio session category and Android audio attributes for your app, and manages your app's audio focus, mixing and ducking behaviour.
1. [auto_route](https://github.com/Milad-Akarie/auto_route_library) - AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.
1. [auto_size_text](https://github.com/leisim/auto_size_text) - Flutter widget that automatically resizes text to fit perfectly within its bounds.
1. [bonsoir](https://bonsoir.skyost.eu) - A Zeroconf library that allows you to discover network services and to broadcast your own. Based on Apple Bonjour and Android NSD.
1. [cached_network_image](https://github.com/Baseflow/flutter_cached_network_image) - Flutter library to load and cache network images. Can also be used with placeholder and error widgets.
1. [connectivity_plus](https://github.com/fluttercommunity/plus_plugins) - Flutter plugin for discovering the state of the network (WiFi & mobile/cellular) connectivity on Android and iOS.
1. [device_info_plus](https://github.com/fluttercommunity/plus_plugins) - Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on.
1. [dio](https://github.com/cfug/dio) - A powerful HTTP networking package,supports Interceptors,Aborting and canceling a request,Custom adapters, Transformers, etc.
1. [drift](https://drift.simonbinder.eu/) - Drift is a reactive library to store relational data in Dart and Flutter applications.
1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration.
1. [encrypt](https://pub.dev/packages/encrypt) - A set of high-level APIs over PointyCastle for two-way cryptography.
1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
1. [file_picker](https://github.com/miguelpruivo/plugins_flutter_file_picker) - A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.
1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI.
1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft.
1. [flutter_cache_manager](https://github.com/Baseflow/flutter_cache_manager/tree/develop/flutter_cache_manager) - Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
1. [flutter_discord_rpc](https://pub.dev/packages/flutter_discord_rpc) - Discord RPC support for Flutter desktop platforms
1. [flutter_displaymode](https://github.com/ajinasokan/flutter_displaymode) - A Flutter plugin to set display mode (resolution, refresh rate) on Android platform. Allows to enable high refresh rate on supported devices.
1. [flutter_feather_icons](https://github.com/muj-programmer/flutter_feather_icons) - Feather is a collection of simply beautiful open source icons. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and usability.
1. [flutter_form_builder](https://github.com/flutter-form-builder-ecosystem) - This package helps in creation of forms in Flutter by removing the boilerplate code, reusing validation, react to changes, and collect final user input.
1. [flutter_hooks](https://github.com/rrousselGit/flutter_hooks) - A flutter implementation of React hooks. It adds a new kind of widget with enhanced code reuse.
1. [flutter_inappwebview](https://inappwebview.dev/) - A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
1. [flutter_native_splash](https://pub.dev/packages/flutter_native_splash) - Customize Flutter's default white native splash screen with background color and splash image. Supports dark mode, full screen, and more.
1. [flutter_riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
1. [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) - Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.
1. [flutter_sharing_intent](https://github.com/bhagat-techind/flutter_sharing_intent.git) - A flutter plugin that allow flutter apps to receive photos, videos, text, urls or any other file types from another app.
1. [flutter_undraw](https://github.com/KRTirtho/flutter_undraw) - Undraw.co Illustrations for Flutter with customization options
1. [form_builder_validators](https://github.com/flutter-form-builder-ecosystem) - Form Builder Validators set of validators for FlutterFormBuilder. Provides common validators and a way to make your own.
1. [form_validator](https://github.com/TheMisir/form-validator) - Simplest form validation library for flutter's form field widgets
1. [freezed_annotation](https://pub.dev/packages/freezed_annotation) - Annotations for the freezed code-generator. This package does nothing without freezed too.
1. [fuzzywuzzy](https://github.com/sphericalkat/dart-fuzzywuzzy) - An implementation of the popular fuzzywuzzy package in Dart, to suit all your fuzzy string matching/searching needs!
1. [gap](https://github.com/letsar/gap) - Flutter widgets for easily adding gaps inside Flex widgets such as Columns and Rows or scrolling views.
1. [google_fonts](https://pub.dev/packages/google_fonts) - A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.
1. [home_widget](https://pub.dev/packages/home_widget) - A plugin to provide a common interface for creating HomeScreen Widgets for Android and iOS.
1. [hooks_riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
1. [html](https://pub.dev/packages/html) - APIs for parsing and manipulating HTML content outside the browser.
1. [html_unescape](https://github.com/filiph/html_unescape) - A small library for un-escaping HTML. Supports all Named Character References, Decimal Character References and Hexadecimal Character References.
1. [http](https://pub.dev/packages/http) - A composable, multi-platform, Future-based API for HTTP requests.
1. [image_picker](https://pub.dev/packages/image_picker) - Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.
1. [intl](https://pub.dev/packages/intl) - Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
1. [invidious](https://pub.dev/packages/invidious) - Invidious API client for Dart and Flutter.
1. [jiosaavn](https://github.com/KRTirtho/jiosaavn) - Unofficial API client for jiosaavn.com
1. [json_annotation](https://pub.dev/packages/json_annotation) - Classes and helper functions that support JSON code generation via the `json_serializable` package.
1. [local_notifier](https://github.com/leanflutter/local_notifier) - This plugin allows Flutter desktop apps to displaying local notifications.
1. [logger](https://pub.dev/packages/logger) - Small, easy to use and extensible logger which prints beautiful logs.
1. [logging](https://pub.dev/packages/logging) - Provides APIs for debugging and error logging, similar to loggers in other languages, such as the Closure JS Logger and java.util.logging.Logger.
1. [lrc](https://pub.dev/packages/lrc) - A Dart-only package that creates, parses, and handles LRC, which is a format that stores song lyrics.
1. [media_kit](https://github.com/media-kit/media-kit) - A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.
1. [media_kit_libs_audio](https://github.com/media-kit/media-kit.git) - package:media_kit audio (only) playback native libraries for all platforms.
1. [metadata_god](https://pub.dev/packages/metadata_god) - Plugin for retrieving and writing audio tags/metadata from audio files
1. [mime](https://pub.dev/packages/mime) - Utilities for handling media (MIME) types, including determining a type from a file extension and file contents.
1. [open_file](https://pub.dev/packages/open_file) - A plug-in that can call native APP to open files with string result in flutter, support iOS(UTI) / android(intent) / PC(ffi) / web(dart:html)
1. [package_info_plus](https://github.com/fluttercommunity/plus_plugins) - Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.
1. [palette_generator](https://pub.dev/packages/palette_generator) - Flutter package for generating palette colors from a source image.
1. [path](https://pub.dev/packages/path) - A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.
1. [path_provider](https://pub.dev/packages/path_provider) - Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.
1. [permission_handler](https://pub.dev/packages/permission_handler) - Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
1. [piped_client](https://github.com/KRTirtho/piped_client) - API Client for piped.video
1. [riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
1. [scroll_to_index](https://github.com/quire-io/scroll-to-index) - Scroll to a specific child of any scrollable widget in Flutter
1. [shadcn_flutter](https://github.com/sunarya-thito/shadcn_flutter) - Beautifully designed components from Shadcn/UI is now available for Flutter
1. [shared_preferences](https://pub.dev/packages/shared_preferences) - Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.
1. [shelf](https://pub.dev/packages/shelf) - A model for web server middleware that encourages composition and easy reuse.
1. [shelf_router](https://pub.dev/packages/shelf_router) - A convenient request router for the shelf web-framework, with support for URL-parameters, nested routers and routers generated from source annotations.
1. [shelf_web_socket](https://pub.dev/packages/shelf_web_socket) - A shelf handler that wires up a listener for every connection.
1. [simple_icons](https://teavelopment.com/) - The Simple Icon pack available as Flutter Icons. Provides over 1500 Free SVG icons for popular brands.
1. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort.
1. [sliding_up_panel](https://github.com/akshathjain/sliding_up_panel) - A draggable Flutter widget that makes implementing a SlidingUpPanel much easier!
1. [sliver_tools](https://github.com/Kavantix) - A set of useful sliver tools that are missing from the flutter framework
1. [smtc_windows](https://pub.dev/packages/smtc_windows) - Windows `SystemMediaTransportControls` implementation for Flutter giving access to Windows OS Media Control applet.
1. [sqlite3](https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3) - Provides lightweight yet convenient bindings to SQLite by using dart:ffi
1. [sqlite3_flutter_libs](https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3_flutter_libs) - Flutter plugin to include native sqlite3 libraries with your app
1. [stroke_text](https://github.com/MohamedAbd0/stroke_text) - A Simple Flutter plugin for applying stroke (border) style to a text widget
1. [system_theme](https://github.com/bdlukaa/system_theme/tree/master/system_theme) - A plugin to get the current system theme info. Supports Android, Web, Windows, Linux and macOS
1. [test](https://pub.dev/packages/test) - A full featured library for writing and running Dart tests across platforms.
1. [timezone](https://pub.dev/packages/timezone) - Time zone database and time zone aware DateTime.
1. [titlebar_buttons](https://github.com/gtk-flutter/titlebar_buttons) - A package which provides most of the titlebar buttons from windows, linux and macos.
1. [tray_manager](https://github.com/leanflutter/tray_manager) - This plugin allows Flutter desktop apps to defines system tray.
1. [url_launcher](https://pub.dev/packages/url_launcher) - Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.
1. [uuid](https://pub.dev/packages/uuid) - RFC4122 (v1, v4, v5, v6, v7, v8) UUID Generator and Parser for Dart
1. [version](https://github.com/dartninja/version) - Provides a simple class for parsing and comparing semantic versions as defined by http://semver.org/
1. [very_good_infinite_list](https://github.com/VeryGoodOpenSource/very_good_infinite_list) - A library for easily displaying paginated data, created by Very Good Ventures. Great for activity feeds, news feeds, and more.
1. [visibility_detector](https://pub.dev/packages/visibility_detector) - A widget that detects the visibility of its child and notifies a callback.
1. [web_socket_channel](https://pub.dev/packages/web_socket_channel) - StreamChannel wrappers for WebSockets. Provides a cross-platform WebSocketChannel API, a cross-platform implementation of that API that communicates over an underlying StreamChannel.
1. [wikipedia_api](https://github.com/KRTirtho/wikipedia_api) - Wikipedia API for dart and flutter
1. [win32_registry](https://pub.dev/packages/win32_registry) - A package that provides a friendly Dart API for accessing the Windows Registry.
1. [window_manager](https://github.com/leanflutter/window_manager) - This plugin allows Flutter desktop apps to resizing and repositioning the window.
1. [youtube_explode_dart](https://github.com/Hexer10/youtube_explode_dart) - A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
1. [http_parser](https://pub.dev/packages/http_parser) - A platform-independent package for parsing and serializing HTTP formats.
1. [collection](https://pub.dev/packages/collection) - Collections and utilities functions and classes related to collections.
1. [otp_util](https://github.com/dushiling) - otp_util is a dart package to generate and verify one-time passwords,it It provides two methods TOPT and HOTP.They are Time-based OTPs and Counter-based OTPs.
1. [dio_http2_adapter](https://github.com/cfug/dio) - An adapter that combines HTTP/2 and dio. Supports reusing connections, header compression, etc.
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
1. [flutter_gen_runner](https://github.com/FlutterGen/flutter_gen) - The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
1. [flutter_launcher_icons](https://github.com/fluttercommunity/flutter_launcher_icons) - A package which simplifies the task of updating your Flutter app's launcher icon.
1. [flutter_lints](https://pub.dev/packages/flutter_lints) - Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
1. [freezed](https://pub.dev/packages/freezed) - Code generation for immutable classes that has a simple syntax/API without compromising on the features.
1. [process_run](https://github.com/tekartik/process_run.dart/blob/master/packages/process_run) - Process run helpers for Linux/Win/Mac and which like feature for finding executables.
1. [pubspec_parse](https://pub.dev/packages/pubspec_parse) - Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.
1. [pub_api_client](https://github.com/leoafarias/pub_api_client) - An API Client for Pub to interact with public package information.
1. [xml](https://github.com/renggli/dart-xml) - A lightweight library for parsing, traversing, querying, transforming and building XML documents.
1. [io](https://pub.dev/packages/io) - Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values.
1. [drift_dev](https://drift.simonbinder.eu/) - Dev-dependency for users of drift. Contains the generator and development tools.
1. [auto_route_generator](https://github.com/Milad-Akarie/auto_route_library) - AutoRoute is a declarative routing solution, where everything needed for navigation is automatically generated for you.
1. [desktop_webview_window](https://github.com/MixinNetwork/flutter-plugins/tree/main/packages/desktop_webview_window) - Show a webview window on your flutter desktop application.
1. [disable_battery_optimization](https://github.com/pvsvamsi/Disable-Battery-Optimizations) - Flutter plugin to check and disable battery optimizations. Also shows custom steps to disable the optimizations in devices like mi, xiaomi, samsung, oppo, huawei, oneplus etc
1. [draggable_scrollbar](https://github.com/fluttercommunity/flutter-draggable-scrollbar) - A scrollbar that can be dragged for quickly navigation through a vertical list. Additional option is showing label next to scrollthumb with information about current item.
1. [flutter_broadcasts](https://github.com/KRTirtho/flutter_broadcasts.git) - A plugin for sending and receiving broadcasts with Android intents and iOS notifications.
1. [scrobblenaut](https://github.com/Nebulino/Scrobblenaut) - A deadly simple LastFM API Wrapper for Dart. So deadly simple that it's gonna hit the mark.
1. [yt_dlp_dart](https://github.com/KRTirtho/yt_dlp_dart.git) - yt-dlp binding in Dart
1. [flutter_new_pipe_extractor](https://github.com/KRTirtho/flutter_new_pipe_extractor) - NewPipeExtractor binding for Flutter (Android only)
</details>
<div align="center"><h4>© Copyright Spotube 2025</h4></div>

View File

@ -33,7 +33,7 @@ def composeVersion = "1.4.8"
android {
namespace "oss.krtirtho.spotube"
compileSdkVersion 35
compileSdkVersion 36
ndkVersion = "27.0.12077973"

View File

@ -1,6 +1,6 @@
{
"title": "Spotube",
"icon": "assets/spotube-logo-macos.png",
"icon": "assets/branding/spotube-logo-macos.png",
"contents": [
{
"x": 448,
@ -15,4 +15,4 @@
"path": "build/macos/Build/Products/Release/Spotube.app"
}
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 790 KiB

After

Width:  |  Height:  |  Size: 790 KiB

View File

Before

Width:  |  Height:  |  Size: 771 KiB

After

Width:  |  Height:  |  Size: 771 KiB

View File

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

View File

Before

Width:  |  Height:  |  Size: 435 KiB

After

Width:  |  Height:  |  Size: 435 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

View File

Before

Width:  |  Height:  |  Size: 351 KiB

After

Width:  |  Height:  |  Size: 351 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

Before

Width:  |  Height:  |  Size: 241 KiB

After

Width:  |  Height:  |  Size: 241 KiB

View File

Before

Width:  |  Height:  |  Size: 531 KiB

After

Width:  |  Height:  |  Size: 531 KiB

View File

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

View File

Before

Width:  |  Height:  |  Size: 396 KiB

After

Width:  |  Height:  |  Size: 396 KiB

View File

Before

Width:  |  Height:  |  Size: 1006 KiB

After

Width:  |  Height:  |  Size: 1006 KiB

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 383 KiB

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

View File

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

View File

@ -15,7 +15,7 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
<authors>Kingkor Roy Tirtho</authors>
<projectUrl>https://spotube.krtirtho.dev</projectUrl>
<iconUrl>
https://rawcdn.githack.com/KRTirtho/spotube/7edb0bb834eb18c05551e30a891720a6abf53dbe/assets/spotube-logo.png</iconUrl>
https://rawcdn.githack.com/KRTirtho/spotube/7edb0bb834eb18c05551e30a891720a6abf53dbe/assets/branding/spotube-logo.png</iconUrl>
<copyright>2022 Spotube</copyright>
<!-- If there is a license Url available, it is required for the community feed -->
<licenseUrl>https://github.com/KRTirtho/spotube/blob/master/LICENSE</licenseUrl>

View File

@ -74,7 +74,7 @@ class LinuxBuildCommand extends Command with BuildCommandCommonSteps {
).copy(
join(tempDir, "com.github.KRTirtho.Spotube.appdata.xml"),
);
await File(join(cwd.path, "assets", "spotube-logo.png")).copy(
await File(join(cwd.path, "assets", "branding", "spotube-logo.png")).copy(
join(tempDir, "spotube-logo.png"),
);

92
cliff.toml Normal file
View File

@ -0,0 +1,92 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
# A Tera template to be rendered for each release in the changelog.
# See https://keats.github.io/tera/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}](<REPO>/compare/v{{ previous.version | trim_start_matches(pat="v") }}...v{{ version | trim_start_matches(pat="v") }}) ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}**: {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}
"""
# Remove leading and trailing whitespaces from the changelog's body.
trim = true
# Render body even when there are no releases to process.
render_always = true
# An array of regex based postprocessors to modify the changelog.
postprocessors = [
# Replace the placeholder <REPO> with a URL.
{ pattern = '<REPO>', replace = "https://github.com/KRTirtho/spotube" },
]
# render body even when there are no releases to process
# render_always = true
# output file path
# output = "test.md"
[git]
# Parse commits according to the conventional commits specification.
# See https://www.conventionalcommits.org
conventional_commits = true
# Exclude commits that do not match the conventional commits specification.
filter_unconventional = true
# Require all commits to be conventional.
# Takes precedence over filter_unconventional.
require_conventional = false
# Split commits on newlines, treating each line as an individual commit.
split_commits = false
# An array of regex based parsers to modify commit messages prior to further processing.
commit_preprocessors = [
# Replace issue numbers with link templates to be updated in `changelog.postprocessors`.
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))" },
# Check spelling of the commit message using https://github.com/crate-ci/typos.
# If the spelling is incorrect, it will be fixed automatically.
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
]
# Prevent commits that are breaking from being excluded by commit parsers.
protect_breaking_commits = false
# An array of regex based parsers for extracting data from the commit message.
# Assigns commits to groups.
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->Features" },
{ message = "^fix", group = "<!-- 1 -->Bug Fixes" },
# { message = "^doc", group = "<!-- 3 -->📚 Documentation" },
# { message = "^perf", group = "<!-- 4 -->⚡ Performance" },
# { message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
# { message = "^style", group = "<!-- 5 -->🎨 Styling" },
# { message = "^test", group = "<!-- 6 -->🧪 Testing" },
# { message = "^chore\\(release\\): prepare for", skip = true },
# { message = "^chore\\(deps.*\\)", skip = true },
# { message = "^chore\\(pr\\)", skip = true },
# { message = "^chore\\(pull\\)", skip = true },
# { message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
# { body = ".*security", group = "<!-- 8 -->🛡️ Security" },
# { message = "^revert", group = "<!-- 9 -->◀️ Revert" },
# { message = ".*", group = "<!-- 10 -->💼 Other" },
]
# Exclude commits that are not matched by any commit parser.
filter_commits = true
# An array of link parsers for extracting external references, and turning them into URLs, using regex.
link_parsers = []
# Include only the tags that belong to the current branch.
use_branch_tags = false
# Order releases topologically instead of chronologically.
topo_order = false
# Order releases topologically instead of chronologically.
topo_order_commits = true
# Order of commits in each group/release within the changelog.
# Allowed values: newest, oldest
sort_commits = "oldest"
# Process submodules commits
recurse_submodules = false

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/spotube-nightly-logo.png"
adaptive_icon_foreground: "assets/spotube-nightly-logo-foreground.png"
image_path: "assets/branding/spotube-nightly-logo.png"
adaptive_icon_foreground: "assets/branding/spotube-nightly-logo-foreground.png"
adaptive_icon_background: "#242832"

View File

@ -1,19 +1,19 @@
# flutter pub run flutter_launcher_icons
flutter_launcher_icons:
image_path: "assets/spotube-logo.png"
image_path: "assets/branding/spotube-logo.png"
android: true
# image_path_android: "assets/icon/icon.png"
# image_path_android: "assets/branding/icon/icon.png"
min_sdk_android: 21 # android min sdk min:16, default 21
adaptive_icon_background: "#242832"
adaptive_icon_foreground: "assets/spotube-logo-foreground.png"
# adaptive_icon_monochrome: "assets/icon/monochrome.png"
adaptive_icon_foreground: "assets/branding/spotube-logo-foreground.png"
# adaptive_icon_monochrome: "assets/branding/icon/monochrome.png"
ios: true
# image_path_ios: "assets/icon/icon.png"
# image_path_ios: "assets/branding/icon/icon.png"
remove_alpha_channel_ios: true
# image_path_ios_dark_transparent: "assets/icon/icon_dark.png"
# image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png"
# image_path_ios_dark_transparent: "assets/branding/icon/icon_dark.png"
# image_path_ios_tinted_grayscale: "assets/branding/icon/icon_tinted.png"
# desaturate_tinted_to_grayscale_ios: true
web:
@ -21,9 +21,9 @@ flutter_launcher_icons:
windows:
generate: true
image_path: "assets/spotube-logo.png"
image_path: "assets/branding/spotube-logo.png"
icon_size: 48 # min:48, max:256, default: 48
macos:
generate: true
image_path: "assets/spotube-logo-macos.png"
image_path: "assets/branding/spotube-logo-macos.png"

View File

@ -1,9 +1,9 @@
flutter_native_splash:
background_image: assets/bengali-patterns-bg.jpg
image: assets/spotube-nightly-logo.png
branding: assets/branding.png
background_image: assets/images/bengali-patterns-bg.jpg
image: assets/branding/spotube-nightly-logo.png
branding: assets/branding/branding.png
android_12:
image: assets/spotube-nightly-logo_android12.png
branding: assets/branding.png
image: assets/branding/spotube-nightly-logo_android12.png
branding: assets/branding/branding.png
color: "#000000"
icon_background_color: "#000000"

View File

@ -2,4 +2,3 @@ arb-dir: lib/l10n
template-arb-file: app_en.arb
output-dir: lib/l10n/generated
untranslated-messages-file: untranslated_messages.json
synthetic-package: false

View File

@ -9,220 +9,89 @@
import 'package:flutter/widgets.dart';
class $AssetsBackgroundsGen {
const $AssetsBackgroundsGen();
class $AssetsBrandingGen {
const $AssetsBrandingGen();
/// File path: assets/backgrounds/xmas-effect.png
AssetGenImage get xmasEffect =>
const AssetGenImage('assets/backgrounds/xmas-effect.png');
/// File path: assets/branding/spotube-logo-light.png
AssetGenImage get spotubeLogoLight =>
const AssetGenImage('assets/branding/spotube-logo-light.png');
/// File path: assets/branding/spotube-logo.ico
String get spotubeLogoIco => 'assets/branding/spotube-logo.ico';
/// File path: assets/branding/spotube-logo.png
AssetGenImage get spotubeLogoPng =>
const AssetGenImage('assets/branding/spotube-logo.png');
/// List of all assets
List<AssetGenImage> get values => [xmasEffect];
List<dynamic> get values =>
[spotubeLogoLight, spotubeLogoIco, spotubeLogoPng];
}
class $AssetsLogosGen {
const $AssetsLogosGen();
class $AssetsImagesGen {
const $AssetsImagesGen();
/// File path: assets/logos/songlink-transparent.png
AssetGenImage get songlinkTransparent =>
const AssetGenImage('assets/logos/songlink-transparent.png');
/// File path: assets/images/album-placeholder.png
AssetGenImage get albumPlaceholder =>
const AssetGenImage('assets/images/album-placeholder.png');
/// File path: assets/logos/songlink.png
AssetGenImage get songlink =>
const AssetGenImage('assets/logos/songlink.png');
/// File path: assets/images/bengali-patterns-bg.jpg
AssetGenImage get bengaliPatternsBg =>
const AssetGenImage('assets/images/bengali-patterns-bg.jpg');
/// List of all assets
List<AssetGenImage> get values => [songlinkTransparent, songlink];
}
/// File path: assets/images/liked-tracks.jpg
AssetGenImage get likedTracks =>
const AssetGenImage('assets/images/liked-tracks.jpg');
class $AssetsPatternsGen {
const $AssetsPatternsGen();
/// Directory path: assets/images/logos
$AssetsImagesLogosGen get logos => const $AssetsImagesLogosGen();
/// File path: assets/patterns/black_white_visualized.jpg
AssetGenImage get blackWhiteVisualized =>
const AssetGenImage('assets/patterns/black_white_visualized.jpg');
/// File path: assets/images/placeholder.png
AssetGenImage get placeholder =>
const AssetGenImage('assets/images/placeholder.png');
/// File path: assets/patterns/brazil_carnival.jpg
AssetGenImage get brazilCarnival =>
const AssetGenImage('assets/patterns/brazil_carnival.jpg');
/// File path: assets/patterns/cotton_balls.jpg
AssetGenImage get cottonBalls =>
const AssetGenImage('assets/patterns/cotton_balls.jpg');
/// File path: assets/patterns/cute_worms.jpg
AssetGenImage get cuteWorms =>
const AssetGenImage('assets/patterns/cute_worms.jpg');
/// File path: assets/patterns/flash_cross_axis.jpg
AssetGenImage get flashCrossAxis =>
const AssetGenImage('assets/patterns/flash_cross_axis.jpg');
/// File path: assets/patterns/memphis_shapes.jpg
AssetGenImage get memphisShapes =>
const AssetGenImage('assets/patterns/memphis_shapes.jpg');
/// File path: assets/patterns/oval_gloomy.jpg
AssetGenImage get ovalGloomy =>
const AssetGenImage('assets/patterns/oval_gloomy.jpg');
/// File path: assets/patterns/oval_sunny.jpg
AssetGenImage get ovalSunny =>
const AssetGenImage('assets/patterns/oval_sunny.jpg');
/// File path: assets/patterns/red_nimbuses.jpg
AssetGenImage get redNimbuses =>
const AssetGenImage('assets/patterns/red_nimbuses.jpg');
/// File path: assets/patterns/tree_bark.jpg
AssetGenImage get treeBark =>
const AssetGenImage('assets/patterns/tree_bark.jpg');
/// File path: assets/patterns/vibrant_pentagons.jpg
AssetGenImage get vibrantPentagons =>
const AssetGenImage('assets/patterns/vibrant_pentagons.jpg');
/// File path: assets/patterns/wiring_pattern.jpg
AssetGenImage get wiringPattern =>
const AssetGenImage('assets/patterns/wiring_pattern.jpg');
/// File path: assets/patterns/zigzags_gloomy.jpg
AssetGenImage get zigzagsGloomy =>
const AssetGenImage('assets/patterns/zigzags_gloomy.jpg');
/// File path: assets/patterns/zigzags_sunny.jpg
AssetGenImage get zigzagsSunny =>
const AssetGenImage('assets/patterns/zigzags_sunny.jpg');
/// File path: assets/images/user-placeholder.png
AssetGenImage get userPlaceholder =>
const AssetGenImage('assets/images/user-placeholder.png');
/// List of all assets
List<AssetGenImage> get values => [
blackWhiteVisualized,
brazilCarnival,
cottonBalls,
cuteWorms,
flashCrossAxis,
memphisShapes,
ovalGloomy,
ovalSunny,
redNimbuses,
treeBark,
vibrantPentagons,
wiringPattern,
zigzagsGloomy,
zigzagsSunny
albumPlaceholder,
bengaliPatternsBg,
likedTracks,
placeholder,
userPlaceholder
];
}
class $AssetsTutorialGen {
const $AssetsTutorialGen();
class $AssetsImagesLogosGen {
const $AssetsImagesLogosGen();
/// File path: assets/tutorial/step-1.png
AssetGenImage get step1 => const AssetGenImage('assets/tutorial/step-1.png');
/// File path: assets/images/logos/invidious.jpg
AssetGenImage get invidious =>
const AssetGenImage('assets/images/logos/invidious.jpg');
/// File path: assets/tutorial/step-2.png
AssetGenImage get step2 => const AssetGenImage('assets/tutorial/step-2.png');
/// File path: assets/images/logos/jiosaavn.png
AssetGenImage get jiosaavn =>
const AssetGenImage('assets/images/logos/jiosaavn.png');
/// File path: assets/tutorial/step-3.png
AssetGenImage get step3 => const AssetGenImage('assets/tutorial/step-3.png');
/// File path: assets/images/logos/songlink-transparent.png
AssetGenImage get songlinkTransparent =>
const AssetGenImage('assets/images/logos/songlink-transparent.png');
/// List of all assets
List<AssetGenImage> get values => [step1, step2, step3];
List<AssetGenImage> get values => [invidious, jiosaavn, songlinkTransparent];
}
class Assets {
Assets._();
static const String license = 'LICENSE';
static const AssetGenImage albumPlaceholder =
AssetGenImage('assets/album-placeholder.png');
static const $AssetsBackgroundsGen backgrounds = $AssetsBackgroundsGen();
static const AssetGenImage bengaliPatternsBg =
AssetGenImage('assets/bengali-patterns-bg.jpg');
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
static const AssetGenImage invidious = AssetGenImage('assets/invidious.jpg');
static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png');
static const AssetGenImage likedTracks =
AssetGenImage('assets/liked-tracks.jpg');
static const $AssetsLogosGen logos = $AssetsLogosGen();
static const $AssetsPatternsGen patterns = $AssetsPatternsGen();
static const AssetGenImage placeholder =
AssetGenImage('assets/placeholder.png');
static const AssetGenImage spotubeHeroBanner =
AssetGenImage('assets/spotube-hero-banner.png');
static const AssetGenImage spotubeLogoForeground =
AssetGenImage('assets/spotube-logo-foreground.png');
static const AssetGenImage spotubeLogoItem =
AssetGenImage('assets/spotube-logo-item.png');
static const AssetGenImage spotubeLogoLight =
AssetGenImage('assets/spotube-logo-light.png');
static const AssetGenImage spotubeLogoMacos =
AssetGenImage('assets/spotube-logo-macos.png');
static const AssetGenImage spotubeLogoBmp =
AssetGenImage('assets/spotube-logo.bmp');
static const String spotubeLogoIco = 'assets/spotube-logo.ico';
static const AssetGenImage spotubeLogoPng =
AssetGenImage('assets/spotube-logo.png');
static const AssetGenImage spotubeLogoAndroid12 =
AssetGenImage('assets/spotube-logo_android12.png');
static const AssetGenImage spotubeNightlyItem =
AssetGenImage('assets/spotube-nightly-item.png');
static const AssetGenImage spotubeNightlyLogoForegroundPng =
AssetGenImage('assets/spotube-nightly-logo-foreground.png');
static const String spotubeNightlyLogoForegroundSvg =
'assets/spotube-nightly-logo-foreground.svg';
static const AssetGenImage spotubeNightlyLogo =
AssetGenImage('assets/spotube-nightly-logo.png');
static const AssetGenImage spotubeNightlyLogoAndroid12 =
AssetGenImage('assets/spotube-nightly-logo_android12.png');
static const AssetGenImage spotubeScreenshot =
AssetGenImage('assets/spotube-screenshot.png');
static const AssetGenImage spotubeTallCapsule =
AssetGenImage('assets/spotube-tall-capsule.png');
static const AssetGenImage spotubeWideCapsuleLarge =
AssetGenImage('assets/spotube-wide-capsule-large.png');
static const AssetGenImage spotubeWideCapsuleSmall =
AssetGenImage('assets/spotube-wide-capsule-small.png');
static const AssetGenImage spotubeBanner =
AssetGenImage('assets/spotube_banner.png');
static const AssetGenImage success = AssetGenImage('assets/success.png');
static const $AssetsTutorialGen tutorial = $AssetsTutorialGen();
static const AssetGenImage userPlaceholder =
AssetGenImage('assets/user-placeholder.png');
static const $AssetsBrandingGen branding = $AssetsBrandingGen();
static const $AssetsImagesGen images = $AssetsImagesGen();
/// List of all assets
static List<dynamic> get values => [
license,
albumPlaceholder,
bengaliPatternsBg,
branding,
emptyBox,
invidious,
jiosaavn,
likedTracks,
placeholder,
spotubeHeroBanner,
spotubeLogoForeground,
spotubeLogoItem,
spotubeLogoLight,
spotubeLogoMacos,
spotubeLogoBmp,
spotubeLogoIco,
spotubeLogoPng,
spotubeLogoAndroid12,
spotubeNightlyItem,
spotubeNightlyLogoForegroundPng,
spotubeNightlyLogoForegroundSvg,
spotubeNightlyLogo,
spotubeNightlyLogoAndroid12,
spotubeScreenshot,
spotubeTallCapsule,
spotubeWideCapsuleLarge,
spotubeWideCapsuleSmall,
spotubeBanner,
success,
userPlaceholder
];
static List<String> get values => [license];
}
class AssetGenImage {

View File

@ -18,4 +18,7 @@ class FontFamily {
/// Font family: RadixIcons
static const String radixIcons = 'RadixIcons';
/// Font family: Ubuntu Mono
static const String ubuntuMono = 'Ubuntu Mono';
}

View File

@ -133,10 +133,14 @@ abstract class LanguageLocals {
// name: "Chichewa",
// nativeName: "chiCheŵa",
// ),
"zh": const ISOLanguageName(
"zh_CN": const ISOLanguageName(
name: "Simplified Chinese",
nativeName: "简体中文",
),
"zh_TW": const ISOLanguageName(
name: "Traditional Chinese",
nativeName: "繁體中文(台灣)",
),
// "cv": const ISOLanguageName(
// name: "Chuvash",
// nativeName: "чӑваш чӗлхи",
@ -747,9 +751,13 @@ abstract class LanguageLocals {
// )
};
static ISOLanguageName getDisplayLanguage(key) {
static ISOLanguageName getDisplayLanguage(String key, String? countryCode) {
if (isoLangs.containsKey(key)) {
return isoLangs[key]!;
} else if (countryCode != null &&
countryCode.isNotEmpty &&
isoLangs.containsKey("${key}_$countryCode")) {
return isoLangs["${key}_$countryCode"]!;
} else {
throw Exception("Language key incorrect");
}

View File

@ -60,8 +60,8 @@ class TrackDetailsDialog extends HookConsumerWidget {
context.l10n.channel: Text(sourceInfo.artists),
if (sourcedTrack.asData?.value.url != null)
context.l10n.streamUrl: Hyperlink(
sourcedTrack.asData!.value.url,
sourcedTrack.asData!.value.url,
sourcedTrack.asData!.value.url ?? "",
sourcedTrack.asData!.value.url ?? "",
maxLines: 2,
overflow: TextOverflow.ellipsis,
),

View File

@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/context.dart';
class ErrorBox extends StatelessWidget {
final Object error;
@ -27,10 +28,10 @@ class ErrorBox extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: [
const Basic(
leading: Icon(SpotubeIcons.error),
Basic(
leading: const Icon(SpotubeIcons.error),
contentSpacing: 8,
title: Text("An error occurred"),
title: Text(context.l10n.an_error_occurred),
),
Card(
padding: const EdgeInsets.all(8.0),
@ -70,7 +71,7 @@ class ErrorBox extends StatelessWidget {
spacing: 8,
children: [
const Icon(SpotubeIcons.logs),
const Text("Logs"),
Text(context.l10n.logs),
const Spacer(),
IconButton.ghost(
icon: const Icon(SpotubeIcons.close),
@ -86,7 +87,7 @@ class ErrorBox extends StatelessWidget {
leading: copied.value
? const Icon(SpotubeIcons.done)
: const Icon(SpotubeIcons.clipboard),
child: const Text("Copy to clipboard"),
child: Text(context.l10n.copy_to_clipboard),
onPressed: () {
Clipboard.setData(
ClipboardData(text: error.toString()),
@ -118,13 +119,13 @@ class ErrorBox extends StatelessWidget {
},
);
},
child: const Text("View logs"),
child: Text(context.l10n.view_logs),
),
if (onRetry != null)
Button.text(
leading: const Icon(SpotubeIcons.refresh),
onPressed: onRetry,
child: const Text("Retry"),
child: Text(context.l10n.retry),
),
],
),

View File

@ -5,6 +5,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/routes.gr.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/context.dart';
class NoDefaultMetadataPlugin extends StatelessWidget {
const NoDefaultMetadataPlugin({super.key});
@ -23,13 +24,13 @@ class NoDefaultMetadataPlugin extends StatelessWidget {
color: context.theme.colorScheme.primary,
),
AutoSizeText(
"You've no default metadata provider set",
context.l10n.no_default_metadata_provider_selected,
style: context.theme.typography.h4,
maxLines: 1,
),
Button.primary(
leading: const Icon(SpotubeIcons.extensions),
child: const Text("Manage metadata providers"),
child: Text(context.l10n.manage_metadata_providers),
onPressed: () {
context.pushRoute(const SettingsMetadataProviderRoute());
},

View File

@ -38,6 +38,7 @@ class HeartButton extends HookConsumerWidget {
child: IconButton(
variance: variance,
size: size,
enabled: onPressed != null,
icon: AnimatedSwitcher(
switchInCurve: Curves.fastOutSlowIn,
switchOutCurve: Curves.fastOutSlowIn,
@ -74,7 +75,8 @@ class TrackHeartButton extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final savedTracks = ref.watch(metadataPluginSavedTracksProvider);
final me = ref.watch(metadataPluginUserProvider);
final (:isLiked, :toggleTrackLike) = useTrackToggleLike(track, ref);
final (:isLiked, :isLoading, :toggleTrackLike) =
useTrackToggleLike(track, ref);
if (me.isLoading) {
return const CircularProgressIndicator();
@ -85,11 +87,11 @@ class TrackHeartButton extends HookConsumerWidget {
? context.l10n.remove_from_favorites
: context.l10n.save_as_favorite,
isLiked: isLiked,
onPressed: savedTracks.asData?.value != null
? () {
onPressed: savedTracks.asData?.value == null || isLoading
? null
: () {
toggleTrackLike(track);
}
: null,
},
);
}
}

View File

@ -4,6 +4,7 @@ import 'package:spotube/provider/metadata_plugin/library/tracks.dart';
typedef UseTrackToggleLike = ({
bool isLiked,
bool isLoading,
Future<void> Function(SpotubeTrackObject track) toggleTrackLike,
});
@ -11,12 +12,11 @@ UseTrackToggleLike useTrackToggleLike(SpotubeTrackObject track, WidgetRef ref) {
final savedTracksNotifier =
ref.watch(metadataPluginSavedTracksProvider.notifier);
final isSavedTrack = ref.watch(
metadataPluginIsSavedTrackProvider(track.id),
);
final isSavedTrack = ref.watch(metadataPluginIsSavedTrackProvider(track.id));
return (
isLiked: isSavedTrack.asData?.value ?? false,
isLoading: isSavedTrack.isLoading,
toggleTrackLike: (track) async {
final isLikedTrack = await ref.read(
metadataPluginIsSavedTrackProvider(track.id).future,

View File

@ -14,6 +14,7 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart';
class HorizontalPlaybuttonCardView<T> extends HookWidget {
final Widget title;
final List<T> items;
final Widget? error;
final VoidCallback onFetchMore;
final bool isLoadingNextPage;
final bool hasNextPage;
@ -26,6 +27,7 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
required this.onFetchMore,
required this.isLoadingNextPage,
this.titleTrailing,
this.error,
super.key,
}) : assert(
items.every(
@ -64,54 +66,57 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
if (titleTrailing != null) titleTrailing!,
],
),
SizedBox(
height: isArtist ? 250 : 225,
child: NotificationListener(
// disable multiple scrollbar to use this
onNotification: (notification) => true,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: PointerDeviceKind.values.toSet(),
),
child: items.isEmpty
? ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 5,
itemBuilder: (context, index) {
return AlbumCard(FakeData.albumSimple);
},
)
: InfiniteList(
scrollController: scrollController,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(vertical: 8.0),
itemCount: items.length,
onFetchData: onFetchMore,
loadingBuilder: (context) => Skeletonizer(
enabled: true,
child: isArtist
? ArtistCard(FakeData.artist)
: AlbumCard(FakeData.albumSimple),
),
isLoading: isLoadingNextPage,
hasReachedMax: !hasNextPage,
separatorBuilder: (context, index) => Gap(12 * scale),
itemBuilder: (context, index) {
final item = items[index];
if (error != null)
error!
else
SizedBox(
height: isArtist ? 250 : 225,
child: NotificationListener(
// disable multiple scrollbar to use this
onNotification: (notification) => true,
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: PointerDeviceKind.values.toSet(),
),
child: items.isEmpty
? ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 5,
itemBuilder: (context, index) {
return AlbumCard(FakeData.albumSimple);
},
)
: InfiniteList(
scrollController: scrollController,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(vertical: 8.0),
itemCount: items.length,
onFetchData: onFetchMore,
loadingBuilder: (context) => Skeletonizer(
enabled: true,
child: isArtist
? ArtistCard(FakeData.artist)
: AlbumCard(FakeData.albumSimple),
),
isLoading: isLoadingNextPage,
hasReachedMax: !hasNextPage,
separatorBuilder: (context, index) => Gap(12 * scale),
itemBuilder: (context, index) {
final item = items[index];
return switch (item) {
SpotubeSimplePlaylistObject() =>
PlaylistCard(item as SpotubeSimplePlaylistObject),
SpotubeSimpleAlbumObject() =>
AlbumCard(item as SpotubeSimpleAlbumObject),
SpotubeFullArtistObject() =>
ArtistCard(item as SpotubeFullArtistObject),
_ => const SizedBox.shrink(),
};
}),
return switch (item) {
SpotubeSimplePlaylistObject() => PlaylistCard(
item as SpotubeSimplePlaylistObject),
SpotubeSimpleAlbumObject() =>
AlbumCard(item as SpotubeSimpleAlbumObject),
SpotubeFullArtistObject() =>
ArtistCard(item as SpotubeFullArtistObject),
_ => const SizedBox.shrink(),
};
}),
),
),
),
),
],
),
);

View File

@ -58,10 +58,10 @@ class UniversalImage extends HookWidget {
),
height: height,
width: width,
placeholder: AssetImage(placeholder ?? Assets.placeholder.path),
placeholder: AssetImage(placeholder ?? Assets.images.placeholder.path),
imageErrorBuilder: (context, error, stackTrace) {
return Image.asset(
placeholder ?? Assets.placeholder.path,
placeholder ?? Assets.images.placeholder.path,
width: width,
height: height,
cacheHeight: height?.toInt(),
@ -82,7 +82,7 @@ class UniversalImage extends HookWidget {
fit: fit,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
placeholder ?? Assets.placeholder.path,
placeholder ?? Assets.images.placeholder.path,
width: width,
height: height,
cacheHeight: height?.toInt(),
@ -102,7 +102,7 @@ class UniversalImage extends HookWidget {
fit: fit,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
placeholder ?? Assets.placeholder.path,
placeholder ?? Assets.images.placeholder.path,
width: width,
height: height,
cacheHeight: height?.toInt(),
@ -123,7 +123,7 @@ class UniversalImage extends HookWidget {
fit: fit,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
placeholder ?? Assets.placeholder.path,
placeholder ?? Assets.images.placeholder.path,
width: width,
height: height,
cacheHeight: height?.toInt(),

View File

@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/context.dart';
import 'package:url_launcher/url_launcher_string.dart';
class AppMarkdown extends StatelessWidget {
@ -30,36 +31,33 @@ class AppMarkdown extends StatelessWidget {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 450),
child: AlertDialog(
title: const Row(
title: Row(
spacing: 8,
children: [
Icon(SpotubeIcons.warning),
Text("Open Link in Browser?"),
const Icon(SpotubeIcons.warning),
Text(context.l10n.open_link_in_browser),
],
),
content: Text.rich(
TextSpan(
children: [
const TextSpan(
text: "Do you want to open the following link:\n",
TextSpan(
text:
"${context.l10n.do_you_want_to_open_the_following_link}:\n",
),
if (href != null)
TextSpan(
text: "$href\n\n",
style: const TextStyle(color: Colors.blue),
),
const TextSpan(
text:
"It can be unsafe to open links from untrusted sources. Be cautious!\n"
"You can also copy the link to your clipboard.",
),
TextSpan(text: context.l10n.unsafe_url_warning),
],
),
),
actions: [
Button.ghost(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
child: Text(context.l10n.cancel),
),
Button.ghost(
onPressed: () {
@ -68,7 +66,7 @@ class AppMarkdown extends StatelessWidget {
}
Navigator.of(context).pop(false);
},
child: const Text("Copy Link"),
child: Text(context.l10n.copy_link),
),
Button.destructive(
onPressed: () {
@ -80,7 +78,7 @@ class AppMarkdown extends StatelessWidget {
}
Navigator.of(context).pop(true);
},
child: const Text("Open"),
child: Text(context.l10n.open),
),
],
),

View File

@ -6,6 +6,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/components/fallbacks/error_box.dart';
import 'package:spotube/components/track_presentation/presentation_props.dart';
import 'package:spotube/components/track_presentation/presentation_state.dart';
import 'package:spotube/components/track_presentation/use_track_tile_play_callback.dart';
@ -30,6 +31,19 @@ class PresentationListSection extends HookConsumerWidget {
final onTileTap = useTrackTilePlayCallback(ref);
if (state.presentationTracks.isEmpty && !options.pagination.isLoading) {
if (options.error != null) {
return SliverToBoxAdapter(
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ErrorBox(
error: options.error!,
onRetry: options.pagination.onRefresh,
),
),
),
);
}
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(8.0),

View File

@ -50,6 +50,7 @@ class TrackPresentationOptions {
final PaginationProps pagination;
final bool isLiked;
final String? shareUrl;
final Object? error;
// events
final FutureOr<bool?> Function()? onHeart; // if null heart button will hidden
@ -67,6 +68,7 @@ class TrackPresentationOptions {
this.shareUrl,
this.isLiked = false,
this.onHeart,
this.error,
}) : assert(collection is SpotubeSimpleAlbumObject ||
collection is SpotubeSimplePlaylistObject);
@ -90,7 +92,8 @@ class TrackPresentationOptions {
other.pagination == pagination &&
other.isLiked == isLiked &&
other.shareUrl == shareUrl &&
other.onHeart == onHeart;
other.onHeart == onHeart &&
other.error == error;
}
@override
@ -105,5 +108,6 @@ class TrackPresentationOptions {
pagination.hashCode ^
isLiked.hashCode ^
shareUrl.hashCode ^
onHeart.hashCode;
onHeart.hashCode ^
error.hashCode;
}

View File

@ -13,6 +13,7 @@ import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/connect/connect.dart';
import 'package:spotube/provider/history/history.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/logger/logger.dart';
typedef UseActionCallbacks = ({
bool isActive,
@ -82,6 +83,9 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) {
allTracks.sublist(initialTracks.length),
);
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
rethrow;
} finally {
isLoading.value = false;
}
@ -134,6 +138,9 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) {
allTracks.sublist(initialTracks.length),
);
}
} catch (e, stack) {
AppLogger.reportError(e, stack);
rethrow;
} finally {
if (context.mounted) {
isLoading.value = false;

View File

@ -18,12 +18,15 @@ class TrackOptions extends HookConsumerWidget {
final bool userPlaylist;
final String? playlistId;
final Widget? icon;
final VoidCallback? onTapItem;
const TrackOptions({
super.key,
required this.track,
this.userPlaylist = false,
this.playlistId,
this.icon,
this.onTapItem,
}) : assert(
track is SpotubeFullTrackObject || track is SpotubeLocalTrackObject,
"Track must be a SpotubeFullTrackObject, SpotubeLocalTrackObject",
@ -60,6 +63,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.delete,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.trash),
title: Text(context.l10n.delete),
@ -73,6 +77,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.album,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.album),
title: Column(
@ -96,6 +101,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.addToQueue,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.queueAdd),
title: Text(context.l10n.add_to_queue),
@ -108,6 +114,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.playNext,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.lightning),
title: Text(context.l10n.play_next),
@ -121,6 +128,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.removeFromQueue,
playlistId,
);
onTapItem?.call();
},
enabled: !isActiveTrack,
leading: const Icon(SpotubeIcons.queueRemove),
@ -135,6 +143,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.favorite,
playlistId,
);
onTapItem?.call();
},
leading: isLiked
? const Icon(
@ -157,6 +166,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.startRadio,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.radio),
title: Text(context.l10n.start_a_radio),
@ -169,6 +179,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.addToPlaylist,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.playlistAdd),
title: Text(context.l10n.add_to_playlist),
@ -183,6 +194,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.removeFromPlaylist,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.removeFilled),
title: Text(context.l10n.remove_from_playlist),
@ -196,6 +208,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.download,
playlistId,
);
onTapItem?.call();
},
enabled: !isInDownloadQueue,
leading: isInDownloadQueue
@ -217,6 +230,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.blacklist,
playlistId,
);
onTapItem?.call();
},
leading: Icon(
SpotubeIcons.playlistRemove,
@ -240,6 +254,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.share,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.share),
title: Text(context.l10n.share),
@ -253,8 +268,9 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.songlink,
playlistId,
);
onTapItem?.call();
},
leading: Assets.logos.songlinkTransparent.image(
leading: Assets.images.logos.songlinkTransparent.image(
width: 22,
height: 22,
color: colorScheme.foreground.withValues(alpha: 0.5),
@ -270,6 +286,7 @@ class TrackOptions extends HookConsumerWidget {
TrackOptionValue.details,
playlistId,
);
onTapItem?.call();
},
leading: const Icon(SpotubeIcons.info),
title: Text(context.l10n.details),

View File

@ -42,6 +42,9 @@ class TrackOptionsButton extends HookConsumerWidget {
track: track,
playlistId: playlistId,
userPlaylist: userPlaylist,
onTapItem: () {
closeOverlay(context);
},
),
),
);
@ -133,6 +136,9 @@ class TrackOptionsButton extends HookConsumerWidget {
track: track,
userPlaylist: userPlaylist,
playlistId: playlistId,
onTapItem: () {
closeDrawer(context);
},
),
],
),

View File

@ -430,5 +430,52 @@
"edit_port": "تعديل المنفذ",
"port_helper_msg": "القيمة الافتراضية هي -1 والتي تشير إلى رقم عشوائي. إذا كان لديك جدار ناري مُعد، يُوصى بتعيين هذا.",
"connect_request": "السماح لـ {client} بالاتصال؟",
"connection_request_denied": "تم رفض الاتصال. المستخدم رفض الوصول."
"connection_request_denied": "تم رفض الاتصال. المستخدم رفض الوصول.",
"hipotetical_calculation": "*تمّ الحساب بمعدّل دفعة تتراوح بين 0.0030.005 دولار أمريكي لكل تشغيل على منصات الموسيقى عبر الإنترنت. هذا حساب افتراضي لتوضيح للمستخدم مقدار ما كان سيدفعه للفنانين لو استمع إلى أغنيتهم على منصات مختلفة.",
"an_error_occurred": "حدث خطأ",
"copy_to_clipboard": "نسخ إلى الحافظة",
"view_logs": "عرض السجلات",
"retry": "إعادة المحاولة",
"no_default_metadata_provider_selected": "لم تقُم بتعيين مزود بيانات افتراضي",
"manage_metadata_providers": "إدارة مزوّدي البيانات",
"open_link_in_browser": "فتح الرابط في المتصفح؟",
"do_you_want_to_open_the_following_link": "هل ترغب في فتح الرابط التالي؟",
"unsafe_url_warning": "قد يكون فتح الروابط من مصادر غير موثوقة غير آمن. تحرّ الحذر!\nيمكنك أيضًا نسخ الرابط إلى الحافظة.",
"copy_link": "نسخ الرابط",
"building_your_timeline": "جاري بناء المخطط الزمني استنادًا إلى استماعاتك...",
"official": "رسمي",
"author_name": "المؤلّف: {author}",
"third_party": "طرف ثالث",
"plugin_requires_authentication": "تتطلّب الإضافة تسجيل الدخول",
"update_available": "تحديث متوفر",
"supports_scrobbling": "يدعم التتبع (scrobbling)",
"plugin_scrobbling_info": "تقوم هذه الإضافة بتتبع مقاطعك الموسيقية لإنشاء سجل الاستماع الخاص بك.",
"default_plugin": "الافتراضي",
"set_default": "تعيين كافتراضي",
"support": "الدعم",
"support_plugin_development": "دعم تطوير الإضافات",
"can_access_name_api": "- يمكن الوصول إلى واجهة برمجة التطبيقات **{name}**",
"do_you_want_to_install_this_plugin": "هل ترغب في تثبيت هذه الإضافة؟",
"third_party_plugin_warning": "هذه الإضافة من مستودع طرف ثالث. تأكد من موثوقية المصدر قبل التثبيت.",
"author": "المؤلف",
"this_plugin_can_do_following": "يمكن لهذه الإضافة القيام بما يلي",
"install": "تثبيت",
"install_a_metadata_provider": "تثبيت مزوّد بيانات",
"no_tracks_playing": "لا توجد مقاطع تعمل حاليًا",
"synced_lyrics_not_available": "الكلمات المتزامنة غير متوفرة لهذه الأغنية. يُرجى استخدام",
"plain_lyrics": "الكلمات العادية",
"tab_instead": "بدلاً من ذلك، استخدم التبويب.",
"disclaimer": "إخلاء المسؤولية",
"third_party_plugin_dmca_notice": "لا تتحمّل فريق Spotube أي مسؤولية (بما في ذلك القانونية) عن أي من الإضافات “لطرف ثالث”.\nاستخدمها على مسؤوليتك الخاصّة. لأيّة أخطاء/مشكلات، يُرجى الإبلاغ عنها في مستودع الإضافة.\n\nإذا كانت أي إضافة “لطرف ثالث” تنتهك شروط الخدمة أو قانون DMCA الخاص بأي خدمة أو كيان قانوني، فيُرجى طلب اتخاذ إجراء من مؤلف الإضافة أو منصة الاستضافة مثل GitHub/Codeberg. الإضافات المدرجة كـ “لطرف ثالث” هي مفعّلة ومُدارة من المجتمع، وليس لدينا صلاحية إدارتها أو التدخل فيها.\n\n",
"input_does_not_match_format": "المدخل لا يتوافق مع التنسيق المطلوب",
"metadata_provider_plugins": "إضافات مزود البيانات",
"paste_plugin_download_url": "الصق رابط التنزيل أو GitHub/Codeberg أو رابط مباشر لملف .smplug",
"download_and_install_plugin_from_url": "تنزيل وتثبيت الإضافة من رابط",
"failed_to_add_plugin_error": "فشل في إضافة الإضافة: {error}",
"upload_plugin_from_file": "رفع الإضافة من ملف",
"installed": "تم التثبيت",
"available_plugins": "الإضافات المتوفّرة",
"configure_your_own_metadata_plugin": "تهيئة مزوّد بيانات للقائمة/الألبوم/الفنان/المصدر خاص بك",
"audio_scrobblers": "أجهزة تتبع الصوت",
"scrobbling": "التتبع"
}

View File

@ -430,5 +430,52 @@
"edit_port": "পোর্ট সম্পাদনা করুন",
"port_helper_msg": "ডিফল্ট হল -1 যা এলোমেলো সংখ্যা নির্দেশ করে। যদি আপনার ফায়ারওয়াল কনফিগার করা থাকে, তবে এটি সেট করা সুপারিশ করা হয়।",
"connect_request": "{client} কে সংযোগ করতে অনুমতি দেবেন?",
"connection_request_denied": "সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।"
"connection_request_denied": "সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।",
"hipotetical_calculation": "*এটি নিরূপণ করা হয়েছে গড় অনলাইন মিউজিক স্ট্রিমিং প্ল্যাটফর্মের প্রতি স্ট্রিম 0.0030.005 USD পেআউটের ভিত্তিতে। এটি একটি কাল্পনিক হিসাব যা ব্যবহারকারীকে ধারণা দিতে পারে তারা অন্যান্য স্ট্রিমিং প্ল্যাটফর্মে একই গান শোনার জন্য শিল্পীদের কত টাকা দিয়েছেন হোক।",
"an_error_occurred": "একটি ত্রুটি ঘটেছে",
"copy_to_clipboard": "ক্লিপবোর্ডে কপি করুন",
"view_logs": "লগ দেখুন",
"retry": "পুনরায় চেষ্টা করুন",
"no_default_metadata_provider_selected": "আপনি কোনো ডিফল্ট মেটাডেটা প্রদানকারী সেট করেননি",
"manage_metadata_providers": "মেটাডেটা প্রদানকারীগণ পরিচালনা করুন",
"open_link_in_browser": "লিংকটি ব্রাউজারে খুলবেন?",
"do_you_want_to_open_the_following_link": "নিচের লিংকটি খুলতে চান?",
"unsafe_url_warning": "অবিশ্বাসযোগ্য উৎস থেকে লিংক খোলা নিরাপদ নাও হতে পারে। সতর্ক থাকুন!\nআপনি এটি ক্লিপবোর্ডে কপি করতে পারেন।",
"copy_link": "লিংক কপি করুন",
"building_your_timeline": "আপনার শোনার ধারা অনুযায়ী টাইমলাইন তৈরি করা হচ্ছে...",
"official": "সরকারি",
"author_name": "লেখক: {author}",
"third_party": "তৃতীয় পক্ষ",
"plugin_requires_authentication": "প্লাগইনটি প্রমাণীকরণ প্রয়োজন",
"update_available": "হালনাগাদ উপলব্ধ",
"supports_scrobbling": "স্ক্রোব্বলিং সমর্থিত",
"plugin_scrobbling_info": "এই প্লাগইনটি আপনার সঙ্গীত স্ক্রোব্বল করে আপনার শোনা ইতিহাস তৈরি করে।",
"default_plugin": "ডিফল্ট",
"set_default": "ডিফল্ট হিসাবে নির্ধারণ করুন",
"support": "সমর্থন",
"support_plugin_development": "প্লাগইন উন্নয়নকে সমর্থন করুন",
"can_access_name_api": "- **{name}** API-তে অ্যাক্সেস করতে পারে",
"do_you_want_to_install_this_plugin": "আপনি কি এই প্লাগইন ইনস্টল করতে চান?",
"third_party_plugin_warning": "এই প্লাগইন একটি তৃতীয় পক্ষের রেপোজিটরির। ইনস্টল করার আগে উৎস বিশ্বস্ত কিনা নিশ্চিত করুন।",
"author": "লেখক",
"this_plugin_can_do_following": "এই প্লাগইন নিচের কাজ করতে পারে",
"install": "ইনস্টল করুন",
"install_a_metadata_provider": "একটি মেটাডেটা প্রদানকারী ইনস্টল করুন",
"no_tracks_playing": "বর্তমানে কোনো ট্র্যাক শোনা হচ্ছে না",
"synced_lyrics_not_available": "এই গানের জন্য সিঙ্ক্রোনাইজড লিরিক্স পাওয়া যায় না। অনুগ্রহ করে ব্যবহার করুন",
"plain_lyrics": "সহজ লিরিক্স",
"tab_instead": "তার পরিবর্তে ট্যাব ব্যবহার করুন।",
"disclaimer": "অস্বীকৃতি",
"third_party_plugin_dmca_notice": "Spotube দল কোনো “তৃতীয় পক্ষ” প্লাগইনের জন্য কোনো (আইনগত সহ) দায়িত্ব নেয় না। নিজের বিপদে ব্যবহার করুন। কোনো বাগ/সমস্যা হলে প্লাগইন রেপোজিটরিতে জানাতে অনুরোধ করা হচ্ছে।\n\nযদি কোনো “তৃতীয় পক্ষ” প্লাগইন কোনো পরিষেবা/আইনগত সংস্থার ToS/DMCA ভূঙ্গ করে, অনুগ্রহ করে “তৃতীয় পক্ষ” প্লাগইনের লেখক বা হোস্টিং প্ল্যাটফর্মে (যেমন GitHub/Codeberg) পদক্ষেপ নিতে বলুন। “তৃতীয় পক্ষ” লেবেলযুক্ত যুক্তিগুলি সকলই পাবলিক/কমিউনিটি দ্বারা রক্ষণাবেক্ষণ করা হয়; আমরা সেগুলি কিউরেট করি না, তাই আমরা কোনো পদক্ষেপ নিতে পারি না।\n\n",
"input_does_not_match_format": "ইনপুট প্রয়োজনীয় ফরম্যাটের সাথে মেলে না",
"metadata_provider_plugins": "মেটাডেটা প্রদানকারী প্লাগইনসমূহ",
"paste_plugin_download_url": "ডাউনলোড URL বা GitHub/Codeberg রিপো URL বা .smplug ফাইলের সরাসরি লিঙ্ক পেস্ট করুন",
"download_and_install_plugin_from_url": "URL থেকে প্লাগইন ডাউনলোড এবং ইনস্টল করুন",
"failed_to_add_plugin_error": "প্লাগইন যোগ করতে ব্যর্থ: {error}",
"upload_plugin_from_file": "ফাইল থেকে প্লাগইন আপলোড করুন",
"installed": "ইনস্টল করা হয়েছে",
"available_plugins": "উপলব্ধ প্লাগইনগুলো",
"configure_your_own_metadata_plugin": "নিজস্ব প্লেলিস্ট/অ্যালবাম/শিল্পী/ফিড মেটাডেটা প্রদানকারী কনফিগার করুন",
"audio_scrobblers": "অডিও স্ক্রোব্বলার্স",
"scrobbling": "স্ক্রোব্বলিং"
}

View File

@ -430,5 +430,52 @@
"edit_port": "Editar port",
"port_helper_msg": "El valor per defecte és -1, que indica un número aleatori. Si teniu un tallafoc configurat, es recomana establir-ho.",
"connect_request": "Permetre que {client} es connecti?",
"connection_request_denied": "Connexió denegada. L'usuari ha denegat l'accés."
"connection_request_denied": "Connexió denegada. L'usuari ha denegat l'accés.",
"hipotetical_calculation": "*Això està calculat en funció dun pagament mitjà per reproducció de 0,0030,005 USD en plataformes de reproducció musical en línia. És un càlcul hipotètic per ajudar lusuari a entendre quant hauria pagat als artistes si hagués escoltat la seva cançó en diferents plataformes.",
"an_error_occurred": "Sha produït un error",
"copy_to_clipboard": "Copiar al porta-retalls",
"view_logs": "Veure registres",
"retry": "Tornar-ho a provar",
"no_default_metadata_provider_selected": "No has configurat cap proveïdor de metadades predeterminat",
"manage_metadata_providers": "Gestionar proveïdors de metadades",
"open_link_in_browser": "Obrir lenllaç en el navegador?",
"do_you_want_to_open_the_following_link": "Vols obrir lenllaç següent?",
"unsafe_url_warning": "Pot ser perillós obrir enllaços de fonts no fiables. Sigues precavís!\nTambé pots copiar lenllaç al porta-retalls.",
"copy_link": "Copiar enllaç",
"building_your_timeline": "Construint la teva cronologia en funció de les teves escoltes...",
"official": "Oficial",
"author_name": "Autor: {author}",
"third_party": "Tercers",
"plugin_requires_authentication": "El complement requereix autenticació",
"update_available": "Actualització disponible",
"supports_scrobbling": "Admet scrobbling",
"plugin_scrobbling_info": "Aquest complement fa scrobbling de la teva música per generar lhistorial descoltes.",
"default_plugin": "Predeterminat",
"set_default": "Establir com a predeterminat",
"support": "Suport",
"support_plugin_development": "Suportar el desenvolupament del complement",
"can_access_name_api": "- Pot accedir a lAPI **{name}**",
"do_you_want_to_install_this_plugin": "Vols instal·lar aquest complement?",
"third_party_plugin_warning": "Aquest complement prové dun repositori de tercers. Assegurat de confiar en la font abans dinstal·lar-lo.",
"author": "Autor",
"this_plugin_can_do_following": "Aquest complement pot fer el següent",
"install": "Instal·lar",
"install_a_metadata_provider": "Instal·lar un proveïdor de metadades",
"no_tracks_playing": "No sestà reproduint cap pista actualment",
"synced_lyrics_not_available": "Les lletres sincronitzades no estan disponibles per a aquesta cançó. Si us plau, usa",
"plain_lyrics": "Lletres sense format",
"tab_instead": "en lloc daixò, utilitza la tecla Tab.",
"disclaimer": "Avís legal",
"third_party_plugin_dmca_notice": "Lequip de Spotube no accepta cap responsabilitat (inclosa legal) pels complements de “tercers”.\nFes-los servir sota la teva responsabilitat. Si detectes errors/problemes, informals al repositori del complement.\n\nSi algun complement de “tercers” incompleix els ToS/DMCA dun servei o entitat legal, contacta amb lautor del complement o amb la plataforma dallotjament (per exemple GitHub/Codeberg) per prendre mesures. Els complements etiquetats com a “tercers” són públics i gestionats per la comunitat; no els curatem, per la qual cosa no podem intervenir-hi.\n\n",
"input_does_not_match_format": "Lentrada no coincideix amb el format requerit",
"metadata_provider_plugins": "Complements de proveïdor de metadades",
"paste_plugin_download_url": "Enllaça lURL de descàrrega o el repositori de GitHub/Codeberg o lenllaç directe al fitxer .smplug",
"download_and_install_plugin_from_url": "Descarrega i instal·la el complement des dun URL",
"failed_to_add_plugin_error": "Error en afegir el complement: {error}",
"upload_plugin_from_file": "Penja el complement des dun fitxer",
"installed": "Instal·lat",
"available_plugins": "Complements disponibles",
"configure_your_own_metadata_plugin": "Configura el teu propi proveïdor de metadades per llistes/reproduccions àlbum/artista/flux",
"audio_scrobblers": "Scrobblers dàudio",
"scrobbling": "Scrobbling"
}

View File

@ -430,5 +430,52 @@
"edit_port": "Upravit port",
"port_helper_msg": "Výchozí hodnota je -1, což znamená náhodné číslo. Pokud máte nakonfigurován firewall, doporučuje se to nastavit.",
"connect_request": "Povolit {client} připojení?",
"connection_request_denied": "Připojení bylo zamítnuto. Uživatel odmítl přístup."
"connection_request_denied": "Připojení bylo zamítnuto. Uživatel odmítl přístup.",
"hipotetical_calculation": "*Toto je vypočítáno na základě průměrného výplatu za přehrání 0,0030,005 USD na online hudebních streamovacích platformách. Jedná se o hypotetický výpočet, který má uživateli ukázat, kolik by umělci dostali, pokud by jeho píseň poslouchal na jiné platformě.",
"an_error_occurred": "Došlo k chybě",
"copy_to_clipboard": "Kopírovat do schránky",
"view_logs": "Zobrazit protokoly",
"retry": "Zkusit znovu",
"no_default_metadata_provider_selected": "Nemáte nastaven výchozí poskytovatel metadat",
"manage_metadata_providers": "Spravovat poskytovatele metadat",
"open_link_in_browser": "Otevřít odkaz v prohlížeči?",
"do_you_want_to_open_the_following_link": "Chcete otevřít následující odkaz?",
"unsafe_url_warning": "Odkazy z nedůvěryhodných zdrojů mohou být nebezpečné. Buďte opatrní!\nOdkaz si také můžete zkopírovat do schránky.",
"copy_link": "Zkopírovat odkaz",
"building_your_timeline": "Vytváří se váš časový přehled podle poslechů...",
"official": "Oficiální",
"author_name": "Autor: {author}",
"third_party": "Třetí strana",
"plugin_requires_authentication": "Plugin vyžaduje ověření",
"update_available": "Aktualizace dostupná",
"supports_scrobbling": "Podpora scrobblování",
"plugin_scrobbling_info": "Tento plugin scrobbles vaši hudbu pro vytvoření historie poslechů.",
"default_plugin": "Výchozí",
"set_default": "Nastavit jako výchozí",
"support": "Podpora",
"support_plugin_development": "Podpořit vývoj pluginu",
"can_access_name_api": "- Může přistupovat k API **{name}**",
"do_you_want_to_install_this_plugin": "Chcete tento plugin nainstalovat?",
"third_party_plugin_warning": "Tento plugin pochází z repozitáře třetí strany. Ujistěte se, že důvěřujete zdroji, než ho nainstalujete.",
"author": "Autor",
"this_plugin_can_do_following": "Tento plugin může provádět následující úkony",
"install": "Instalovat",
"install_a_metadata_provider": "Nainstalovat poskytovatele metadat",
"no_tracks_playing": "Momentálně není přehrávána žádná skladba",
"synced_lyrics_not_available": "Synchronizované texty nejsou k dispozici k této písni. Prosím použijte",
"plain_lyrics": "Prostý text",
"tab_instead": "místo toho použijte tabulátor.",
"disclaimer": "Prohlášení",
"third_party_plugin_dmca_notice": "Tým Spotube nenese žádnou odpovědnost (včetně právní) za pluginy „třetích stran“.\nPoužívejte je na vlastní riziko. Pro chyby/problémy je nahlaste do repozitáře pluginu.\n\nPokud jakýkoli plugin „třetí strany“ porušuje podmínky služby nebo DMCA kteréhokoli poskytovatele či právního subjektu, požádejte autora pluginu nebo hostingovou platformu (např. GitHub/Codeberg), aby podnikla kroky. Pluginy označené jako „třetí strana“ jsou otevřené a spravovány komunitou; nespravujeme je, tudíž nemůžeme jednat.\n\n",
"input_does_not_match_format": "Vstup neodpovídá požadovanému formátu",
"metadata_provider_plugins": "Pluginy poskytovatelů metadat",
"paste_plugin_download_url": "Vložte URL ke stažení nebo GitHub/Codeberg repozitář či přímý odkaz na soubor .smplug",
"download_and_install_plugin_from_url": "Stáhnout a nainstalovat plugin z URL",
"failed_to_add_plugin_error": "Nepodařilo se přidat plugin: {error}",
"upload_plugin_from_file": "Nahrát plugin ze souboru",
"installed": "Nainstalováno",
"available_plugins": "Dostupné pluginy",
"configure_your_own_metadata_plugin": "Nakonfigurujte si vlastního poskytovatele metadat pro playlist/album/umělec/fid",
"audio_scrobblers": "Audio scrobblers",
"scrobbling": "Scrobbling"
}

View File

@ -430,5 +430,52 @@
"edit_port": "Port bearbeiten",
"port_helper_msg": "Der Standardwert ist -1, was eine zufällige Zahl bedeutet. Wenn Sie eine Firewall konfiguriert haben, wird empfohlen, dies einzustellen.",
"connect_request": "{client} die Verbindung erlauben?",
"connection_request_denied": "Verbindung abgelehnt. Benutzer hat den Zugriff verweigert."
"connection_request_denied": "Verbindung abgelehnt. Benutzer hat den Zugriff verweigert.",
"hipotetical_calculation": "*Diese Berechnung basiert auf der durchschnittlichen Auszahlung pro Stream (0,003 USD bis 0,005 USD) auf Online-Musik-Streaming-Plattformen. Sie ist hypothetisch und soll dem Nutzer veranschaulichen, wie viel er den Künstlern bezahlt hätte, wenn er ihren Song auf verschiedenen Streaming-Plattformen gehört hätte.",
"an_error_occurred": "Ein Fehler ist aufgetreten",
"copy_to_clipboard": "In die Zwischenablage kopieren",
"view_logs": "Protokolle anzeigen",
"retry": "Erneut versuchen",
"no_default_metadata_provider_selected": "Sie haben keinen Standard-Metadatenanbieter festgelegt",
"manage_metadata_providers": "Metadatenanbieter verwalten",
"open_link_in_browser": "Link im Browser öffnen?",
"do_you_want_to_open_the_following_link": "Möchten Sie folgenden Link öffnen?",
"unsafe_url_warning": "Das Öffnen von Links aus nicht vertrauenswürdigen Quellen kann unsicher sein. Seien Sie vorsichtig!\nSie können den Link auch in Ihre Zwischenablage kopieren.",
"copy_link": "Link kopieren",
"building_your_timeline": "Ihr Zeitverlauf wird basierend auf Ihren Hördaten erstellt…",
"official": "Offiziell",
"author_name": "Autor: {author}",
"third_party": "Drittanbieter",
"plugin_requires_authentication": "Plugin erfordert Authentifizierung",
"update_available": "Update verfügbar",
"supports_scrobbling": "Unterstützt Scrobbling",
"plugin_scrobbling_info": "Dieses Plugin scrobbelt Ihre Musik, um Ihre Hörhistorie zu erstellen.",
"default_plugin": "Standard",
"set_default": "Als Standard festlegen",
"support": "Unterstützung",
"support_plugin_development": "Plugin-Entwicklung unterstützen",
"can_access_name_api": "- Kann auf **{name}**-API zugreifen",
"do_you_want_to_install_this_plugin": "Möchten Sie dieses Plugin installieren?",
"third_party_plugin_warning": "Dieses Plugin stammt aus einem Drittanbieter-Repository. Bitte stellen Sie sicher, dass Sie der Quelle vertrauen, bevor Sie es installieren.",
"author": "Autor",
"this_plugin_can_do_following": "Dieses Plugin kann Folgendes:",
"install": "Installieren",
"install_a_metadata_provider": "Einen Metadatenanbieter installieren",
"no_tracks_playing": "Derzeit wird kein Titel abgespielt",
"synced_lyrics_not_available": "Synchronisierte Liedtexte sind für dieses Lied nicht verfügbar. Bitte verwenden Sie stattdessen",
"plain_lyrics": "Einfache Liedtexte",
"tab_instead": "stattdessen die Tab-Taste verwenden.",
"disclaimer": "Haftungsausschluss",
"third_party_plugin_dmca_notice": "Das Spotube-Team übernimmt keine Verantwortung (auch nicht rechtlicher Art) für Plugins \"Drittanbieter\". Nutzen Sie diese auf eigenes Risiko. Für Fehler/Probleme melden Sie sich bitte beim Plugin-Repository.\n\nWenn ein Plugin \"Drittanbieter\" gegen die ToS/DMCA eines Dienstes bzw. gesetzlicher Vorschriften verstößt, wenden Sie sich bitte an den Plugin-Autor oder die Hosting-Plattform (z. B. GitHub/Codeberg), um Maßnahmen zu ergreifen. Die genannten Plugins (mit \"Drittanbieter\"-Kennzeichnung) werden öffentlich und gemeinschaftlich gepflegt. Wir kuratieren sie nicht und können keine Maßnahmen ergreifen.\n\n",
"input_does_not_match_format": "Eingabe entspricht nicht dem geforderten Format",
"metadata_provider_plugins": "Plugins für Metadatenanbieter",
"paste_plugin_download_url": "Download-URL, GitHub/Codeberg-Repo-URL oder direkten Link zur .smplug-Datei einfügen",
"download_and_install_plugin_from_url": "Plugin per URL herunterladen und installieren",
"failed_to_add_plugin_error": "Plugin konnte nicht hinzugefügt werden: {error}",
"upload_plugin_from_file": "Plugin per Datei hochladen",
"installed": "Installiert",
"available_plugins": "Verfügbare Plugins",
"configure_your_own_metadata_plugin": "Eigenen Anbieter für Playlist-/Album-/Künstler-/Feed-Metadaten konfigurieren",
"audio_scrobblers": "Audio-Scrobbler",
"scrobbling": "Scrobbling"
}

View File

@ -415,5 +415,51 @@
"edit_port": "Edit port",
"port_helper_msg": "Default is -1 which indicates random number. If you've firewall configured, setting this is recommended.",
"connect_request": "Allow {client} to connect?",
"connection_request_denied": "Connection denied. User denied access."
"connection_request_denied": "Connection denied. User denied access.",
"an_error_occurred": "An error occurred",
"copy_to_clipboard": "Copy to clipboard",
"view_logs": "View logs",
"retry": "Retry",
"no_default_metadata_provider_selected": "You've no default metadata provider set",
"manage_metadata_providers": "Manage metadata providers",
"open_link_in_browser": "Open Link in Browser?",
"do_you_want_to_open_the_following_link": "Do you want to open the following link",
"unsafe_url_warning": "It can be unsafe to open links from untrusted sources. Be cautious!\nYou can also copy the link to your clipboard.",
"copy_link": "Copy Link",
"building_your_timeline": "Building your timeline based on your listenings...",
"official": "Official",
"author_name": "Author: {author}",
"third_party": "Third-party",
"plugin_requires_authentication": "Plugin requires authentication",
"update_available": "Update available",
"supports_scrobbling": "Supports scrobbling",
"plugin_scrobbling_info": "This plugin scrobbles your music to generate your listening history.",
"default_plugin": "Default",
"set_default": "Set default",
"support": "Support",
"support_plugin_development": "Support plugin development",
"can_access_name_api": "- Can access **{name}** API",
"do_you_want_to_install_this_plugin": "Do you want to install this plugin?",
"third_party_plugin_warning": "This plugin is from a third-party repository. Please ensure you trust the source before installing.",
"author": "Author",
"this_plugin_can_do_following": "This plugin can do following",
"install": "Install",
"install_a_metadata_provider": "Install a Metadata Provider",
"no_tracks_playing": "No Track being played currently",
"synced_lyrics_not_available": "Synced lyrics are not available for this song. Please use the",
"plain_lyrics": "Plain Lyrics",
"tab_instead": "tab instead.",
"disclaimer": "Disclaimer",
"third_party_plugin_dmca_notice": "The Spotube team does not hold any responsibility (including legal) for any \"Third-party\" plugins.\nPlease use them at your own risk. For any bugs/issues, please report them to the plugin repository.\n\nIf any \"Third-party\" plugin is breaking ToS/DMCA of any service/legal entity, please ask the \"Third-party\" plugin author or the hosting platform .e.g GitHub/Codeberg to take action. Above listed (\"Third-party\" labelled) are all public/community maintained plugins. We're not curating them, so we cannot take any action on them.\n\n",
"input_does_not_match_format": "Input doesn't match the required format",
"metadata_provider_plugins": "Metadata Provider Plugins",
"paste_plugin_download_url": "Paste download url or GitHub/Codeberg repo url or direct link to .smplug file",
"download_and_install_plugin_from_url": "Download and install plugin from url",
"failed_to_add_plugin_error": "Failed to add plugin: {error}",
"upload_plugin_from_file": "Upload plugin from file",
"installed": "Installed",
"available_plugins": "Available plugins",
"configure_your_own_metadata_plugin": "Configure your own playlist/album/artist/feed metadata provider",
"audio_scrobblers": "Audio Scrobblers",
"scrobbling": "Scrobbling"
}

View File

@ -430,5 +430,52 @@
"edit_port": "Editar puerto",
"port_helper_msg": "El valor predeterminado es -1, lo que indica un número aleatorio. Si tienes un firewall configurado, se recomienda establecer esto.",
"connect_request": "¿Permitir que {client} se conecte?",
"connection_request_denied": "Conexión denegada. El usuario denegó el acceso."
"connection_request_denied": "Conexión denegada. El usuario denegó el acceso.",
"hipotetical_calculation": "*Este cálculo se basa en el pago promedio por reproducción en plataformas de música en línea (de 0,003 a 0,005 USD). Es hipotético y sirve para dar al usuario una idea de cuánto habría pagado a los artistas si hubiera escuchado su canción en distintas plataformas.",
"an_error_occurred": "Ocurrió un error",
"copy_to_clipboard": "Copiar al portapapeles",
"view_logs": "Ver registros",
"retry": "Reintentar",
"no_default_metadata_provider_selected": "No has configurado un proveedor de metadatos predeterminado",
"manage_metadata_providers": "Gestionar proveedores de metadatos",
"open_link_in_browser": "¿Abrir enlace en el navegador?",
"do_you_want_to_open_the_following_link": "¿Quieres abrir el siguiente enlace?",
"unsafe_url_warning": "Abrir enlaces de fuentes no confiables puede ser inseguro. ¡Ten cuidado!\nTambién puedes copiar el enlace al portapapeles.",
"copy_link": "Copiar enlace",
"building_your_timeline": "Construyendo tu línea de tiempo según tus escuchas…",
"official": "Oficial",
"author_name": "Autor: {author}",
"third_party": "Terceros",
"plugin_requires_authentication": "El complemento requiere autenticación",
"update_available": "Actualización disponible",
"supports_scrobbling": "Admite scrobbling",
"plugin_scrobbling_info": "Este complemento scrobblea tu música para generar tu historial de reproducción.",
"default_plugin": "Predeterminado",
"set_default": "Establecer como predeterminado",
"support": "Soporte",
"support_plugin_development": "Apoyar el desarrollo del complemento",
"can_access_name_api": "- Puede acceder a la API de **{name}**",
"do_you_want_to_install_this_plugin": "¿Deseas instalar este complemento?",
"third_party_plugin_warning": "Este complemento proviene de un repositorio de terceros. Asegúrate de confiar en la fuente antes de instalarlo.",
"author": "Autor",
"this_plugin_can_do_following": "Este complemento puede hacer lo siguiente",
"install": "Instalar",
"install_a_metadata_provider": "Instalar un proveedor de metadatos",
"no_tracks_playing": "No hay ninguna pista reproduciéndose actualmente",
"synced_lyrics_not_available": "Las letras sincronizadas no están disponibles para esta canción. Por favor, utiliza",
"plain_lyrics": "Letras sin formato",
"tab_instead": "en su lugar, usa la tecla Tab.",
"disclaimer": "Descargo de responsabilidad",
"third_party_plugin_dmca_notice": "El equipo de Spotube no asume ninguna responsabilidad (incluida la legal) por complementos de \"terceros\". Úsalos bajo tu propio riesgo. Para errores o problemas, repórtalos en el repositorio del complemento.\n\nSi algún complemento de “terceros” infringe los ToS/DMCA de algún servicio o entidad legal, por favor, solicita al autor del complemento o a la plataforma de alojamiento (p. ej., GitHub/Codeberg) que tome medidas. Los complementos etiquetados como “de terceros” son mantenidos públicamente por la comunidad; no los gestionamos y no podemos intervenir.\n\n",
"input_does_not_match_format": "La entrada no coincide con el formato requerido",
"metadata_provider_plugins": "Complementos de proveedor de metadatos",
"paste_plugin_download_url": "Pega la URL de descarga, el repositorio de GitHub/Codeberg o el enlace directo al archivo .smplug",
"download_and_install_plugin_from_url": "Descargar e instalar el complemento desde una URL",
"failed_to_add_plugin_error": "Error al añadir el complemento: {error}",
"upload_plugin_from_file": "Subir complemento desde archivo",
"installed": "Instalado",
"available_plugins": "Complementos disponibles",
"configure_your_own_metadata_plugin": "Configura tu propio proveedor de metadatos para listas/álbum/artista/feeds",
"audio_scrobblers": "Scrobblers de audio",
"scrobbling": "Scrobbling"
}

Some files were not shown because too many files have changed in this diff Show More