diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index 83ef36f5..58b893ee 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,3 +1,3 @@ { - "flutterSdkVersion": "3.32.7" + "flutterSdkVersion": "3.35.2" } \ No newline at end of file diff --git a/.fvmrc b/.fvmrc index cb8a5fa5..cf986e39 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,4 +1,4 @@ { - "flutter": "3.32.7", + "flutter": "3.35.2", "flavors": {} } \ No newline at end of file diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 891d6a5a..3e73be4d 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -4,7 +4,7 @@ on: pull_request: env: - FLUTTER_VERSION: 3.32.7 + FLUTTER_VERSION: 3.35.2 jobs: lint: diff --git a/.github/workflows/spotube-release-binary.yml b/.github/workflows/spotube-release-binary.yml index 4ecf642e..4f2cff34 100644 --- a/.github/workflows/spotube-release-binary.yml +++ b/.github/workflows/spotube-release-binary.yml @@ -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: @@ -56,7 +56,7 @@ jobs: files: | dist/Spotube-windows-x86_64.nupkg dist/Spotube-windows-x86_64-setup.exe - - os: macos-latest + - os: macos-14 platform: ios arch: all files: | @@ -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: | diff --git a/.metadata b/.metadata index 828f2c0a..e8b36fde 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "300451adae589accbece3490f4396f10bdf15e6e" + revision: "d7b523b356d15fb81e7d340bbe52b47f93937323" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 300451adae589accbece3490f4396f10bdf15e6e - base_revision: 300451adae589accbece3490f4396f10bdf15e6e + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 - platform: windows - create_revision: 300451adae589accbece3490f4396f10bdf15e6e - base_revision: 300451adae589accbece3490f4396f10bdf15e6e + create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 + base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323 # User provided section diff --git a/.vscode/settings.json b/.vscode/settings.json index ba3f905e..69c80bb3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index bf906a76..21ae3f15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,68 @@ # Changelog +## [5.0.0](https://github.com/KRTirtho/spotube/compare/v4.0.2...v5.0.0) (2025-09-08) + +### Features + +- Add ISRC track search for YouTube ([#2594](https://github.com/KRTirtho/spotube/issues/2594)) +- Add new icons #2676 by @alexio-dev ([#2678](https://github.com/KRTirtho/spotube/issues/2678)) +- Add connect confirmation dialog +- Add metadata api service and models +- **metadata-plugin**: Add pagination support, feed and playlist CRUD endpoints +- **metadata-plugin**: Add local storage api +- Add webview, totp and setInterval apis for plugins +- Enhance local storage and webview APIs with improved error handling and resource management +- **metadata_plugin**: Add logout method +- Update plugin configuration with more fields +- Implement metadata plugins based on hetu +- Update models to match hetu_spotube_plugin signature +- Add user endpoint calls in metadata and paginated async notifiers +- Add playlist endpoint and providers +- Add albums metadata endpoint and provider +- Add artist and album providers +- Add track endpoint for metadata service +- Remove green corp names formally +- **metadata**: Add plugin form +- Add support for entity specific search +- Enhance image handling +- Add support for automatic plugin repository from github and codeberg +- Use isolate for youtube_explode engine +- Add repository and plugin API version fields to metadata plugins +- Update new pipe version +- **metadata**: Add plugin update checker and dialog for available updates +- Optimize track options and related artists +- Add plugin scrobbling support and support button +- Add ErrorBox and NoDefaultMetadataPlugin components + +### Bug Fixes + +- Calling /track/:streamId endpoint causes active sourced track to be anything +- **mobile**: Dialogs in bottom sheet are not opening +- Default accent color is orange but it shows blue in settings +- Artist images are not loading up +- CVE: Remote path traversal through websocket when devices are on same network +- Endless playback not working +- **android**: NewPipe invalid search content filters +- Make YoutubeExplode engine faster +- Create and delete playlist not working +- Local track not working and images of local not showing up +- Local playback not working for tracks with special # (hashtag) characters +- Inaccessible streaming url causing rapid skips +- **yt**: Fallback to different search result if all streaming url is inaccessible +- **playback**: Skip network requests if cached file already exists +- Yt-dlp playback not working and add partial support for HLS streaming +- Windows webview2 environment permission issue +- **playback**: Play not fetching full playlist if playlist is too long +- **track_options**: Tapping on option doesn't close the menu +- **playback**: Alternative track sources switch not working +- **ui**: Lyrics white text in white background and small player buttons + +### Translation + +- Add Traditional Chinese translation ([#2762](https://github.com/KRTirtho/spotube/issues/2762)) +- Fix Japanese translations ([#2732](https://github.com/KRTirtho/spotube/issues/2732)) +- Correction of the dutch language ([#1306](https://github.com/KRTirtho/spotube/issues/1306)) + ## [4.0.2](https://github.com/krtirtho/spotube/compare/v4.0.1...v4.0.2) (2025-03-16) ### Bug Fixes diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index d4746a1a..db77a5f1 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -119,7 +119,7 @@ Enhancement suggestions are tracked as [GitHub issues](https://github.com/KRTirt Do the following: -- Download the latest Flutter SDK (>=3.16.0) & enable desktop support +- Install [Dart](https://dart.dev/get-dart) and [fvm](https://fvm.app/documentation/getting-started/installation) - Install Development dependencies in linux - Debian (>=12/Bookworm)/Ubuntu ```bash @@ -138,11 +138,11 @@ Do the following: - Create a `.env` in root of the project following the `.env.example` template - Now run the following to bootstrap the project ```bash - flutter pub get && dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns + fvm flutter pub get && fvm dart run build_runner build --delete-conflicting-outputs ``` - Finally run these following commands in the root of the project to start the Spotube Locally ```bash - flutter run -d )> + fvm flutter run -d )> ``` Do debugging/testing/build etc then submit to us with PR against the development branch (dev) & we'll review your code diff --git a/Makefile b/Makefile index 48626312..49ae034a 100644 --- a/Makefile +++ b/Makefile @@ -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 \ No newline at end of file + appdmg appdmg.json dist/Spotube-macos-universal.dmg + +changelog: + git-cliff --unreleased \ No newline at end of file diff --git a/README.md b/README.md index 26bb1d71..4c5c1f1c 100644 --- a/README.md +++ b/README.md @@ -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 Spotify’s 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' - -### Their exact reasoning: (any) "uses of Spotify’s 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. -
- Spotube Logo + Spotube Logo -An open source, cross-platform music client
-utilizing selected music provider API and YouTube®, Piped.video or JioSaavn as an audio source +A cross-platform extensible open-source music streaming platform.
+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 😉 @@ -57,34 +18,320 @@ Btw it's not just another Electron app 😉 --- +![Spotube Desktop](assets/branding/spotube-screenshot.png) + +![Spotube Mobile](assets/branding/mobile-screenshots/combined.jpg) +
## 🌃 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.
+This handy table lists all the methods you can use to install Spotube: -- 🗣️ **Shows & Podcasts:** Shows and Podcasts will **never be supported** because the audio tracks are _only_ available on music providers and accessing them would require premium. -- 🎧 **Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlatformPackage/Installation Method
Windows + + Windows Download + +
MacOS + + MacOS Download + +
Android + + APK download + +
+ + Download from F-Droid + +
iOS + + Download iOS IPA + +
+
+ *iPA file only. Requires sideloading with AltStore or similar tools. +
+
Flatpak +

flatpak install com.github.KRTirtho.Spotube

+ + Download on Flathub + +
AppImageAppImage's lacking stability led to it's temporary removal. More information at https://github.com/KRTirtho/spotube/issues/1082
Debian/Ubuntu + + Debian/Ubuntu Download + +

Then run: sudo apt install ./Spotube-linux-x86_64.deb

+
Arch/Manjaro +

With pamac: sudo pamac install spotube-bin

+

With yay: yay -Sy spotube-bin

+
Fedora/OpenSuse + + Fedora/OpenSuse Download + +

For Fedora: sudo dnf install ./Spotube-linux-x86_64.rpm

+

For OpenSuse: sudo zypper in ./Spotube-linux-x86_64.rpm

+
Linux (tarball) + + Tarball Download + +
Macos - Homebrew +
+brew tap krtirtho/apps
+brew install --cask spotube
+
+
Windows - Chocolatey +

choco install spotube

+
Windows - Scoop +

scoop bucket add extras

+

scoop install spotube

+
Windows - WinGet +

winget install --id KRTirtho.Spotube

+
+ +### 🔄 Nightly Builds + +Grab the latest nightly builds of Spotube [from the GitHub Releases](https://github.com/KRTirtho/spotube/releases/tag/nightly). + +## 🕳️ Building from source + +GitHub Workflow Status + +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). + +
+ +

[Click to show] 🙏 Services/Package/Plugin Credits

+
+ +### 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) +
+ +

© Copyright Spotube 2025

diff --git a/android/app/build.gradle b/android/app/build.gradle index 5051f5a3..ee481eca 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -33,7 +33,7 @@ def composeVersion = "1.4.8" android { namespace "oss.krtirtho.spotube" - compileSdkVersion 35 + compileSdkVersion 36 ndkVersion = "27.0.12077973" diff --git a/appdmg.json b/appdmg.json index eb9b5236..6e365f23 100644 --- a/appdmg.json +++ b/appdmg.json @@ -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" } ] -} \ No newline at end of file +} diff --git a/assets/backgrounds/xmas-effect.png b/assets/backgrounds/xmas-effect.png deleted file mode 100644 index e7c8eeef..00000000 Binary files a/assets/backgrounds/xmas-effect.png and /dev/null differ diff --git a/assets/bengali-patterns-bg.jpg b/assets/bengali-patterns-bg.jpg deleted file mode 100644 index 513557a3..00000000 Binary files a/assets/bengali-patterns-bg.jpg and /dev/null differ diff --git a/assets/branding.png b/assets/branding/branding.png similarity index 100% rename from assets/branding.png rename to assets/branding/branding.png diff --git a/assets/branding/mobile-screenshots/android-1.jpg b/assets/branding/mobile-screenshots/android-1.jpg new file mode 100644 index 00000000..16debbd7 Binary files /dev/null and b/assets/branding/mobile-screenshots/android-1.jpg differ diff --git a/assets/branding/mobile-screenshots/android-2.jpg b/assets/branding/mobile-screenshots/android-2.jpg new file mode 100644 index 00000000..d594227e Binary files /dev/null and b/assets/branding/mobile-screenshots/android-2.jpg differ diff --git a/assets/branding/mobile-screenshots/android-3.jpg b/assets/branding/mobile-screenshots/android-3.jpg new file mode 100644 index 00000000..ed2d61f2 Binary files /dev/null and b/assets/branding/mobile-screenshots/android-3.jpg differ diff --git a/assets/branding/mobile-screenshots/android-4.jpg b/assets/branding/mobile-screenshots/android-4.jpg new file mode 100644 index 00000000..36a3549f Binary files /dev/null and b/assets/branding/mobile-screenshots/android-4.jpg differ diff --git a/assets/branding/mobile-screenshots/android-5.jpg b/assets/branding/mobile-screenshots/android-5.jpg new file mode 100644 index 00000000..29be481b Binary files /dev/null and b/assets/branding/mobile-screenshots/android-5.jpg differ diff --git a/assets/branding/mobile-screenshots/combined.jpg b/assets/branding/mobile-screenshots/combined.jpg new file mode 100644 index 00000000..03740580 Binary files /dev/null and b/assets/branding/mobile-screenshots/combined.jpg differ diff --git a/assets/spotube-hero-banner.png b/assets/branding/spotube-hero-banner.png similarity index 100% rename from assets/spotube-hero-banner.png rename to assets/branding/spotube-hero-banner.png diff --git a/assets/spotube-logo-foreground.png b/assets/branding/spotube-logo-foreground.png similarity index 100% rename from assets/spotube-logo-foreground.png rename to assets/branding/spotube-logo-foreground.png diff --git a/assets/spotube-logo-item.png b/assets/branding/spotube-logo-item.png similarity index 100% rename from assets/spotube-logo-item.png rename to assets/branding/spotube-logo-item.png diff --git a/assets/spotube-logo-light.png b/assets/branding/spotube-logo-light.png similarity index 100% rename from assets/spotube-logo-light.png rename to assets/branding/spotube-logo-light.png diff --git a/assets/spotube-logo-macos.png b/assets/branding/spotube-logo-macos.png similarity index 100% rename from assets/spotube-logo-macos.png rename to assets/branding/spotube-logo-macos.png diff --git a/assets/spotube-logo.bmp b/assets/branding/spotube-logo.bmp similarity index 100% rename from assets/spotube-logo.bmp rename to assets/branding/spotube-logo.bmp diff --git a/assets/spotube-logo.ico b/assets/branding/spotube-logo.ico similarity index 100% rename from assets/spotube-logo.ico rename to assets/branding/spotube-logo.ico diff --git a/assets/spotube-logo.png b/assets/branding/spotube-logo.png similarity index 100% rename from assets/spotube-logo.png rename to assets/branding/spotube-logo.png diff --git a/assets/spotube-logo_android12.png b/assets/branding/spotube-logo_android12.png similarity index 100% rename from assets/spotube-logo_android12.png rename to assets/branding/spotube-logo_android12.png diff --git a/assets/spotube-nightly-item.png b/assets/branding/spotube-nightly-item.png similarity index 100% rename from assets/spotube-nightly-item.png rename to assets/branding/spotube-nightly-item.png diff --git a/assets/spotube-nightly-logo-foreground.png b/assets/branding/spotube-nightly-logo-foreground.png similarity index 100% rename from assets/spotube-nightly-logo-foreground.png rename to assets/branding/spotube-nightly-logo-foreground.png diff --git a/assets/spotube-nightly-logo-foreground.svg b/assets/branding/spotube-nightly-logo-foreground.svg similarity index 100% rename from assets/spotube-nightly-logo-foreground.svg rename to assets/branding/spotube-nightly-logo-foreground.svg diff --git a/assets/spotube-nightly-logo.png b/assets/branding/spotube-nightly-logo.png similarity index 100% rename from assets/spotube-nightly-logo.png rename to assets/branding/spotube-nightly-logo.png diff --git a/assets/spotube-nightly-logo_android12.png b/assets/branding/spotube-nightly-logo_android12.png similarity index 100% rename from assets/spotube-nightly-logo_android12.png rename to assets/branding/spotube-nightly-logo_android12.png diff --git a/assets/branding/spotube-screenshot.png b/assets/branding/spotube-screenshot.png new file mode 100644 index 00000000..c708e240 Binary files /dev/null and b/assets/branding/spotube-screenshot.png differ diff --git a/assets/spotube-tall-capsule.png b/assets/branding/spotube-tall-capsule.png similarity index 100% rename from assets/spotube-tall-capsule.png rename to assets/branding/spotube-tall-capsule.png diff --git a/assets/spotube-wide-capsule-large.png b/assets/branding/spotube-wide-capsule-large.png similarity index 100% rename from assets/spotube-wide-capsule-large.png rename to assets/branding/spotube-wide-capsule-large.png diff --git a/assets/spotube-wide-capsule-small.png b/assets/branding/spotube-wide-capsule-small.png similarity index 100% rename from assets/spotube-wide-capsule-small.png rename to assets/branding/spotube-wide-capsule-small.png diff --git a/assets/spotube_banner.png b/assets/branding/spotube_banner.png similarity index 100% rename from assets/spotube_banner.png rename to assets/branding/spotube_banner.png diff --git a/assets/empty_box.png b/assets/empty_box.png deleted file mode 100644 index 24e95b23..00000000 Binary files a/assets/empty_box.png and /dev/null differ diff --git a/assets/fonts/Ubuntu_Mono/UFL.txt b/assets/fonts/Ubuntu_Mono/UFL.txt new file mode 100644 index 00000000..6e722c88 --- /dev/null +++ b/assets/fonts/Ubuntu_Mono/UFL.txt @@ -0,0 +1,96 @@ +------------------------------- +UBUNTU FONT LICENCE Version 1.0 +------------------------------- + +PREAMBLE +This licence allows the licensed fonts to be used, studied, modified and +redistributed freely. The fonts, including any derivative works, can be +bundled, embedded, and redistributed provided the terms of this licence +are met. The fonts and derivatives, however, cannot be released under +any other licence. The requirement for fonts to remain under this +licence does not require any document created using the fonts or their +derivatives to be published under this licence, as long as the primary +purpose of the document is not to be a vehicle for the distribution of +the fonts. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this licence and clearly marked as such. This may +include source files, build scripts and documentation. + +"Original Version" refers to the collection of Font Software components +as received under this licence. + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to +a new environment. + +"Copyright Holder(s)" refers to all individuals and companies who have a +copyright ownership of the Font Software. + +"Substantially Changed" refers to Modified Versions which can be easily +identified as dissimilar to the Font Software by users of the Font +Software comparing the Original Version with the Modified Version. + +To "Propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification and with or without charging +a redistribution fee), making available to the public, and in some +countries other activities as well. + +PERMISSION & CONDITIONS +This licence does not grant any rights under trademark law and all such +rights are reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to propagate the Font Software, subject to +the below conditions: + +1) Each copy of the Font Software must contain the above copyright +notice and this licence. These can be included either as stand-alone +text files, human-readable headers or in the appropriate machine- +readable metadata fields within text or binary files as long as those +fields can be easily viewed by the user. + +2) The font name complies with the following: +(a) The Original Version must retain its name, unmodified. +(b) Modified Versions which are Substantially Changed must be renamed to +avoid use of the name of the Original Version or similar names entirely. +(c) Modified Versions which are not Substantially Changed must be +renamed to both (i) retain the name of the Original Version and (ii) add +additional naming elements to distinguish the Modified Version from the +Original Version. The name of such Modified Versions must be the name of +the Original Version, with "derivative X" where X represents the name of +the new work, appended to that name. + +3) The name(s) of the Copyright Holder(s) and any contributor to the +Font Software shall not be used to promote, endorse or advertise any +Modified Version, except (i) as required by this licence, (ii) to +acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with +their explicit written permission. + +4) The Font Software, modified or unmodified, in part or in whole, must +be distributed entirely under this licence, and must not be distributed +under any other licence. The requirement for fonts to remain under this +licence does not affect any document created using the Font Software, +except any version of the Font Software extracted from a document +created using the Font Software may only be distributed under this +licence. + +TERMINATION +This licence becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/Ubuntu_Mono/UbuntuMono-Bold.ttf b/assets/fonts/Ubuntu_Mono/UbuntuMono-Bold.ttf new file mode 100644 index 00000000..01ad81bf Binary files /dev/null and b/assets/fonts/Ubuntu_Mono/UbuntuMono-Bold.ttf differ diff --git a/assets/fonts/Ubuntu_Mono/UbuntuMono-BoldItalic.ttf b/assets/fonts/Ubuntu_Mono/UbuntuMono-BoldItalic.ttf new file mode 100644 index 00000000..731884eb Binary files /dev/null and b/assets/fonts/Ubuntu_Mono/UbuntuMono-BoldItalic.ttf differ diff --git a/assets/fonts/Ubuntu_Mono/UbuntuMono-Italic.ttf b/assets/fonts/Ubuntu_Mono/UbuntuMono-Italic.ttf new file mode 100644 index 00000000..b89338d4 Binary files /dev/null and b/assets/fonts/Ubuntu_Mono/UbuntuMono-Italic.ttf differ diff --git a/assets/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf b/assets/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf new file mode 100644 index 00000000..4977028d Binary files /dev/null and b/assets/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf differ diff --git a/assets/album-placeholder.png b/assets/images/album-placeholder.png similarity index 100% rename from assets/album-placeholder.png rename to assets/images/album-placeholder.png diff --git a/assets/images/bengali-patterns-bg.jpg b/assets/images/bengali-patterns-bg.jpg new file mode 100644 index 00000000..a4090a01 Binary files /dev/null and b/assets/images/bengali-patterns-bg.jpg differ diff --git a/assets/images/liked-tracks.jpg b/assets/images/liked-tracks.jpg new file mode 100644 index 00000000..71e010dc Binary files /dev/null and b/assets/images/liked-tracks.jpg differ diff --git a/assets/images/logos/invidious.jpg b/assets/images/logos/invidious.jpg new file mode 100644 index 00000000..3a54ace1 Binary files /dev/null and b/assets/images/logos/invidious.jpg differ diff --git a/assets/jiosaavn.png b/assets/images/logos/jiosaavn.png similarity index 100% rename from assets/jiosaavn.png rename to assets/images/logos/jiosaavn.png diff --git a/assets/images/logos/songlink-transparent.png b/assets/images/logos/songlink-transparent.png new file mode 100644 index 00000000..fc4ae541 Binary files /dev/null and b/assets/images/logos/songlink-transparent.png differ diff --git a/assets/placeholder.png b/assets/images/placeholder.png similarity index 100% rename from assets/placeholder.png rename to assets/images/placeholder.png diff --git a/assets/user-placeholder.png b/assets/images/user-placeholder.png similarity index 100% rename from assets/user-placeholder.png rename to assets/images/user-placeholder.png diff --git a/assets/invidious.jpg b/assets/invidious.jpg deleted file mode 100644 index 12c5f107..00000000 Binary files a/assets/invidious.jpg and /dev/null differ diff --git a/assets/liked-tracks.jpg b/assets/liked-tracks.jpg deleted file mode 100644 index 62dad65e..00000000 Binary files a/assets/liked-tracks.jpg and /dev/null differ diff --git a/assets/logos/songlink-transparent.png b/assets/logos/songlink-transparent.png deleted file mode 100644 index 6b7064c9..00000000 Binary files a/assets/logos/songlink-transparent.png and /dev/null differ diff --git a/assets/logos/songlink.png b/assets/logos/songlink.png deleted file mode 100644 index 43d823a5..00000000 Binary files a/assets/logos/songlink.png and /dev/null differ diff --git a/assets/mobile-screenshots/android-1.jpg b/assets/mobile-screenshots/android-1.jpg deleted file mode 100644 index 574c86c8..00000000 Binary files a/assets/mobile-screenshots/android-1.jpg and /dev/null differ diff --git a/assets/mobile-screenshots/android-2.jpg b/assets/mobile-screenshots/android-2.jpg deleted file mode 100644 index 62da3c86..00000000 Binary files a/assets/mobile-screenshots/android-2.jpg and /dev/null differ diff --git a/assets/mobile-screenshots/android-3.jpg b/assets/mobile-screenshots/android-3.jpg deleted file mode 100644 index ccdbee84..00000000 Binary files a/assets/mobile-screenshots/android-3.jpg and /dev/null differ diff --git a/assets/mobile-screenshots/android-4.jpg b/assets/mobile-screenshots/android-4.jpg deleted file mode 100644 index 278aae21..00000000 Binary files a/assets/mobile-screenshots/android-4.jpg and /dev/null differ diff --git a/assets/mobile-screenshots/android-5.jpg b/assets/mobile-screenshots/android-5.jpg deleted file mode 100644 index 563b26c6..00000000 Binary files a/assets/mobile-screenshots/android-5.jpg and /dev/null differ diff --git a/assets/mobile-screenshots/android-6.jpg b/assets/mobile-screenshots/android-6.jpg deleted file mode 100644 index fc8d4750..00000000 Binary files a/assets/mobile-screenshots/android-6.jpg and /dev/null differ diff --git a/assets/mobile-screenshots/combined.png b/assets/mobile-screenshots/combined.png deleted file mode 100644 index a7a394f6..00000000 Binary files a/assets/mobile-screenshots/combined.png and /dev/null differ diff --git a/assets/patterns/black_white_visualized.jpg b/assets/patterns/black_white_visualized.jpg deleted file mode 100644 index e56a2780..00000000 Binary files a/assets/patterns/black_white_visualized.jpg and /dev/null differ diff --git a/assets/patterns/brazil_carnival.jpg b/assets/patterns/brazil_carnival.jpg deleted file mode 100644 index a7cdb3a1..00000000 Binary files a/assets/patterns/brazil_carnival.jpg and /dev/null differ diff --git a/assets/patterns/cotton_balls.jpg b/assets/patterns/cotton_balls.jpg deleted file mode 100644 index db6f02a8..00000000 Binary files a/assets/patterns/cotton_balls.jpg and /dev/null differ diff --git a/assets/patterns/cute_worms.jpg b/assets/patterns/cute_worms.jpg deleted file mode 100644 index 0c9f4fbb..00000000 Binary files a/assets/patterns/cute_worms.jpg and /dev/null differ diff --git a/assets/patterns/flash_cross_axis.jpg b/assets/patterns/flash_cross_axis.jpg deleted file mode 100644 index c6e52283..00000000 Binary files a/assets/patterns/flash_cross_axis.jpg and /dev/null differ diff --git a/assets/patterns/memphis_shapes.jpg b/assets/patterns/memphis_shapes.jpg deleted file mode 100644 index 2db8e775..00000000 Binary files a/assets/patterns/memphis_shapes.jpg and /dev/null differ diff --git a/assets/patterns/oval_gloomy.jpg b/assets/patterns/oval_gloomy.jpg deleted file mode 100644 index b44bf945..00000000 Binary files a/assets/patterns/oval_gloomy.jpg and /dev/null differ diff --git a/assets/patterns/oval_sunny.jpg b/assets/patterns/oval_sunny.jpg deleted file mode 100644 index bc07ae83..00000000 Binary files a/assets/patterns/oval_sunny.jpg and /dev/null differ diff --git a/assets/patterns/red_nimbuses.jpg b/assets/patterns/red_nimbuses.jpg deleted file mode 100644 index 6527999c..00000000 Binary files a/assets/patterns/red_nimbuses.jpg and /dev/null differ diff --git a/assets/patterns/tree_bark.jpg b/assets/patterns/tree_bark.jpg deleted file mode 100644 index 0dac37d7..00000000 Binary files a/assets/patterns/tree_bark.jpg and /dev/null differ diff --git a/assets/patterns/vibrant_pentagons.jpg b/assets/patterns/vibrant_pentagons.jpg deleted file mode 100644 index d9e8d537..00000000 Binary files a/assets/patterns/vibrant_pentagons.jpg and /dev/null differ diff --git a/assets/patterns/wiring_pattern.jpg b/assets/patterns/wiring_pattern.jpg deleted file mode 100644 index 9fc3b781..00000000 Binary files a/assets/patterns/wiring_pattern.jpg and /dev/null differ diff --git a/assets/patterns/zigzags_gloomy.jpg b/assets/patterns/zigzags_gloomy.jpg deleted file mode 100644 index c6ccd2a3..00000000 Binary files a/assets/patterns/zigzags_gloomy.jpg and /dev/null differ diff --git a/assets/patterns/zigzags_sunny.jpg b/assets/patterns/zigzags_sunny.jpg deleted file mode 100644 index 7470d5ef..00000000 Binary files a/assets/patterns/zigzags_sunny.jpg and /dev/null differ diff --git a/assets/spotube-screenshot.png b/assets/spotube-screenshot.png deleted file mode 100644 index c76d2f8a..00000000 Binary files a/assets/spotube-screenshot.png and /dev/null differ diff --git a/assets/success.png b/assets/success.png deleted file mode 100644 index 65cdba35..00000000 Binary files a/assets/success.png and /dev/null differ diff --git a/assets/tutorial/step-1.png b/assets/tutorial/step-1.png deleted file mode 100644 index 1182f054..00000000 Binary files a/assets/tutorial/step-1.png and /dev/null differ diff --git a/assets/tutorial/step-2.png b/assets/tutorial/step-2.png deleted file mode 100644 index af4616b0..00000000 Binary files a/assets/tutorial/step-2.png and /dev/null differ diff --git a/assets/tutorial/step-3.png b/assets/tutorial/step-3.png deleted file mode 100644 index ddbea140..00000000 Binary files a/assets/tutorial/step-3.png and /dev/null differ diff --git a/choco-struct/spotube.nuspec b/choco-struct/spotube.nuspec index e3588d98..40941c08 100644 --- a/choco-struct/spotube.nuspec +++ b/choco-struct/spotube.nuspec @@ -15,7 +15,7 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t Kingkor Roy Tirtho https://spotube.krtirtho.dev - https://rawcdn.githack.com/KRTirtho/spotube/7edb0bb834eb18c05551e30a891720a6abf53dbe/assets/spotube-logo.png + https://rawcdn.githack.com/KRTirtho/spotube/7edb0bb834eb18c05551e30a891720a6abf53dbe/assets/branding/spotube-logo.png 2022 Spotube https://github.com/KRTirtho/spotube/blob/master/LICENSE diff --git a/cli/commands/build/linux.dart b/cli/commands/build/linux.dart index 3fd8a0b9..378f5a72 100644 --- a/cli/commands/build/linux.dart +++ b/cli/commands/build/linux.dart @@ -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"), ); diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..311a7d7c --- /dev/null +++ b/cliff.toml @@ -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") }}](/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 with a URL. + { pattern = '', 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}](/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 = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^translation", group = " Translation" }, + # { message = "^perf", group = "⚡ Performance" }, + # { message = "^refactor", group = "🚜 Refactor" }, + # { message = "^style", group = "🎨 Styling" }, + # { message = "^test", group = "🧪 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 = "⚙️ Miscellaneous Tasks" }, + # { body = ".*security", group = "🛡️ Security" }, + # { message = "^revert", group = "◀️ Revert" }, + # { message = ".*", group = "💼 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 diff --git a/drift_schemas/app_db/drift_schema_v8.json b/drift_schemas/app_db/drift_schema_v8.json new file mode 100644 index 00000000..eba4c46e --- /dev/null +++ b/drift_schemas/app_db/drift_schema_v8.json @@ -0,0 +1,1143 @@ +{ + "_meta": { + "description": "This file contains a serialized version of schema entities for drift.", + "version": "1.2.0" + }, + "options": { "store_date_time_values_as_text": false }, + "entities": [ + { + "id": 0, + "references": [], + "type": "table", + "data": { + "name": "authentication_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "cookie", + "getter_name": "cookie", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "EncryptedTextConverter()", + "dart_type_name": "DecryptedText" + } + }, + { + "name": "access_token", + "getter_name": "accessToken", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "EncryptedTextConverter()", + "dart_type_name": "DecryptedText" + } + }, + { + "name": "expiration", + "getter_name": "expiration", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 1, + "references": [], + "type": "table", + "data": { + "name": "blacklist_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "element_type", + "getter_name": "elementType", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(BlacklistedType.values)", + "dart_type_name": "BlacklistedType" + } + }, + { + "name": "element_id", + "getter_name": "elementId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 2, + "references": [], + "type": "table", + "data": { + "name": "preferences_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "audio_quality", + "getter_name": "audioQuality", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(SourceQualities.high.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(SourceQualities.values)", + "dart_type_name": "SourceQualities" + } + }, + { + "name": "album_color_sync", + "getter_name": "albumColorSync", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"album_color_sync\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"album_color_sync\" IN (0, 1))" + }, + "default_dart": "const Constant(true)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "amoled_dark_theme", + "getter_name": "amoledDarkTheme", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"amoled_dark_theme\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"amoled_dark_theme\" IN (0, 1))" + }, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "check_update", + "getter_name": "checkUpdate", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"check_update\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"check_update\" IN (0, 1))" + }, + "default_dart": "const Constant(true)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "normalize_audio", + "getter_name": "normalizeAudio", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"normalize_audio\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"normalize_audio\" IN (0, 1))" + }, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "show_system_tray_icon", + "getter_name": "showSystemTrayIcon", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"show_system_tray_icon\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"show_system_tray_icon\" IN (0, 1))" + }, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "system_title_bar", + "getter_name": "systemTitleBar", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"system_title_bar\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"system_title_bar\" IN (0, 1))" + }, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "skip_non_music", + "getter_name": "skipNonMusic", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"skip_non_music\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"skip_non_music\" IN (0, 1))" + }, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "close_behavior", + "getter_name": "closeBehavior", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(CloseBehavior.close.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(CloseBehavior.values)", + "dart_type_name": "CloseBehavior" + } + }, + { + "name": "accent_color_scheme", + "getter_name": "accentColorScheme", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(\"Slate:0xff64748b\")", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const SpotubeColorConverter()", + "dart_type_name": "SpotubeColor" + } + }, + { + "name": "layout_mode", + "getter_name": "layoutMode", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(LayoutMode.adaptive.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(LayoutMode.values)", + "dart_type_name": "LayoutMode" + } + }, + { + "name": "locale", + "getter_name": "locale", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const LocaleConverter()", + "dart_type_name": "Locale" + } + }, + { + "name": "market", + "getter_name": "market", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(Market.US.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(Market.values)", + "dart_type_name": "Market" + } + }, + { + "name": "search_mode", + "getter_name": "searchMode", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(SearchMode.youtube.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(SearchMode.values)", + "dart_type_name": "SearchMode" + } + }, + { + "name": "download_location", + "getter_name": "downloadLocation", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(\"\")", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "local_library_location", + "getter_name": "localLibraryLocation", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(\"\")", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const StringListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "piped_instance", + "getter_name": "pipedInstance", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(\"https://pipedapi.kavin.rocks\")", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "invidious_instance", + "getter_name": "invidiousInstance", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(\"https://inv.nadeko.net\")", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "theme_mode", + "getter_name": "themeMode", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(ThemeMode.system.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(ThemeMode.values)", + "dart_type_name": "ThemeMode" + } + }, + { + "name": "audio_source", + "getter_name": "audioSource", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(AudioSource.youtube.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(AudioSource.values)", + "dart_type_name": "AudioSource" + } + }, + { + "name": "youtube_client_engine", + "getter_name": "youtubeClientEngine", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(YoutubeClientEngine.youtubeExplode.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(YoutubeClientEngine.values)", + "dart_type_name": "YoutubeClientEngine" + } + }, + { + "name": "stream_music_codec", + "getter_name": "streamMusicCodec", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(SourceCodecs.weba.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(SourceCodecs.values)", + "dart_type_name": "SourceCodecs" + } + }, + { + "name": "download_music_codec", + "getter_name": "downloadMusicCodec", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(SourceCodecs.m4a.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(SourceCodecs.values)", + "dart_type_name": "SourceCodecs" + } + }, + { + "name": "discord_presence", + "getter_name": "discordPresence", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"discord_presence\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"discord_presence\" IN (0, 1))" + }, + "default_dart": "const Constant(true)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "endless_playback", + "getter_name": "endlessPlayback", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"endless_playback\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"endless_playback\" IN (0, 1))" + }, + "default_dart": "const Constant(true)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "enable_connect", + "getter_name": "enableConnect", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"enable_connect\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"enable_connect\" IN (0, 1))" + }, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "connect_port", + "getter_name": "connectPort", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(-1)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "cache_music", + "getter_name": "cacheMusic", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"cache_music\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"cache_music\" IN (0, 1))" + }, + "default_dart": "const Constant(true)", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 3, + "references": [], + "type": "table", + "data": { + "name": "scrobbler_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "currentDateAndTime", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "username", + "getter_name": "username", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "password_hash", + "getter_name": "passwordHash", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "EncryptedTextConverter()", + "dart_type_name": "DecryptedText" + } + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 4, + "references": [], + "type": "table", + "data": { + "name": "skip_segment_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "start", + "getter_name": "start", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "end", + "getter_name": "end", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "track_id", + "getter_name": "trackId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "currentDateAndTime", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 5, + "references": [], + "type": "table", + "data": { + "name": "source_match_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "track_id", + "getter_name": "trackId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source_id", + "getter_name": "sourceId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "source_type", + "getter_name": "sourceType", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "Constant(SourceType.youtube.name)", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(SourceType.values)", + "dart_type_name": "SourceType" + } + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "currentDateAndTime", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 6, + "references": [], + "type": "table", + "data": { + "name": "audio_player_state_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "playing", + "getter_name": "playing", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"playing\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"playing\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "loop_mode", + "getter_name": "loopMode", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(PlaylistMode.values)", + "dart_type_name": "PlaylistMode" + } + }, + { + "name": "shuffled", + "getter_name": "shuffled", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"shuffled\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"shuffled\" IN (0, 1))" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "collections", + "getter_name": "collections", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const StringListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "tracks", + "getter_name": "tracks", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(\"[]\")", + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const SpotubeTrackObjectListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "current_index", + "getter_name": "currentIndex", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant(0)", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 7, + "references": [], + "type": "table", + "data": { + "name": "history_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "created_at", + "getter_name": "createdAt", + "moor_type": "dateTime", + "nullable": false, + "customConstraints": null, + "default_dart": "currentDateAndTime", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "type", + "getter_name": "type", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const EnumNameConverter(HistoryEntryType.values)", + "dart_type_name": "HistoryEntryType" + } + }, + { + "name": "item_id", + "getter_name": "itemId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "data", + "getter_name": "data", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const MapTypeConverter()", + "dart_type_name": "Map" + } + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 8, + "references": [], + "type": "table", + "data": { + "name": "lyrics_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "track_id", + "getter_name": "trackId", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "data", + "getter_name": "data", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "SubtitleTypeConverter()", + "dart_type_name": "SubtitleSimple" + } + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 9, + "references": [], + "type": "table", + "data": { + "name": "metadata_plugins_table", + "was_declared_in_moor": false, + "columns": [ + { + "name": "id", + "getter_name": "id", + "moor_type": "int", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "PRIMARY KEY AUTOINCREMENT", + "dialectAwareDefaultConstraints": { + "sqlite": "PRIMARY KEY AUTOINCREMENT" + }, + "default_dart": null, + "default_client_dart": null, + "dsl_features": ["auto-increment"] + }, + { + "name": "name", + "getter_name": "name", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [{ "allowed-lengths": { "min": 1, "max": 50 } }] + }, + { + "name": "description", + "getter_name": "description", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "version", + "getter_name": "version", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "author", + "getter_name": "author", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "entry_point", + "getter_name": "entryPoint", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "apis", + "getter_name": "apis", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const StringListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "abilities", + "getter_name": "abilities", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [], + "type_converter": { + "dart_expr": "const StringListConverter()", + "dart_type_name": "List" + } + }, + { + "name": "selected", + "getter_name": "selected", + "moor_type": "bool", + "nullable": false, + "customConstraints": null, + "defaultConstraints": "CHECK (\"selected\" IN (0, 1))", + "dialectAwareDefaultConstraints": { + "sqlite": "CHECK (\"selected\" IN (0, 1))" + }, + "default_dart": "const Constant(false)", + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "repository", + "getter_name": "repository", + "moor_type": "string", + "nullable": true, + "customConstraints": null, + "default_dart": null, + "default_client_dart": null, + "dsl_features": [] + }, + { + "name": "plugin_api_version", + "getter_name": "pluginApiVersion", + "moor_type": "string", + "nullable": false, + "customConstraints": null, + "default_dart": "const Constant('1.0.0')", + "default_client_dart": null, + "dsl_features": [] + } + ], + "is_virtual": false, + "without_rowid": false, + "constraints": [] + } + }, + { + "id": 10, + "references": [1], + "type": "index", + "data": { + "on": 1, + "name": "unique_blacklist", + "sql": null, + "unique": true, + "columns": ["element_type", "element_id"] + } + }, + { + "id": 11, + "references": [5], + "type": "index", + "data": { + "on": 5, + "name": "uniq_track_match", + "sql": null, + "unique": true, + "columns": ["track_id", "source_id", "source_type"] + } + } + ] +} diff --git a/flutter_launcher_icons-nightly.yaml b/flutter_launcher_icons-nightly.yaml index 770033bd..9e4e805c 100644 --- a/flutter_launcher_icons-nightly.yaml +++ b/flutter_launcher_icons-nightly.yaml @@ -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" diff --git a/flutter_launcher_icons.yaml b/flutter_launcher_icons.yaml index 2c558583..e5b26882 100644 --- a/flutter_launcher_icons.yaml +++ b/flutter_launcher_icons.yaml @@ -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" diff --git a/flutter_native_splash-nightly.yaml b/flutter_native_splash-nightly.yaml index 37da37d9..3b7daeec 100644 --- a/flutter_native_splash-nightly.yaml +++ b/flutter_native_splash-nightly.yaml @@ -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" diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 31ffe436..2ff415a0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,13 +1,16 @@ PODS: - - app_links (0.0.2): + - app_links (6.4.1): - Flutter - audio_service (0.0.1): - Flutter + - FlutterMacOS - audio_session (0.0.1): - Flutter - bonsoir_darwin (0.0.1): - Flutter - FlutterMacOS + - connectivity_plus (0.0.1): + - Flutter - device_info_plus (0.0.1): - Flutter - DKImagePickerController/Core (4.3.4): @@ -46,6 +49,8 @@ PODS: - Flutter - file_selector_ios (0.0.1): - Flutter + - fk_user_agent (2.0.0): + - Flutter - Flutter (1.0.0) - flutter_broadcasts (0.0.1): - Flutter @@ -64,15 +69,17 @@ PODS: - Flutter - flutter_sharing_intent (0.0.1): - Flutter + - flutter_timezone (0.0.1): + - Flutter - home_widget (0.0.1): - Flutter - image_picker_ios (0.0.1): - Flutter - integration_test (0.0.1): - Flutter - - media_kit_libs_ios_audio (1.0.4): + - irondash_engine_context (0.0.1): - Flutter - - media_kit_native_event_loop (1.0.0): + - media_kit_libs_ios_audio (1.0.4): - Flutter - metadata_god (0.0.1): - Flutter @@ -95,25 +102,33 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - sqlite3 (3.47.1): - - sqlite3/common (= 3.47.1) - - sqlite3/common (3.47.1) - - sqlite3/dbstatvtab (3.47.1): + - sqlite3 (3.50.4): + - sqlite3/common (= 3.50.4) + - sqlite3/common (3.50.4) + - sqlite3/dbstatvtab (3.50.4): - sqlite3/common - - sqlite3/fts5 (3.47.1): + - sqlite3/fts5 (3.50.4): - sqlite3/common - - sqlite3/perf-threadsafe (3.47.1): + - sqlite3/math (3.50.4): - sqlite3/common - - sqlite3/rtree (3.47.1): + - sqlite3/perf-threadsafe (3.50.4): + - sqlite3/common + - sqlite3/rtree (3.50.4): + - sqlite3/common + - sqlite3/session (3.50.4): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - FlutterMacOS - - sqlite3 (~> 3.47.1) + - sqlite3 (~> 3.50.4) - sqlite3/dbstatvtab - sqlite3/fts5 + - sqlite3/math - sqlite3/perf-threadsafe - sqlite3/rtree + - sqlite3/session + - super_native_extensions (0.0.1): + - Flutter - SwiftyGif (5.4.4) - system_theme (0.0.1): - Flutter @@ -122,12 +137,14 @@ PODS: DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - - audio_service (from `.symlinks/plugins/audio_service/ios`) + - audio_service (from `.symlinks/plugins/audio_service/darwin`) - audio_session (from `.symlinks/plugins/audio_session/ios`) - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) + - fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`) - Flutter (from `Flutter`) - flutter_broadcasts (from `.symlinks/plugins/flutter_broadcasts/ios`) - flutter_discord_rpc (from `.symlinks/plugins/flutter_discord_rpc/ios`) @@ -135,11 +152,12 @@ DEPENDENCIES: - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_sharing_intent (from `.symlinks/plugins/flutter_sharing_intent/ios`) + - flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`) - home_widget (from `.symlinks/plugins/home_widget/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) + - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`) - media_kit_libs_ios_audio (from `.symlinks/plugins/media_kit_libs_ios_audio/ios`) - - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) - metadata_god (from `.symlinks/plugins/metadata_god/ios`) - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) @@ -148,6 +166,7 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) + - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`) - system_theme (from `.symlinks/plugins/system_theme/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -164,17 +183,21 @@ EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" audio_service: - :path: ".symlinks/plugins/audio_service/ios" + :path: ".symlinks/plugins/audio_service/darwin" audio_session: :path: ".symlinks/plugins/audio_session/ios" bonsoir_darwin: :path: ".symlinks/plugins/bonsoir_darwin/darwin" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" file_selector_ios: :path: ".symlinks/plugins/file_selector_ios/ios" + fk_user_agent: + :path: ".symlinks/plugins/fk_user_agent/ios" Flutter: :path: Flutter flutter_broadcasts: @@ -189,16 +212,18 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" flutter_sharing_intent: :path: ".symlinks/plugins/flutter_sharing_intent/ios" + flutter_timezone: + :path: ".symlinks/plugins/flutter_timezone/ios" home_widget: :path: ".symlinks/plugins/home_widget/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" + irondash_engine_context: + :path: ".symlinks/plugins/irondash_engine_context/ios" media_kit_libs_ios_audio: :path: ".symlinks/plugins/media_kit_libs_ios_audio/ios" - media_kit_native_event_loop: - :path: ".symlinks/plugins/media_kit_native_event_loop/ios" metadata_god: :path: ".symlinks/plugins/metadata_god/ios" open_file_ios: @@ -215,47 +240,53 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite_darwin/darwin" sqlite3_flutter_libs: :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" + super_native_extensions: + :path: ".symlinks/plugins/super_native_extensions/ios" system_theme: :path: ".symlinks/plugins/system_theme/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0 - audio_service: f509d65da41b9521a61f1c404dd58651f265a567 - audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207 - bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842 - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a + audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd + audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0 + bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 - file_selector_ios: f0670c1064a8c8450e38145d8043160105d0b97c - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_broadcasts: 3ece15b27d8ccbe2132c3df303e7c3401feab882 - flutter_discord_rpc: e1c342f29ceb9dd76cdc01db59a70c93bb4d9ec5 - flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 - flutter_native_splash: e8a1e01082d97a8099d973f919f57904c925008a - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - flutter_sharing_intent: e35380d0e1501d7111dbb7e46d5ac6339da6da98 - home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 - media_kit_libs_ios_audio: 8f39d96a9c630685dfb844c289bd1d114c486fb3 - media_kit_native_event_loop: 99111eded5acbdc9c2738021ea6550dd36ca8837 - metadata_god: 4bbd8523cdb5d42c5e59d2fabad01ff8f4bc53f9 - open_file_ios: 461db5853723763573e140de3193656f91990d9e + file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 + file_selector_ios: f92e583d43608aebc2e4a18daac30b8902845502 + fk_user_agent: 137145b086229251761678fe034da53753f4ce59 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_broadcasts: 7bb7cc1024900a7f85e98b6faab795290b7c2339 + flutter_discord_rpc: 0572e8227ea730c5afe5876a37c08c728ce95f3a + flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + flutter_sharing_intent: afdc98985814d2c01d8c0956a177d6b6dfbdc373 + flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544 + home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 + media_kit_libs_ios_audio: 905e6323b72e65c63ab9262b2e473f52c024a3a8 + metadata_god: 018b59c2f3617569928550dcbd17481591557c1d + open_file_ios: 5ff7526df64e4394b4fe207636b67a95e83078bb OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d SDWebImage: a81bbb3ba4ea5f810f4069c68727cb118467a04a - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - sqlite3: 1e522f0938463e44b7faf50393b40bdc1e1e456d - sqlite3_flutter_libs: 1b4e98da20ebd4e9b1240269b78cdcf492dbe9f3 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b + sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 + super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f - system_theme: bfc1b0913d08f38d8c6bbe94b202a58df599d9f7 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + system_theme: a94f91f49eeb97cfa768c7d5a9b2f6aa51b00494 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d PODFILE CHECKSUM: 0659b64ac6e9e96b61d8550decffa8bff51a957e diff --git a/l10n.yaml b/l10n.yaml index ffab1c86..d5911fe1 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -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 diff --git a/lib/collections/assets.gen.dart b/lib/collections/assets.gen.dart index 09b6cdd2..31fb54b8 100644 --- a/lib/collections/assets.gen.dart +++ b/lib/collections/assets.gen.dart @@ -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 get values => [xmasEffect]; + List 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 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 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 get values => [step1, step2, step3]; + List 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 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 get values => [license]; } class AssetGenImage { diff --git a/lib/collections/fonts.gen.dart b/lib/collections/fonts.gen.dart index c51dfd40..16cc6e82 100644 --- a/lib/collections/fonts.gen.dart +++ b/lib/collections/fonts.gen.dart @@ -18,4 +18,7 @@ class FontFamily { /// Font family: RadixIcons static const String radixIcons = 'RadixIcons'; + + /// Font family: Ubuntu Mono + static const String ubuntuMono = 'Ubuntu Mono'; } diff --git a/lib/collections/language_codes.dart b/lib/collections/language_codes.dart index 75d1e65d..b5d3f7c8 100644 --- a/lib/collections/language_codes.dart +++ b/lib/collections/language_codes.dart @@ -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"); } diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index d38303a7..4dcd9657 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -108,6 +108,10 @@ class AppRouter extends RootStackRouter { path: "settings/about", page: AboutSpotubeRoute.page, ), + AutoRoute( + path: "settings/scrobbling", + page: SettingsScrobblingRoute.page, + ), AutoRoute( path: "album/:id", page: AlbumRoute.page, diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index a5138c2f..e039abb9 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -8,10 +8,10 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i40; -import 'package:flutter/material.dart' as _i41; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i43; -import 'package:spotube/models/metadata/metadata.dart' as _i42; +import 'package:auto_route/auto_route.dart' as _i41; +import 'package:flutter/material.dart' as _i42; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i44; +import 'package:spotube/models/metadata/metadata.dart' as _i43; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -21,14 +21,14 @@ import 'package:spotube/pages/home/home.dart' as _i9; import 'package:spotube/pages/home/sections/section_items.dart' as _i8; import 'package:spotube/pages/lastfm_login/lastfm_login.dart' as _i10; import 'package:spotube/pages/library/library.dart' as _i11; -import 'package:spotube/pages/library/user_albums.dart' as _i35; -import 'package:spotube/pages/library/user_artists.dart' as _i36; -import 'package:spotube/pages/library/user_downloads.dart' as _i37; +import 'package:spotube/pages/library/user_albums.dart' as _i36; +import 'package:spotube/pages/library/user_artists.dart' as _i37; +import 'package:spotube/pages/library/user_downloads.dart' as _i38; import 'package:spotube/pages/library/user_local_tracks/local_folder.dart' as _i13; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart' - as _i38; -import 'package:spotube/pages/library/user_playlists.dart' as _i39; + as _i39; +import 'package:spotube/pages/library/user_playlists.dart' as _i40; import 'package:spotube/pages/lyrics/lyrics.dart' as _i15; import 'package:spotube/pages/lyrics/mini_lyrics.dart' as _i16; import 'package:spotube/pages/player/lyrics.dart' as _i17; @@ -44,20 +44,21 @@ import 'package:spotube/pages/settings/blacklist.dart' as _i4; import 'package:spotube/pages/settings/logs.dart' as _i14; import 'package:spotube/pages/settings/metadata/metadata_form.dart' as _i24; import 'package:spotube/pages/settings/metadata_plugins.dart' as _i25; +import 'package:spotube/pages/settings/scrobbling/scrobbling.dart' as _i27; import 'package:spotube/pages/settings/settings.dart' as _i26; -import 'package:spotube/pages/stats/albums/albums.dart' as _i27; -import 'package:spotube/pages/stats/artists/artists.dart' as _i28; -import 'package:spotube/pages/stats/fees/fees.dart' as _i32; -import 'package:spotube/pages/stats/minutes/minutes.dart' as _i29; -import 'package:spotube/pages/stats/playlists/playlists.dart' as _i31; -import 'package:spotube/pages/stats/stats.dart' as _i30; -import 'package:spotube/pages/stats/streams/streams.dart' as _i33; -import 'package:spotube/pages/track/track.dart' as _i34; +import 'package:spotube/pages/stats/albums/albums.dart' as _i28; +import 'package:spotube/pages/stats/artists/artists.dart' as _i29; +import 'package:spotube/pages/stats/fees/fees.dart' as _i33; +import 'package:spotube/pages/stats/minutes/minutes.dart' as _i30; +import 'package:spotube/pages/stats/playlists/playlists.dart' as _i32; +import 'package:spotube/pages/stats/stats.dart' as _i31; +import 'package:spotube/pages/stats/streams/streams.dart' as _i34; +import 'package:spotube/pages/track/track.dart' as _i35; /// generated route for /// [_i1.AboutSpotubePage] -class AboutSpotubeRoute extends _i40.PageRouteInfo { - const AboutSpotubeRoute({List<_i40.PageRouteInfo>? children}) +class AboutSpotubeRoute extends _i41.PageRouteInfo { + const AboutSpotubeRoute({List<_i41.PageRouteInfo>? children}) : super( AboutSpotubeRoute.name, initialChildren: children, @@ -65,7 +66,7 @@ class AboutSpotubeRoute extends _i40.PageRouteInfo { static const String name = 'AboutSpotubeRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i1.AboutSpotubePage(); @@ -75,12 +76,12 @@ class AboutSpotubeRoute extends _i40.PageRouteInfo { /// generated route for /// [_i2.AlbumPage] -class AlbumRoute extends _i40.PageRouteInfo { +class AlbumRoute extends _i41.PageRouteInfo { AlbumRoute({ - _i41.Key? key, + _i42.Key? key, required String id, - required _i42.SpotubeSimpleAlbumObject album, - List<_i40.PageRouteInfo>? children, + required _i43.SpotubeSimpleAlbumObject album, + List<_i41.PageRouteInfo>? children, }) : super( AlbumRoute.name, args: AlbumRouteArgs( @@ -94,7 +95,7 @@ class AlbumRoute extends _i40.PageRouteInfo { static const String name = 'AlbumRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -114,11 +115,11 @@ class AlbumRouteArgs { required this.album, }); - final _i41.Key? key; + final _i42.Key? key; final String id; - final _i42.SpotubeSimpleAlbumObject album; + final _i43.SpotubeSimpleAlbumObject album; @override String toString() { @@ -128,11 +129,11 @@ class AlbumRouteArgs { /// generated route for /// [_i3.ArtistPage] -class ArtistRoute extends _i40.PageRouteInfo { +class ArtistRoute extends _i41.PageRouteInfo { ArtistRoute({ required String artistId, - _i41.Key? key, - List<_i40.PageRouteInfo>? children, + _i42.Key? key, + List<_i41.PageRouteInfo>? children, }) : super( ArtistRoute.name, args: ArtistRouteArgs( @@ -145,7 +146,7 @@ class ArtistRoute extends _i40.PageRouteInfo { static const String name = 'ArtistRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -167,7 +168,7 @@ class ArtistRouteArgs { final String artistId; - final _i41.Key? key; + final _i42.Key? key; @override String toString() { @@ -177,8 +178,8 @@ class ArtistRouteArgs { /// generated route for /// [_i4.BlackListPage] -class BlackListRoute extends _i40.PageRouteInfo { - const BlackListRoute({List<_i40.PageRouteInfo>? children}) +class BlackListRoute extends _i41.PageRouteInfo { + const BlackListRoute({List<_i41.PageRouteInfo>? children}) : super( BlackListRoute.name, initialChildren: children, @@ -186,7 +187,7 @@ class BlackListRoute extends _i40.PageRouteInfo { static const String name = 'BlackListRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i4.BlackListPage(); @@ -196,8 +197,8 @@ class BlackListRoute extends _i40.PageRouteInfo { /// generated route for /// [_i5.ConnectControlPage] -class ConnectControlRoute extends _i40.PageRouteInfo { - const ConnectControlRoute({List<_i40.PageRouteInfo>? children}) +class ConnectControlRoute extends _i41.PageRouteInfo { + const ConnectControlRoute({List<_i41.PageRouteInfo>? children}) : super( ConnectControlRoute.name, initialChildren: children, @@ -205,7 +206,7 @@ class ConnectControlRoute extends _i40.PageRouteInfo { static const String name = 'ConnectControlRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i5.ConnectControlPage(); @@ -215,8 +216,8 @@ class ConnectControlRoute extends _i40.PageRouteInfo { /// generated route for /// [_i6.ConnectPage] -class ConnectRoute extends _i40.PageRouteInfo { - const ConnectRoute({List<_i40.PageRouteInfo>? children}) +class ConnectRoute extends _i41.PageRouteInfo { + const ConnectRoute({List<_i41.PageRouteInfo>? children}) : super( ConnectRoute.name, initialChildren: children, @@ -224,7 +225,7 @@ class ConnectRoute extends _i40.PageRouteInfo { static const String name = 'ConnectRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i6.ConnectPage(); @@ -234,8 +235,8 @@ class ConnectRoute extends _i40.PageRouteInfo { /// generated route for /// [_i7.GettingStartedPage] -class GettingStartedRoute extends _i40.PageRouteInfo { - const GettingStartedRoute({List<_i40.PageRouteInfo>? children}) +class GettingStartedRoute extends _i41.PageRouteInfo { + const GettingStartedRoute({List<_i41.PageRouteInfo>? children}) : super( GettingStartedRoute.name, initialChildren: children, @@ -243,7 +244,7 @@ class GettingStartedRoute extends _i40.PageRouteInfo { static const String name = 'GettingStartedRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i7.GettingStartedPage(); @@ -254,12 +255,12 @@ class GettingStartedRoute extends _i40.PageRouteInfo { /// generated route for /// [_i8.HomeBrowseSectionItemsPage] class HomeBrowseSectionItemsRoute - extends _i40.PageRouteInfo { + extends _i41.PageRouteInfo { HomeBrowseSectionItemsRoute({ - _i43.Key? key, + _i44.Key? key, required String sectionId, - required _i42.SpotubeBrowseSectionObject section, - List<_i40.PageRouteInfo>? children, + required _i43.SpotubeBrowseSectionObject section, + List<_i41.PageRouteInfo>? children, }) : super( HomeBrowseSectionItemsRoute.name, args: HomeBrowseSectionItemsRouteArgs( @@ -273,7 +274,7 @@ class HomeBrowseSectionItemsRoute static const String name = 'HomeBrowseSectionItemsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -293,11 +294,11 @@ class HomeBrowseSectionItemsRouteArgs { required this.section, }); - final _i43.Key? key; + final _i44.Key? key; final String sectionId; - final _i42.SpotubeBrowseSectionObject section; + final _i43.SpotubeBrowseSectionObject section; @override String toString() { @@ -307,8 +308,8 @@ class HomeBrowseSectionItemsRouteArgs { /// generated route for /// [_i9.HomePage] -class HomeRoute extends _i40.PageRouteInfo { - const HomeRoute({List<_i40.PageRouteInfo>? children}) +class HomeRoute extends _i41.PageRouteInfo { + const HomeRoute({List<_i41.PageRouteInfo>? children}) : super( HomeRoute.name, initialChildren: children, @@ -316,7 +317,7 @@ class HomeRoute extends _i40.PageRouteInfo { static const String name = 'HomeRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i9.HomePage(); @@ -326,8 +327,8 @@ class HomeRoute extends _i40.PageRouteInfo { /// generated route for /// [_i10.LastFMLoginPage] -class LastFMLoginRoute extends _i40.PageRouteInfo { - const LastFMLoginRoute({List<_i40.PageRouteInfo>? children}) +class LastFMLoginRoute extends _i41.PageRouteInfo { + const LastFMLoginRoute({List<_i41.PageRouteInfo>? children}) : super( LastFMLoginRoute.name, initialChildren: children, @@ -335,7 +336,7 @@ class LastFMLoginRoute extends _i40.PageRouteInfo { static const String name = 'LastFMLoginRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i10.LastFMLoginPage(); @@ -345,8 +346,8 @@ class LastFMLoginRoute extends _i40.PageRouteInfo { /// generated route for /// [_i11.LibraryPage] -class LibraryRoute extends _i40.PageRouteInfo { - const LibraryRoute({List<_i40.PageRouteInfo>? children}) +class LibraryRoute extends _i41.PageRouteInfo { + const LibraryRoute({List<_i41.PageRouteInfo>? children}) : super( LibraryRoute.name, initialChildren: children, @@ -354,7 +355,7 @@ class LibraryRoute extends _i40.PageRouteInfo { static const String name = 'LibraryRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i11.LibraryPage(); @@ -364,11 +365,11 @@ class LibraryRoute extends _i40.PageRouteInfo { /// generated route for /// [_i12.LikedPlaylistPage] -class LikedPlaylistRoute extends _i40.PageRouteInfo { +class LikedPlaylistRoute extends _i41.PageRouteInfo { LikedPlaylistRoute({ - _i41.Key? key, - required _i42.SpotubeSimplePlaylistObject playlist, - List<_i40.PageRouteInfo>? children, + _i42.Key? key, + required _i43.SpotubeSimplePlaylistObject playlist, + List<_i41.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, args: LikedPlaylistRouteArgs( @@ -380,7 +381,7 @@ class LikedPlaylistRoute extends _i40.PageRouteInfo { static const String name = 'LikedPlaylistRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -398,9 +399,9 @@ class LikedPlaylistRouteArgs { required this.playlist, }); - final _i41.Key? key; + final _i42.Key? key; - final _i42.SpotubeSimplePlaylistObject playlist; + final _i43.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -410,13 +411,13 @@ class LikedPlaylistRouteArgs { /// generated route for /// [_i13.LocalLibraryPage] -class LocalLibraryRoute extends _i40.PageRouteInfo { +class LocalLibraryRoute extends _i41.PageRouteInfo { LocalLibraryRoute({ required String location, - _i41.Key? key, + _i42.Key? key, bool isDownloads = false, bool isCache = false, - List<_i40.PageRouteInfo>? children, + List<_i41.PageRouteInfo>? children, }) : super( LocalLibraryRoute.name, args: LocalLibraryRouteArgs( @@ -430,7 +431,7 @@ class LocalLibraryRoute extends _i40.PageRouteInfo { static const String name = 'LocalLibraryRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -454,7 +455,7 @@ class LocalLibraryRouteArgs { final String location; - final _i41.Key? key; + final _i42.Key? key; final bool isDownloads; @@ -468,8 +469,8 @@ class LocalLibraryRouteArgs { /// generated route for /// [_i14.LogsPage] -class LogsRoute extends _i40.PageRouteInfo { - const LogsRoute({List<_i40.PageRouteInfo>? children}) +class LogsRoute extends _i41.PageRouteInfo { + const LogsRoute({List<_i41.PageRouteInfo>? children}) : super( LogsRoute.name, initialChildren: children, @@ -477,7 +478,7 @@ class LogsRoute extends _i40.PageRouteInfo { static const String name = 'LogsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i14.LogsPage(); @@ -487,8 +488,8 @@ class LogsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i15.LyricsPage] -class LyricsRoute extends _i40.PageRouteInfo { - const LyricsRoute({List<_i40.PageRouteInfo>? children}) +class LyricsRoute extends _i41.PageRouteInfo { + const LyricsRoute({List<_i41.PageRouteInfo>? children}) : super( LyricsRoute.name, initialChildren: children, @@ -496,7 +497,7 @@ class LyricsRoute extends _i40.PageRouteInfo { static const String name = 'LyricsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i15.LyricsPage(); @@ -506,11 +507,11 @@ class LyricsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i16.MiniLyricsPage] -class MiniLyricsRoute extends _i40.PageRouteInfo { +class MiniLyricsRoute extends _i41.PageRouteInfo { MiniLyricsRoute({ - _i43.Key? key, - required _i43.Size prevSize, - List<_i40.PageRouteInfo>? children, + _i44.Key? key, + required _i44.Size prevSize, + List<_i41.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, args: MiniLyricsRouteArgs( @@ -522,7 +523,7 @@ class MiniLyricsRoute extends _i40.PageRouteInfo { static const String name = 'MiniLyricsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -540,9 +541,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i43.Key? key; + final _i44.Key? key; - final _i43.Size prevSize; + final _i44.Size prevSize; @override String toString() { @@ -552,8 +553,8 @@ class MiniLyricsRouteArgs { /// generated route for /// [_i17.PlayerLyricsPage] -class PlayerLyricsRoute extends _i40.PageRouteInfo { - const PlayerLyricsRoute({List<_i40.PageRouteInfo>? children}) +class PlayerLyricsRoute extends _i41.PageRouteInfo { + const PlayerLyricsRoute({List<_i41.PageRouteInfo>? children}) : super( PlayerLyricsRoute.name, initialChildren: children, @@ -561,7 +562,7 @@ class PlayerLyricsRoute extends _i40.PageRouteInfo { static const String name = 'PlayerLyricsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i17.PlayerLyricsPage(); @@ -571,8 +572,8 @@ class PlayerLyricsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i18.PlayerQueuePage] -class PlayerQueueRoute extends _i40.PageRouteInfo { - const PlayerQueueRoute({List<_i40.PageRouteInfo>? children}) +class PlayerQueueRoute extends _i41.PageRouteInfo { + const PlayerQueueRoute({List<_i41.PageRouteInfo>? children}) : super( PlayerQueueRoute.name, initialChildren: children, @@ -580,7 +581,7 @@ class PlayerQueueRoute extends _i40.PageRouteInfo { static const String name = 'PlayerQueueRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i18.PlayerQueuePage(); @@ -590,8 +591,8 @@ class PlayerQueueRoute extends _i40.PageRouteInfo { /// generated route for /// [_i19.PlayerTrackSourcesPage] -class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { - const PlayerTrackSourcesRoute({List<_i40.PageRouteInfo>? children}) +class PlayerTrackSourcesRoute extends _i41.PageRouteInfo { + const PlayerTrackSourcesRoute({List<_i41.PageRouteInfo>? children}) : super( PlayerTrackSourcesRoute.name, initialChildren: children, @@ -599,7 +600,7 @@ class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { static const String name = 'PlayerTrackSourcesRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i19.PlayerTrackSourcesPage(); @@ -609,12 +610,12 @@ class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { /// generated route for /// [_i20.PlaylistPage] -class PlaylistRoute extends _i40.PageRouteInfo { +class PlaylistRoute extends _i41.PageRouteInfo { PlaylistRoute({ - _i41.Key? key, + _i42.Key? key, required String id, - required _i42.SpotubeSimplePlaylistObject playlist, - List<_i40.PageRouteInfo>? children, + required _i43.SpotubeSimplePlaylistObject playlist, + List<_i41.PageRouteInfo>? children, }) : super( PlaylistRoute.name, args: PlaylistRouteArgs( @@ -628,7 +629,7 @@ class PlaylistRoute extends _i40.PageRouteInfo { static const String name = 'PlaylistRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -648,11 +649,11 @@ class PlaylistRouteArgs { required this.playlist, }); - final _i41.Key? key; + final _i42.Key? key; final String id; - final _i42.SpotubeSimplePlaylistObject playlist; + final _i43.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -662,8 +663,8 @@ class PlaylistRouteArgs { /// generated route for /// [_i21.ProfilePage] -class ProfileRoute extends _i40.PageRouteInfo { - const ProfileRoute({List<_i40.PageRouteInfo>? children}) +class ProfileRoute extends _i41.PageRouteInfo { + const ProfileRoute({List<_i41.PageRouteInfo>? children}) : super( ProfileRoute.name, initialChildren: children, @@ -671,7 +672,7 @@ class ProfileRoute extends _i40.PageRouteInfo { static const String name = 'ProfileRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i21.ProfilePage(); @@ -681,8 +682,8 @@ class ProfileRoute extends _i40.PageRouteInfo { /// generated route for /// [_i22.RootAppPage] -class RootAppRoute extends _i40.PageRouteInfo { - const RootAppRoute({List<_i40.PageRouteInfo>? children}) +class RootAppRoute extends _i41.PageRouteInfo { + const RootAppRoute({List<_i41.PageRouteInfo>? children}) : super( RootAppRoute.name, initialChildren: children, @@ -690,7 +691,7 @@ class RootAppRoute extends _i40.PageRouteInfo { static const String name = 'RootAppRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i22.RootAppPage(); @@ -700,8 +701,8 @@ class RootAppRoute extends _i40.PageRouteInfo { /// generated route for /// [_i23.SearchPage] -class SearchRoute extends _i40.PageRouteInfo { - const SearchRoute({List<_i40.PageRouteInfo>? children}) +class SearchRoute extends _i41.PageRouteInfo { + const SearchRoute({List<_i41.PageRouteInfo>? children}) : super( SearchRoute.name, initialChildren: children, @@ -709,7 +710,7 @@ class SearchRoute extends _i40.PageRouteInfo { static const String name = 'SearchRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i23.SearchPage(); @@ -720,12 +721,12 @@ class SearchRoute extends _i40.PageRouteInfo { /// generated route for /// [_i24.SettingsMetadataProviderFormPage] class SettingsMetadataProviderFormRoute - extends _i40.PageRouteInfo { + extends _i41.PageRouteInfo { SettingsMetadataProviderFormRoute({ - _i43.Key? key, + _i44.Key? key, required String title, - required List<_i42.MetadataFormFieldObject> fields, - List<_i40.PageRouteInfo>? children, + required List<_i43.MetadataFormFieldObject> fields, + List<_i41.PageRouteInfo>? children, }) : super( SettingsMetadataProviderFormRoute.name, args: SettingsMetadataProviderFormRouteArgs( @@ -738,7 +739,7 @@ class SettingsMetadataProviderFormRoute static const String name = 'SettingsMetadataProviderFormRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -758,11 +759,11 @@ class SettingsMetadataProviderFormRouteArgs { required this.fields, }); - final _i43.Key? key; + final _i44.Key? key; final String title; - final List<_i42.MetadataFormFieldObject> fields; + final List<_i43.MetadataFormFieldObject> fields; @override String toString() { @@ -772,8 +773,8 @@ class SettingsMetadataProviderFormRouteArgs { /// generated route for /// [_i25.SettingsMetadataProviderPage] -class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { - const SettingsMetadataProviderRoute({List<_i40.PageRouteInfo>? children}) +class SettingsMetadataProviderRoute extends _i41.PageRouteInfo { + const SettingsMetadataProviderRoute({List<_i41.PageRouteInfo>? children}) : super( SettingsMetadataProviderRoute.name, initialChildren: children, @@ -781,7 +782,7 @@ class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { static const String name = 'SettingsMetadataProviderRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i25.SettingsMetadataProviderPage(); @@ -791,8 +792,8 @@ class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { /// generated route for /// [_i26.SettingsPage] -class SettingsRoute extends _i40.PageRouteInfo { - const SettingsRoute({List<_i40.PageRouteInfo>? children}) +class SettingsRoute extends _i41.PageRouteInfo { + const SettingsRoute({List<_i41.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -800,7 +801,7 @@ class SettingsRoute extends _i40.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { return const _i26.SettingsPage(); @@ -809,9 +810,28 @@ class SettingsRoute extends _i40.PageRouteInfo { } /// generated route for -/// [_i27.StatsAlbumsPage] -class StatsAlbumsRoute extends _i40.PageRouteInfo { - const StatsAlbumsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i27.SettingsScrobblingPage] +class SettingsScrobblingRoute extends _i41.PageRouteInfo { + const SettingsScrobblingRoute({List<_i41.PageRouteInfo>? children}) + : super( + SettingsScrobblingRoute.name, + initialChildren: children, + ); + + static const String name = 'SettingsScrobblingRoute'; + + static _i41.PageInfo page = _i41.PageInfo( + name, + builder: (data) { + return const _i27.SettingsScrobblingPage(); + }, + ); +} + +/// generated route for +/// [_i28.StatsAlbumsPage] +class StatsAlbumsRoute extends _i41.PageRouteInfo { + const StatsAlbumsRoute({List<_i41.PageRouteInfo>? children}) : super( StatsAlbumsRoute.name, initialChildren: children, @@ -819,18 +839,18 @@ class StatsAlbumsRoute extends _i40.PageRouteInfo { static const String name = 'StatsAlbumsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i27.StatsAlbumsPage(); + return const _i28.StatsAlbumsPage(); }, ); } /// generated route for -/// [_i28.StatsArtistsPage] -class StatsArtistsRoute extends _i40.PageRouteInfo { - const StatsArtistsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i29.StatsArtistsPage] +class StatsArtistsRoute extends _i41.PageRouteInfo { + const StatsArtistsRoute({List<_i41.PageRouteInfo>? children}) : super( StatsArtistsRoute.name, initialChildren: children, @@ -838,18 +858,18 @@ class StatsArtistsRoute extends _i40.PageRouteInfo { static const String name = 'StatsArtistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i28.StatsArtistsPage(); + return const _i29.StatsArtistsPage(); }, ); } /// generated route for -/// [_i29.StatsMinutesPage] -class StatsMinutesRoute extends _i40.PageRouteInfo { - const StatsMinutesRoute({List<_i40.PageRouteInfo>? children}) +/// [_i30.StatsMinutesPage] +class StatsMinutesRoute extends _i41.PageRouteInfo { + const StatsMinutesRoute({List<_i41.PageRouteInfo>? children}) : super( StatsMinutesRoute.name, initialChildren: children, @@ -857,18 +877,18 @@ class StatsMinutesRoute extends _i40.PageRouteInfo { static const String name = 'StatsMinutesRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i29.StatsMinutesPage(); + return const _i30.StatsMinutesPage(); }, ); } /// generated route for -/// [_i30.StatsPage] -class StatsRoute extends _i40.PageRouteInfo { - const StatsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i31.StatsPage] +class StatsRoute extends _i41.PageRouteInfo { + const StatsRoute({List<_i41.PageRouteInfo>? children}) : super( StatsRoute.name, initialChildren: children, @@ -876,18 +896,18 @@ class StatsRoute extends _i40.PageRouteInfo { static const String name = 'StatsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i30.StatsPage(); + return const _i31.StatsPage(); }, ); } /// generated route for -/// [_i31.StatsPlaylistsPage] -class StatsPlaylistsRoute extends _i40.PageRouteInfo { - const StatsPlaylistsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i32.StatsPlaylistsPage] +class StatsPlaylistsRoute extends _i41.PageRouteInfo { + const StatsPlaylistsRoute({List<_i41.PageRouteInfo>? children}) : super( StatsPlaylistsRoute.name, initialChildren: children, @@ -895,18 +915,18 @@ class StatsPlaylistsRoute extends _i40.PageRouteInfo { static const String name = 'StatsPlaylistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i31.StatsPlaylistsPage(); + return const _i32.StatsPlaylistsPage(); }, ); } /// generated route for -/// [_i32.StatsStreamFeesPage] -class StatsStreamFeesRoute extends _i40.PageRouteInfo { - const StatsStreamFeesRoute({List<_i40.PageRouteInfo>? children}) +/// [_i33.StatsStreamFeesPage] +class StatsStreamFeesRoute extends _i41.PageRouteInfo { + const StatsStreamFeesRoute({List<_i41.PageRouteInfo>? children}) : super( StatsStreamFeesRoute.name, initialChildren: children, @@ -914,18 +934,18 @@ class StatsStreamFeesRoute extends _i40.PageRouteInfo { static const String name = 'StatsStreamFeesRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i32.StatsStreamFeesPage(); + return const _i33.StatsStreamFeesPage(); }, ); } /// generated route for -/// [_i33.StatsStreamsPage] -class StatsStreamsRoute extends _i40.PageRouteInfo { - const StatsStreamsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i34.StatsStreamsPage] +class StatsStreamsRoute extends _i41.PageRouteInfo { + const StatsStreamsRoute({List<_i41.PageRouteInfo>? children}) : super( StatsStreamsRoute.name, initialChildren: children, @@ -933,21 +953,21 @@ class StatsStreamsRoute extends _i40.PageRouteInfo { static const String name = 'StatsStreamsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i33.StatsStreamsPage(); + return const _i34.StatsStreamsPage(); }, ); } /// generated route for -/// [_i34.TrackPage] -class TrackRoute extends _i40.PageRouteInfo { +/// [_i35.TrackPage] +class TrackRoute extends _i41.PageRouteInfo { TrackRoute({ - _i43.Key? key, + _i44.Key? key, required String trackId, - List<_i40.PageRouteInfo>? children, + List<_i41.PageRouteInfo>? children, }) : super( TrackRoute.name, args: TrackRouteArgs( @@ -960,13 +980,13 @@ class TrackRoute extends _i40.PageRouteInfo { static const String name = 'TrackRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => TrackRouteArgs(trackId: pathParams.getString('id'))); - return _i34.TrackPage( + return _i35.TrackPage( key: args.key, trackId: args.trackId, ); @@ -980,7 +1000,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i43.Key? key; + final _i44.Key? key; final String trackId; @@ -991,9 +1011,9 @@ class TrackRouteArgs { } /// generated route for -/// [_i35.UserAlbumsPage] -class UserAlbumsRoute extends _i40.PageRouteInfo { - const UserAlbumsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i36.UserAlbumsPage] +class UserAlbumsRoute extends _i41.PageRouteInfo { + const UserAlbumsRoute({List<_i41.PageRouteInfo>? children}) : super( UserAlbumsRoute.name, initialChildren: children, @@ -1001,18 +1021,18 @@ class UserAlbumsRoute extends _i40.PageRouteInfo { static const String name = 'UserAlbumsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i35.UserAlbumsPage(); + return const _i36.UserAlbumsPage(); }, ); } /// generated route for -/// [_i36.UserArtistsPage] -class UserArtistsRoute extends _i40.PageRouteInfo { - const UserArtistsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i37.UserArtistsPage] +class UserArtistsRoute extends _i41.PageRouteInfo { + const UserArtistsRoute({List<_i41.PageRouteInfo>? children}) : super( UserArtistsRoute.name, initialChildren: children, @@ -1020,18 +1040,18 @@ class UserArtistsRoute extends _i40.PageRouteInfo { static const String name = 'UserArtistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i36.UserArtistsPage(); + return const _i37.UserArtistsPage(); }, ); } /// generated route for -/// [_i37.UserDownloadsPage] -class UserDownloadsRoute extends _i40.PageRouteInfo { - const UserDownloadsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i38.UserDownloadsPage] +class UserDownloadsRoute extends _i41.PageRouteInfo { + const UserDownloadsRoute({List<_i41.PageRouteInfo>? children}) : super( UserDownloadsRoute.name, initialChildren: children, @@ -1039,18 +1059,18 @@ class UserDownloadsRoute extends _i40.PageRouteInfo { static const String name = 'UserDownloadsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i37.UserDownloadsPage(); + return const _i38.UserDownloadsPage(); }, ); } /// generated route for -/// [_i38.UserLocalLibraryPage] -class UserLocalLibraryRoute extends _i40.PageRouteInfo { - const UserLocalLibraryRoute({List<_i40.PageRouteInfo>? children}) +/// [_i39.UserLocalLibraryPage] +class UserLocalLibraryRoute extends _i41.PageRouteInfo { + const UserLocalLibraryRoute({List<_i41.PageRouteInfo>? children}) : super( UserLocalLibraryRoute.name, initialChildren: children, @@ -1058,18 +1078,18 @@ class UserLocalLibraryRoute extends _i40.PageRouteInfo { static const String name = 'UserLocalLibraryRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i38.UserLocalLibraryPage(); + return const _i39.UserLocalLibraryPage(); }, ); } /// generated route for -/// [_i39.UserPlaylistsPage] -class UserPlaylistsRoute extends _i40.PageRouteInfo { - const UserPlaylistsRoute({List<_i40.PageRouteInfo>? children}) +/// [_i40.UserPlaylistsPage] +class UserPlaylistsRoute extends _i41.PageRouteInfo { + const UserPlaylistsRoute({List<_i41.PageRouteInfo>? children}) : super( UserPlaylistsRoute.name, initialChildren: children, @@ -1077,10 +1097,10 @@ class UserPlaylistsRoute extends _i40.PageRouteInfo { static const String name = 'UserPlaylistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i41.PageInfo page = _i41.PageInfo( name, builder: (data) { - return const _i39.UserPlaylistsPage(); + return const _i40.UserPlaylistsPage(); }, ); } diff --git a/lib/components/button/back_button.dart b/lib/components/button/back_button.dart index 42c952ab..dc899616 100644 --- a/lib/components/button/back_button.dart +++ b/lib/components/button/back_button.dart @@ -13,7 +13,7 @@ class BackButton extends StatelessWidget { @override Widget build(BuildContext context) { return IconButton.ghost( - size: const ButtonSize(.9), + size: const ButtonSize(1.2), icon: Icon(icon, color: color), onPressed: () => Navigator.of(context).pop(), ); diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index 4e686c06..3d3fd7e9 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -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, ), diff --git a/lib/components/fallbacks/error_box.dart b/lib/components/fallbacks/error_box.dart new file mode 100644 index 00000000..fd56cb58 --- /dev/null +++ b/lib/components/fallbacks/error_box.dart @@ -0,0 +1,138 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/services.dart'; +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; + final VoidCallback? onRetry; + const ErrorBox({ + super.key, + required this.error, + this.onRetry, + }); + + @override + Widget build(BuildContext context) { + // Make a monospace error log view. Make sure it's only 4 lines + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + spacing: 12, + children: [ + Basic( + leading: const Icon(SpotubeIcons.error), + contentSpacing: 8, + title: Text(context.l10n.an_error_occurred), + ), + Card( + padding: const EdgeInsets.all(8.0), + filled: true, + fillColor: context.theme.colorScheme.muted, + child: Text( + error.toString(), + style: TextStyle( + // Use monospace + fontFamily: 'Ubuntu Mono', + color: context.theme.colorScheme.mutedForeground, + fontSize: 14, + ), + maxLines: 6, + overflow: TextOverflow.ellipsis, + ), + ), + // Show a dialog with full log and a retry button as well + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Button.text( + leading: const Icon(SpotubeIcons.logs), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 480, + maxHeight: + MediaQuery.of(context).size.height * 0.8, + ), + child: AlertDialog( + padding: const EdgeInsets.all(12), + title: Row( + spacing: 8, + children: [ + const Icon(SpotubeIcons.logs), + Text(context.l10n.logs), + const Spacer(), + IconButton.ghost( + icon: const Icon(SpotubeIcons.close), + onPressed: () => context.maybePop(), + ) + ], + ), + actions: [ + HookBuilder(builder: (context) { + final copied = useState(false); + + return Button.ghost( + leading: copied.value + ? const Icon(SpotubeIcons.done) + : const Icon(SpotubeIcons.clipboard), + child: Text(context.l10n.copy_to_clipboard), + onPressed: () { + Clipboard.setData( + ClipboardData(text: error.toString()), + ); + copied.value = true; + }, + ); + }) + ], + content: SingleChildScrollView( + child: Card( + padding: const EdgeInsets.all(8.0), + filled: true, + fillColor: context.theme.colorScheme.muted, + child: SelectableText( + error.toString(), + style: TextStyle( + // Use monospace + fontFamily: 'Ubuntu Mono', + color: context + .theme.colorScheme.mutedForeground, + fontSize: 16, + ), + ), + ), + ), + ), + ); + }, + ); + }, + child: Text(context.l10n.view_logs), + ), + if (onRetry != null) + Button.text( + leading: const Icon(SpotubeIcons.refresh), + onPressed: onRetry, + child: Text(context.l10n.retry), + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/components/fallbacks/no_default_metadata_plugin.dart b/lib/components/fallbacks/no_default_metadata_plugin.dart new file mode 100644 index 00000000..1cabcdb1 --- /dev/null +++ b/lib/components/fallbacks/no_default_metadata_plugin.dart @@ -0,0 +1,42 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; +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}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Undraw( + height: 200 * context.theme.scaling, + illustration: UndrawIllustration.stars, + color: context.theme.colorScheme.primary, + ), + AutoSizeText( + context.l10n.no_default_metadata_provider_selected, + style: context.theme.typography.h4, + maxLines: 1, + ), + Button.primary( + leading: const Icon(SpotubeIcons.extensions), + child: Text(context.l10n.manage_metadata_providers), + onPressed: () { + context.pushRoute(const SettingsMetadataProviderRoute()); + }, + ), + ], + ), + ); + } +} diff --git a/lib/components/form/text_form_field.dart b/lib/components/form/text_form_field.dart index b07c33e3..dc92c257 100644 --- a/lib/components/form/text_form_field.dart +++ b/lib/components/form/text_form_field.dart @@ -14,7 +14,7 @@ class TextFormBuilderField extends StatelessWidget { // final AlignmentGeometry? placeholderAlignment; // final AlignmentGeometry? leadingAlignment; // final AlignmentGeometry? trailingAlignment; - final bool border; + final Border? border; final List features; final EdgeInsetsGeometry? padding; final ValueChanged? onSubmitted; @@ -61,7 +61,7 @@ class TextFormBuilderField extends StatelessWidget { this.minLines, this.filled = false, this.placeholder, - this.border = true, + this.border, this.padding, this.onSubmitted, this.onEditingComplete, diff --git a/lib/components/heart_button/heart_button.dart b/lib/components/heart_button/heart_button.dart index eca3c513..14a0572f 100644 --- a/lib/components/heart_button/heart_button.dart +++ b/lib/components/heart_button/heart_button.dart @@ -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, + }, ); } } diff --git a/lib/components/heart_button/use_track_toggle_like.dart b/lib/components/heart_button/use_track_toggle_like.dart index 349a4ab2..af961578 100644 --- a/lib/components/heart_button/use_track_toggle_like.dart +++ b/lib/components/heart_button/use_track_toggle_like.dart @@ -4,6 +4,7 @@ import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; typedef UseTrackToggleLike = ({ bool isLiked, + bool isLoading, Future 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, diff --git a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart index 0c120e71..3ac90a06 100644 --- a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart +++ b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart @@ -14,6 +14,7 @@ import 'package:very_good_infinite_list/very_good_infinite_list.dart'; class HorizontalPlaybuttonCardView extends HookWidget { final Widget title; final List items; + final Widget? error; final VoidCallback onFetchMore; final bool isLoadingNextPage; final bool hasNextPage; @@ -26,6 +27,7 @@ class HorizontalPlaybuttonCardView extends HookWidget { required this.onFetchMore, required this.isLoadingNextPage, this.titleTrailing, + this.error, super.key, }) : assert( items.every( @@ -64,54 +66,57 @@ class HorizontalPlaybuttonCardView 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(), + }; + }), + ), ), ), - ), ], ), ); diff --git a/lib/components/image/universal_image.dart b/lib/components/image/universal_image.dart index d8902e63..e157f96a 100644 --- a/lib/components/image/universal_image.dart +++ b/lib/components/image/universal_image.dart @@ -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(), diff --git a/lib/components/markdown/markdown.dart b/lib/components/markdown/markdown.dart index 52c7f488..9ea2e77c 100644 --- a/lib/components/markdown/markdown.dart +++ b/lib/components/markdown/markdown.dart @@ -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), ), ], ), diff --git a/lib/components/track_presentation/presentation_list.dart b/lib/components/track_presentation/presentation_list.dart index d41416b4..19772c7c 100644 --- a/lib/components/track_presentation/presentation_list.dart +++ b/lib/components/track_presentation/presentation_list.dart @@ -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), diff --git a/lib/components/track_presentation/presentation_props.dart b/lib/components/track_presentation/presentation_props.dart index 72f65c71..1992487f 100644 --- a/lib/components/track_presentation/presentation_props.dart +++ b/lib/components/track_presentation/presentation_props.dart @@ -50,6 +50,7 @@ class TrackPresentationOptions { final PaginationProps pagination; final bool isLiked; final String? shareUrl; + final Object? error; // events final FutureOr 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; } diff --git a/lib/components/track_presentation/use_action_callbacks.dart b/lib/components/track_presentation/use_action_callbacks.dart index 22f60ded..6707dd36 100644 --- a/lib/components/track_presentation/use_action_callbacks.dart +++ b/lib/components/track_presentation/use_action_callbacks.dart @@ -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; diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index be8c35b6..7943fe3d 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -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), diff --git a/lib/components/track_tile/track_options_button.dart b/lib/components/track_tile/track_options_button.dart index b4217957..51fff5ea 100644 --- a/lib/components/track_tile/track_options_button.dart +++ b/lib/components/track_tile/track_options_button.dart @@ -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); + }, ), ], ), diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 4a7f176d..99af2097 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -430,5 +430,52 @@ "edit_port": "تعديل المنفذ", "port_helper_msg": "القيمة الافتراضية هي -1 والتي تشير إلى رقم عشوائي. إذا كان لديك جدار ناري مُعد، يُوصى بتعيين هذا.", "connect_request": "السماح لـ {client} بالاتصال؟", - "connection_request_denied": "تم رفض الاتصال. المستخدم رفض الوصول." + "connection_request_denied": "تم رفض الاتصال. المستخدم رفض الوصول.", + "hipotetical_calculation": "*تمّ الحساب بمعدّل دفعة تتراوح بين 0.003–0.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": "التتبع" } \ No newline at end of file diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index 7d5a3e5a..222e28b4 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -430,5 +430,52 @@ "edit_port": "পোর্ট সম্পাদনা করুন", "port_helper_msg": "ডিফল্ট হল -1 যা এলোমেলো সংখ্যা নির্দেশ করে। যদি আপনার ফায়ারওয়াল কনফিগার করা থাকে, তবে এটি সেট করা সুপারিশ করা হয়।", "connect_request": "{client} কে সংযোগ করতে অনুমতি দেবেন?", - "connection_request_denied": "সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।" + "connection_request_denied": "সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।", + "hipotetical_calculation": "*এটি নিরূপণ করা হয়েছে গড় অনলাইন মিউজিক স্ট্রিমিং প্ল্যাটফর্মের প্রতি স্ট্রিম 0.003–0.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": "স্ক্রোব্বলিং" } \ No newline at end of file diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb index 8afb33f3..0482468b 100644 --- a/lib/l10n/app_ca.arb +++ b/lib/l10n/app_ca.arb @@ -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ó d’un pagament mitjà per reproducció de 0,003–0,005 USD en plataformes de reproducció musical en línia. És un càlcul hipotètic per ajudar l’usuari a entendre quant hauria pagat als artistes si hagués escoltat la seva cançó en diferents plataformes.", + "an_error_occurred": "S’ha 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 l’enllaç en el navegador?", + "do_you_want_to_open_the_following_link": "Vols obrir l’enllaç següent?", + "unsafe_url_warning": "Pot ser perillós obrir enllaços de fonts no fiables. Sigues precavís!\nTambé pots copiar l’enllaç 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 l’historial d’escoltes.", + "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 l’API **{name}**", + "do_you_want_to_install_this_plugin": "Vols instal·lar aquest complement?", + "third_party_plugin_warning": "Aquest complement prové d’un repositori de tercers. Assegura’t de confiar en la font abans d’instal·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 s’està 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 d’això, utilitza la tecla Tab.", + "disclaimer": "Avís legal", + "third_party_plugin_dmca_notice": "L’equip de Spotube no accepta cap responsabilitat (inclosa legal) pels complements de “tercers”.\nFes-los servir sota la teva responsabilitat. Si detectes errors/problemes, informa’ls al repositori del complement.\n\nSi algun complement de “tercers” incompleix els ToS/DMCA d’un servei o entitat legal, contacta amb l’autor del complement o amb la plataforma d’allotjament (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": "L’entrada no coincideix amb el format requerit", + "metadata_provider_plugins": "Complements de proveïdor de metadades", + "paste_plugin_download_url": "Enllaça l’URL de descàrrega o el repositori de GitHub/Codeberg o l’enllaç directe al fitxer .smplug", + "download_and_install_plugin_from_url": "Descarrega i instal·la el complement des d’un URL", + "failed_to_add_plugin_error": "Error en afegir el complement: {error}", + "upload_plugin_from_file": "Penja el complement des d’un 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" } \ No newline at end of file diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 44631945..d5d0f7af 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -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,003–0,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" } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 7ce9c333..8ef22fac 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -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" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cb81d3ce..34b56489 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -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" } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 8a6e02ad..8321f2f5 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -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" } \ No newline at end of file diff --git a/lib/l10n/app_eu.arb b/lib/l10n/app_eu.arb index f28d5f8e..0b49bea1 100644 --- a/lib/l10n/app_eu.arb +++ b/lib/l10n/app_eu.arb @@ -430,5 +430,52 @@ "edit_port": "Editatu portua", "port_helper_msg": "Lehenetsitako balioa -1 da, zenbaki aleatorioa adierazten duena. Su firewall konfiguratu baduzu, gomendatzen da hau ezartzea.", "connect_request": "{client} konektatzea baimendu?", - "connection_request_denied": "Konektatzea ukatu da. Erabiltzaileak sarbidea ukatu du." + "connection_request_denied": "Konektatzea ukatu da. Erabiltzaileak sarbidea ukatu du.", + "hipotetical_calculation": "*Kalkulu hau online musika-streaming plataformetako batez besteko irteerako ordainari (0,003–0,005 USD) oinarrituta dago. Hipotetikoa da eta erabiltzaileari ideia bat ematen laguntzen dio artista nork zenbat kobratu zuen jakiteko, bere abestia plataform desberdinetan entzungo balu.", + "an_error_occurred": "Errore bat gertatu da", + "copy_to_clipboard": "Hiztegiraino kopiatzea", + "view_logs": "Erregistroak ikusi", + "retry": "Berriro saiatu", + "no_default_metadata_provider_selected": "Ezarri ez duzu metadaten hornitzaile lehenetsirik", + "manage_metadata_providers": "Metadaten hornitzaileak kudeatu", + "open_link_in_browser": "Esteka nabigatzailean irekiko duzu?", + "do_you_want_to_open_the_following_link": "Hurrengo esteka irekiko duzu?", + "unsafe_url_warning": "Iturri seguru gabeko estekak irekiz gero, ez da seguru suerta daiteke. Arduratu zaitez!\nEsteka ere hiztegirainokoan kopiatu dezakezu.", + "copy_link": "Esteka kopiatu", + "building_your_timeline": "Zure entzuteen arabera zure kronologia eraikitzen…", + "official": "Ofiziala", + "author_name": "Egilea: {author}", + "third_party": "Hirugarrena", + "plugin_requires_authentication": "Pluginak autentifikazioa eskatzen du", + "update_available": "Eguneratze bat dago eskuragarri", + "supports_scrobbling": "Scrobbling-a onartzen du", + "plugin_scrobbling_info": "Plugin honek zure musika scrobbled egiten du zure entzuteen historia sortzeko.", + "default_plugin": "Lehenetsia", + "set_default": "Lehenetsi gisa ezarri", + "support": "Laguntza", + "support_plugin_development": "Pluginaren garapena lagundu", + "can_access_name_api": "- **{name}** API-ra sar daiteke", + "do_you_want_to_install_this_plugin": "Plugin hau instalatu nahiko zenuke?", + "third_party_plugin_warning": "Plugin hau hirugarrenen biltegi batetik dator. Instalatu aurretik iturriari konfiantza behar diozu.", + "author": "Egilea", + "this_plugin_can_do_following": "Plugin honek honako hau egin dezake:", + "install": "Instalatu", + "install_a_metadata_provider": "Metadaten hornitzaile bat instalatu", + "no_tracks_playing": "Une honetan ez dago abestirik erreproduzitzen", + "synced_lyrics_not_available": "Abestiarentzako letra sinkronizatua ez dago erabilgarri. Mesedez, erabili", + "plain_lyrics": "Letra arrunta", + "tab_instead": "horren ordez, Tab teklatxaza erabili.", + "disclaimer": "Aldez aurreko oharra", + "third_party_plugin_dmca_notice": "Spotube taldea ezin da arduratu (“hirugarrenen”) plugin-en>gatik (barne legala). Erabili zure arriskuarekin. Erroreak/ arazoak dituzu, jakinarazi pluginaren biltegiari.\n\nPlugin batek edozein zerbitzu/legalki entitate baten ToS/DMCA hautsi baditu, eska iezaiozu pluginaren egileari edo hosting plataformari (adibidez GitHub/Codeberg) neurriak har ditzaten. “Hirugarrena” etiketatutako plugin guztiak komunitate publikoaren bidez mantentzen dira; ez ditugu kuratoriatu, beraz ezin dugu inplikatu.\n\n", + "input_does_not_match_format": "Sarrera ezin da beharrezko formatutik desberdina izan", + "metadata_provider_plugins": "Metadaten hornitzailearen pluginak", + "paste_plugin_download_url": "Kopiatu deskarga-URLa, GitHub/Codeberg biltegi-URLa edo .smplug fitxategiaren esteka zuzena", + "download_and_install_plugin_from_url": "Download eta instalatu plugin-a URL batetik", + "failed_to_add_plugin_error": "Plugin gehitu ezin izan da: {error}", + "upload_plugin_from_file": "Plugin fitxategi batetik igo", + "installed": "Instalatuta", + "available_plugins": "Eskaintzen diren pluginak", + "configure_your_own_metadata_plugin": "Konfiguratu zureko playlists-/album-/artista-/feed-metadaten hornitzailea", + "audio_scrobblers": "Audio scrobbler-ak", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_fa.arb b/lib/l10n/app_fa.arb index 0791d0b6..73bb4d48 100644 --- a/lib/l10n/app_fa.arb +++ b/lib/l10n/app_fa.arb @@ -430,5 +430,52 @@ "edit_port": "ویرایش پورت", "port_helper_msg": "پیش‌فرض -1 است که نشان‌دهنده یک عدد تصادفی است. اگر فایروال شما پیکربندی شده است، توصیه می‌شود این را تنظیم کنید.", "connect_request": "آیا اجازه می‌دهید {client} متصل شود؟", - "connection_request_denied": "اتصال رد شد. کاربر دسترسی را رد کرد." + "connection_request_denied": "اتصال رد شد. کاربر دسترسی را رد کرد.", + "hipotetical_calculation": "*این محاسبه بر اساس میانگین پرداخت به ازای هر پخش (0.003 تا 0.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": "پشتیبانی از اسکراب‌بلینگ", + "plugin_scrobbling_info": "این افزونه موسیقی شما را اسکراب می‌کند تا تاریخچهٔ شنیداری‌تان را تولید کند.", + "default_plugin": "پیش‌فرض", + "set_default": "تنظیم به عنوان پیش‌فرض", + "support": "پشتیبانی", + "support_plugin_development": "حمایت از توسعهٔ افزونه", + "can_access_name_api": "- می‌تواند به 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": "به‌جای آن از کلید Tab استفاده کنید.", + "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 یا لینک مستقیم فایل .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": "اسکراب‌بلینگ" } \ No newline at end of file diff --git a/lib/l10n/app_fi.arb b/lib/l10n/app_fi.arb index 57a5a0b8..93c6c577 100644 --- a/lib/l10n/app_fi.arb +++ b/lib/l10n/app_fi.arb @@ -430,5 +430,52 @@ "edit_port": "Muokkaa porttia", "port_helper_msg": "Oletusarvo on -1, mikä tarkoittaa satunnaista numeroa. Jos sinulla on palomuuri määritetty, tämän asettamista suositellaan.", "connect_request": "Salli {client} yhdistää?", - "connection_request_denied": "Yhteys evätty. Käyttäjä eväsi pääsyn." + "connection_request_denied": "Yhteys evätty. Käyttäjä eväsi pääsyn.", + "hipotetical_calculation": "*Tämä on laskettu keskimääräisen musiikin suoratoistopalvelun 0,003–0,005 dollarin kappalekohtaisen maksun perusteella. Tämä on hypoteettinen laskelma, joka antaa käyttäjälle käsityksen siitä, kuinka paljon he olisivat maksaneet artisteille, jos he kuuntelisivat heidän kappaleitaan eri musiikin suoratoistopalveluissa.", + "an_error_occurred": "Tapahtui virhe", + "copy_to_clipboard": "Kopioi leikepöydälle", + "view_logs": "Näytä lokit", + "retry": "Yritä uudelleen", + "no_default_metadata_provider_selected": "Et ole asettanut oletusmetatietojen tarjoajaa", + "manage_metadata_providers": "Hallinnoi metatietojen tarjoajia", + "open_link_in_browser": "Avaa linkki selaimessa?", + "do_you_want_to_open_the_following_link": "Haluatko avata seuraavan linkin", + "unsafe_url_warning": "Linkkien avaaminen epäluotettavista lähteistä voi olla vaarallista. Ole varovainen!\nVoit myös kopioida linkin leikepöydälle.", + "copy_link": "Kopioi linkki", + "building_your_timeline": "Rakennetaan aikajanaasi kuuntelujesi perusteella...", + "official": "Virallinen", + "author_name": "Tekijä: {author}", + "third_party": "Kolmannen osapuolen", + "plugin_requires_authentication": "Lisäosa vaatii todentamisen", + "update_available": "Päivitys saatavilla", + "supports_scrobbling": "Tukee scrobblingia", + "plugin_scrobbling_info": "Tämä lisäosa scrobblaa musiikkisi luodakseen kuunteluhistoriasi.", + "default_plugin": "Oletus", + "set_default": "Aseta oletukseksi", + "support": "Tuki", + "support_plugin_development": "Tue lisäosan kehitystä", + "can_access_name_api": "- Voi käyttää **{name}** APIa", + "do_you_want_to_install_this_plugin": "Haluatko asentaa tämän lisäosan?", + "third_party_plugin_warning": "Tämä lisäosa on kolmannen osapuolen arkistosta. Varmista, että luotat lähteeseen ennen asennusta.", + "author": "Tekijä", + "this_plugin_can_do_following": "Tämä lisäosa voi tehdä seuraavaa", + "install": "Asenna", + "install_a_metadata_provider": "Asenna metatietojen tarjoaja", + "no_tracks_playing": "Ei kappaletta toistossa tällä hetkellä", + "synced_lyrics_not_available": "Synkronoidut sanoitukset eivät ole saatavilla tälle kappaleelle. Käytä sen sijaan", + "plain_lyrics": "Pelkät sanoitukset", + "tab_instead": "välilehteä.", + "disclaimer": "Vastuuvapauslauseke", + "third_party_plugin_dmca_notice": "Spotube-tiimi ei ota mitään vastuuta (mukaan lukien oikeudellinen) mistään \"kolmannen osapuolen\" lisäosista.\nKäytä niitä omalla vastuullasi. Ilmoita kaikista virheistä/ongelmista lisäosan arkistoon.\n\nJos jokin \"kolmannen osapuolen\" lisäosa rikkoo jonkin palvelun/oikeushenkilön käyttöehtoja/DMCA:ta, pyydä \"kolmannen osapuolen\" lisäosan tekijää tai isännöintialustaa, esim. GitHubia/Codebergiä, ryhtymään toimiin. Yllä luetellut (\"kolmannen osapuolen\" merkityt) ovat kaikki julkisia/yhteisön ylläpitämiä lisäosia. Emme kuratoi niitä, joten emme voi ryhtyä niihin toimiin.\n\n", + "input_does_not_match_format": "Syöte ei vastaa vaadittua muotoa", + "metadata_provider_plugins": "Metatietojen tarjoajan lisäosat", + "paste_plugin_download_url": "Liitä lataus-URL-osoite tai GitHub/Codeberg-arkiston URL-osoite tai suora linkki .smplug-tiedostoon", + "download_and_install_plugin_from_url": "Lataa ja asenna lisäosa URL-osoitteesta", + "failed_to_add_plugin_error": "Lisäosan lisääminen epäonnistui: {error}", + "upload_plugin_from_file": "Lataa lisäosa tiedostosta", + "installed": "Asennettu", + "available_plugins": "Saatavilla olevat lisäosat", + "configure_your_own_metadata_plugin": "Määritä oma soittolistan/albumin/artistin/syötteen metatietojen tarjoaja", + "audio_scrobblers": "Äänen scrobblerit", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 192b6c5c..1851dbe1 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -430,5 +430,53 @@ "edit_port": "Modifier le port", "port_helper_msg": "La valeur par défaut est -1, ce qui indique un nombre aléatoire. Si vous avez configuré un pare-feu, il est recommandé de le définir.", "connect_request": "Autoriser {client} à se connecter ?", - "connection_request_denied ": "Connexion refusée. L'utilisateur a refusé l'accès." + "connection_request_denied ": "Connexion refusée. L'utilisateur a refusé l'accès.", + "hipotetical_calculation": "*Ce calcul est basé sur le paiement moyen par lecture des plateformes de streaming musical en ligne, de 0,003 $ à 0,005 $. Il s'agit d'un calcul hypothétique pour donner à l'utilisateur un aperçu de ce qu'il aurait payé aux artistes s'il écoutait leur chanson sur différentes plateformes de streaming musical.", + "connection_request_denied": "Connexion refusée. L'utilisateur a refusé l'accès.", + "an_error_occurred": "Une erreur est survenue", + "copy_to_clipboard": "Copier dans le presse-papiers", + "view_logs": "Afficher les journaux", + "retry": "Réessayer", + "no_default_metadata_provider_selected": "Vous n'avez pas de fournisseur de métadonnées par défaut", + "manage_metadata_providers": "Gérer les fournisseurs de métadonnées", + "open_link_in_browser": "Ouvrir le lien dans le navigateur ?", + "do_you_want_to_open_the_following_link": "Voulez-vous ouvrir le lien suivant", + "unsafe_url_warning": "L'ouverture de liens provenant de sources non fiables peut être dangereuse. Soyez prudent !\nVous pouvez également copier le lien dans votre presse-papiers.", + "copy_link": "Copier le lien", + "building_your_timeline": "Construction de votre chronologie en fonction de vos écoutes...", + "official": "Officiel", + "author_name": "Auteur : {author}", + "third_party": "Tiers", + "plugin_requires_authentication": "Le plugin nécessite une authentification", + "update_available": "Mise à jour disponible", + "supports_scrobbling": "Supporte le scrobbling", + "plugin_scrobbling_info": "Ce plugin scrobble votre musique pour générer votre historique d'écoute.", + "default_plugin": "Par défaut", + "set_default": "Définir par défaut", + "support": "Soutien", + "support_plugin_development": "Soutenir le développement de plugins", + "can_access_name_api": "- Peut accéder à l'API **{name}**", + "do_you_want_to_install_this_plugin": "Voulez-vous installer ce plugin ?", + "third_party_plugin_warning": "Ce plugin provient d'un dépôt tiers. Assurez-vous de faire confiance à la source avant de l'installer.", + "author": "Auteur", + "this_plugin_can_do_following": "Ce plugin peut faire ce qui suit", + "install": "Installer", + "install_a_metadata_provider": "Installer un fournisseur de métadonnées", + "no_tracks_playing": "Aucune piste n'est en cours de lecture actuellement", + "synced_lyrics_not_available": "Les paroles synchronisées ne sont pas disponibles pour cette chanson. Veuillez utiliser l'onglet", + "plain_lyrics": "Paroles simples", + "tab_instead": "à la place.", + "disclaimer": "Avertissement", + "third_party_plugin_dmca_notice": "L'équipe de Spotube n'assume aucune responsabilité (y compris juridique) pour les plugins \"tiers\".\nVeuillez les utiliser à vos propres risques. Pour tout bug/problème, veuillez le signaler au dépôt du plugin.\n\nSi un plugin \"tiers\" enfreint les conditions d'utilisation/DMCA d'un service/entité juridique, veuillez demander à l'auteur du plugin \"tiers\" ou à la plateforme d'hébergement (par exemple GitHub/Codeberg) de prendre des mesures. Les plugins listés ci-dessus (étiquetés \"tiers\") sont tous des plugins publics/maintenus par la communauté. Nous ne les gérons pas, nous ne pouvons donc prendre aucune mesure à leur sujet.\n\n", + "input_does_not_match_format": "L'entrée ne correspond pas au format requis", + "metadata_provider_plugins": "Plugins de fournisseur de métadonnées", + "paste_plugin_download_url": "Collez l'URL de téléchargement ou l'URL du dépôt GitHub/Codeberg ou un lien direct vers le fichier .smplug", + "download_and_install_plugin_from_url": "Télécharger et installer le plugin à partir de l'URL", + "failed_to_add_plugin_error": "Échec de l'ajout du plugin : {error}", + "upload_plugin_from_file": "Télécharger le plugin à partir d'un fichier", + "installed": "Installé", + "available_plugins": "Plugins disponibles", + "configure_your_own_metadata_plugin": "Configurer votre propre fournisseur de métadonnées de playlist/album/artiste/flux", + "audio_scrobblers": "Scrobblers audio", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index 124d0634..d0c6ba36 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -430,5 +430,52 @@ "edit_port": "पोर्ट संपादित करें", "port_helper_msg": "डिफ़ॉल्ट -1 है जो यादृच्छिक संख्या को दर्शाता है। यदि आपने फ़ायरवॉल कॉन्फ़िगर किया है, तो इसे सेट करना अनुशंसित है।", "connect_request": "{client} को कनेक्ट करने की अनुमति दें?", - "connection_request_denied": "कनेक्शन अस्वीकृत। उपयोगकर्ता ने पहुंच अस्वीकृत कर दी।" + "connection_request_denied": "कनेक्शन अस्वीकृत। उपयोगकर्ता ने पहुंच अस्वीकृत कर दी।", + "hipotetical_calculation": "*यह औसत ऑनलाइन संगीत स्ट्रीमिंग प्लेटफ़ॉर्म के प्रति स्ट्रीम भुगतान ($0.003 से $0.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": "स्क्रॉबलिंग का समर्थन करता है", + "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": "स्पॉट्यूब टीम किसी भी \"तृतीय-पक्ष\" प्लगइन के लिए कोई जिम्मेदारी (कानूनी सहित) नहीं लेती है।\nकृपया उन्हें अपने जोखिम पर उपयोग करें। किसी भी बग/समस्या के लिए, कृपया उन्हें प्लगइन रिपॉजिटरी को रिपोर्ट करें।\n\nयदि कोई \"तृतीय-पक्ष\" प्लगइन किसी सेवा/कानूनी इकाई के ToS/DMCA को तोड़ रहा है, तो कृपया \"तृतीय-पक्ष\" प्लगइन लेखक या होस्टिंग प्लेटफ़ॉर्म जैसे GitHub/Codeberg से कार्रवाई करने के लिए कहें। ऊपर सूचीबद्ध (\"तृतीय-पक्ष\" लेबल वाले) सभी सार्वजनिक/समुदाय-द्वारा-रखरखाव किए गए प्लगइन हैं। हम उन्हें क्यूरेट नहीं कर रहे हैं, इसलिए हम उन पर कोई कार्रवाई नहीं कर सकते हैं।\n\n", + "input_does_not_match_format": "इनपुट आवश्यक प्रारूप से मेल नहीं खाता है", + "metadata_provider_plugins": "मेटाडेटा प्रदाता प्लगइन", + "paste_plugin_download_url": "डाउनलोड यूआरएल या गिटहब/कोडबर्ग रेपो यूआरएल या .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": "स्क्रॉबलिंग" } \ No newline at end of file diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index ffe36295..8ab234a7 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -430,5 +430,52 @@ "edit_port": "Edit port", "port_helper_msg": "Default adalah -1 yang menunjukkan angka acak. Jika Anda telah mengonfigurasi firewall, disarankan untuk mengatur ini.", "connect_request": "Izinkan {client} untuk terhubung?", - "connection_request_denied": "Koneksi ditolak. Pengguna menolak akses." + "connection_request_denied": "Koneksi ditolak. Pengguna menolak akses.", + "hipotetical_calculation": "*Ini dihitung berdasarkan pembayaran rata-rata per streaming dari platform streaming musik online sebesar $0,003 hingga $0,005. Ini adalah perhitungan hipotetis untuk memberikan wawasan kepada pengguna tentang seberapa banyak yang akan mereka bayarkan kepada artis jika mereka mendengarkan lagu mereka di platform streaming musik yang berbeda.", + "an_error_occurred": "Terjadi kesalahan", + "copy_to_clipboard": "Salin ke papan klip", + "view_logs": "Lihat log", + "retry": "Coba lagi", + "no_default_metadata_provider_selected": "Anda belum mengatur penyedia metadata default", + "manage_metadata_providers": "Kelola penyedia metadata", + "open_link_in_browser": "Buka Tautan di Peramban?", + "do_you_want_to_open_the_following_link": "Apakah Anda ingin membuka tautan berikut", + "unsafe_url_warning": "Tidak aman untuk membuka tautan dari sumber yang tidak tepercaya. Berhati-hatilah!\nAnda juga dapat menyalin tautan ke papan klip Anda.", + "copy_link": "Salin Tautan", + "building_your_timeline": "Membangun garis waktu Anda berdasarkan riwayat mendengarkan Anda...", + "official": "Resmi", + "author_name": "Penulis: {author}", + "third_party": "Pihak ketiga", + "plugin_requires_authentication": "Plugin memerlukan otentikasi", + "update_available": "Pembaruan tersedia", + "supports_scrobbling": "Mendukung scrobbling", + "plugin_scrobbling_info": "Plugin ini scrobble musik Anda untuk menghasilkan riwayat mendengarkan Anda.", + "default_plugin": "Bawaan", + "set_default": "Atur sebagai bawaan", + "support": "Dukungan", + "support_plugin_development": "Dukung pengembangan plugin", + "can_access_name_api": "- Dapat mengakses API **{name}**", + "do_you_want_to_install_this_plugin": "Apakah Anda ingin menginstal plugin ini?", + "third_party_plugin_warning": "Plugin ini berasal dari repositori pihak ketiga. Pastikan Anda memercayai sumbernya sebelum menginstal.", + "author": "Penulis", + "this_plugin_can_do_following": "Plugin ini dapat melakukan hal berikut", + "install": "Instal", + "install_a_metadata_provider": "Instal Penyedia Metadata", + "no_tracks_playing": "Tidak ada Lagu yang sedang diputar saat ini", + "synced_lyrics_not_available": "Lirik tersinkronisasi tidak tersedia untuk lagu ini. Silakan gunakan tab", + "plain_lyrics": "Lirik Polos", + "tab_instead": "sebagai gantinya.", + "disclaimer": "Penafian", + "third_party_plugin_dmca_notice": "Tim Spotube tidak bertanggung jawab (termasuk hukum) atas plugin \"Pihak ketiga\" mana pun.\nSilakan gunakan dengan risiko Anda sendiri. Untuk bug/masalah apa pun, silakan laporkan ke repositori plugin.\n\nJika ada plugin \"Pihak ketiga\" yang melanggar ToS/DMCA dari layanan/entitas hukum mana pun, silakan minta penulis plugin \"Pihak ketiga\" atau platform hosting, mis. GitHub/Codeberg, untuk mengambil tindakan. Yang tercantum di atas (berlabel \"Pihak ketiga\") adalah semua plugin publik/yang dikelola oleh komunitas. Kami tidak mengkurasi mereka, jadi kami tidak dapat mengambil tindakan apa pun terhadap mereka.\n\n", + "input_does_not_match_format": "Masukan tidak cocok dengan format yang diperlukan", + "metadata_provider_plugins": "Plugin Penyedia Metadata", + "paste_plugin_download_url": "Tempel url unduhan atau url repo GitHub/Codeberg atau tautan langsung ke file .smplug", + "download_and_install_plugin_from_url": "Unduh dan instal plugin dari url", + "failed_to_add_plugin_error": "Gagal menambahkan plugin: {error}", + "upload_plugin_from_file": "Unggah plugin dari file", + "installed": "Terinstal", + "available_plugins": "Plugin yang tersedia", + "configure_your_own_metadata_plugin": "Konfigurasi penyedia metadata playlist/album/artis/feed Anda sendiri", + "audio_scrobblers": "Scrobblers Audio", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 1c7dc114..f489db5d 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -431,5 +431,52 @@ "edit_port": "Modifica porta", "port_helper_msg": "Il valore predefinito è -1, che indica un numero casuale. Se hai configurato un firewall, si consiglia di impostarlo.", "connect_request": "Consentire a {client} di connettersi?", - "connection_request_denied": "Connessione negata. L'utente ha negato l'accesso." + "connection_request_denied": "Connessione negata. L'utente ha negato l'accesso.", + "hipotetical_calculation": "*Questo è calcolato in base al pagamento medio per stream delle piattaforme di streaming musicale online, che va da $0.003 a $0.005. Si tratta di un calcolo ipotetico per dare all'utente un'idea di quanto avrebbe pagato agli artisti se avesse ascoltato la loro canzone su diverse piattaforme di streaming musicale.", + "an_error_occurred": "Si è verificato un errore", + "copy_to_clipboard": "Copia negli appunti", + "view_logs": "Visualizza log", + "retry": "Riprova", + "no_default_metadata_provider_selected": "Non hai impostato alcun provider di metadati predefinito", + "manage_metadata_providers": "Gestisci provider di metadati", + "open_link_in_browser": "Aprire il link nel browser?", + "do_you_want_to_open_the_following_link": "Vuoi aprire il seguente link", + "unsafe_url_warning": "Potrebbe essere pericoloso aprire link da fonti non attendibili. Sii cauto!\nPuoi anche copiare il link negli appunti.", + "copy_link": "Copia link", + "building_your_timeline": "Creazione della tua cronologia in base ai tuoi ascolti...", + "official": "Ufficiale", + "author_name": "Autore: {author}", + "third_party": "Terze parti", + "plugin_requires_authentication": "Il plugin richiede l'autenticazione", + "update_available": "Aggiornamento disponibile", + "supports_scrobbling": "Supporta lo scrobbling", + "plugin_scrobbling_info": "Questo plugin scrobbla la tua musica per generare la tua cronologia di ascolti.", + "default_plugin": "Predefinito", + "set_default": "Imposta come predefinito", + "support": "Supporto", + "support_plugin_development": "Sostieni lo sviluppo del plugin", + "can_access_name_api": "- Può accedere all'API **{name}**", + "do_you_want_to_install_this_plugin": "Vuoi installare questo plugin?", + "third_party_plugin_warning": "Questo plugin proviene da un repository di terze parti. Assicurati di fidarti della fonte prima di installarlo.", + "author": "Autore", + "this_plugin_can_do_following": "Questo plugin può fare quanto segue", + "install": "Installa", + "install_a_metadata_provider": "Installa un provider di metadati", + "no_tracks_playing": "Nessun brano in riproduzione al momento", + "synced_lyrics_not_available": "Testi sincronizzati non disponibili per questa canzone. Si prega di utilizzare la scheda", + "plain_lyrics": "Testi semplici", + "tab_instead": "invece.", + "disclaimer": "Disclaimer", + "third_party_plugin_dmca_notice": "Il team di Spotube non si assume alcuna responsabilità (anche legale) per i plugin di \"terze parti\".\nUsali a tuo rischio e pericolo. Per eventuali bug/problemi, segnalali al repository del plugin.\n\nSe un plugin di \"terze parti\" sta violando i ToS/DMCA di un servizio/entità legale, per favore chiedi all'autore del plugin \"terzo\" o alla piattaforma di hosting, ad esempio GitHub/Codeberg, di agire. Quelli elencati sopra (etichettati come \"terze parti\") sono tutti plugin pubblici/mantenuti dalla comunità. Non li curiamo, quindi non possiamo intraprendere alcuna azione su di essi.\n\n", + "input_does_not_match_format": "L'input non corrisponde al formato richiesto", + "metadata_provider_plugins": "Plugin del provider di metadati", + "paste_plugin_download_url": "Incolla l'URL di download o l'URL del repository GitHub/Codeberg o il link diretto al file .smplug", + "download_and_install_plugin_from_url": "Scarica e installa il plugin da URL", + "failed_to_add_plugin_error": "Impossibile aggiungere il plugin: {error}", + "upload_plugin_from_file": "Carica plugin da file", + "installed": "Installato", + "available_plugins": "Plugin disponibili", + "configure_your_own_metadata_plugin": "Configura il tuo provider di metadati per playlist/album/artista/feed", + "audio_scrobblers": "Scrobbler audio", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 230b2b49..88fc51c3 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -22,7 +22,7 @@ "filter_playlists": "あなたの再生リストを絞り込み...", "liked_tracks": "いいねした曲", "liked_tracks_description": "いいねしたすべての曲", - "create_playlist": "再生リストの作成", + "playlist": "再生リスト", "create_a_playlist": "再生リストの作成", "create": "作成", "cancel": "キャンセル", @@ -39,8 +39,9 @@ "sort_z_a": "Z-A 順に並び替え", "sort_artist": "アーティスト順に並び替え", "sort_album": "アルバム順に並び替え", + "sort_duration": "長さ順に並べ替え", "sort_tracks": "曲の並び替え", - "currently_downloading": "いまダウンロード中 ({tracks_length}) 曲", + "currently_downloading": "ダウンロード中 ({tracks_length}) 曲", "cancel_all": "すべてキャンセル", "filter_artist": "アーティストを絞り込み...", "followers": "{followers} フォロワー", @@ -94,6 +95,7 @@ "pause_playback": "再生を停止", "resume_playback": "再生を再開", "loop_track": "曲をループ", + "no_loop": "ループなし", "repeat_playlist": "再生リストをリピート", "queue": "再生キュー", "alternative_track_sources": "この曲の別の音源を選ぶ", @@ -112,8 +114,8 @@ "language_region": "言語 & 地域", "language": "言語", "system_default": "システムの既定値", - "market_place_region": "市場の地域", - "recommendation_country": "推薦先の国", + "market_place_region": "音楽市場の地域", + "recommendation_country": "おすすめの国", "appearance": "外観", "layout_mode": "レイアウトの種類", "override_layout_settings": "レスポンシブなレイアウトの種類の設定を上書きする", @@ -175,13 +177,18 @@ "step_2": "ステップ 2", "step_2_steps": "1. ログインしたら、F12を押すか、マウス右クリック > 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます", "step_3": "ステップ 3", + "step_3_steps": "\"sp_dc\" Cookieの値をコピー", "success_emoji": "成功🥳", "success_message": "アカウントへのログインに成功しました。よくできました!", "step_4": "ステップ 4", + "step_4_steps": "コピーした\"sp_dc\"の値を貼り付け", "something_went_wrong": "何か誤りがあります", "piped_instance": "Piped サーバーのインスタンス", "piped_description": "曲の一致に使う Piped サーバーのインスタンス", "piped_warning": "それらの一部ではうまく動作しないこともあります。自己責任で使用してください", + "invidious_instance": "Invidiousサーバーインスタンス", + "invidious_description": "曲の一致に使用するInvidiousサーバーインスタンス", + "invidious_warning": "一部はうまく機能しない可能性があります。自己責任で使用してください", "generate_playlist": "再生リストの生成", "track_exists": "曲 {track} は既に存在します", "replace_downloaded_tracks": "すべてのダウンロード済みの曲を置換", @@ -247,102 +254,100 @@ "developers": "開発", "not_logged_in": "ログインしていません", "search_mode": "検索モード", - "audio_source": "音声ソース", - "ok": "分かりました", + "audio_source": "音声の提供元", + "ok": "OK", "failed_to_encrypt": "暗号化に失敗しました", - "encryption_failed_warning": "Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください", + "encryption_failed_warning": "SpoTubeはデータを安全に保存するために暗号化を用いますが、暗号化に失敗しました。このため、安全でない保存領域への保存に切り替えます\nOSがLinuxなら、gnome-keyring、kde-wallet、keepassxcなどの管理ツールがインストールされていることを確認してください", "querying_info": "情報を取得中...", "piped_api_down": "Piped APIがダウンしています", - "piped_down_error_instructions": "Pipedインスタンス{pipedInstance}は現在ダウンしています\n\nインスタンスを変更するか、'APIタイプ'を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください", + "piped_down_error_instructions": "Pipedインスタンス {pipedInstance} は現在ダウンしています\n\nインスタンスを変更するか、「APIの種類」を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください", "you_are_offline": "現在、オフラインです", "connection_restored": "インターネット接続が復旧しました", - "use_system_title_bar": "システムタイトルバーを使用する", - "update_playlist": "プレイリストを更新", + "use_system_title_bar": "システムのタイトルバーを使う", + "update_playlist": "再生リストを更新", "update": "更新", + "local_library": "端末内ライブラリ", + "add_library_location": "ライブラリに追加", + "remove_library_location": "ライブラリから削除", "crunching_results": "結果を処理中...", "search_to_get_results": "結果を取得するために検索", - "use_amoled_mode": "AMOLEDモードを使用する", - "pitch_dark_theme": "ピッチブラックダートテーマ", - "normalize_audio": "オーディオを正規化する", - "change_cover": "カバーを変更する", - "add_cover": "カバーを追加する", - "restore_defaults": "デフォルト値に戻す", - "download_music_codec": "音楽コーデックをダウンロードする", - "streaming_music_codec": "ストリーミング音楽コーデック", - "login_with_lastfm": "Last.fmでログインする", - "connect": "接続する", - "disconnect_lastfm": "Last.fmから切断する", - "disconnect": "切断する", + "use_amoled_mode": "AMOLEDモードを使用", + "pitch_dark_theme": "ピッチブラック ダークテーマ", + "normalize_audio": "音声を正規化", + "change_cover": "カバーを変更", + "add_cover": "カバーを追加", + "restore_defaults": "設定を初期化", + "download_music_codec": "ダウンロード用の音声コーデック", + "streaming_music_codec": "ストリーミング用の音声コーデック", + "login_with_lastfm": "Last.fmでログイン", + "connect": "接続", + "disconnect_lastfm": "Last.fmから切断", + "disconnect": "切断", "username": "ユーザー名", "password": "パスワード", - "login": "ログインする", - "login_with_your_lastfm": "あなたのLast.fmアカウントでログインする", + "login": "ログイン", + "login_with_your_lastfm": "Last.fmアカウントでログイン", "scrobble_to_lastfm": "Last.fmにスクロブルする", "go_to_album": "アルバムに移動", - "discord_rich_presence": "ディスコードリッチプレゼンス", + "discord_rich_presence": "Discord リッチプレゼンス", "browse_all": "すべてを閲覧", "genres": "ジャンル", "explore_genres": "ジャンルを探索", - "step_3_steps": "\"sp_dc\" Cookieの値をコピー", - "step_4_steps": "コピーした\"sp_dc\"の値を貼り付け", "friends": "友達", - "no_lyrics_available": "申し訳ありませんが、このトラックの歌詞を見つけることができません", - "sort_duration": "時間で並べ替え", + "no_lyrics_available": "すみません、この曲の歌詞が見つかりません", "start_a_radio": "ラジオを開始", "how_to_start_radio": "ラジオをどのように開始しますか?", "replace_queue_question": "現在のキューを置き換えるか、追加しますか?", "endless_playback": "エンドレス再生", - "delete_playlist": "プレイリストを削除", - "delete_playlist_confirmation": "このプレイリストを削除してもよろしいですか?", - "local_tracks": "ローカルトラック", + "delete_playlist": "再生リストを削除", + "delete_playlist_confirmation": "この再生リストを削除しますか?", + "local_tracks": "端末内の曲", + "local_tab": "端末内", "song_link": "曲のリンク", - "skip_this_nonsense": "この愚かなことをスキップ", + "skip_this_nonsense": "こんなことはスキップ", "freedom_of_music": "“音楽の自由”", - "freedom_of_music_palm": "“手のひらの中の音楽の自由”", + "freedom_of_music_palm": "“音楽の自由を思いのままに”", "get_started": "さあ始めましょう", "youtube_source_description": "推奨され、最適に機能します。", - "piped_source_description": "自由に感じますか? YouTubeと同じですが、はるかに無料です。", - "jiosaavn_source_description": "南アジア地域向けの最適です。", + "piped_source_description": "自由を感じる?YouTubeと同じだけど、はるかに自由です。", + "jiosaavn_source_description": "南アジア地域では最適です。", + "invidious_source_description": "Pipedに似ていますが、より利用性があります。", "highest_quality": "最高品質:{quality}", - "select_audio_source": "オーディオソースを選択", - "endless_playback_description": "新しい曲をキューの最後に自動的に追加", + "select_audio_source": "音声の提供元を選択", + "endless_playback_description": "キューの最後に新しい曲を自動で追加", "choose_your_region": "地域を選択", - "choose_your_region_description": "これにより、Spotubeがあなたの場所に適したコンテンツを表示できます。", + "choose_your_region_description": "Spotubeがあなたの地域に適したコンテンツを表示します。", "choose_your_language": "言語を選択してください", - "help_project_grow": "このプロジェクトの成長を支援する", - "help_project_grow_description": "Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグを報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。", - "contribute_on_github": "GitHubで貢献する", - "donate_on_open_collective": "Open Collectiveで寄付する", + "help_project_grow": "プロジェクトの成長を支援する", + "help_project_grow_description": "SpoTubeはオープンソースプロジェクトです。貢献したり、バグ報告したり、新機能を提案することで、プロジェクトの成長に貢献できます。", + "contribute_on_github": "GitHubで貢献", + "donate_on_open_collective": "Open Collectiveで寄付", "browse_anonymously": "匿名で閲覧する", - "enable_connect": "接続を有効にする", - "enable_connect_description": "他のデバイスからSpotubeを制御する", - "devices": "デバイス", - "select": "選択する", - "connect_client_alert": "{client} によって操作されています", - "this_device": "このデバイス", + "enable_connect": "接続する", + "enable_connect_description": "他の端末からSpotubeを制御する", + "devices": "機器", + "select": "選択", + "connect_client_alert": "{client} から操作されています", + "this_device": "この端末", "remote": "リモート", - "local_library": "ローカルライブラリ", - "add_library_location": "ライブラリに追加", - "remove_library_location": "ライブラリから削除", - "local_tab": "ローカル", "stats": "統計", - "and_n_more": "そして {count} つのアイテム", - "recently_played": "最近再生された", - "browse_more": "もっと見る", + "and_n_more": "さらに {count} 項目", + "recently_played": "最近聴いた曲", + "browse_more": "もっと表示", "no_title": "タイトルなし", - "not_playing": "再生中ではありません", - "epic_failure": "壮大な失敗!", + "not_playing": "再生なし", + "epic_failure": "壮大なエラー!", "added_num_tracks_to_queue": "{tracks_length} 曲をキューに追加しました", - "spotube_has_an_update": "Spotube にアップデートがあります", + "spotube_has_an_update": "Spotube の最新版あり", "download_now": "今すぐダウンロード", "nightly_version": "Spotube Nightly {nightlyBuildNum} がリリースされました", "release_version": "Spotube v{version} がリリースされました", "read_the_latest": "最新の ", - "release_notes": "リリースノート", - "pick_color_scheme": "カラースキームを選択", + "release_notes": "更新情報を読む", + "pick_color_scheme": "カラーテーマを選択", "save": "保存", - "choose_the_device": "デバイスを選択:", - "multiple_device_connected": "複数のデバイスが接続されています。\nこのアクションを実行するデバイスを選択してください", + "choose_the_device": "端末を選択:", + "multiple_device_connected": "複数の端末が接続されています。\nこの操作を実行する端末を選択", "nothing_found": "何も見つかりませんでした", "the_box_is_empty": "ボックスは空です", "top_artists": "トップアーティスト", @@ -357,7 +362,7 @@ "email": "メール", "profile_followers": "フォロワー", "birthday": "誕生日", - "subscription": "サブスクリプション", + "subscription": "登録", "not_born": "未出生", "hacker": "ハッカー", "profile": "プロフィール", @@ -365,33 +370,29 @@ "edit": "編集", "user_profile": "ユーザープロフィール", "count_plays": "{count} 回再生", - "streaming_fees_hypothetical": "*これは Spotify のストリームあたりの支払い\nが $0.003 から $0.005 であると仮定して計算されています。\nこれは、Spotify でその曲を聴いた場合にアーティストにいくら支払ったかの\n洞察を得るための仮定の計算です。", - "count_mins": "{minutes} 分", - "summary_minutes": "分", - "summary_listened_to_music": "音楽を聴いた", - "summary_songs": "曲", - "summary_streamed_overall": "全体のストリーミング", - "summary_owed_to_artists": "今月アーティストに支払うべき額", - "summary_artists": "アーティストの", - "summary_music_reached_you": "音楽があなたに届いた", - "summary_full_albums": "フルアルバム", - "summary_got_your_love": "あなたの愛を受け取った", - "summary_playlists": "プレイリスト", - "summary_were_on_repeat": "リピートしていた", - "total_money": "合計 {money}", - "minutes_listened": "リスニング時間", + "streaming_fees_hypothetical": "ストリーミング料金 (概算)", + "minutes_listened": "視聴時間", "streamed_songs": "ストリーミングされた曲", "count_streams": "{count} 回のストリーム", "owned_by_you": "あなたが所有", "copied_shareurl_to_clipboard": "{shareUrl} をクリップボードにコピーしました", - "spotify_hipotetical_calculation": "*これは、Spotifyのストリームごとの支払い\nが $0.003 から $0.005 の範囲で計算されています。これは仮想的な\n計算で、Spotify で曲を聴いた場合に、アーティストに\nどれくらい支払ったかをユーザーに示すためのものです。", + "spotify_hipotetical_calculation": "*これは、Spotifyのストリームあたり\n$0.003 から $0.005 として計算されています。\n概算であり、Spotify で曲を聴いていたら、アーティストに\nどれくらい支払われたかを示すものです。", + "count_mins": "{minutes} 分", + "summary_minutes": "分", + "summary_listened_to_music": "音楽を聴いた", + "summary_songs": "曲", + "summary_streamed_overall": "まるごと聴いた", + "summary_owed_to_artists": "今月アーティストに払う\nべき額", + "summary_artists": "アーティスト", + "summary_music_reached_you": "の音楽が届いた", + "summary_full_albums": "フルアルバム", + "summary_got_your_love": "があなたの愛を受け取った", + "summary_playlists": "再生リスト", + "summary_were_on_repeat": "をリピートしました", + "total_money": "計 {money}", "webview_not_found": "Webviewが見つかりません", - "webview_not_found_description": "デバイスにWebviewランタイムがインストールされていません。\nインストールされている場合は、environment PATHにあることを確認してください\n\nインストール後、アプリを再起動してください", - "unsupported_platform": "サポートされていないプラットフォーム", - "invidious_instance": "Invidiousサーバーインスタンス", - "invidious_description": "トラックマッチングに使用するInvidiousサーバーインスタンス", - "invidious_warning": "一部はうまく機能しない可能性があります。自己責任で使用してください", - "invidious_source_description": "Pipedに似ていますが、より高い可用性があります。", + "webview_not_found_description": "端末にWebviewランタイムがインストールされていません。\nインストールされている場合は、環境変数のパスにあるか確認してください\n\nインストール後、アプリを再起動してください", + "unsupported_platform": "未対応のプラットフォーム", "cache_music": "音楽をキャッシュ", "open": "開く", "cache_folder": "キャッシュフォルダー", @@ -402,22 +403,20 @@ "found_n_files": "{count}ファイルが見つかりました", "export_cache_confirmation": "これらのファイルをエクスポートしますか", "exported_n_out_of_m_files": "{filesExported} / {files}ファイルがエクスポートされました", - "playlist": "プレイリスト", - "no_loop": "ループなし", "generate": "生成", "undo": "元に戻す", - "download_all": "すべてをダウンロード", - "add_all_to_playlist": "すべてをプレイリストに追加", - "add_all_to_queue": "すべてをキューに追加", - "play_all_next": "次にすべてを再生", + "download_all": "すべてダウンロード", + "add_all_to_playlist": "すべて再生リストに追加", + "add_all_to_queue": "すべてキューに追加", + "play_all_next": "すべてを次に再生", "pause": "一時停止", - "view_all": "すべてを見る", + "view_all": "すべて表示", "no_tracks_added_yet": "まだ曲を追加していないようです", "no_tracks": "ここには曲がないようです", "no_tracks_listened_yet": "まだ何も聞いていないようです", "not_following_artists": "アーティストをフォローしていません", "no_favorite_albums_yet": "まだお気に入りのアルバムを追加していないようです", - "no_logs_found": "ログが見つかりませんでした", + "no_logs_found": "ログなし", "youtube_engine": "YouTubeエンジン", "youtube_engine_not_installed_title": "{engine}はインストールされていません", "youtube_engine_not_installed_message": "{engine}はシステムにインストールされていません。", @@ -425,10 +424,57 @@ "youtube_engine_unix_issue_message": "macOS/Linux/Unix系OSでは、.zshrc/.bashrc/.bash_profileなどでパスを設定しても動作しません。\nシェルの設定ファイルにパスを設定する必要があります", "download": "ダウンロード", "file_not_found": "ファイルが見つかりません", - "custom": "カスタム", - "add_custom_url": "カスタムURLを追加", + "custom": "独自", + "add_custom_url": "独自にURLを追加", "edit_port": "ポートを編集", - "port_helper_msg": "デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。", + "port_helper_msg": "初期設定は-1で、ランダムな番号を示します。ファイアウォールを設定している場合に設定することを推奨します。", "connect_request": "{client}の接続を許可しますか?", - "connection_request_denied": "接続が拒否されました。ユーザーがアクセスを拒否しました。" -} \ No newline at end of file + "connection_request_denied": "接続が拒否されました。ユーザーがアクセスを拒否しました。", + "hipotetical_calculation": "*これは、オンライン音楽ストリーミングプラットフォームの1ストリームあたりの平均支払い額である$0.003〜$0.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": "このプラグインは、あなたの音楽をscrobbleして視聴履歴を生成します。", + "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\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": "Scrobbling" +} diff --git a/lib/l10n/app_ka.arb b/lib/l10n/app_ka.arb index 67f90361..7aaa5cfb 100644 --- a/lib/l10n/app_ka.arb +++ b/lib/l10n/app_ka.arb @@ -430,5 +430,52 @@ "edit_port": "პორტის რედაქტირება", "port_helper_msg": "ნაგულისხმევი არის -1, რაც შემთხვევითი ნომრის მითითებას ნიშნავს. თუ لديك firewall настроен, рекомендуется установить это.", "connect_request": "{client}-ის დაკავშირების ნებართვა?", - "connection_request_denied": "კავშირი უარყოფილია. მომხმარებელმა უარყო წვდომა." + "connection_request_denied": "კავშირი უარყოფილია. მომხმარებელმა უარყო წვდომა.", + "hipotetical_calculation": "*ეს გამოითვლება ონლაინ მუსიკალური სტრიმინგის პლატფორმების საშუალო ანაზღაურების საფუძველზე, რომელიც შეადგენს $0.003-დან $0.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": "მხარს უჭერს სქრობლინგს", + "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\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": "სქრობლინგი" } \ No newline at end of file diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 037e1fb1..b38e35c5 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -431,5 +431,52 @@ "edit_port": "포트 편집", "port_helper_msg": "기본값은 -1로 무작위 숫자를 나타냅니다. 방화벽이 구성된 경우 이를 설정하는 것이 좋습니다.", "connect_request": "{client}의 연결을 허용하시겠습니까?", - "connection_request_denied": "연결이 거부되었습니다. 사용자가 액세스를 거부했습니다." + "connection_request_denied": "연결이 거부되었습니다. 사용자가 액세스를 거부했습니다.", + "hipotetical_calculation": "*이것은 온라인 음악 스트리밍 플랫폼의 스트림당 평균 지불액인 $0.003에서 $0.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": "스크로블링 지원", + "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\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": "스크로블링" } \ No newline at end of file diff --git a/lib/l10n/app_ne.arb b/lib/l10n/app_ne.arb index 823dadb1..1da053c4 100644 --- a/lib/l10n/app_ne.arb +++ b/lib/l10n/app_ne.arb @@ -430,5 +430,52 @@ "edit_port": "पोर्ट सम्पादन गर्नुहोस्", "port_helper_msg": "डिफ़ॉल्ट -1 हो जुन यादृच्छिक संख्या जनाउँछ। यदि तपाईंले फायरवाल कन्फिगर गर्नुभएको छ भने, यसलाई सेट गर्न सिफारिस गरिन्छ।", "connect_request": "{client} लाई जडान गर्न अनुमति दिनुहोस्?", - "connection_request_denied": "जडान अस्वीकृत। प्रयोगकर्ताले पहुँच अस्वीकृत गर्यो।" + "connection_request_denied": "जडान अस्वीकृत। प्रयोगकर्ताले पहुँच अस्वीकृत गर्यो।", + "hipotetical_calculation": "*यो अनलाइन संगीत स्ट्रिमिङ प्लेटफर्मको प्रति स्ट्रिम भुक्तानी $0.003 देखि $0.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": "स्क्रब्बलिंगलाई समर्थन गर्दछ", + "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": "स्पोट्यूब टोलीले कुनै पनि \"तेस्रो-पक्ष\" प्लगइनहरूको लागि कुनै जिम्मेवारी (कानुनी सहित) लिँदैन।\nकृपया तिनीहरूलाई आफ्नो जोखिममा प्रयोग गर्नुहोस्। कुनै पनि बग/समस्याहरूको लागि, कृपया तिनीहरूलाई प्लगइन रिपोसिटरीमा रिपोर्ट गर्नुहोस्।\n\nयदि कुनै \"तेस्रो-पक्ष\" प्लगइनले कुनै सेवा/कानुनी संस्थाको ToS/DMCA तोडिरहेको छ भने, कृपया \"तेस्रो-पक्ष\" प्लगइन लेखक वा होस्टिङ प्लेटफर्म e.g. GitHub/Codeberg लाई कारबाही गर्न अनुरोध गर्नुहोस्। माथि सूचीबद्ध (\"तेस्रो-पक्ष\" लेबल गरिएका) सबै सार्वजनिक/सामुदायिक रूपमा राखिएका प्लगइनहरू हुन्। हामी तिनीहरूलाई क्युरेट गरिरहेका छैनौं, त्यसैले हामी तिनीहरूमा कुनै कारबाही गर्न सक्दैनौं।\n\n", + "input_does_not_match_format": "इनपुट आवश्यक ढाँचासँग मेल खाँदैन", + "metadata_provider_plugins": "मेटाडेटा प्रदायक प्लगइनहरू", + "paste_plugin_download_url": "डाउनलोड url वा GitHub/Codeberg repo 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": "स्क्रब्बलिंग" } \ No newline at end of file diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 01aa6ef8..9395ec35 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -9,7 +9,7 @@ "genre": "Genre", "personalized": "Gepersonaliseerd", "featured": "Aanbevolen", - "new_releases": "Nieuwe uitgaves", + "new_releases": "Nieuwe uitgaven", "songs": "Liedjes", "playing_track": "{track} afspelen", "queue_clear_alert": "Dit zal de huidige wachtrij wissen. {track_length} nummers worden verwijderd\nWil je doorgaan?", @@ -41,14 +41,15 @@ "sort_z_a": "Sorteren op Z-A", "sort_artist": "Sorteren op artiest", "sort_album": "Sorteren op album", + "sort_duration": "Sorteren op lengte", "sort_tracks": "Nummers sorteren", "currently_downloading": "Momenteel aan het downloaden ({tracks_length})", - "cancel_all": "Alle annuleren", + "cancel_all": "Alles annuleren", "filter_artist": "Artiesten filteren…", "followers": "{followers} volgers", "add_artist_to_blacklist": "Artiest toevoegen aan zwarte lijst", - "top_tracks": "Topsporen", - "fans_also_like": "Liefhebbers willen ook", + "top_tracks": "Topnummers", + "fans_also_like": "Fans luisteren ook", "loading": "Laden…", "artist": "Artiest", "blacklisted": "Zwarte lijst", @@ -89,8 +90,8 @@ "share": "Delen", "mini_player": "Minispeler", "slide_to_seek": "Schuiven om vooruit of achteruit te zoeken", - "shuffle_playlist": "Afspeellijst schuifelen", - "unshuffle_playlist": "Afspeellijst onschuifelen", + "shuffle_playlist": "Afspeellijst willekeurig", + "unshuffle_playlist": "Afspeellijst op volgorde", "previous_track": "Vorige nummer", "next_track": "Volgende nummer", "pause_playback": "Afspelen pauzeren", @@ -98,7 +99,7 @@ "loop_track": "Nummer herhalen", "repeat_playlist": "Afspeellijst herhalen", "queue": "Wachtrij", - "alternative_track_sources": "Alternatieve nummerbronnen", + "alternative_track_sources": "Alternatieve bronnen voor nummers", "download_track": "Nummer downloaden", "tracks_in_queue": "{tracks} nummers in wachtrij", "clear_all": "Alles wissen", @@ -240,8 +241,8 @@ "views": "Weergaven", "streamUrl": "Stream-URL", "stop": "Stoppen", - "sort_newest": "Sorteren op nieuwste toegevoegd", - "sort_oldest": "Sorteren op oudste toegevoegd", + "sort_newest": "Sorteren op recent toegevoegd", + "sort_oldest": "Sorteren op langst toegevoegd", "sleep_timer": "Slaaptimer", "mins": "{minutes} minuten", "hours": "{hours} uren", @@ -287,34 +288,32 @@ "explore_genres": "Genres verkennen", "friends": "Vrienden", "no_lyrics_available": "Sorry, geen teksten gevonden voor dit nummer", - "sort_duration": "Sorteer op Duur", - "audio_source": "Audiobron", - "start_a_radio": "Start een Radio", - "how_to_start_radio": "Hoe wilt u de radio starten?", - "replace_queue_question": "Wilt u de huidige wachtrij vervangen of eraan toevoegen?", - "endless_playback": "Eindeloze Afspelen", - "delete_playlist": "Verwijder Afspeellijst", - "delete_playlist_confirmation": "Weet u zeker dat u deze afspeellijst wilt verwijderen?", - "local_tracks": "Lokale Nummers", - "song_link": "Nummer Link", - "skip_this_nonsense": "Sla deze onzin over", - "freedom_of_music": "“Vrijheid van Muziek”", - "freedom_of_music_palm": "“Vrijheid van Muziek in de palm van je hand”", + "start_a_radio": "Een radio starten", + "how_to_start_radio": "Hoe wil je de radio starten?", + "replace_queue_question": "Wil je de huidige wachtrij vervangen of eraan toevoegen?", + "endless_playback": "Oneindig afspelen", + "delete_playlist": "Afspeellijst verwijderen", + "delete_playlist_confirmation": "Weet je zeker dat je deze afspeellijst wilt verwijderen?", + "local_tracks": "Lokale nummers", + "song_link": "Song-link", + "skip_this_nonsense": "Deze onzin overslaan", + "freedom_of_music": "“Vrijheid van muziek”", + "freedom_of_music_palm": "“Vrijheid van muziek in je hand”", "get_started": "Laten we beginnen", - "youtube_source_description": "Aanbevolen en werkt het beste.", - "piped_source_description": "Voel je vrij? Hetzelfde als YouTube maar veel gratis.", - "jiosaavn_source_description": "Het beste voor de Zuid-Aziatische regio.", - "highest_quality": "Hoogste Kwaliteit: {quality}", - "select_audio_source": "Selecteer Audiobron", - "endless_playback_description": "Voeg automatisch nieuwe nummers toe aan het einde van de wachtrij", - "choose_your_region": "Kies uw regio", - "choose_your_region_description": "Dit zal Spotube helpen om de juiste inhoud voor uw locatie te tonen.", - "choose_your_language": "Kies uw taal", - "help_project_grow": "Help dit project groeien", - "help_project_grow_description": "Spotube is een open-source project. U kunt dit project helpen groeien door bij te dragen aan het project, bugs te melden of nieuwe functies voor te stellen.", - "contribute_on_github": "Bijdragen op GitHub", - "donate_on_open_collective": "Doneren op Open Collective", - "browse_anonymously": "Anoniem Bladeren", + "youtube_source_description": "Aangeraden en werkt het best.", + "piped_source_description": "Voel je je vrij? Net als YouTube, maar meer vrij.", + "jiosaavn_source_description": "Het beste voor de regio Zuid-Azië.", + "highest_quality": "Hoogste kwaliteit: {quality}", + "select_audio_source": "Audiobron kiezen", + "endless_playback_description": "Nieuwe nummers automatisch achteraan de wachtrij toevoegen", + "choose_your_region": "Kies je regio", + "choose_your_region_description": "Dit helpt Spotube om de juiste inhoud\nvoor jouw locatie te tonen.", + "choose_your_language": "Kies je taal", + "help_project_grow": "Help dit project met groeien", + "help_project_grow_description": "Spotube is een open-source project. Je kunt dit project helpen groeien door eraan bij te dragen, problemen te melden of nieuwe functies voor te stellen.", + "contribute_on_github": "Bijdragen on GitHub", + "donate_on_open_collective": "Doneren on Open Collective", + "browse_anonymously": "Anoniem browsen", "enable_connect": "Verbinding inschakelen", "enable_connect_description": "Spotube bedienen vanaf andere apparaten", "devices": "Apparaten", @@ -431,5 +430,52 @@ "edit_port": "Poort bewerken", "port_helper_msg": "Standaard is -1, wat een willekeurig nummer aangeeft. Als je een firewall hebt geconfigureerd, wordt aanbevolen dit in te stellen.", "connect_request": "Toestaan dat {client} verbinding maakt?", - "connection_request_denied": "Verbinding geweigerd. Gebruiker heeft toegang geweigerd." -} \ No newline at end of file + "connection_request_denied": "Verbinding geweigerd. Gebruiker heeft toegang geweigerd.", + "hipotetical_calculation": "*Dit is berekend op basis van de gemiddelde uitbetaling per stream van online muziekstreamingplatforms van $0,003 tot $0,005. Dit is een hypothetische berekening om de gebruiker inzicht te geven in hoeveel ze aan de artiesten zouden hebben betaald als ze hun nummer op een ander muziekstreamingplatform zouden beluisteren.", + "an_error_occurred": "Er is een fout opgetreden", + "copy_to_clipboard": "Kopiëren naar klembord", + "view_logs": "Logboeken bekijken", + "retry": "Opnieuw proberen", + "no_default_metadata_provider_selected": "U heeft geen standaard metadata-aanbieder ingesteld", + "manage_metadata_providers": "Metadata-aanbieders beheren", + "open_link_in_browser": "Link openen in browser?", + "do_you_want_to_open_the_following_link": "Wilt u de volgende link openen", + "unsafe_url_warning": "Het kan onveilig zijn om links van onbetrouwbare bronnen te openen. Wees voorzichtig!\nU kunt de link ook naar uw klembord kopiëren.", + "copy_link": "Link kopiëren", + "building_your_timeline": "Uw tijdlijn wordt opgebouwd op basis van uw luistergedrag...", + "official": "Officieel", + "author_name": "Auteur: {author}", + "third_party": "Derden", + "plugin_requires_authentication": "Plugin vereist authenticatie", + "update_available": "Update beschikbaar", + "supports_scrobbling": "Ondersteunt scrobbling", + "plugin_scrobbling_info": "Deze plugin scrobblet uw muziek om uw luistergeschiedenis te genereren.", + "default_plugin": "Standaard", + "set_default": "Instellen als standaard", + "support": "Ondersteuning", + "support_plugin_development": "Ondersteun plugin-ontwikkeling", + "can_access_name_api": "- Kan de **{name}** API benaderen", + "do_you_want_to_install_this_plugin": "Wilt u deze plugin installeren?", + "third_party_plugin_warning": "Deze plugin is afkomstig van een repository van derden. Zorg ervoor dat u de bron vertrouwt voordat u installeert.", + "author": "Auteur", + "this_plugin_can_do_following": "Deze plugin kan het volgende doen", + "install": "Installeren", + "install_a_metadata_provider": "Een metadata-aanbieder installeren", + "no_tracks_playing": "Er wordt momenteel geen nummer afgespeeld", + "synced_lyrics_not_available": "Gesynchroniseerde songteksten zijn niet beschikbaar voor dit nummer. Gebruik in plaats daarvan het tabblad", + "plain_lyrics": "Eenvoudige songteksten", + "tab_instead": "in plaats daarvan.", + "disclaimer": "Disclaimer", + "third_party_plugin_dmca_notice": "Het Spotube-team draagt geen enkele verantwoordelijkheid (inclusief juridische) voor \"derden\" plugins.\nGebruik ze op eigen risico. Voor bugs/problemen kunt u deze melden bij de plugin-repository.\n\nAls een \"derden\" plugin de ToS/DMCA van een service/juridische entiteit schendt, vraag dan de auteur van de \"derden\" plugin of het hostingplatform, bijvoorbeeld GitHub/Codeberg, om actie te ondernemen. De hierboven vermelde (gelabelde \"derden\") plugins zijn allemaal openbare/door de gemeenschap onderhouden plugins. We beheren ze niet, dus we kunnen geen actie tegen ze ondernemen.\n\n", + "input_does_not_match_format": "Invoer komt niet overeen met het vereiste formaat", + "metadata_provider_plugins": "Metadata-aanbieder Plugins", + "paste_plugin_download_url": "Plak de download-URL of de URL van de GitHub/Codeberg-repository of een directe link naar het .smplug-bestand", + "download_and_install_plugin_from_url": "Download en installeer de plugin via URL", + "failed_to_add_plugin_error": "Kon de plugin niet toevoegen: {error}", + "upload_plugin_from_file": "Plugin uploaden vanuit bestand", + "installed": "Geïnstalleerd", + "available_plugins": "Beschikbare plugins", + "configure_your_own_metadata_plugin": "Configureer uw eigen metadata-aanbieder voor afspeellijst/album/artiest/feed", + "audio_scrobblers": "Audioscrobblers", + "scrobbling": "Scrobbling" +} diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index a2388614..fac9070a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -430,5 +430,52 @@ "edit_port": "Edytuj port", "port_helper_msg": "Domyślna wartość to -1, co oznacza losową liczbę. Jeśli masz skonfigurowany zaporę, zaleca się jej ustawienie.", "connect_request": "Zezwolić {client} na połączenie?", - "connection_request_denied": "Połączenie odrzucone. Użytkownik odmówił dostępu." + "connection_request_denied": "Połączenie odrzucone. Użytkownik odmówił dostępu.", + "hipotetical_calculation": "*Jest to obliczone na podstawie średniej wypłaty z internetowych platform streamingowych za jeden stream w wysokości 0,003 do 0,005 USD. Jest to hipotetyczne obliczenie, które ma na celu dać użytkownikowi wgląd w to, ile zapłaciłby artystom, gdyby słuchał ich piosenek na różnych platformach streamingowych.", + "an_error_occurred": "Wystąpił błąd", + "copy_to_clipboard": "Kopiuj do schowka", + "view_logs": "Wyświetl logi", + "retry": "Ponów", + "no_default_metadata_provider_selected": "Nie masz ustawionego domyślnego dostawcy metadanych", + "manage_metadata_providers": "Zarządzaj dostawcami metadanych", + "open_link_in_browser": "Otworzyć link w przeglądarce?", + "do_you_want_to_open_the_following_link": "Czy chcesz otworzyć następujący link", + "unsafe_url_warning": "Otwieranie linków z niezaufanych źródeł może być niebezpieczne. Zachowaj ostrożność!\nMożesz również skopiować link do schowka.", + "copy_link": "Kopiuj link", + "building_your_timeline": "Budowanie Twojej osi czasu na podstawie Twoich odsłuchań...", + "official": "Oficjalny", + "author_name": "Autor: {author}", + "third_party": "Zewnętrzny", + "plugin_requires_authentication": "Wtyczka wymaga uwierzytelnienia", + "update_available": "Dostępna aktualizacja", + "supports_scrobbling": "Obsługuje scrobbling", + "plugin_scrobbling_info": "Ta wtyczka scrobbluje Twoją muzykę, aby wygenerować historię odsłuchań.", + "default_plugin": "Domyślna", + "set_default": "Ustaw jako domyślną", + "support": "Wsparcie", + "support_plugin_development": "Wspieraj rozwój wtyczki", + "can_access_name_api": "- Może uzyskać dostęp do API **{name}**", + "do_you_want_to_install_this_plugin": "Czy chcesz zainstalować tę wtyczkę?", + "third_party_plugin_warning": "Ta wtyczka pochodzi z zewnętrznego repozytorium. Upewnij się, że ufasz źródłu przed instalacją.", + "author": "Autor", + "this_plugin_can_do_following": "Ta wtyczka może wykonywać następujące czynności", + "install": "Instaluj", + "install_a_metadata_provider": "Zainstaluj dostawcę metadanych", + "no_tracks_playing": "Obecnie nie odtwarzany jest żaden utwór", + "synced_lyrics_not_available": "Zsynchronizowane teksty nie są dostępne dla tego utworu. Zamiast tego użyj zakładki", + "plain_lyrics": "Zwykłe teksty", + "tab_instead": "zamiast tego.", + "disclaimer": "Zastrzeżenie", + "third_party_plugin_dmca_notice": "Zespół Spotube nie ponosi żadnej odpowiedzialności (w tym prawnej) za żadne wtyczki \"zewnętrzne\".\nUżywaj ich na własne ryzyko. Wszelkie błędy/problemy prosimy zgłaszać w repozytorium wtyczki.\n\nJeśli jakakolwiek wtyczka \"zewnętrzna\" narusza ToS/DMCA jakiejkolwiek usługi/podmiotu prawnego, prosimy o kontakt z autorem wtyczki \"zewnętrznej\" lub platformą hostingową, np. GitHub/Codeberg, w celu podjęcia działań. Wymienione powyżej (oznaczone jako \"zewnętrzne\") są publicznymi wtyczkami utrzymywanymi przez społeczność. Nie kuratujemy ich, więc nie możemy podjąć żadnych działań w ich sprawie.\n\n", + "input_does_not_match_format": "Wprowadzony tekst nie pasuje do wymaganego formatu", + "metadata_provider_plugins": "Wtyczki dostawców metadanych", + "paste_plugin_download_url": "Wklej adres URL do pobrania lub adres URL repozytorium GitHub/Codeberg lub bezpośredni link do pliku .smplug", + "download_and_install_plugin_from_url": "Pobierz i zainstaluj wtyczkę z adresu URL", + "failed_to_add_plugin_error": "Nie udało się dodać wtyczki: {error}", + "upload_plugin_from_file": "Prześlij wtyczkę z pliku", + "installed": "Zainstalowane", + "available_plugins": "Dostępne wtyczki", + "configure_your_own_metadata_plugin": "Skonfiguruj własnego dostawcę metadanych dla playlisty/albumu/artysty/kanału", + "audio_scrobblers": "Scrobblery audio", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index bec4f7c9..71e5ab55 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -430,5 +430,52 @@ "edit_port": "Editar porta", "port_helper_msg": "O padrão é -1, que indica um número aleatório. Se você tiver um firewall configurado, é recomendável definir isso.", "connect_request": "Permitir que {client} se conecte?", - "connection_request_denied": "Conexão negada. O usuário negou o acesso ." + "connection_request_denied": "Conexão negada. O usuário negou o acesso .", + "hipotetical_calculation": "*Isso é calculado com base no pagamento médio por stream de plataformas de streaming de música online de US$ 0,003 a US$ 0,005. Esta é uma estimativa hipotética para dar ao usuário uma ideia de quanto ele teria pago aos artistas se ouvisse sua música em diferentes plataformas de streaming de música.", + "an_error_occurred": "Ocorreu um erro", + "copy_to_clipboard": "Copiar para a área de transferência", + "view_logs": "Ver logs", + "retry": "Tentar novamente", + "no_default_metadata_provider_selected": "Você não tem um provedor de metadados padrão definido", + "manage_metadata_providers": "Gerenciar provedores de metadados", + "open_link_in_browser": "Abrir link no navegador?", + "do_you_want_to_open_the_following_link": "Você deseja abrir o seguinte link", + "unsafe_url_warning": "Pode ser inseguro abrir links de fontes não confiáveis. Tenha cautela!\nVocê também pode copiar o link para sua área de transferência.", + "copy_link": "Copiar link", + "building_your_timeline": "Construindo sua linha do tempo com base em suas audições...", + "official": "Oficial", + "author_name": "Autor: {author}", + "third_party": "Terceiros", + "plugin_requires_authentication": "Plugin requer autenticação", + "update_available": "Atualização disponível", + "supports_scrobbling": "Suporta scrobbling", + "plugin_scrobbling_info": "Este plugin faz o scrobbling de sua música para gerar seu histórico de audição.", + "default_plugin": "Padrão", + "set_default": "Definir como padrão", + "support": "Suporte", + "support_plugin_development": "Apoiar o desenvolvimento do plugin", + "can_access_name_api": "- Pode acessar a API **{name}**", + "do_you_want_to_install_this_plugin": "Você deseja instalar este plugin?", + "third_party_plugin_warning": "Este plugin é de um repositório de terceiros. Certifique-se de que você confia na fonte antes de instalá-lo.", + "author": "Autor", + "this_plugin_can_do_following": "Este plugin pode fazer o seguinte", + "install": "Instalar", + "install_a_metadata_provider": "Instalar um provedor de metadados", + "no_tracks_playing": "Nenhuma música sendo reproduzida no momento", + "synced_lyrics_not_available": "As letras sincronizadas não estão disponíveis para esta música. Por favor, use a aba", + "plain_lyrics": "Letras simples", + "tab_instead": "em vez disso.", + "disclaimer": "Aviso", + "third_party_plugin_dmca_notice": "A equipe Spotube não se responsabiliza (incluindo legalmente) por quaisquer plugins de \"terceiros\".\nUse-os por sua conta e risco. Para quaisquer bugs/problemas, por favor, relate-os ao repositório do plugin.\n\nSe algum plugin de \"terceiros\" estiver violando os Termos de Serviço/DMCA de qualquer serviço/entidade legal, por favor, peça ao autor do plugin \"terceiro\" ou à plataforma de hospedagem, por exemplo, GitHub/Codeberg, para tomar medidas. Os plugins listados acima (rotulados como \"terceiros\") são todos plugins públicos/mantidos pela comunidade. Não os estamos curando, então não podemos tomar nenhuma medida sobre eles.\n\n", + "input_does_not_match_format": "A entrada não corresponde ao formato exigido", + "metadata_provider_plugins": "Plugins do provedor de metadados", + "paste_plugin_download_url": "Cole a url de download ou a url do repositório GitHub/Codeberg ou o link direto para o arquivo .smplug", + "download_and_install_plugin_from_url": "Baixar e instalar o plugin a partir da url", + "failed_to_add_plugin_error": "Falha ao adicionar plugin: {error}", + "upload_plugin_from_file": "Carregar plugin a partir de arquivo", + "installed": "Instalado", + "available_plugins": "Plugins disponíveis", + "configure_your_own_metadata_plugin": "Configure seu próprio provedor de metadados de playlist/álbum/artista/feed", + "audio_scrobblers": "Scrobblers de áudio", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 9e8cbb3f..bc7586ed 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -430,5 +430,52 @@ "edit_port": "Редактировать порт", "port_helper_msg": "По умолчанию -1, что означает случайное число. Если у вас настроен брандмауэр, рекомендуется установить это.", "connect_request": "Разрешить {client} подключение?", - "connection_request_denied": "Подключение отклонено. Пользователь отказал в доступе." + "connection_request_denied": "Подключение отклонено. Пользователь отказал в доступе.", + "hipotetical_calculation": "*Это рассчитано на основе средней выплаты за прослушивание на онлайн-платформах для потоковой передачи музыки в размере от 0,003 до 0,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": "Поддерживает скробблинг", + "plugin_scrobbling_info": "Этот плагин скробблит вашу музыку для создания вашей истории прослушиваний.", + "default_plugin": "По умолчанию", + "set_default": "Установить по умолчанию", + "support": "Поддержка", + "support_plugin_development": "Поддержать разработку плагина", + "can_access_name_api": "- Может получить доступ к 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Если какой-либо \"сторонний\" плагин нарушает ToS/DMCA какого-либо сервиса/юридического лица, пожалуйста, попросите автора плагина \"стороннего\" или хостинговую платформу, например, GitHub/Codeberg, принять меры. Перечисленные выше (помеченные как \"сторонние\") являются общедоступными/поддерживаемыми сообществом плагинами. Мы не курируем их, поэтому не можем принимать по ним никаких мер.\n\n", + "input_does_not_match_format": "Введенные данные не соответствуют требуемому формату", + "metadata_provider_plugins": "Плагины поставщика метаданных", + "paste_plugin_download_url": "Вставьте URL-адрес для загрузки или URL-адрес репозитория GitHub/Codeberg или прямую ссылку на файл .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": "Скробблинг" } \ No newline at end of file diff --git a/lib/l10n/app_ta.arb b/lib/l10n/app_ta.arb index 15c8d54f..01991a39 100644 --- a/lib/l10n/app_ta.arb +++ b/lib/l10n/app_ta.arb @@ -428,5 +428,52 @@ "edit_port": "போர்டு திருத்தவும்", "port_helper_msg": "இயல்புநிலை -1 ஆகும், இது சீரற்ற எண்ணை குறிக்கிறது. நீங்கள் தீயணைப்பு அமைக்கப்பட்டிருந்தால், இதை அமைப்பது பரிந்துரைக்கப்படுகிறது.", "connect_request": "{client} க்கு இணைக்க அனுமதிக்கவா?", - "connection_request_denied": "இணைப்பு மறுக்கப்பட்டது. பயனர் அணுகலை மறுத்தார்." + "connection_request_denied": "இணைப்பு மறுக்கப்பட்டது. பயனர் அணுகலை மறுத்தார்.", + "hipotetical_calculation": "*இது சராசரி ஆன்லைன் இசை ஸ்ட்ரீமிங் தளத்தின் ஒரு ஸ்ட்ரீமிற்கான $0.003 முதல் $0.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": "ஸ்க்ரோப்ளிங்கை ஆதரிக்கிறது", + "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": "ஸ்பாட்யூப் குழு எந்த \"மூன்றாம் தரப்பு\" பிளகின்களுக்கும் எந்தப் பொறுப்பையும் (சட்டரீதியான உட்பட) ஏற்காது.\nதயவுசெய்து உங்கள் சொந்த ஆபத்தில் அவற்றைப் பயன்படுத்தவும். ஏதேனும் பிழைகள்/சிக்கல்களுக்கு, பிளகின் களஞ்சியத்தில் அவற்றைப் புகாரளிக்கவும்.\n\nஏதேனும் ஒரு \"மூன்றாம் தரப்பு\" பிளகின் ஒரு சேவை/சட்ட நிறுவனத்தின் ToS/DMCA ஐ மீறினால், தயவுசெய்து \"மூன்றாம் தரப்பு\" பிளகின் ஆசிரியரையோ அல்லது ஹோஸ்டிங் தளத்தையோ, எ.கா. GitHub/Codeberg, நடவடிக்கை எடுக்கக் கோரவும். மேலே பட்டியலிடப்பட்ட (\"மூன்றாம் தரப்பு\" என பெயரிடப்பட்ட) அனைத்து பொதுவான/சமூகத்தால் பராமரிக்கப்படும் பிளகின்கள். நாங்கள் அவற்றை க்யூரேட் செய்யவில்லை, எனவே அவற்றின் மீது எந்த நடவடிக்கையும் எடுக்க முடியாது.\n\n", + "input_does_not_match_format": "உள்ளீடு தேவையான வடிவத்துடன் பொருந்தவில்லை", + "metadata_provider_plugins": "மெட்டாடேட்டா வழங்குநர் பிளகின்கள்", + "paste_plugin_download_url": "பதிவிறக்க url அல்லது GitHub/Codeberg repo 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": "ஸ்க்ரோப்ளிங்" } \ No newline at end of file diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb index 38decf66..4b999fe0 100644 --- a/lib/l10n/app_th.arb +++ b/lib/l10n/app_th.arb @@ -431,5 +431,52 @@ "edit_port": "แก้ไขพอร์ต", "port_helper_msg": "ค่าเริ่มต้นคือ -1 ซึ่งหมายถึงหมายเลขสุ่ม หากคุณได้กำหนดค่าไฟร์วอลล์แล้ว แนะนำให้ตั้งค่านี้", "connect_request": "อนุญาตให้ {client} เชื่อมต่อหรือไม่?", - "connection_request_denied": "การเชื่อมต่อล้มเหลว ผู้ใช้ปฏิเสธการเข้าถึง" + "connection_request_denied": "การเชื่อมต่อล้มเหลว ผู้ใช้ปฏิเสธการเข้าถึง", + "hipotetical_calculation": "*การคำนวณนี้อิงจากค่าเฉลี่ยการจ่ายเงินต่อสตรีมของแพลตฟอร์มสตรีมมิ่งเพลงออนไลน์ที่ $0.003 ถึง $0.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": "ปลั๊กอินนี้จะ scrobble เพลงของคุณเพื่อสร้างประวัติการฟังของคุณ", + "default_plugin": "ค่าเริ่มต้น", + "set_default": "ตั้งค่าเริ่มต้น", + "support": "สนับสนุน", + "support_plugin_development": "สนับสนุนการพัฒนาปลั๊กอิน", + "can_access_name_api": "- สามารถเข้าถึง 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หากปลั๊กอิน \"บุคคลที่สาม\" ใดๆ ละเมิด ToS/DMCA ของบริการ/นิติบุคคลใดๆ โปรดขอให้ผู้เขียนปลั๊กอิน \"บุคคลที่สาม\" หรือแพลตฟอร์มโฮสติ้ง เช่น GitHub/Codeberg ดำเนินการ ที่ระบุไว้ข้างต้น (ที่ติดป้าย \"บุคคลที่สาม\") เป็นปลั๊กอินสาธารณะ/ที่ดูแลโดยชุมชนทั้งหมด เราไม่ได้จัดการดูแล ดังนั้นเราจึงไม่สามารถดำเนินการใดๆ กับพวกเขาได้\n\n", + "input_does_not_match_format": "อินพุตไม่ตรงกับรูปแบบที่ต้องการ", + "metadata_provider_plugins": "ปลั๊กอินผู้ให้บริการเมตาดาต้า", + "paste_plugin_download_url": "วาง url ดาวน์โหลดหรือ url ที่เก็บ GitHub/Codeberg หรือลิงก์โดยตรงไปยังไฟล์ .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": "เครื่อง scrobbler เสียง", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_tl.arb b/lib/l10n/app_tl.arb index a1c4264c..45e0b070 100644 --- a/lib/l10n/app_tl.arb +++ b/lib/l10n/app_tl.arb @@ -428,5 +428,52 @@ "edit_port": "I-edit ang port", "port_helper_msg": "Ang default ay -1 na nagpapahiwatig ng random na numero. Kung na-configure mo ang firewall, inirerekomenda na itakda ito.", "connect_request": "Payagan ang {client} na kumonekta?", - "connection_request_denied": "Tanggihan ang koneksyon. Tinanggihan ng gumagamit ang pag-access." + "connection_request_denied": "Tanggihan ang koneksyon. Tinanggihan ng gumagamit ang pag-access.", + "hipotetical_calculation": "*Ito ay kinakalkula batay sa average na payout ng online music streaming platform na $0.003 hanggang $0.005 kada stream. Ito ay isang hypothetical na kalkulasyon upang bigyan ang user ng insight kung magkano ang babayaran nila sa mga artist kung sakaling makinig sila ng kanilang kanta sa iba't ibang music streaming platform.", + "an_error_occurred": "May naganap na error", + "copy_to_clipboard": "Kopyahin sa clipboard", + "view_logs": "Tingnan ang mga log", + "retry": "Subukang muli", + "no_default_metadata_provider_selected": "Wala kang nakatakdang default na metadata provider", + "manage_metadata_providers": "Pamahalaan ang mga metadata provider", + "open_link_in_browser": "Buksan ang Link sa Browser?", + "do_you_want_to_open_the_following_link": "Gusto mo bang buksan ang sumusunod na link", + "unsafe_url_warning": "Maaaring hindi ligtas ang pagbukas ng mga link mula sa hindi pinagkakatiwalaang pinagmulan. Mag-ingat!\nMaaari mo ring kopyahin ang link sa iyong clipboard.", + "copy_link": "Kopyahin ang Link", + "building_your_timeline": "Binubuo ang iyong timeline batay sa iyong mga pinakinggan...", + "official": "Opisyal", + "author_name": "May-akda: {author}", + "third_party": "Third-party", + "plugin_requires_authentication": "Nangangailangan ng authentication ang plugin", + "update_available": "May available na update", + "supports_scrobbling": "Sinusuportahan ang scrobbling", + "plugin_scrobbling_info": "Sinis-scrobble ng plugin na ito ang iyong musika upang mabuo ang iyong kasaysayan ng pakikinig.", + "default_plugin": "Default", + "set_default": "Itakda bilang default", + "support": "Suporta", + "support_plugin_development": "Suportahan ang pagbuo ng plugin", + "can_access_name_api": "- Maaaring i-access ang **{name}** API", + "do_you_want_to_install_this_plugin": "Gusto mo bang i-install ang plugin na ito?", + "third_party_plugin_warning": "Ang plugin na ito ay mula sa third-party na repository. Mangyaring tiyakin na pinagkakatiwalaan mo ang pinagmulan bago mag-install.", + "author": "May-akda", + "this_plugin_can_do_following": "Maaaring gawin ng plugin na ito ang sumusunod", + "install": "I-install", + "install_a_metadata_provider": "Mag-install ng Metadata Provider", + "no_tracks_playing": "Walang Track na kasalukuyang tumutugtog", + "synced_lyrics_not_available": "Hindi available ang mga naka-sync na lyrics para sa kantang ito. Mangyaring gamitin ang", + "plain_lyrics": "Simpleng Lyrics", + "tab_instead": "na tab sa halip.", + "disclaimer": "Disclaimer", + "third_party_plugin_dmca_notice": "Ang Spotube team ay walang hawak na anumang responsibilidad (kabilang ang legal) para sa anumang \"Third-party\" plugins.\nMangyaring gamitin ang mga ito sa iyong sariling peligro. Para sa anumang mga bug/isyu, mangyaring iulat ang mga ito sa repository ng plugin.\n\nKung ang anumang \"Third-party\" plugin ay lumalabag sa ToS/DMCA ng anumang serbisyo/legal na entity, mangyaring hilingin sa \"Third-party\" plugin author o sa hosting platform e.g. GitHub/Codeberg na gumawa ng aksyon. Ang nakalista sa itaas (\"Third-party\" na may label) ay lahat ng pampubliko/komunidad na pinananatiling mga plugin. Hindi namin sila kinukurusado, kaya hindi kami makakagawa ng anumang aksyon sa kanila.\n\n", + "input_does_not_match_format": "Ang input ay hindi tumutugma sa kinakailangang format", + "metadata_provider_plugins": "Mga Plugin ng Metadata Provider", + "paste_plugin_download_url": "I-paste ang download url o GitHub/Codeberg repo url o direktang link sa .smplug file", + "download_and_install_plugin_from_url": "I-download at i-install ang plugin mula sa url", + "failed_to_add_plugin_error": "Nabigo ang pagdagdag ng plugin: {error}", + "upload_plugin_from_file": "I-upload ang plugin mula sa file", + "installed": "Naka-install", + "available_plugins": "Mga available na plugin", + "configure_your_own_metadata_plugin": "I-configure ang iyong sariling playlist/album/artist/feed metadata provider", + "audio_scrobblers": "Mga Audio Scrobbler", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 7c3bc42f..ff5785dd 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -430,5 +430,52 @@ "edit_port": "Portu düzenle", "port_helper_msg": "Varsayılan -1'dir, bu da rastgele bir sayıyı gösterir. Bir güvenlik duvarınız varsa, bunu ayarlamanız önerilir.", "connect_request": "{client} bağlantısına izin verilsin mi?", - "connection_request_denied": "Bağlantı reddedildi. Kullanıcı erişimi reddetti." + "connection_request_denied": "Bağlantı reddedildi. Kullanıcı erişimi reddetti.", + "hipotetical_calculation": "*Bu, çevrimiçi müzik akışı platformlarının ortalama akış başına $0,003 ile $0,005 arasındaki ödemesine göre hesaplanmıştır. Bu, kullanıcının farklı müzik akışı platformlarında şarkılarını dinleselerdi sanatçılara ne kadar ödeme yapacaklarına dair fikir vermek için yapılan varsayımsal bir hesaplamadır.", + "an_error_occurred": "Bir hata oluştu", + "copy_to_clipboard": "Panoya kopyala", + "view_logs": "Günlükleri görüntüle", + "retry": "Tekrar dene", + "no_default_metadata_provider_selected": "Varsayılan bir meta veri sağlayıcısı ayarlanmadı", + "manage_metadata_providers": "Meta veri sağlayıcılarını yönet", + "open_link_in_browser": "Bağlantıyı Tarayıcıda Aç?", + "do_you_want_to_open_the_following_link": "Aşağıdaki bağlantıyı açmak istiyor musunuz", + "unsafe_url_warning": "Güvenilmeyen kaynaklardan bağlantı açmak güvensiz olabilir. Dikkatli olun!\nBağlantıyı panonuza da kopyalayabilirsiniz.", + "copy_link": "Bağlantıyı Kopyala", + "building_your_timeline": "Dinlemelerinize göre zaman çizelgeniz oluşturuluyor...", + "official": "Resmi", + "author_name": "Yazar: {author}", + "third_party": "Üçüncü taraf", + "plugin_requires_authentication": "Eklenti kimlik doğrulama gerektirir", + "update_available": "Güncelleme mevcut", + "supports_scrobbling": "Scrobbling'i destekler", + "plugin_scrobbling_info": "Bu eklenti, dinleme geçmişinizi oluşturmak için müziğinizi scrobble eder.", + "default_plugin": "Varsayılan", + "set_default": "Varsayılan olarak ayarla", + "support": "Destek", + "support_plugin_development": "Eklenti geliştirmeyi destekle", + "can_access_name_api": "- **{name}** API'ye erişebilir", + "do_you_want_to_install_this_plugin": "Bu eklentiyi yüklemek istiyor musunuz?", + "third_party_plugin_warning": "Bu eklenti üçüncü taraf bir depodan gelmektedir. Lütfen yüklemeden önce kaynağa güvendiğinizden emin olun.", + "author": "Yazar", + "this_plugin_can_do_following": "Bu eklenti aşağıdakileri yapabilir", + "install": "Yükle", + "install_a_metadata_provider": "Bir Meta Veri Sağlayıcısı Yükle", + "no_tracks_playing": "Şu anda çalınan bir Parça yok", + "synced_lyrics_not_available": "Bu şarkı için senkronize şarkı sözleri mevcut değil. Lütfen", + "plain_lyrics": "Düz Şarkı Sözleri", + "tab_instead": "sekmesini kullanın.", + "disclaimer": "Sorumluluk Reddi", + "third_party_plugin_dmca_notice": "Spotube ekibi, herhangi bir \"Üçüncü taraf\" eklentisi için herhangi bir sorumluluk (yasal olanlar dahil) kabul etmez.\nLütfen bunları kendi riskinizde kullanın. Herhangi bir hata/sorun için lütfen bunları eklenti deposuna bildirin.\n\nHerhangi bir \"Üçüncü taraf\" eklentisi bir hizmetin/yasal varlığın ToS/DMCA'sını ihlal ediyorsa, lütfen \"Üçüncü taraf\" eklenti yazarından veya barındırma platformundan, örneğin GitHub/Codeberg'den harekete geçmesini isteyin. Yukarıda listelenen (\"Üçüncü taraf\" olarak etiketlenen) eklentilerin tümü genel/topluluk tarafından sürdürülen eklentilerdir. Biz bunları küratörlüğünü yapmıyoruz, bu yüzden onlar üzerinde herhangi bir işlem yapamayız.\n\n", + "input_does_not_match_format": "Girdi, gerekli biçimle eşleşmiyor", + "metadata_provider_plugins": "Meta Veri Sağlayıcısı Eklentileri", + "paste_plugin_download_url": "İndirme url'sini veya GitHub/Codeberg repo url'sini veya .smplug dosyasına doğrudan bağlantıyı yapıştırın", + "download_and_install_plugin_from_url": "url'den eklentiyi indir ve yükle", + "failed_to_add_plugin_error": "Eklenti eklenemedi: {error}", + "upload_plugin_from_file": "Dosyadan eklenti yükle", + "installed": "Yüklü", + "available_plugins": "Mevcut eklentiler", + "configure_your_own_metadata_plugin": "Kendi çalma listenizi/albümünüzü/sanatçınızı/akış meta veri sağlayıcınızı yapılandırın", + "audio_scrobblers": "Ses Scrobbler'lar", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 9cefc4a8..8eb5a8e9 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -430,5 +430,52 @@ "edit_port": "Редагувати порт", "port_helper_msg": "За замовчуванням -1, що означає випадкове число. Якщо у вас налаштований брандмауер, рекомендується це налаштувати.", "connect_request": "Дозволити {client} підключення?", - "connection_request_denied": "Підключення відхилено. Користувач відмовив у доступі." + "connection_request_denied": "Підключення відхилено. Користувач відмовив у доступі.", + "hipotetical_calculation": "*Це розраховано на основі середньої виплати за стрім онлайн-платформ для потокового відтворення музики, що становить від $0,003 до $0,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": "Підтримує скроблінг", + "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\nЯкщо якийсь плагін \"третьої сторони\" порушує ToS/DMCA будь-якої служби/юридичної особи, будь ласка, попросіть автора плагіна \"третьої сторони\" або хостингову платформу, наприклад, GitHub/Codeberg, вжити заходів. Усі перераховані вище (позначені як \"треті сторони\") є плагінами, які підтримуються публічно/спільнотою. Ми не куруємо їх, тому не можемо вжити жодних заходів щодо них.\n\n", + "input_does_not_match_format": "Введені дані не відповідають необхідному формату", + "metadata_provider_plugins": "Плагіни провайдера метаданих", + "paste_plugin_download_url": "Вставте URL-адресу для завантаження або URL-адресу репозиторію GitHub/Codeberg або пряме посилання на файл .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": "Скроблінг" } \ No newline at end of file diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index 47e5cf27..453e12e9 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -430,5 +430,52 @@ "edit_port": "Chỉnh sửa cổng", "port_helper_msg": "Mặc định là -1, có nghĩa là số ngẫu nhiên. Nếu bạn đã cấu hình tường lửa, nên đặt điều này.", "connect_request": "Cho phép {client} kết nối?", - "connection_request_denied": "Kết nối bị từ chối. Người dùng đã từ chối quyền truy cập." + "connection_request_denied": "Kết nối bị từ chối. Người dùng đã từ chối quyền truy cập.", + "hipotetical_calculation": "*Điều này được tính toán dựa trên khoản thanh toán trung bình mỗi luồng của nền tảng phát nhạc trực tuyến là $0,003 đến $0,005. Đây là một phép tính giả định để cung cấp cho người dùng cái nhìn sâu sắc về số tiền họ đã trả cho các nghệ sĩ nếu họ nghe bài hát của họ trên các nền tảng phát nhạc trực tuyến khác nhau.", + "an_error_occurred": "Đã xảy ra lỗi", + "copy_to_clipboard": "Sao chép vào khay nhớ tạm", + "view_logs": "Xem nhật ký", + "retry": "Thử lại", + "no_default_metadata_provider_selected": "Bạn chưa đặt nhà cung cấp siêu dữ liệu mặc định nào", + "manage_metadata_providers": "Quản lý nhà cung cấp siêu dữ liệu", + "open_link_in_browser": "Mở liên kết trong Trình duyệt?", + "do_you_want_to_open_the_following_link": "Bạn có muốn mở liên kết sau không", + "unsafe_url_warning": "Việc mở các liên kết từ các nguồn không đáng tin cậy có thể không an toàn. Hãy thận trọng!\nBạn cũng có thể sao chép liên kết vào khay nhớ tạm của mình.", + "copy_link": "Sao chép liên kết", + "building_your_timeline": "Đang xây dựng dòng thời gian của bạn dựa trên những gì bạn đã nghe...", + "official": "Chính thức", + "author_name": "Tác giả: {author}", + "third_party": "Bên thứ ba", + "plugin_requires_authentication": "Plugin yêu cầu xác thực", + "update_available": "Có bản cập nhật", + "supports_scrobbling": "Hỗ trợ scrobbling", + "plugin_scrobbling_info": "Plugin này scrobble nhạc của bạn để tạo lịch sử nghe của bạn.", + "default_plugin": "Mặc định", + "set_default": "Đặt làm mặc định", + "support": "Hỗ trợ", + "support_plugin_development": "Hỗ trợ phát triển plugin", + "can_access_name_api": "- Có thể truy cập API **{name}**", + "do_you_want_to_install_this_plugin": "Bạn có muốn cài đặt plugin này không?", + "third_party_plugin_warning": "Plugin này đến từ một kho lưu trữ của bên thứ ba. Vui lòng đảm bảo rằng bạn tin tưởng nguồn trước khi cài đặt.", + "author": "Tác giả", + "this_plugin_can_do_following": "Plugin này có thể làm những việc sau", + "install": "Cài đặt", + "install_a_metadata_provider": "Cài đặt một Nhà cung cấp siêu dữ liệu", + "no_tracks_playing": "Hiện không có bản nhạc nào đang phát", + "synced_lyrics_not_available": "Lời bài hát được đồng bộ hóa không có sẵn cho bài hát này. Vui lòng sử dụng", + "plain_lyrics": "Lời bài hát thuần túy", + "tab_instead": "thay thế.", + "disclaimer": "Miễn trừ trách nhiệm", + "third_party_plugin_dmca_notice": "Nhóm Spotube không chịu bất kỳ trách nhiệm nào (bao gồm cả pháp lý) đối với bất kỳ plugin \"Bên thứ ba\" nào.\nVui lòng sử dụng chúng với rủi ro của riêng bạn. Đối với bất kỳ lỗi/vấn đề nào, vui lòng báo cáo chúng cho kho lưu trữ plugin.\n\nNếu bất kỳ plugin \"Bên thứ ba\" nào vi phạm ToS/DMCA của bất kỳ dịch vụ/thực thể pháp lý nào, vui lòng yêu cầu tác giả plugin \"Bên thứ ba\" hoặc nền tảng lưu trữ, ví dụ: GitHub/Codeberg, thực hiện hành động. Tất cả các plugin được liệt kê ở trên (được gắn nhãn \"Bên thứ ba\") đều là các plugin công cộng/do cộng đồng duy trì. Chúng tôi không quản lý chúng, vì vậy chúng tôi không thể thực hiện bất kỳ hành động nào đối với chúng.\n\n", + "input_does_not_match_format": "Đầu vào không khớp với định dạng yêu cầu", + "metadata_provider_plugins": "Plugin Nhà cung cấp siêu dữ liệu", + "paste_plugin_download_url": "Dán url tải xuống hoặc url kho lưu trữ GitHub/Codeberg hoặc liên kết trực tiếp đến tệp .smplug", + "download_and_install_plugin_from_url": "Tải xuống và cài đặt plugin từ url", + "failed_to_add_plugin_error": "Không thể thêm plugin: {error}", + "upload_plugin_from_file": "Tải lên plugin từ tệp", + "installed": "Đã cài đặt", + "available_plugins": "Các plugin có sẵn", + "configure_your_own_metadata_plugin": "Cấu hình nhà cung cấp siêu dữ liệu danh sách phát/album/nghệ sĩ/nguồn cấp dữ liệu của riêng bạn", + "audio_scrobblers": "Bộ scrobbler âm thanh", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f68b30f5..cccb3214 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -179,8 +179,8 @@ "success_message": "你已经成功使用 Spotify 登录。干得漂亮!", "step_4": "步骤 4", "something_went_wrong": "某些地方出现了问题", - "piped_instance": "管道服务器实例", - "piped_description": "管道服务器实例用于匹配歌曲", + "piped_instance": "Piped 服务器实例", + "piped_description": "Piped 服务器实例用于匹配歌曲", "piped_warning": "它们中的一部分可能并不能正常工作。使用时请自行承担风险", "generate_playlist": "生成歌单", "track_exists": "歌曲 {track} 已存在", @@ -430,5 +430,52 @@ "edit_port": "编辑端口", "port_helper_msg": "默认值为-1,表示随机数。如果您已配置防火墙,建议设置此项。", "connect_request": "允许 {client} 连接吗?", - "connection_request_denied": "连接被拒绝。用户拒绝访问。" + "connection_request_denied": "连接被拒绝。用户拒绝访问。", + "hipotetical_calculation": "*这是根据在线音乐流媒体平台每流平均支付0.003美元至0.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": "此插件会 scrobble 您的音乐以生成您的收听历史记录。", + "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\n如果任何“第三方”插件违反了任何服务/法律实体的服务条款/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": "音频 Scrobblers", + "scrobbling": "Scrobbling" } \ No newline at end of file diff --git a/lib/l10n/app_zh_TW.arb b/lib/l10n/app_zh_TW.arb new file mode 100644 index 00000000..fa4c3e67 --- /dev/null +++ b/lib/l10n/app_zh_TW.arb @@ -0,0 +1,481 @@ +{ + "guest": "訪客", + "browse": "瀏覽", + "search": "搜尋", + "library": "音樂庫", + "lyrics": "歌詞", + "settings": "設定", + "genre_categories_filter": "過濾分類...", + "genre": "探索歌單", + "personalized": "為你打造", + "featured": "推薦", + "new_releases": "新歌熱播", + "songs": "歌曲", + "playing_track": "播放 {track}", + "queue_clear_alert": "這將清空目前的播放清單。{track_length} 首歌曲將被移除\n你確定要繼續嗎?", + "load_more": "載入更多", + "playlists": "歌單", + "artists": "藝人", + "albums": "專輯", + "tracks": "歌曲", + "downloads": "下載", + "filter_playlists": "過濾歌單...", + "liked_tracks": "已按讚的歌曲", + "liked_tracks_description": "你按過讚的所有歌曲", + "create_playlist": "建立歌單", + "create_a_playlist": "建立一個歌單", + "create": "建立", + "cancel": "取消", + "playlist_name": "歌單名稱", + "name_of_playlist": "歌單的名稱", + "description": "說明", + "public": "公開", + "collaborative": "共享協作", + "search_local_tracks": "搜尋本地歌曲...", + "play": "播放", + "delete": "刪除", + "none": "無", + "sort_a_z": "依字母順序", + "sort_z_a": "依字母倒序", + "sort_artist": "按藝人", + "sort_album": "按專輯", + "sort_tracks": "排序方式", + "currently_downloading": "正在下載 ({tracks_length})", + "cancel_all": "取消全部", + "filter_artist": "過濾藝人...", + "followers": "{followers} 名追蹤者", + "add_artist_to_blacklist": "封鎖該藝人", + "top_tracks": "熱門歌曲", + "fans_also_like": "粉絲也喜歡", + "loading": "載入中...", + "artist": "藝人", + "blacklisted": "已封鎖", + "following": "關注中", + "follow": "關注", + "artist_url_copied": "此名藝人的分享連結已複製至剪貼簿", + "added_to_queue": "已新增 {tracks} 首歌曲到播放清單", + "filter_albums": "過濾專輯...", + "synced": "同步", + "plain": "未同步", + "shuffle": "隨機播放", + "search_tracks": "搜尋歌曲...", + "released": "發表時間", + "error": "發生錯誤: {error}", + "title": "標題", + "time": "時長", + "more_actions": "更多動作", + "download_count": "下載 ({count}) 首歌曲", + "add_count_to_playlist": "將 ({count}) 首歌曲新增到歌單中", + "add_count_to_queue": "新增 ({count}) 首歌曲到播放清單", + "play_count_next": "接下來將播放 ({count}) 首歌曲", + "album": "專輯", + "copied_to_clipboard": "已將 {data} 複製至剪貼簿", + "add_to_following_playlists": "新增 {track} 到以下播放清單", + "add": "新增", + "added_track_to_queue": "新增 {track} 到播放清單", + "add_to_queue": "新增至播放清單", + "track_will_play_next": "{track} 將在下一首播放", + "play_next": "下一首播放", + "removed_track_from_queue": "將 {track} 從播放清單移除", + "remove_from_queue": "從播放清單移除", + "remove_from_favorites": "取消按讚", + "save_as_favorite": "按讚", + "add_to_playlist": "新增到歌單", + "remove_from_playlist": "從歌單移除", + "add_to_blacklist": "新增到已封鎖清單", + "remove_from_blacklist": "從已封鎖清單移除", + "share": "分享", + "mini_player": "小窗模式", + "slide_to_seek": "滑動以前進或後退", + "shuffle_playlist": "隨機播放歌單", + "unshuffle_playlist": "取消隨機播放歌單", + "previous_track": "上一首歌曲", + "next_track": "下一首歌", + "pause_playback": "暫停播放", + "resume_playback": "恢復播放", + "loop_track": "單曲循環", + "repeat_playlist": "歌單循環", + "queue": "播放清單", + "alternative_track_sources": "其它音源", + "download_track": "下載歌曲", + "tracks_in_queue": "{tracks} 首歌曲在播放清單中", + "clear_all": "清除全部", + "show_hide_ui_on_hover": "游標暫留時顯示 / 隱藏控制列", + "always_on_top": "置頂", + "exit_mini_player": "退出小窗模式", + "download_location": "下載路徑", + "account": "帳戶", + "login_with_spotify": "使用 Spotify 登入", + "connect_with_spotify": "與 Spotify 帳號連結", + "logout": "退出", + "logout_of_this_account": "退出該帳戶", + "language_region": "語言與地區", + "language": "語言", + "system_default": "系統預設", + "market_place_region": "市集地區", + "recommendation_country": "請選擇國家與地區以取得對應的音樂推薦", + "appearance": "外觀", + "layout_mode": "佈局類型", + "override_layout_settings": "將覆寫響應式佈局設定", + "adaptive": "響應式", + "compact": "緊湊", + "extended": "寬闊", + "theme": "主題", + "dark": "深色", + "light": "淺色", + "system": "依循系統", + "accent_color": "主色調", + "sync_album_color": "符合封面顏色", + "sync_album_color_description": "選取專輯封面主題色為主色調", + "playback": "播放", + "audio_quality": "音質", + "high": "高", + "low": "低", + "pre_download_play": "下載後播放", + "pre_download_play_description": "先下載歌曲後再播放而非串流播放(建議頻寬較高使用者使用)", + "skip_non_music": "跳過非音樂片段(跳過贊助商廣告)", + "blacklist_description": "已封鎖的歌曲與藝人", + "wait_for_download_to_finish": "請等待目前下載工作完成", + "desktop": "桌面版設定", + "close_behavior": "點選關閉按鈕行為", + "close": "關閉", + "minimize_to_tray": "最小化到工作列", + "show_tray_icon": "顯示工作列圖示", + "about": "關於", + "u_love_spotube": "我們明白你喜歡 Spotube", + "check_for_updates": "檢查更新", + "about_spotube": "關於 Spotube", + "blacklist": "黑名單", + "please_sponsor": "請考慮贊助或捐款", + "spotube_description": "Spotube,一款輕量、跨平台且完全免費的 Spotify 用戶端。", + "version": "版本", + "build_number": "建置編號", + "founder": "發起人", + "repository": "專案儲存庫", + "bug_issues": "缺陷與問題報告", + "made_with": "於孟加拉🇧🇩用 ❤️ 發電", + "kingkor_roy_tirtho": "Kingkor Roy Tirtho", + "copyright": "© 2021-{current_year} Kingkor Roy Tirtho", + "license": "授權", + "add_spotify_credentials": "新增你的 Spotify 登入資訊以開始使用", + "credentials_will_not_be_shared_disclaimer": "您大可放心,軟體不會收集或分享任何個人資料給第三方", + "know_how_to_login": "不知道該怎麼辦?", + "follow_step_by_step_guide": "請依照以下說明進行", + "spotify_cookie": "Spotify {name} Cookie", + "cookie_name_cookie": "{name} Cookie", + "fill_in_all_fields": "請填入所有欄位", + "submit": "提交", + "exit": "退出", + "previous": "上一步", + "next": "下一步", + "done": "完成", + "step_1": "步驟 1", + "first_go_to": "首先,前往", + "login_if_not_logged_in": "如果尚未登入,請登入或註冊帳戶", + "step_2": "步驟 2", + "step_2_steps": "1. 一旦你已經完成登入, 按 F12 鍵或滑鼠右鍵點選網頁空白區域 > 選擇「檢查」以開啟瀏覽器開發者工具(DevTools)\n2. 選擇 \"應用程式(Application)\" 分頁(Chrome, Edge, Brave 等基於 Chromium 記憶體或基於 Choxage, nox Firefox 的瀏覽器))\n3. 選擇 \"Cookies\" 欄位然後選擇 \"https://accounts.spotify.com\" 子選單", + "step_3": "步驟 3", + "success_emoji": "成功🥳", + "success_message": "你已經成功使用 Spotify 登入。幹得漂亮!", + "step_4": "步驟 4", + "something_went_wrong": "某些地方出現了問題", + "piped_instance": "Piped 伺服器實例", + "piped_description": "Piped 伺服器實例用於匹配歌曲", + "piped_warning": "它們之中的一部分可能無法正常運作。使用時請自行承擔風險", + "generate_playlist": "產生歌單", + "track_exists": "曲目 {track} 已存在", + "replace_downloaded_tracks": "替換已下載的歌曲", + "skip_download_tracks": "下載時跳過已下載的歌曲", + "do_you_want_to_replace": "你確定要取代已下載的歌曲嗎??", + "replace": "取代", + "skip": "跳過", + "select_up_to_count_type": "選擇最多 {count} 種的類型 {type}", + "select_genres": "選擇曲風", + "add_genres": "新增曲風", + "country": "國家和地區", + "number_of_tracks_generate": "產生歌曲的數目", + "acousticness": "原聲程度", + "danceability": "律動感", + "energy": "衝擊感", + "instrumentalness": "歌唱部分佔比", + "liveness": "現場感", + "loudness": "響度", + "speechiness": "朗誦比例", + "valence": "心理感受", + "popularity": "流行度", + "key": "曲調", + "duration": "歌曲長度 (s)", + "tempo": "每分鐘拍數 (BPM)", + "mode": "旋律重複度", + "time_signature": "音符時值", + "short": "短", + "medium": "中", + "long": "長", + "min": "最低", + "max": "最高", + "target": "目標", + "moderate": "中", + "deselect_all": "取消全選", + "select_all": "全選", + "are_you_sure": "你確定嗎?", + "generating_playlist": "正在產生你的自訂歌單...", + "selected_count_tracks": "已選取 {count} 首歌曲", + "download_warning": "如果你大量下載這些歌曲,你顯然在侵犯音樂的版權並對音樂創作社區造成了傷害。我希望你能意識到這一點。永遠要尊重並支持藝術家們的辛勤工作", + "download_ip_ban_warning": "小心,如果出現超出正常的下載請求,那你的 IP 可能會被 YouTube 封鎖,這意味著你的裝置將在長達 2-3 個月的時間內無法使用該 IP 訪問 YouTube(即使你沒登入)。Spotube 不會因而承擔任何責任", + "by_clicking_accept_terms": "點擊 '同意' 代表你同意以下的條款", + "download_agreement_1": "我明白侵害音樂版權是一件不好的事", + "download_agreement_2": "我將盡可能支持藝術家的工作。我現在之所以做不到是因為缺乏資金來購買正版", + "download_agreement_3": "我完全了解我的 IP 存在被 YouTube 封鎖的風險。並且我明白 Spotube 的擁有者與貢獻者們無須對我目前的行為所導致的任何後果負責", + "decline": "拒絕", + "accept": "同意", + "details": "詳細資訊", + "youtube": "YouTube", + "channel": "頻道", + "likes": "讚", + "dislikes": "倒讚", + "views": "瀏覽次數", + "streamUrl": "播放串流 URL", + "stop": "停止", + "sort_newest": "依新增日期順序", + "sort_oldest": "依新增日期倒序", + "sleep_timer": "睡眠計時器", + "mins": "{minutes} 分", + "hours": "{hours} 時", + "hour": "{hours} 時", + "custom_hours": "自訂時長", + "logs": "記錄檔(Log)", + "developers": "開發者", + "not_logged_in": "你尚未登入", + "search_mode": "搜尋模式", + "audio_source": "音訊來源", + "ok": "確定", + "failed_to_encrypt": "加密失敗", + "encryption_failed_warning": "Spotube使用加密來安全地儲存您的資料。但是失敗了。因此,它將回退到不安全的儲存空間\n如果您使用Linux,請確保已安裝gnome-keyring、kde-wallet和keepassxc等加密服務", + "querying_info": "正在查詢資訊...", + "piped_api_down": "Piped API 無法使用", + "piped_down_error_instructions": "當前Piped實例 {pipedInstance} 不可用\n\n請更改實例或將'API類型'更改為官方YouTube API\n\n更改後請確保重新啟動應用程式", + "you_are_offline": "您目前處於離線狀態", + "connection_restored": "您的網路連線已恢復", + "use_system_title_bar": "使用作業系統的預設視窗標題列", + "update_playlist": "更新播放清單", + "update": "更新", + "crunching_results": "處理結果中...", + "search_to_get_results": "搜尋以取得結果", + "use_amoled_mode": "使用 AMOLED 模式", + "pitch_dark_theme": "漆黑主題", + "normalize_audio": "標準化音訊", + "change_cover": "更改封面", + "add_cover": "新增封面", + "restore_defaults": "恢復預設值", + "download_music_codec": "下載音樂編解碼器", + "streaming_music_codec": "串流音樂編解碼器", + "login_with_lastfm": "使用 Last.fm 登入", + "connect": "連線", + "disconnect_lastfm": "切斷 Last.fm 連線", + "disconnect": "斷開連線", + "username": "帳號", + "password": "密碼", + "login": "登入", + "login_with_your_lastfm": "使用您的 Last.fm 帳號登入", + "scrobble_to_lastfm": "在 Last.fm 上記錄你的播放", + "go_to_album": "前往專輯", + "discord_rich_presence": "Discord Rick Presence(Discord 狀態)", + "browse_all": "瀏覽全部", + "genres": "音樂類型", + "explore_genres": "探索音樂類型", + "step_3_steps": "複製\"sp_dc\" Cookie的值", + "step_4_steps": "貼上複製的\"sp_dc\"值", + "friends": "好友", + "no_lyrics_available": "抱歉,無法找到這首歌的歌詞", + "sort_duration": "依長度排序", + "start_a_radio": "開始收聽電台", + "how_to_start_radio": "您想如何開始收聽電台?", + "replace_queue_question": "您想要取代目前清單還是追加到清單?", + "endless_playback": "無限播放", + "delete_playlist": "刪除播放清單", + "delete_playlist_confirmation": "您確定要刪除此播放清單嗎?", + "local_tracks": "本地音訊", + "song_link": "歌曲連結", + "skip_this_nonsense": "跳過這個無聊內容", + "freedom_of_music": "“音樂的自由”", + "freedom_of_music_palm": "「音樂的自由掌握在您手中」", + "get_started": "我們開始吧", + "youtube_source_description": "建議且效果最佳。", + "piped_source_description": "感覺自由?與 YouTube 一樣,但更自由。", + "jiosaavn_source_description": "最適合南亞地區。", + "highest_quality": "最高音質:{quality}", + "select_audio_source": "選擇音訊來源", + "endless_playback_description": "自動將新歌曲加入清單的結尾", + "choose_your_region": "選擇您的所在地區", + "choose_your_region_description": "這能幫助 Spotube 為您的所在位置顯示正確的內容。", + "choose_your_language": "選擇您的語言", + "help_project_grow": "幫助這個專案成長", + "help_project_grow_description": "Spotube是一個開源專案。您可以透過為專案做出貢獻、回報錯誤或建議新功能來幫助專案成長。", + "contribute_on_github": "在GitHub上做出貢獻", + "donate_on_open_collective": "在Open Collective上捐款", + "browse_anonymously": "匿名瀏覽", + "enable_connect": "啟用連線", + "enable_connect_description": "從其他裝置控制Spotube", + "devices": "裝置", + "select": "選擇", + "connect_client_alert": "您正在被 {client} 控制", + "this_device": "此裝置", + "remote": "遠端", + "local_library": "本地媒體庫", + "add_library_location": "新增至媒體庫", + "remove_library_location": "從媒體庫移除", + "local_tab": "本地", + "stats": "統計", + "and_n_more": "還有 {count} 個", + "recently_played": "最近播放", + "browse_more": "瀏覽更多", + "no_title": "無標題", + "not_playing": "未播放", + "epic_failure": "史詩級的失敗!", + "added_num_tracks_to_queue": "已將 {tracks_length} 首曲目新增至清單", + "spotube_has_an_update": "Spotube 有更新版本", + "download_now": "立即下載", + "nightly_version": "Spotube Nightly {nightlyBuildNum} 已發佈", + "release_version": "Spotube v{version} 已發布", + "read_the_latest": "閱讀最新", + "release_notes": "版本說明", + "pick_color_scheme": "選擇配色方案", + "save": "儲存", + "choose_the_device": "選擇裝置:", + "multiple_device_connected": "已連接多個裝置。\n選擇您希望執行此操作的裝置", + "nothing_found": "未找到任何內容", + "the_box_is_empty": "箱子為空", + "top_artists": "熱門藝人", + "top_albums": "熱門專輯", + "this_week": "本週", + "this_month": "本月", + "last_6_months": "過去6個月", + "this_year": "今年", + "last_2_years": "過去2年", + "all_time": "所有時間", + "powered_by_provider": "由 {providerName} 提供支援", + "email": "電子郵件", + "profile_followers": "追蹤者", + "birthday": "生日", + "subscription": "訂閱", + "not_born": "尚未建立", + "hacker": "駭客", + "profile": "個人資訊", + "no_name": "沒有名字", + "edit": "編輯", + "user_profile": "使用者資料", + "count_plays": "{count} 次播放", + "streaming_fees_hypothetical": "*基於 Spotify 每次播放的支付金額\n從 $0.003 到 $0.005 計算。這是一個假設性的\n計算,旨在讓用戶了解如果他們在 Spotify 上收聽\n這些歌曲,可能會付給作者的金額。", + "count_mins": "{minutes} 分鐘", + "summary_minutes": "分鐘", + "summary_listened_to_music": "聽音樂", + "summary_songs": "歌曲", + "summary_streamed_overall": "整體串流媒體", + "summary_owed_to_artists": "本月欠藝術家的", + "summary_artists": "藝術家的", + "summary_music_reached_you": "音樂接觸到你", + "summary_full_albums": "完整專輯", + "summary_got_your_love": "獲得了你的愛心", + "summary_playlists": "播放清單", + "summary_were_on_repeat": "已經重複播放", + "total_money": "總計 {money}", + "minutes_listened": "聽的分鐘數", + "streamed_songs": "已串流歌曲", + "count_streams": "{count} 次串流", + "owned_by_you": "由您所有", + "copied_shareurl_to_clipboard": "{shareUrl} 已複製到剪貼簿", + "spotify_hipotetical_calculation": "*根據 Spotify 每次串流媒體的支付金額\n$0.003 到 $0.005 進行計算。這是一個假設性的\n計算,用於給用戶了解他們如果在 Spotify 上\n收聽歌曲會支付給藝術家的金額。", + "webview_not_found": "未找到 Webview 框架", + "webview_not_found_description": "您的裝置中未安裝 Webview Runtime。\n如果已安裝,請確保它的位置在系統環境變數(PATH)中\n\n安裝後,重新啟動應用程式", + "unsupported_platform": "不支援的平台", + "invidious_instance": "Invidious 伺服器實例", + "invidious_description": "用於音軌匹配的 Invidious 伺服器實例", + "invidious_warning": "有些可能無法正常運作。請自行承擔風險", + "invidious_source_description": "類似 Piped,但可用性更高。", + "cache_music": "快取音樂", + "open": "開啟", + "cache_folder": "快取資料夾", + "export": "導出", + "clear_cache": "清除快取", + "clear_cache_confirmation": "您要清除快取嗎?", + "export_cache_files": "匯出快取檔案", + "found_n_files": "找到 {count} 個檔案", + "export_cache_confirmation": "您要匯出這些檔案到", + "exported_n_out_of_m_files": "匯出了 {filesExported} / {files} 個檔案", + "playlist": "播放清單", + "no_loop": "無循環", + "generate": "生成", + "undo": "取消", + "download_all": "下載全部", + "add_all_to_playlist": "全部加入到播放清單", + "add_all_to_queue": "全部加入清單", + "play_all_next": "播放全部下一首", + "pause": "暫停", + "view_all": "檢視全部", + "no_tracks_added_yet": "看起來你還沒有加入任何歌曲", + "no_tracks": "看起來這裡沒有任何歌曲", + "no_tracks_listened_yet": "看起來你還沒聽任何歌曲", + "not_following_artists": "你沒有關注任何藝術家", + "no_favorite_albums_yet": "看起來你還沒有將任何專輯加入到收藏夾", + "no_logs_found": "未找到日誌", + "youtube_engine": "YouTube 引擎", + "youtube_engine_not_installed_title": "{engine} 未安裝", + "youtube_engine_not_installed_message": "{engine} 未在您的系統中安裝。", + "youtube_engine_set_path": "確保它可用在 PATH 變數中,或\n設定 {engine} 執行檔的絕對路徑", + "youtube_engine_unix_issue_message": "在類 Unix 作業系統(如 macOS/Linux/Unix)中,請在 .zshrc/.bashrc/.bash_profile 等檔案中設定路徑無效。\n您需要在 shell 設定檔中設定路徑", + "download": "下載", + "file_not_found": "找不到檔案", + "custom": "自訂", + "add_custom_url": "新增自訂 URL", + "edit_port": "編輯端口", + "port_helper_msg": "預設值為 -1,表示隨機數。如果您已配置防火牆,建議設定此項目。", + "connect_request": "允許 {client} 連線嗎?", + "connection_request_denied": "連線被拒絕。請求被使用者拒絕。", + "hipotetical_calculation": "*此為根據線上音樂串流平台平均每次播放 $0.003 至 $0.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": "此外掛程式會 Scrobble 您的音樂以產生您的收聽記錄。", + "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\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": "音訊 Scrobblers", + "scrobbling": "Scrobbling" +} \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 1758532a..bf6f5211 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -149,7 +149,8 @@ abstract class AppLocalizations { Locale('tr'), Locale('uk'), Locale('vi'), - Locale('zh') + Locale('zh'), + Locale('zh', 'TW') ]; /// No description provided for @guest. @@ -2653,6 +2654,282 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Connection denied. User denied access.'** String get connection_request_denied; + + /// No description provided for @an_error_occurred. + /// + /// In en, this message translates to: + /// **'An error occurred'** + String get an_error_occurred; + + /// No description provided for @copy_to_clipboard. + /// + /// In en, this message translates to: + /// **'Copy to clipboard'** + String get copy_to_clipboard; + + /// No description provided for @view_logs. + /// + /// In en, this message translates to: + /// **'View logs'** + String get view_logs; + + /// No description provided for @retry. + /// + /// In en, this message translates to: + /// **'Retry'** + String get retry; + + /// No description provided for @no_default_metadata_provider_selected. + /// + /// In en, this message translates to: + /// **'You\'ve no default metadata provider set'** + String get no_default_metadata_provider_selected; + + /// No description provided for @manage_metadata_providers. + /// + /// In en, this message translates to: + /// **'Manage metadata providers'** + String get manage_metadata_providers; + + /// No description provided for @open_link_in_browser. + /// + /// In en, this message translates to: + /// **'Open Link in Browser?'** + String get open_link_in_browser; + + /// No description provided for @do_you_want_to_open_the_following_link. + /// + /// In en, this message translates to: + /// **'Do you want to open the following link'** + String get do_you_want_to_open_the_following_link; + + /// No description provided for @unsafe_url_warning. + /// + /// In en, this message translates to: + /// **'It can be unsafe to open links from untrusted sources. Be cautious!\nYou can also copy the link to your clipboard.'** + String get unsafe_url_warning; + + /// No description provided for @copy_link. + /// + /// In en, this message translates to: + /// **'Copy Link'** + String get copy_link; + + /// No description provided for @building_your_timeline. + /// + /// In en, this message translates to: + /// **'Building your timeline based on your listenings...'** + String get building_your_timeline; + + /// No description provided for @official. + /// + /// In en, this message translates to: + /// **'Official'** + String get official; + + /// No description provided for @author_name. + /// + /// In en, this message translates to: + /// **'Author: {author}'** + String author_name(Object author); + + /// No description provided for @third_party. + /// + /// In en, this message translates to: + /// **'Third-party'** + String get third_party; + + /// No description provided for @plugin_requires_authentication. + /// + /// In en, this message translates to: + /// **'Plugin requires authentication'** + String get plugin_requires_authentication; + + /// No description provided for @update_available. + /// + /// In en, this message translates to: + /// **'Update available'** + String get update_available; + + /// No description provided for @supports_scrobbling. + /// + /// In en, this message translates to: + /// **'Supports scrobbling'** + String get supports_scrobbling; + + /// No description provided for @plugin_scrobbling_info. + /// + /// In en, this message translates to: + /// **'This plugin scrobbles your music to generate your listening history.'** + String get plugin_scrobbling_info; + + /// No description provided for @default_plugin. + /// + /// In en, this message translates to: + /// **'Default'** + String get default_plugin; + + /// No description provided for @set_default. + /// + /// In en, this message translates to: + /// **'Set default'** + String get set_default; + + /// No description provided for @support. + /// + /// In en, this message translates to: + /// **'Support'** + String get support; + + /// No description provided for @support_plugin_development. + /// + /// In en, this message translates to: + /// **'Support plugin development'** + String get support_plugin_development; + + /// No description provided for @can_access_name_api. + /// + /// In en, this message translates to: + /// **'- Can access **{name}** API'** + String can_access_name_api(Object name); + + /// No description provided for @do_you_want_to_install_this_plugin. + /// + /// In en, this message translates to: + /// **'Do you want to install this plugin?'** + String get do_you_want_to_install_this_plugin; + + /// No description provided for @third_party_plugin_warning. + /// + /// In en, this message translates to: + /// **'This plugin is from a third-party repository. Please ensure you trust the source before installing.'** + String get third_party_plugin_warning; + + /// No description provided for @author. + /// + /// In en, this message translates to: + /// **'Author'** + String get author; + + /// No description provided for @this_plugin_can_do_following. + /// + /// In en, this message translates to: + /// **'This plugin can do following'** + String get this_plugin_can_do_following; + + /// No description provided for @install. + /// + /// In en, this message translates to: + /// **'Install'** + String get install; + + /// No description provided for @install_a_metadata_provider. + /// + /// In en, this message translates to: + /// **'Install a Metadata Provider'** + String get install_a_metadata_provider; + + /// No description provided for @no_tracks_playing. + /// + /// In en, this message translates to: + /// **'No Track being played currently'** + String get no_tracks_playing; + + /// No description provided for @synced_lyrics_not_available. + /// + /// In en, this message translates to: + /// **'Synced lyrics are not available for this song. Please use the'** + String get synced_lyrics_not_available; + + /// No description provided for @plain_lyrics. + /// + /// In en, this message translates to: + /// **'Plain Lyrics'** + String get plain_lyrics; + + /// No description provided for @tab_instead. + /// + /// In en, this message translates to: + /// **'tab instead.'** + String get tab_instead; + + /// No description provided for @disclaimer. + /// + /// In en, this message translates to: + /// **'Disclaimer'** + String get disclaimer; + + /// No description provided for @third_party_plugin_dmca_notice. + /// + /// In en, this message translates to: + /// **'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'** + String get third_party_plugin_dmca_notice; + + /// No description provided for @input_does_not_match_format. + /// + /// In en, this message translates to: + /// **'Input doesn\'t match the required format'** + String get input_does_not_match_format; + + /// No description provided for @metadata_provider_plugins. + /// + /// In en, this message translates to: + /// **'Metadata Provider Plugins'** + String get metadata_provider_plugins; + + /// No description provided for @paste_plugin_download_url. + /// + /// In en, this message translates to: + /// **'Paste download url or GitHub/Codeberg repo url or direct link to .smplug file'** + String get paste_plugin_download_url; + + /// No description provided for @download_and_install_plugin_from_url. + /// + /// In en, this message translates to: + /// **'Download and install plugin from url'** + String get download_and_install_plugin_from_url; + + /// No description provided for @failed_to_add_plugin_error. + /// + /// In en, this message translates to: + /// **'Failed to add plugin: {error}'** + String failed_to_add_plugin_error(Object error); + + /// No description provided for @upload_plugin_from_file. + /// + /// In en, this message translates to: + /// **'Upload plugin from file'** + String get upload_plugin_from_file; + + /// No description provided for @installed. + /// + /// In en, this message translates to: + /// **'Installed'** + String get installed; + + /// No description provided for @available_plugins. + /// + /// In en, this message translates to: + /// **'Available plugins'** + String get available_plugins; + + /// No description provided for @configure_your_own_metadata_plugin. + /// + /// In en, this message translates to: + /// **'Configure your own playlist/album/artist/feed metadata provider'** + String get configure_your_own_metadata_plugin; + + /// No description provided for @audio_scrobblers. + /// + /// In en, this message translates to: + /// **'Audio Scrobblers'** + String get audio_scrobblers; + + /// No description provided for @scrobbling. + /// + /// In en, this message translates to: + /// **'Scrobbling'** + String get scrobbling; } class _AppLocalizationsDelegate @@ -2702,6 +2979,18 @@ class _AppLocalizationsDelegate } AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when language+country codes are specified. + switch (locale.languageCode) { + case 'zh': + { + switch (locale.countryCode) { + case 'TW': + return AppLocalizationsZhTw(); + } + break; + } + } + // Lookup logic when only language code is specified. switch (locale.languageCode) { case 'ar': diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index 362ab69d..b5734db6 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -1202,7 +1202,7 @@ class AppLocalizationsAr extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*تمّ الحساب بمعدّل دفعة تتراوح بين 0.003–0.005 دولار أمريكي لكل تشغيل على منصات الموسيقى عبر الإنترنت. هذا حساب افتراضي لتوضيح للمستخدم مقدار ما كان سيدفعه للفنانين لو استمع إلى أغنيتهم على منصات مختلفة.'; @override String count_mins(Object minutes) { @@ -1380,4 +1380,161 @@ class AppLocalizationsAr extends AppLocalizations { @override String get connection_request_denied => 'تم رفض الاتصال. المستخدم رفض الوصول.'; + + @override + String get an_error_occurred => 'حدث خطأ'; + + @override + String get copy_to_clipboard => 'نسخ إلى الحافظة'; + + @override + String get view_logs => 'عرض السجلات'; + + @override + String get retry => 'إعادة المحاولة'; + + @override + String get no_default_metadata_provider_selected => + 'لم تقُم بتعيين مزود بيانات افتراضي'; + + @override + String get manage_metadata_providers => 'إدارة مزوّدي البيانات'; + + @override + String get open_link_in_browser => 'فتح الرابط في المتصفح؟'; + + @override + String get do_you_want_to_open_the_following_link => + 'هل ترغب في فتح الرابط التالي؟'; + + @override + String get unsafe_url_warning => + 'قد يكون فتح الروابط من مصادر غير موثوقة غير آمن. تحرّ الحذر!\nيمكنك أيضًا نسخ الرابط إلى الحافظة.'; + + @override + String get copy_link => 'نسخ الرابط'; + + @override + String get building_your_timeline => + 'جاري بناء المخطط الزمني استنادًا إلى استماعاتك...'; + + @override + String get official => 'رسمي'; + + @override + String author_name(Object author) { + return 'المؤلّف: $author'; + } + + @override + String get third_party => 'طرف ثالث'; + + @override + String get plugin_requires_authentication => 'تتطلّب الإضافة تسجيل الدخول'; + + @override + String get update_available => 'تحديث متوفر'; + + @override + String get supports_scrobbling => 'يدعم التتبع (scrobbling)'; + + @override + String get plugin_scrobbling_info => + 'تقوم هذه الإضافة بتتبع مقاطعك الموسيقية لإنشاء سجل الاستماع الخاص بك.'; + + @override + String get default_plugin => 'الافتراضي'; + + @override + String get set_default => 'تعيين كافتراضي'; + + @override + String get support => 'الدعم'; + + @override + String get support_plugin_development => 'دعم تطوير الإضافات'; + + @override + String can_access_name_api(Object name) { + return '- يمكن الوصول إلى واجهة برمجة التطبيقات **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'هل ترغب في تثبيت هذه الإضافة؟'; + + @override + String get third_party_plugin_warning => + 'هذه الإضافة من مستودع طرف ثالث. تأكد من موثوقية المصدر قبل التثبيت.'; + + @override + String get author => 'المؤلف'; + + @override + String get this_plugin_can_do_following => 'يمكن لهذه الإضافة القيام بما يلي'; + + @override + String get install => 'تثبيت'; + + @override + String get install_a_metadata_provider => 'تثبيت مزوّد بيانات'; + + @override + String get no_tracks_playing => 'لا توجد مقاطع تعمل حاليًا'; + + @override + String get synced_lyrics_not_available => + 'الكلمات المتزامنة غير متوفرة لهذه الأغنية. يُرجى استخدام'; + + @override + String get plain_lyrics => 'الكلمات العادية'; + + @override + String get tab_instead => 'بدلاً من ذلك، استخدم التبويب.'; + + @override + String get disclaimer => 'إخلاء المسؤولية'; + + @override + String get third_party_plugin_dmca_notice => + 'لا تتحمّل فريق Spotube أي مسؤولية (بما في ذلك القانونية) عن أي من الإضافات “لطرف ثالث”.\nاستخدمها على مسؤوليتك الخاصّة. لأيّة أخطاء/مشكلات، يُرجى الإبلاغ عنها في مستودع الإضافة.\n\nإذا كانت أي إضافة “لطرف ثالث” تنتهك شروط الخدمة أو قانون DMCA الخاص بأي خدمة أو كيان قانوني، فيُرجى طلب اتخاذ إجراء من مؤلف الإضافة أو منصة الاستضافة مثل GitHub/Codeberg. الإضافات المدرجة كـ “لطرف ثالث” هي مفعّلة ومُدارة من المجتمع، وليس لدينا صلاحية إدارتها أو التدخل فيها.\n\n'; + + @override + String get input_does_not_match_format => + 'المدخل لا يتوافق مع التنسيق المطلوب'; + + @override + String get metadata_provider_plugins => 'إضافات مزود البيانات'; + + @override + String get paste_plugin_download_url => + 'الصق رابط التنزيل أو GitHub/Codeberg أو رابط مباشر لملف .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'تنزيل وتثبيت الإضافة من رابط'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'فشل في إضافة الإضافة: $error'; + } + + @override + String get upload_plugin_from_file => 'رفع الإضافة من ملف'; + + @override + String get installed => 'تم التثبيت'; + + @override + String get available_plugins => 'الإضافات المتوفّرة'; + + @override + String get configure_your_own_metadata_plugin => + 'تهيئة مزوّد بيانات للقائمة/الألبوم/الفنان/المصدر خاص بك'; + + @override + String get audio_scrobblers => 'أجهزة تتبع الصوت'; + + @override + String get scrobbling => 'التتبع'; } diff --git a/lib/l10n/generated/app_localizations_bn.dart b/lib/l10n/generated/app_localizations_bn.dart index 1014254a..4503564e 100644 --- a/lib/l10n/generated/app_localizations_bn.dart +++ b/lib/l10n/generated/app_localizations_bn.dart @@ -1201,7 +1201,7 @@ class AppLocalizationsBn extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*এটি নিরূপণ করা হয়েছে গড় অনলাইন মিউজিক স্ট্রিমিং প্ল্যাটফর্মের প্রতি স্ট্রিম 0.003–0.005 USD পেআউটের ভিত্তিতে। এটি একটি কাল্পনিক হিসাব যা ব্যবহারকারীকে ধারণা দিতে পারে তারা অন্যান্য স্ট্রিমিং প্ল্যাটফর্মে একই গান শোনার জন্য শিল্পীদের কত টাকা দিয়েছেন হোক।'; @override String count_mins(Object minutes) { @@ -1380,4 +1380,162 @@ class AppLocalizationsBn extends AppLocalizations { @override String get connection_request_denied => 'সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।'; + + @override + String get an_error_occurred => 'একটি ত্রুটি ঘটেছে'; + + @override + String get copy_to_clipboard => 'ক্লিপবোর্ডে কপি করুন'; + + @override + String get view_logs => 'লগ দেখুন'; + + @override + String get retry => 'পুনরায় চেষ্টা করুন'; + + @override + String get no_default_metadata_provider_selected => + 'আপনি কোনো ডিফল্ট মেটাডেটা প্রদানকারী সেট করেননি'; + + @override + String get manage_metadata_providers => 'মেটাডেটা প্রদানকারীগণ পরিচালনা করুন'; + + @override + String get open_link_in_browser => 'লিংকটি ব্রাউজারে খুলবেন?'; + + @override + String get do_you_want_to_open_the_following_link => + 'নিচের লিংকটি খুলতে চান?'; + + @override + String get unsafe_url_warning => + 'অবিশ্বাসযোগ্য উৎস থেকে লিংক খোলা নিরাপদ নাও হতে পারে। সতর্ক থাকুন!\nআপনি এটি ক্লিপবোর্ডে কপি করতে পারেন।'; + + @override + String get copy_link => 'লিংক কপি করুন'; + + @override + String get building_your_timeline => + 'আপনার শোনার ধারা অনুযায়ী টাইমলাইন তৈরি করা হচ্ছে...'; + + @override + String get official => 'সরকারি'; + + @override + String author_name(Object author) { + return 'লেখক: $author'; + } + + @override + String get third_party => 'তৃতীয় পক্ষ'; + + @override + String get plugin_requires_authentication => 'প্লাগইনটি প্রমাণীকরণ প্রয়োজন'; + + @override + String get update_available => 'হালনাগাদ উপলব্ধ'; + + @override + String get supports_scrobbling => 'স্ক্রোব্বলিং সমর্থিত'; + + @override + String get plugin_scrobbling_info => + 'এই প্লাগইনটি আপনার সঙ্গীত স্ক্রোব্বল করে আপনার শোনা ইতিহাস তৈরি করে।'; + + @override + String get default_plugin => 'ডিফল্ট'; + + @override + String get set_default => 'ডিফল্ট হিসাবে নির্ধারণ করুন'; + + @override + String get support => 'সমর্থন'; + + @override + String get support_plugin_development => 'প্লাগইন উন্নয়নকে সমর্থন করুন'; + + @override + String can_access_name_api(Object name) { + return '- **$name** API-তে অ্যাক্সেস করতে পারে'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'আপনি কি এই প্লাগইন ইনস্টল করতে চান?'; + + @override + String get third_party_plugin_warning => + 'এই প্লাগইন একটি তৃতীয় পক্ষের রেপোজিটরির। ইনস্টল করার আগে উৎস বিশ্বস্ত কিনা নিশ্চিত করুন।'; + + @override + String get author => 'লেখক'; + + @override + String get this_plugin_can_do_following => 'এই প্লাগইন নিচের কাজ করতে পারে'; + + @override + String get install => 'ইনস্টল করুন'; + + @override + String get install_a_metadata_provider => + 'একটি মেটাডেটা প্রদানকারী ইনস্টল করুন'; + + @override + String get no_tracks_playing => 'বর্তমানে কোনো ট্র্যাক শোনা হচ্ছে না'; + + @override + String get synced_lyrics_not_available => + 'এই গানের জন্য সিঙ্ক্রোনাইজড লিরিক্স পাওয়া যায় না। অনুগ্রহ করে ব্যবহার করুন'; + + @override + String get plain_lyrics => 'সহজ লিরিক্স'; + + @override + String get tab_instead => 'তার পরিবর্তে ট্যাব ব্যবহার করুন।'; + + @override + String get disclaimer => 'অস্বীকৃতি'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube দল কোনো “তৃতীয় পক্ষ” প্লাগইনের জন্য কোনো (আইনগত সহ) দায়িত্ব নেয় না। নিজের বিপদে ব্যবহার করুন। কোনো বাগ/সমস্যা হলে প্লাগইন রেপোজিটরিতে জানাতে অনুরোধ করা হচ্ছে।\n\nযদি কোনো “তৃতীয় পক্ষ” প্লাগইন কোনো পরিষেবা/আইনগত সংস্থার ToS/DMCA ভূঙ্গ করে, অনুগ্রহ করে “তৃতীয় পক্ষ” প্লাগইনের লেখক বা হোস্টিং প্ল্যাটফর্মে (যেমন GitHub/Codeberg) পদক্ষেপ নিতে বলুন। “তৃতীয় পক্ষ” লেবেলযুক্ত যুক্তিগুলি সকলই পাবলিক/কমিউনিটি দ্বারা রক্ষণাবেক্ষণ করা হয়; আমরা সেগুলি কিউরেট করি না, তাই আমরা কোনো পদক্ষেপ নিতে পারি না।\n\n'; + + @override + String get input_does_not_match_format => + 'ইনপুট প্রয়োজনীয় ফরম্যাটের সাথে মেলে না'; + + @override + String get metadata_provider_plugins => 'মেটাডেটা প্রদানকারী প্লাগইনসমূহ'; + + @override + String get paste_plugin_download_url => + 'ডাউনলোড URL বা GitHub/Codeberg রিপো URL বা .smplug ফাইলের সরাসরি লিঙ্ক পেস্ট করুন'; + + @override + String get download_and_install_plugin_from_url => + 'URL থেকে প্লাগইন ডাউনলোড এবং ইনস্টল করুন'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'প্লাগইন যোগ করতে ব্যর্থ: $error'; + } + + @override + String get upload_plugin_from_file => 'ফাইল থেকে প্লাগইন আপলোড করুন'; + + @override + String get installed => 'ইনস্টল করা হয়েছে'; + + @override + String get available_plugins => 'উপলব্ধ প্লাগইনগুলো'; + + @override + String get configure_your_own_metadata_plugin => + 'নিজস্ব প্লেলিস্ট/অ্যালবাম/শিল্পী/ফিড মেটাডেটা প্রদানকারী কনফিগার করুন'; + + @override + String get audio_scrobblers => 'অডিও স্ক্রোব্বলার্স'; + + @override + String get scrobbling => 'স্ক্রোব্বলিং'; } diff --git a/lib/l10n/generated/app_localizations_ca.dart b/lib/l10n/generated/app_localizations_ca.dart index 7069651f..70899f04 100644 --- a/lib/l10n/generated/app_localizations_ca.dart +++ b/lib/l10n/generated/app_localizations_ca.dart @@ -1208,7 +1208,7 @@ class AppLocalizationsCa extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Això està calculat en funció d’un pagament mitjà per reproducció de 0,003–0,005 USD en plataformes de reproducció musical en línia. És un càlcul hipotètic per ajudar l’usuari a entendre quant hauria pagat als artistes si hagués escoltat la seva cançó en diferents plataformes.'; @override String count_mins(Object minutes) { @@ -1386,4 +1386,166 @@ class AppLocalizationsCa extends AppLocalizations { @override String get connection_request_denied => 'Connexió denegada. L\'usuari ha denegat l\'accés.'; + + @override + String get an_error_occurred => 'S’ha produït un error'; + + @override + String get copy_to_clipboard => 'Copiar al porta-retalls'; + + @override + String get view_logs => 'Veure registres'; + + @override + String get retry => 'Tornar-ho a provar'; + + @override + String get no_default_metadata_provider_selected => + 'No has configurat cap proveïdor de metadades predeterminat'; + + @override + String get manage_metadata_providers => 'Gestionar proveïdors de metadades'; + + @override + String get open_link_in_browser => 'Obrir l’enllaç en el navegador?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Vols obrir l’enllaç següent?'; + + @override + String get unsafe_url_warning => + 'Pot ser perillós obrir enllaços de fonts no fiables. Sigues precavís!\nTambé pots copiar l’enllaç al porta-retalls.'; + + @override + String get copy_link => 'Copiar enllaç'; + + @override + String get building_your_timeline => + 'Construint la teva cronologia en funció de les teves escoltes...'; + + @override + String get official => 'Oficial'; + + @override + String author_name(Object author) { + return 'Autor: $author'; + } + + @override + String get third_party => 'Tercers'; + + @override + String get plugin_requires_authentication => + 'El complement requereix autenticació'; + + @override + String get update_available => 'Actualització disponible'; + + @override + String get supports_scrobbling => 'Admet scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Aquest complement fa scrobbling de la teva música per generar l’historial d’escoltes.'; + + @override + String get default_plugin => 'Predeterminat'; + + @override + String get set_default => 'Establir com a predeterminat'; + + @override + String get support => 'Suport'; + + @override + String get support_plugin_development => + 'Suportar el desenvolupament del complement'; + + @override + String can_access_name_api(Object name) { + return '- Pot accedir a l’API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Vols instal·lar aquest complement?'; + + @override + String get third_party_plugin_warning => + 'Aquest complement prové d’un repositori de tercers. Assegura’t de confiar en la font abans d’instal·lar-lo.'; + + @override + String get author => 'Autor'; + + @override + String get this_plugin_can_do_following => + 'Aquest complement pot fer el següent'; + + @override + String get install => 'Instal·lar'; + + @override + String get install_a_metadata_provider => + 'Instal·lar un proveïdor de metadades'; + + @override + String get no_tracks_playing => 'No s’està reproduint cap pista actualment'; + + @override + String get synced_lyrics_not_available => + 'Les lletres sincronitzades no estan disponibles per a aquesta cançó. Si us plau, usa'; + + @override + String get plain_lyrics => 'Lletres sense format'; + + @override + String get tab_instead => 'en lloc d’això, utilitza la tecla Tab.'; + + @override + String get disclaimer => 'Avís legal'; + + @override + String get third_party_plugin_dmca_notice => + 'L’equip de Spotube no accepta cap responsabilitat (inclosa legal) pels complements de “tercers”.\nFes-los servir sota la teva responsabilitat. Si detectes errors/problemes, informa’ls al repositori del complement.\n\nSi algun complement de “tercers” incompleix els ToS/DMCA d’un servei o entitat legal, contacta amb l’autor del complement o amb la plataforma d’allotjament (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'; + + @override + String get input_does_not_match_format => + 'L’entrada no coincideix amb el format requerit'; + + @override + String get metadata_provider_plugins => + 'Complements de proveïdor de metadades'; + + @override + String get paste_plugin_download_url => + 'Enllaça l’URL de descàrrega o el repositori de GitHub/Codeberg o l’enllaç directe al fitxer .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Descarrega i instal·la el complement des d’un URL'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Error en afegir el complement: $error'; + } + + @override + String get upload_plugin_from_file => 'Penja el complement des d’un fitxer'; + + @override + String get installed => 'Instal·lat'; + + @override + String get available_plugins => 'Complements disponibles'; + + @override + String get configure_your_own_metadata_plugin => + 'Configura el teu propi proveïdor de metadades per llistes/reproduccions àlbum/artista/flux'; + + @override + String get audio_scrobblers => 'Scrobblers d’àudio'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_cs.dart b/lib/l10n/generated/app_localizations_cs.dart index 0b084314..3cb620e0 100644 --- a/lib/l10n/generated/app_localizations_cs.dart +++ b/lib/l10n/generated/app_localizations_cs.dart @@ -1199,7 +1199,7 @@ class AppLocalizationsCs extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Toto je vypočítáno na základě průměrného výplatu za přehrání 0,003–0,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ě.'; @override String count_mins(Object minutes) { @@ -1379,4 +1379,163 @@ class AppLocalizationsCs extends AppLocalizations { @override String get connection_request_denied => 'Připojení bylo zamítnuto. Uživatel odmítl přístup.'; + + @override + String get an_error_occurred => 'Došlo k chybě'; + + @override + String get copy_to_clipboard => 'Kopírovat do schránky'; + + @override + String get view_logs => 'Zobrazit protokoly'; + + @override + String get retry => 'Zkusit znovu'; + + @override + String get no_default_metadata_provider_selected => + 'Nemáte nastaven výchozí poskytovatel metadat'; + + @override + String get manage_metadata_providers => 'Spravovat poskytovatele metadat'; + + @override + String get open_link_in_browser => 'Otevřít odkaz v prohlížeči?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Chcete otevřít následující odkaz?'; + + @override + String get 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.'; + + @override + String get copy_link => 'Zkopírovat odkaz'; + + @override + String get building_your_timeline => + 'Vytváří se váš časový přehled podle poslechů...'; + + @override + String get official => 'Oficiální'; + + @override + String author_name(Object author) { + return 'Autor: $author'; + } + + @override + String get third_party => 'Třetí strana'; + + @override + String get plugin_requires_authentication => 'Plugin vyžaduje ověření'; + + @override + String get update_available => 'Aktualizace dostupná'; + + @override + String get supports_scrobbling => 'Podpora scrobblování'; + + @override + String get plugin_scrobbling_info => + 'Tento plugin scrobbles vaši hudbu pro vytvoření historie poslechů.'; + + @override + String get default_plugin => 'Výchozí'; + + @override + String get set_default => 'Nastavit jako výchozí'; + + @override + String get support => 'Podpora'; + + @override + String get support_plugin_development => 'Podpořit vývoj pluginu'; + + @override + String can_access_name_api(Object name) { + return '- Může přistupovat k API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Chcete tento plugin nainstalovat?'; + + @override + String get 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.'; + + @override + String get author => 'Autor'; + + @override + String get this_plugin_can_do_following => + 'Tento plugin může provádět následující úkony'; + + @override + String get install => 'Instalovat'; + + @override + String get install_a_metadata_provider => + 'Nainstalovat poskytovatele metadat'; + + @override + String get no_tracks_playing => 'Momentálně není přehrávána žádná skladba'; + + @override + String get synced_lyrics_not_available => + 'Synchronizované texty nejsou k dispozici k této písni. Prosím použijte'; + + @override + String get plain_lyrics => 'Prostý text'; + + @override + String get tab_instead => 'místo toho použijte tabulátor.'; + + @override + String get disclaimer => 'Prohlášení'; + + @override + String get 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'; + + @override + String get input_does_not_match_format => + 'Vstup neodpovídá požadovanému formátu'; + + @override + String get metadata_provider_plugins => 'Pluginy poskytovatelů metadat'; + + @override + String get paste_plugin_download_url => + 'Vložte URL ke stažení nebo GitHub/Codeberg repozitář či přímý odkaz na soubor .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Stáhnout a nainstalovat plugin z URL'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Nepodařilo se přidat plugin: $error'; + } + + @override + String get upload_plugin_from_file => 'Nahrát plugin ze souboru'; + + @override + String get installed => 'Nainstalováno'; + + @override + String get available_plugins => 'Dostupné pluginy'; + + @override + String get configure_your_own_metadata_plugin => + 'Nakonfigurujte si vlastního poskytovatele metadat pro playlist/album/umělec/fid'; + + @override + String get audio_scrobblers => 'Audio scrobblers'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 1187eadd..75c858f5 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -1210,7 +1210,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*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.'; @override String count_mins(Object minutes) { @@ -1391,4 +1391,163 @@ class AppLocalizationsDe extends AppLocalizations { @override String get connection_request_denied => 'Verbindung abgelehnt. Benutzer hat den Zugriff verweigert.'; + + @override + String get an_error_occurred => 'Ein Fehler ist aufgetreten'; + + @override + String get copy_to_clipboard => 'In die Zwischenablage kopieren'; + + @override + String get view_logs => 'Protokolle anzeigen'; + + @override + String get retry => 'Erneut versuchen'; + + @override + String get no_default_metadata_provider_selected => + 'Sie haben keinen Standard-Metadatenanbieter festgelegt'; + + @override + String get manage_metadata_providers => 'Metadatenanbieter verwalten'; + + @override + String get open_link_in_browser => 'Link im Browser öffnen?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Möchten Sie folgenden Link öffnen?'; + + @override + String get 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.'; + + @override + String get copy_link => 'Link kopieren'; + + @override + String get building_your_timeline => + 'Ihr Zeitverlauf wird basierend auf Ihren Hördaten erstellt…'; + + @override + String get official => 'Offiziell'; + + @override + String author_name(Object author) { + return 'Autor: $author'; + } + + @override + String get third_party => 'Drittanbieter'; + + @override + String get plugin_requires_authentication => + 'Plugin erfordert Authentifizierung'; + + @override + String get update_available => 'Update verfügbar'; + + @override + String get supports_scrobbling => 'Unterstützt Scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Dieses Plugin scrobbelt Ihre Musik, um Ihre Hörhistorie zu erstellen.'; + + @override + String get default_plugin => 'Standard'; + + @override + String get set_default => 'Als Standard festlegen'; + + @override + String get support => 'Unterstützung'; + + @override + String get support_plugin_development => 'Plugin-Entwicklung unterstützen'; + + @override + String can_access_name_api(Object name) { + return '- Kann auf **$name**-API zugreifen'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Möchten Sie dieses Plugin installieren?'; + + @override + String get third_party_plugin_warning => + 'Dieses Plugin stammt aus einem Drittanbieter-Repository. Bitte stellen Sie sicher, dass Sie der Quelle vertrauen, bevor Sie es installieren.'; + + @override + String get author => 'Autor'; + + @override + String get this_plugin_can_do_following => 'Dieses Plugin kann Folgendes:'; + + @override + String get install => 'Installieren'; + + @override + String get install_a_metadata_provider => + 'Einen Metadatenanbieter installieren'; + + @override + String get no_tracks_playing => 'Derzeit wird kein Titel abgespielt'; + + @override + String get synced_lyrics_not_available => + 'Synchronisierte Liedtexte sind für dieses Lied nicht verfügbar. Bitte verwenden Sie stattdessen'; + + @override + String get plain_lyrics => 'Einfache Liedtexte'; + + @override + String get tab_instead => 'stattdessen die Tab-Taste verwenden.'; + + @override + String get disclaimer => 'Haftungsausschluss'; + + @override + String get 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'; + + @override + String get input_does_not_match_format => + 'Eingabe entspricht nicht dem geforderten Format'; + + @override + String get metadata_provider_plugins => 'Plugins für Metadatenanbieter'; + + @override + String get paste_plugin_download_url => + 'Download-URL, GitHub/Codeberg-Repo-URL oder direkten Link zur .smplug-Datei einfügen'; + + @override + String get download_and_install_plugin_from_url => + 'Plugin per URL herunterladen und installieren'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Plugin konnte nicht hinzugefügt werden: $error'; + } + + @override + String get upload_plugin_from_file => 'Plugin per Datei hochladen'; + + @override + String get installed => 'Installiert'; + + @override + String get available_plugins => 'Verfügbare Plugins'; + + @override + String get configure_your_own_metadata_plugin => + 'Eigenen Anbieter für Playlist-/Album-/Künstler-/Feed-Metadaten konfigurieren'; + + @override + String get audio_scrobblers => 'Audio-Scrobbler'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index cbecb68c..e6d4db1e 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -1379,4 +1379,161 @@ class AppLocalizationsEn extends AppLocalizations { @override String get connection_request_denied => 'Connection denied. User denied access.'; + + @override + String get an_error_occurred => 'An error occurred'; + + @override + String get copy_to_clipboard => 'Copy to clipboard'; + + @override + String get view_logs => 'View logs'; + + @override + String get retry => 'Retry'; + + @override + String get no_default_metadata_provider_selected => + 'You\'ve no default metadata provider set'; + + @override + String get manage_metadata_providers => 'Manage metadata providers'; + + @override + String get open_link_in_browser => 'Open Link in Browser?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Do you want to open the following link'; + + @override + String get unsafe_url_warning => + 'It can be unsafe to open links from untrusted sources. Be cautious!\nYou can also copy the link to your clipboard.'; + + @override + String get copy_link => 'Copy Link'; + + @override + String get building_your_timeline => + 'Building your timeline based on your listenings...'; + + @override + String get official => 'Official'; + + @override + String author_name(Object author) { + return 'Author: $author'; + } + + @override + String get third_party => 'Third-party'; + + @override + String get plugin_requires_authentication => 'Plugin requires authentication'; + + @override + String get update_available => 'Update available'; + + @override + String get supports_scrobbling => 'Supports scrobbling'; + + @override + String get plugin_scrobbling_info => + 'This plugin scrobbles your music to generate your listening history.'; + + @override + String get default_plugin => 'Default'; + + @override + String get set_default => 'Set default'; + + @override + String get support => 'Support'; + + @override + String get support_plugin_development => 'Support plugin development'; + + @override + String can_access_name_api(Object name) { + return '- Can access **$name** API'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Do you want to install this plugin?'; + + @override + String get third_party_plugin_warning => + 'This plugin is from a third-party repository. Please ensure you trust the source before installing.'; + + @override + String get author => 'Author'; + + @override + String get this_plugin_can_do_following => 'This plugin can do following'; + + @override + String get install => 'Install'; + + @override + String get install_a_metadata_provider => 'Install a Metadata Provider'; + + @override + String get no_tracks_playing => 'No Track being played currently'; + + @override + String get synced_lyrics_not_available => + 'Synced lyrics are not available for this song. Please use the'; + + @override + String get plain_lyrics => 'Plain Lyrics'; + + @override + String get tab_instead => 'tab instead.'; + + @override + String get disclaimer => 'Disclaimer'; + + @override + String get 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'; + + @override + String get input_does_not_match_format => + 'Input doesn\'t match the required format'; + + @override + String get metadata_provider_plugins => 'Metadata Provider Plugins'; + + @override + String get paste_plugin_download_url => + 'Paste download url or GitHub/Codeberg repo url or direct link to .smplug file'; + + @override + String get download_and_install_plugin_from_url => + 'Download and install plugin from url'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Failed to add plugin: $error'; + } + + @override + String get upload_plugin_from_file => 'Upload plugin from file'; + + @override + String get installed => 'Installed'; + + @override + String get available_plugins => 'Available plugins'; + + @override + String get configure_your_own_metadata_plugin => + 'Configure your own playlist/album/artist/feed metadata provider'; + + @override + String get audio_scrobblers => 'Audio Scrobblers'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 9fc35f31..f51f829c 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -1208,7 +1208,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*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.'; @override String count_mins(Object minutes) { @@ -1388,4 +1388,167 @@ class AppLocalizationsEs extends AppLocalizations { @override String get connection_request_denied => 'Conexión denegada. El usuario denegó el acceso.'; + + @override + String get an_error_occurred => 'Ocurrió un error'; + + @override + String get copy_to_clipboard => 'Copiar al portapapeles'; + + @override + String get view_logs => 'Ver registros'; + + @override + String get retry => 'Reintentar'; + + @override + String get no_default_metadata_provider_selected => + 'No has configurado un proveedor de metadatos predeterminado'; + + @override + String get manage_metadata_providers => 'Gestionar proveedores de metadatos'; + + @override + String get open_link_in_browser => '¿Abrir enlace en el navegador?'; + + @override + String get do_you_want_to_open_the_following_link => + '¿Quieres abrir el siguiente enlace?'; + + @override + String get unsafe_url_warning => + 'Abrir enlaces de fuentes no confiables puede ser inseguro. ¡Ten cuidado!\nTambién puedes copiar el enlace al portapapeles.'; + + @override + String get copy_link => 'Copiar enlace'; + + @override + String get building_your_timeline => + 'Construyendo tu línea de tiempo según tus escuchas…'; + + @override + String get official => 'Oficial'; + + @override + String author_name(Object author) { + return 'Autor: $author'; + } + + @override + String get third_party => 'Terceros'; + + @override + String get plugin_requires_authentication => + 'El complemento requiere autenticación'; + + @override + String get update_available => 'Actualización disponible'; + + @override + String get supports_scrobbling => 'Admite scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Este complemento scrobblea tu música para generar tu historial de reproducción.'; + + @override + String get default_plugin => 'Predeterminado'; + + @override + String get set_default => 'Establecer como predeterminado'; + + @override + String get support => 'Soporte'; + + @override + String get support_plugin_development => + 'Apoyar el desarrollo del complemento'; + + @override + String can_access_name_api(Object name) { + return '- Puede acceder a la API de **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + '¿Deseas instalar este complemento?'; + + @override + String get third_party_plugin_warning => + 'Este complemento proviene de un repositorio de terceros. Asegúrate de confiar en la fuente antes de instalarlo.'; + + @override + String get author => 'Autor'; + + @override + String get this_plugin_can_do_following => + 'Este complemento puede hacer lo siguiente'; + + @override + String get install => 'Instalar'; + + @override + String get install_a_metadata_provider => + 'Instalar un proveedor de metadatos'; + + @override + String get no_tracks_playing => + 'No hay ninguna pista reproduciéndose actualmente'; + + @override + String get synced_lyrics_not_available => + 'Las letras sincronizadas no están disponibles para esta canción. Por favor, utiliza'; + + @override + String get plain_lyrics => 'Letras sin formato'; + + @override + String get tab_instead => 'en su lugar, usa la tecla Tab.'; + + @override + String get disclaimer => 'Descargo de responsabilidad'; + + @override + String get 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'; + + @override + String get input_does_not_match_format => + 'La entrada no coincide con el formato requerido'; + + @override + String get metadata_provider_plugins => + 'Complementos de proveedor de metadatos'; + + @override + String get paste_plugin_download_url => + 'Pega la URL de descarga, el repositorio de GitHub/Codeberg o el enlace directo al archivo .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Descargar e instalar el complemento desde una URL'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Error al añadir el complemento: $error'; + } + + @override + String get upload_plugin_from_file => 'Subir complemento desde archivo'; + + @override + String get installed => 'Instalado'; + + @override + String get available_plugins => 'Complementos disponibles'; + + @override + String get configure_your_own_metadata_plugin => + 'Configura tu propio proveedor de metadatos para listas/álbum/artista/feeds'; + + @override + String get audio_scrobblers => 'Scrobblers de audio'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_eu.dart b/lib/l10n/generated/app_localizations_eu.dart index 0dba9560..523f110f 100644 --- a/lib/l10n/generated/app_localizations_eu.dart +++ b/lib/l10n/generated/app_localizations_eu.dart @@ -1206,7 +1206,7 @@ class AppLocalizationsEu extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Kalkulu hau online musika-streaming plataformetako batez besteko irteerako ordainari (0,003–0,005 USD) oinarrituta dago. Hipotetikoa da eta erabiltzaileari ideia bat ematen laguntzen dio artista nork zenbat kobratu zuen jakiteko, bere abestia plataform desberdinetan entzungo balu.'; @override String count_mins(Object minutes) { @@ -1387,4 +1387,165 @@ class AppLocalizationsEu extends AppLocalizations { @override String get connection_request_denied => 'Konektatzea ukatu da. Erabiltzaileak sarbidea ukatu du.'; + + @override + String get an_error_occurred => 'Errore bat gertatu da'; + + @override + String get copy_to_clipboard => 'Hiztegiraino kopiatzea'; + + @override + String get view_logs => 'Erregistroak ikusi'; + + @override + String get retry => 'Berriro saiatu'; + + @override + String get no_default_metadata_provider_selected => + 'Ezarri ez duzu metadaten hornitzaile lehenetsirik'; + + @override + String get manage_metadata_providers => 'Metadaten hornitzaileak kudeatu'; + + @override + String get open_link_in_browser => 'Esteka nabigatzailean irekiko duzu?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Hurrengo esteka irekiko duzu?'; + + @override + String get unsafe_url_warning => + 'Iturri seguru gabeko estekak irekiz gero, ez da seguru suerta daiteke. Arduratu zaitez!\nEsteka ere hiztegirainokoan kopiatu dezakezu.'; + + @override + String get copy_link => 'Esteka kopiatu'; + + @override + String get building_your_timeline => + 'Zure entzuteen arabera zure kronologia eraikitzen…'; + + @override + String get official => 'Ofiziala'; + + @override + String author_name(Object author) { + return 'Egilea: $author'; + } + + @override + String get third_party => 'Hirugarrena'; + + @override + String get plugin_requires_authentication => + 'Pluginak autentifikazioa eskatzen du'; + + @override + String get update_available => 'Eguneratze bat dago eskuragarri'; + + @override + String get supports_scrobbling => 'Scrobbling-a onartzen du'; + + @override + String get plugin_scrobbling_info => + 'Plugin honek zure musika scrobbled egiten du zure entzuteen historia sortzeko.'; + + @override + String get default_plugin => 'Lehenetsia'; + + @override + String get set_default => 'Lehenetsi gisa ezarri'; + + @override + String get support => 'Laguntza'; + + @override + String get support_plugin_development => 'Pluginaren garapena lagundu'; + + @override + String can_access_name_api(Object name) { + return '- **$name** API-ra sar daiteke'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Plugin hau instalatu nahiko zenuke?'; + + @override + String get third_party_plugin_warning => + 'Plugin hau hirugarrenen biltegi batetik dator. Instalatu aurretik iturriari konfiantza behar diozu.'; + + @override + String get author => 'Egilea'; + + @override + String get this_plugin_can_do_following => + 'Plugin honek honako hau egin dezake:'; + + @override + String get install => 'Instalatu'; + + @override + String get install_a_metadata_provider => + 'Metadaten hornitzaile bat instalatu'; + + @override + String get no_tracks_playing => + 'Une honetan ez dago abestirik erreproduzitzen'; + + @override + String get synced_lyrics_not_available => + 'Abestiarentzako letra sinkronizatua ez dago erabilgarri. Mesedez, erabili'; + + @override + String get plain_lyrics => 'Letra arrunta'; + + @override + String get tab_instead => 'horren ordez, Tab teklatxaza erabili.'; + + @override + String get disclaimer => 'Aldez aurreko oharra'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube taldea ezin da arduratu (“hirugarrenen”) plugin-en>gatik (barne legala). Erabili zure arriskuarekin. Erroreak/ arazoak dituzu, jakinarazi pluginaren biltegiari.\n\nPlugin batek edozein zerbitzu/legalki entitate baten ToS/DMCA hautsi baditu, eska iezaiozu pluginaren egileari edo hosting plataformari (adibidez GitHub/Codeberg) neurriak har ditzaten. “Hirugarrena” etiketatutako plugin guztiak komunitate publikoaren bidez mantentzen dira; ez ditugu kuratoriatu, beraz ezin dugu inplikatu.\n\n'; + + @override + String get input_does_not_match_format => + 'Sarrera ezin da beharrezko formatutik desberdina izan'; + + @override + String get metadata_provider_plugins => 'Metadaten hornitzailearen pluginak'; + + @override + String get paste_plugin_download_url => + 'Kopiatu deskarga-URLa, GitHub/Codeberg biltegi-URLa edo .smplug fitxategiaren esteka zuzena'; + + @override + String get download_and_install_plugin_from_url => + 'Download eta instalatu plugin-a URL batetik'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Plugin gehitu ezin izan da: $error'; + } + + @override + String get upload_plugin_from_file => 'Plugin fitxategi batetik igo'; + + @override + String get installed => 'Instalatuta'; + + @override + String get available_plugins => 'Eskaintzen diren pluginak'; + + @override + String get configure_your_own_metadata_plugin => + 'Konfiguratu zureko playlists-/album-/artista-/feed-metadaten hornitzailea'; + + @override + String get audio_scrobblers => 'Audio scrobbler-ak'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_fa.dart b/lib/l10n/generated/app_localizations_fa.dart index 86c0cbbb..c63e723a 100644 --- a/lib/l10n/generated/app_localizations_fa.dart +++ b/lib/l10n/generated/app_localizations_fa.dart @@ -1197,7 +1197,7 @@ class AppLocalizationsFa extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*این محاسبه بر اساس میانگین پرداخت به ازای هر پخش (0.003 تا 0.005 دلار) در پلتفرم‌های استریم موزیک آنلاین انجام شده است. این یک محاسبه فرضی است که به کاربر دیدی از مقدار پرداختی به هنرمندان در صورت گوش دادن به آهنگ آن‌ها در پلتفرم‌های مختلف ارائه می‌دهد.'; @override String count_mins(Object minutes) { @@ -1378,4 +1378,162 @@ class AppLocalizationsFa extends AppLocalizations { @override String get connection_request_denied => 'اتصال رد شد. کاربر دسترسی را رد کرد.'; + + @override + String get an_error_occurred => 'خطایی رخ داد'; + + @override + String get copy_to_clipboard => 'کپی به کلیپ‌بورد'; + + @override + String get view_logs => 'مشاهده لاگ‌ها'; + + @override + String get retry => 'دوباره تلاش کن'; + + @override + String get no_default_metadata_provider_selected => + 'هیچ ارائه‌دهندهٔ پیش‌فرض متادیتا تعیین نکرده‌اید'; + + @override + String get manage_metadata_providers => 'مدیریت ارائه‌دهندگان متادیتا'; + + @override + String get open_link_in_browser => 'باز کردن لینک در مرورگر؟'; + + @override + String get do_you_want_to_open_the_following_link => + 'آیا می‌خواهید لینک زیر را باز کنید؟'; + + @override + String get unsafe_url_warning => + 'باز کردن لینک از منابع نامطمئن می‌تواند ناامن باشد. مراقب باشید!\nهمچنین می‌توانید لینک را در کلیپ‌بورد خود کپی کنید.'; + + @override + String get copy_link => 'کپی لینک'; + + @override + String get building_your_timeline => + 'در حال ساخت جدول زمانی بر اساس شنیده‌هایتان…'; + + @override + String get official => 'رسمی'; + + @override + String author_name(Object author) { + return 'نویسنده: $author'; + } + + @override + String get third_party => 'سوم‌شخص'; + + @override + String get plugin_requires_authentication => 'افزونه نیاز به احراز هویت دارد'; + + @override + String get update_available => 'به‌روزرسانی در دسترس است'; + + @override + String get supports_scrobbling => 'پشتیبانی از اسکراب‌بلینگ'; + + @override + String get plugin_scrobbling_info => + 'این افزونه موسیقی شما را اسکراب می‌کند تا تاریخچهٔ شنیداری‌تان را تولید کند.'; + + @override + String get default_plugin => 'پیش‌فرض'; + + @override + String get set_default => 'تنظیم به عنوان پیش‌فرض'; + + @override + String get support => 'پشتیبانی'; + + @override + String get support_plugin_development => 'حمایت از توسعهٔ افزونه'; + + @override + String can_access_name_api(Object name) { + return '- می‌تواند به API **$name** دسترسی پیدا کند'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'می‌خواهید این افزونه را نصب کنید؟'; + + @override + String get third_party_plugin_warning => + 'این افزونه از مخزن شخص ثالث آمده است. لطفاً قبل از نصب از منابع آن مطمئن شوید.'; + + @override + String get author => 'نویسنده'; + + @override + String get this_plugin_can_do_following => + 'این افزونه می‌تواند موارد زیر را انجام دهد'; + + @override + String get install => 'نصب'; + + @override + String get install_a_metadata_provider => 'نصب یک ارائه‌دهندهٔ متادیتا'; + + @override + String get no_tracks_playing => 'در حال‌ حاضر هیچ تراکی در حال پخش نیست'; + + @override + String get synced_lyrics_not_available => + 'متن هم‌زمان‌شده برای این آهنگ در دسترس نیست. لطفاً از'; + + @override + String get plain_lyrics => 'متن ساده'; + + @override + String get tab_instead => 'به‌جای آن از کلید Tab استفاده کنید.'; + + @override + String get disclaimer => 'سلب مسئولیت'; + + @override + String get third_party_plugin_dmca_notice => + 'تیم Spotube هیچ مسئولیتی (حتی قانونی) در قبال افزونه‌های \"شخص ثالث\" ندارد. از آن‌ها به‌خاطر خود استفاده کنید. برای خطاها/مشکلات، لطفاً در مخزن افزونه گزارش دهید.\n\nاگر هر افزونهٔ \"شخص ثالث\" قوانین ToS/DMCA سرویس یا نهاد قانونی را نقض کند، لطفاً از نویسندهٔ افزونه یا پلتفرم میزبانی (مثل GitHub/Codeberg) درخواست اقدام کنید. افزونه‌هایی که با برچسب \"شخص ثالث\" مشخص شده‌اند، عمومی هستند و توسط جامعه نگهداری می‌شوند؛ ما آن‌ها را تغییر یا مدیریت نمی‌کنیم و نمی‌توانیم دخالت کنیم.\n\n'; + + @override + String get input_does_not_match_format => + 'ورودی با قالب مورد نیاز تطابق ندارد'; + + @override + String get metadata_provider_plugins => 'افزونه‌های ارائه‌دهندهٔ متادیتا'; + + @override + String get paste_plugin_download_url => + 'URL دانلود یا مخزن GitHub/Codeberg یا لینک مستقیم فایل .smplug را الصاق کنید'; + + @override + String get download_and_install_plugin_from_url => + 'دانلود و نصب افزونه از طریق لینک'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'افزونه اضافه نشد: $error'; + } + + @override + String get upload_plugin_from_file => 'بارگذاری افزونه از فایل'; + + @override + String get installed => 'نصب شد'; + + @override + String get available_plugins => 'افزونه‌های موجود'; + + @override + String get configure_your_own_metadata_plugin => + 'پیکربندی ارائه‌دهندهٔ متادیتا برای پلی‌لیست/آلبوم/هنرمند/فید به‌صورت سفارشی'; + + @override + String get audio_scrobblers => 'اسکراب‌بلرهای صوتی'; + + @override + String get scrobbling => 'اسکراب‌بلینگ'; } diff --git a/lib/l10n/generated/app_localizations_fi.dart b/lib/l10n/generated/app_localizations_fi.dart index 6fbd9cb4..e1ba7f5a 100644 --- a/lib/l10n/generated/app_localizations_fi.dart +++ b/lib/l10n/generated/app_localizations_fi.dart @@ -1200,7 +1200,7 @@ class AppLocalizationsFi extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Tämä on laskettu keskimääräisen musiikin suoratoistopalvelun 0,003–0,005 dollarin kappalekohtaisen maksun perusteella. Tämä on hypoteettinen laskelma, joka antaa käyttäjälle käsityksen siitä, kuinka paljon he olisivat maksaneet artisteille, jos he kuuntelisivat heidän kappaleitaan eri musiikin suoratoistopalveluissa.'; @override String count_mins(Object minutes) { @@ -1380,4 +1380,160 @@ class AppLocalizationsFi extends AppLocalizations { @override String get connection_request_denied => 'Yhteys evätty. Käyttäjä eväsi pääsyn.'; + + @override + String get an_error_occurred => 'Tapahtui virhe'; + + @override + String get copy_to_clipboard => 'Kopioi leikepöydälle'; + + @override + String get view_logs => 'Näytä lokit'; + + @override + String get retry => 'Yritä uudelleen'; + + @override + String get no_default_metadata_provider_selected => + 'Et ole asettanut oletusmetatietojen tarjoajaa'; + + @override + String get manage_metadata_providers => 'Hallinnoi metatietojen tarjoajia'; + + @override + String get open_link_in_browser => 'Avaa linkki selaimessa?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Haluatko avata seuraavan linkin'; + + @override + String get unsafe_url_warning => + 'Linkkien avaaminen epäluotettavista lähteistä voi olla vaarallista. Ole varovainen!\nVoit myös kopioida linkin leikepöydälle.'; + + @override + String get copy_link => 'Kopioi linkki'; + + @override + String get building_your_timeline => + 'Rakennetaan aikajanaasi kuuntelujesi perusteella...'; + + @override + String get official => 'Virallinen'; + + @override + String author_name(Object author) { + return 'Tekijä: $author'; + } + + @override + String get third_party => 'Kolmannen osapuolen'; + + @override + String get plugin_requires_authentication => 'Lisäosa vaatii todentamisen'; + + @override + String get update_available => 'Päivitys saatavilla'; + + @override + String get supports_scrobbling => 'Tukee scrobblingia'; + + @override + String get plugin_scrobbling_info => + 'Tämä lisäosa scrobblaa musiikkisi luodakseen kuunteluhistoriasi.'; + + @override + String get default_plugin => 'Oletus'; + + @override + String get set_default => 'Aseta oletukseksi'; + + @override + String get support => 'Tuki'; + + @override + String get support_plugin_development => 'Tue lisäosan kehitystä'; + + @override + String can_access_name_api(Object name) { + return '- Voi käyttää **$name** APIa'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Haluatko asentaa tämän lisäosan?'; + + @override + String get third_party_plugin_warning => + 'Tämä lisäosa on kolmannen osapuolen arkistosta. Varmista, että luotat lähteeseen ennen asennusta.'; + + @override + String get author => 'Tekijä'; + + @override + String get this_plugin_can_do_following => 'Tämä lisäosa voi tehdä seuraavaa'; + + @override + String get install => 'Asenna'; + + @override + String get install_a_metadata_provider => 'Asenna metatietojen tarjoaja'; + + @override + String get no_tracks_playing => 'Ei kappaletta toistossa tällä hetkellä'; + + @override + String get synced_lyrics_not_available => + 'Synkronoidut sanoitukset eivät ole saatavilla tälle kappaleelle. Käytä sen sijaan'; + + @override + String get plain_lyrics => 'Pelkät sanoitukset'; + + @override + String get tab_instead => 'välilehteä.'; + + @override + String get disclaimer => 'Vastuuvapauslauseke'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube-tiimi ei ota mitään vastuuta (mukaan lukien oikeudellinen) mistään \"kolmannen osapuolen\" lisäosista.\nKäytä niitä omalla vastuullasi. Ilmoita kaikista virheistä/ongelmista lisäosan arkistoon.\n\nJos jokin \"kolmannen osapuolen\" lisäosa rikkoo jonkin palvelun/oikeushenkilön käyttöehtoja/DMCA:ta, pyydä \"kolmannen osapuolen\" lisäosan tekijää tai isännöintialustaa, esim. GitHubia/Codebergiä, ryhtymään toimiin. Yllä luetellut (\"kolmannen osapuolen\" merkityt) ovat kaikki julkisia/yhteisön ylläpitämiä lisäosia. Emme kuratoi niitä, joten emme voi ryhtyä niihin toimiin.\n\n'; + + @override + String get input_does_not_match_format => 'Syöte ei vastaa vaadittua muotoa'; + + @override + String get metadata_provider_plugins => 'Metatietojen tarjoajan lisäosat'; + + @override + String get paste_plugin_download_url => + 'Liitä lataus-URL-osoite tai GitHub/Codeberg-arkiston URL-osoite tai suora linkki .smplug-tiedostoon'; + + @override + String get download_and_install_plugin_from_url => + 'Lataa ja asenna lisäosa URL-osoitteesta'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Lisäosan lisääminen epäonnistui: $error'; + } + + @override + String get upload_plugin_from_file => 'Lataa lisäosa tiedostosta'; + + @override + String get installed => 'Asennettu'; + + @override + String get available_plugins => 'Saatavilla olevat lisäosat'; + + @override + String get configure_your_own_metadata_plugin => + 'Määritä oma soittolistan/albumin/artistin/syötteen metatietojen tarjoaja'; + + @override + String get audio_scrobblers => 'Äänen scrobblerit'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index d19a31f8..88350997 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -1211,7 +1211,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Ce calcul est basé sur le paiement moyen par lecture des plateformes de streaming musical en ligne, de 0,003 \$ à 0,005 \$. Il s\'agit d\'un calcul hypothétique pour donner à l\'utilisateur un aperçu de ce qu\'il aurait payé aux artistes s\'il écoutait leur chanson sur différentes plateformes de streaming musical.'; @override String count_mins(Object minutes) { @@ -1391,5 +1391,169 @@ class AppLocalizationsFr extends AppLocalizations { @override String get connection_request_denied => - 'Connection denied. User denied access.'; + 'Connexion refusée. L\'utilisateur a refusé l\'accès.'; + + @override + String get an_error_occurred => 'Une erreur est survenue'; + + @override + String get copy_to_clipboard => 'Copier dans le presse-papiers'; + + @override + String get view_logs => 'Afficher les journaux'; + + @override + String get retry => 'Réessayer'; + + @override + String get no_default_metadata_provider_selected => + 'Vous n\'avez pas de fournisseur de métadonnées par défaut'; + + @override + String get manage_metadata_providers => + 'Gérer les fournisseurs de métadonnées'; + + @override + String get open_link_in_browser => 'Ouvrir le lien dans le navigateur ?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Voulez-vous ouvrir le lien suivant'; + + @override + String get unsafe_url_warning => + 'L\'ouverture de liens provenant de sources non fiables peut être dangereuse. Soyez prudent !\nVous pouvez également copier le lien dans votre presse-papiers.'; + + @override + String get copy_link => 'Copier le lien'; + + @override + String get building_your_timeline => + 'Construction de votre chronologie en fonction de vos écoutes...'; + + @override + String get official => 'Officiel'; + + @override + String author_name(Object author) { + return 'Auteur : $author'; + } + + @override + String get third_party => 'Tiers'; + + @override + String get plugin_requires_authentication => + 'Le plugin nécessite une authentification'; + + @override + String get update_available => 'Mise à jour disponible'; + + @override + String get supports_scrobbling => 'Supporte le scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Ce plugin scrobble votre musique pour générer votre historique d\'écoute.'; + + @override + String get default_plugin => 'Par défaut'; + + @override + String get set_default => 'Définir par défaut'; + + @override + String get support => 'Soutien'; + + @override + String get support_plugin_development => + 'Soutenir le développement de plugins'; + + @override + String can_access_name_api(Object name) { + return '- Peut accéder à l\'API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Voulez-vous installer ce plugin ?'; + + @override + String get third_party_plugin_warning => + 'Ce plugin provient d\'un dépôt tiers. Assurez-vous de faire confiance à la source avant de l\'installer.'; + + @override + String get author => 'Auteur'; + + @override + String get this_plugin_can_do_following => 'Ce plugin peut faire ce qui suit'; + + @override + String get install => 'Installer'; + + @override + String get install_a_metadata_provider => + 'Installer un fournisseur de métadonnées'; + + @override + String get no_tracks_playing => + 'Aucune piste n\'est en cours de lecture actuellement'; + + @override + String get synced_lyrics_not_available => + 'Les paroles synchronisées ne sont pas disponibles pour cette chanson. Veuillez utiliser l\'onglet'; + + @override + String get plain_lyrics => 'Paroles simples'; + + @override + String get tab_instead => 'à la place.'; + + @override + String get disclaimer => 'Avertissement'; + + @override + String get third_party_plugin_dmca_notice => + 'L\'équipe de Spotube n\'assume aucune responsabilité (y compris juridique) pour les plugins \"tiers\".\nVeuillez les utiliser à vos propres risques. Pour tout bug/problème, veuillez le signaler au dépôt du plugin.\n\nSi un plugin \"tiers\" enfreint les conditions d\'utilisation/DMCA d\'un service/entité juridique, veuillez demander à l\'auteur du plugin \"tiers\" ou à la plateforme d\'hébergement (par exemple GitHub/Codeberg) de prendre des mesures. Les plugins listés ci-dessus (étiquetés \"tiers\") sont tous des plugins publics/maintenus par la communauté. Nous ne les gérons pas, nous ne pouvons donc prendre aucune mesure à leur sujet.\n\n'; + + @override + String get input_does_not_match_format => + 'L\'entrée ne correspond pas au format requis'; + + @override + String get metadata_provider_plugins => + 'Plugins de fournisseur de métadonnées'; + + @override + String get paste_plugin_download_url => + 'Collez l\'URL de téléchargement ou l\'URL du dépôt GitHub/Codeberg ou un lien direct vers le fichier .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Télécharger et installer le plugin à partir de l\'URL'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Échec de l\'ajout du plugin : $error'; + } + + @override + String get upload_plugin_from_file => + 'Télécharger le plugin à partir d\'un fichier'; + + @override + String get installed => 'Installé'; + + @override + String get available_plugins => 'Plugins disponibles'; + + @override + String get configure_your_own_metadata_plugin => + 'Configurer votre propre fournisseur de métadonnées de playlist/album/artiste/flux'; + + @override + String get audio_scrobblers => 'Scrobblers audio'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index a3f56a25..f3ba4802 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -1203,7 +1203,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*यह औसत ऑनलाइन संगीत स्ट्रीमिंग प्लेटफ़ॉर्म के प्रति स्ट्रीम भुगतान (\$0.003 से \$0.005) के आधार पर गणना की गई है। यह एक काल्पनिक गणना है जो उपयोगकर्ता को यह जानकारी देने के लिए है कि यदि वे विभिन्न संगीत स्ट्रीमिंग प्लेटफ़ॉर्म पर अपने गाने सुनते हैं तो उन्होंने कलाकारों को कितना भुगतान किया होगा।'; @override String count_mins(Object minutes) { @@ -1384,4 +1384,162 @@ class AppLocalizationsHi extends AppLocalizations { @override String get connection_request_denied => 'कनेक्शन अस्वीकृत। उपयोगकर्ता ने पहुंच अस्वीकृत कर दी।'; + + @override + String get an_error_occurred => 'एक त्रुटि हुई'; + + @override + String get copy_to_clipboard => 'क्लिपबोर्ड पर कॉपी करें'; + + @override + String get view_logs => 'लॉग देखें'; + + @override + String get retry => 'पुनः प्रयास करें'; + + @override + String get no_default_metadata_provider_selected => + 'आपने कोई डिफ़ॉल्ट मेटाडेटा प्रदाता सेट नहीं किया है'; + + @override + String get manage_metadata_providers => 'मेटाडेटा प्रदाताओं को प्रबंधित करें'; + + @override + String get open_link_in_browser => 'ब्राउज़र में लिंक खोलें?'; + + @override + String get do_you_want_to_open_the_following_link => + 'क्या आप निम्नलिखित लिंक खोलना चाहते हैं'; + + @override + String get unsafe_url_warning => + 'अविश्वसनीय स्रोतों से लिंक खोलना असुरक्षित हो सकता है। सावधान रहें!\nआप लिंक को अपने क्लिपबोर्ड पर भी कॉपी कर सकते हैं।'; + + @override + String get copy_link => 'लिंक कॉपी करें'; + + @override + String get building_your_timeline => + 'आपकी सुनने की आदतों के आधार पर आपकी टाइमलाइन बनाई जा रही है...'; + + @override + String get official => 'आधिकारिक'; + + @override + String author_name(Object author) { + return 'लेखक: $author'; + } + + @override + String get third_party => 'तृतीय-पक्ष'; + + @override + String get plugin_requires_authentication => + 'प्लगइन को प्रमाणीकरण की आवश्यकता है'; + + @override + String get update_available => 'अपडेट उपलब्ध है'; + + @override + String get supports_scrobbling => 'स्क्रॉबलिंग का समर्थन करता है'; + + @override + String get plugin_scrobbling_info => + 'यह प्लगइन आपके सुनने के इतिहास को उत्पन्न करने के लिए आपके संगीत को स्क्रॉबल करता है।'; + + @override + String get default_plugin => 'डिफ़ॉल्ट'; + + @override + String get set_default => 'डिफ़ॉल्ट सेट करें'; + + @override + String get support => 'समर्थन'; + + @override + String get support_plugin_development => 'प्लगइन विकास का समर्थन करें'; + + @override + String can_access_name_api(Object name) { + return '- **$name** API तक पहुंच सकता है'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'क्या आप इस प्लगइन को स्थापित करना चाहते हैं?'; + + @override + String get third_party_plugin_warning => + 'यह प्लगइन एक तृतीय-पक्ष रिपॉजिटरी से है। कृपया सुनिश्चित करें कि आप इसे स्थापित करने से पहले स्रोत पर भरोसा करते हैं।'; + + @override + String get author => 'लेखक'; + + @override + String get this_plugin_can_do_following => 'यह प्लगइन निम्नलिखित कर सकता है'; + + @override + String get install => 'स्थापित करें'; + + @override + String get install_a_metadata_provider => 'एक मेटाडेटा प्रदाता स्थापित करें'; + + @override + String get no_tracks_playing => 'वर्तमान में कोई ट्रैक नहीं चल रहा है'; + + @override + String get synced_lyrics_not_available => + 'इस गाने के लिए सिंक्रनाइज़ किए गए बोल उपलब्ध नहीं हैं। कृपया'; + + @override + String get plain_lyrics => 'सादे बोल'; + + @override + String get tab_instead => 'टैब का उपयोग करें।'; + + @override + String get disclaimer => 'अस्वीकरण'; + + @override + String get third_party_plugin_dmca_notice => + 'स्पॉट्यूब टीम किसी भी \"तृतीय-पक्ष\" प्लगइन के लिए कोई जिम्मेदारी (कानूनी सहित) नहीं लेती है।\nकृपया उन्हें अपने जोखिम पर उपयोग करें। किसी भी बग/समस्या के लिए, कृपया उन्हें प्लगइन रिपॉजिटरी को रिपोर्ट करें।\n\nयदि कोई \"तृतीय-पक्ष\" प्लगइन किसी सेवा/कानूनी इकाई के ToS/DMCA को तोड़ रहा है, तो कृपया \"तृतीय-पक्ष\" प्लगइन लेखक या होस्टिंग प्लेटफ़ॉर्म जैसे GitHub/Codeberg से कार्रवाई करने के लिए कहें। ऊपर सूचीबद्ध (\"तृतीय-पक्ष\" लेबल वाले) सभी सार्वजनिक/समुदाय-द्वारा-रखरखाव किए गए प्लगइन हैं। हम उन्हें क्यूरेट नहीं कर रहे हैं, इसलिए हम उन पर कोई कार्रवाई नहीं कर सकते हैं।\n\n'; + + @override + String get input_does_not_match_format => + 'इनपुट आवश्यक प्रारूप से मेल नहीं खाता है'; + + @override + String get metadata_provider_plugins => 'मेटाडेटा प्रदाता प्लगइन'; + + @override + String get paste_plugin_download_url => + 'डाउनलोड यूआरएल या गिटहब/कोडबर्ग रेपो यूआरएल या .smplug फ़ाइल का सीधा लिंक पेस्ट करें'; + + @override + String get download_and_install_plugin_from_url => + 'यूआरएल से प्लगइन डाउनलोड और स्थापित करें'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'प्लगइन जोड़ने में विफल: $error'; + } + + @override + String get upload_plugin_from_file => 'फ़ाइल से प्लगइन अपलोड करें'; + + @override + String get installed => 'स्थापित'; + + @override + String get available_plugins => 'उपलब्ध प्लगइन'; + + @override + String get configure_your_own_metadata_plugin => + 'अपनी खुद की प्लेलिस्ट/एल्बम/कलाकार/फ़ीड मेटाडेटा प्रदाता कॉन्फ़िगर करें'; + + @override + String get audio_scrobblers => 'ऑडियो स्क्रॉबलर्स'; + + @override + String get scrobbling => 'स्क्रॉबलिंग'; } diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index 2ec73009..c56f1ece 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -1205,7 +1205,7 @@ class AppLocalizationsId extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Ini dihitung berdasarkan pembayaran rata-rata per streaming dari platform streaming musik online sebesar \$0,003 hingga \$0,005. Ini adalah perhitungan hipotetis untuk memberikan wawasan kepada pengguna tentang seberapa banyak yang akan mereka bayarkan kepada artis jika mereka mendengarkan lagu mereka di platform streaming musik yang berbeda.'; @override String count_mins(Object minutes) { @@ -1386,4 +1386,162 @@ class AppLocalizationsId extends AppLocalizations { @override String get connection_request_denied => 'Koneksi ditolak. Pengguna menolak akses.'; + + @override + String get an_error_occurred => 'Terjadi kesalahan'; + + @override + String get copy_to_clipboard => 'Salin ke papan klip'; + + @override + String get view_logs => 'Lihat log'; + + @override + String get retry => 'Coba lagi'; + + @override + String get no_default_metadata_provider_selected => + 'Anda belum mengatur penyedia metadata default'; + + @override + String get manage_metadata_providers => 'Kelola penyedia metadata'; + + @override + String get open_link_in_browser => 'Buka Tautan di Peramban?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Apakah Anda ingin membuka tautan berikut'; + + @override + String get unsafe_url_warning => + 'Tidak aman untuk membuka tautan dari sumber yang tidak tepercaya. Berhati-hatilah!\nAnda juga dapat menyalin tautan ke papan klip Anda.'; + + @override + String get copy_link => 'Salin Tautan'; + + @override + String get building_your_timeline => + 'Membangun garis waktu Anda berdasarkan riwayat mendengarkan Anda...'; + + @override + String get official => 'Resmi'; + + @override + String author_name(Object author) { + return 'Penulis: $author'; + } + + @override + String get third_party => 'Pihak ketiga'; + + @override + String get plugin_requires_authentication => 'Plugin memerlukan otentikasi'; + + @override + String get update_available => 'Pembaruan tersedia'; + + @override + String get supports_scrobbling => 'Mendukung scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Plugin ini scrobble musik Anda untuk menghasilkan riwayat mendengarkan Anda.'; + + @override + String get default_plugin => 'Bawaan'; + + @override + String get set_default => 'Atur sebagai bawaan'; + + @override + String get support => 'Dukungan'; + + @override + String get support_plugin_development => 'Dukung pengembangan plugin'; + + @override + String can_access_name_api(Object name) { + return '- Dapat mengakses API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Apakah Anda ingin menginstal plugin ini?'; + + @override + String get third_party_plugin_warning => + 'Plugin ini berasal dari repositori pihak ketiga. Pastikan Anda memercayai sumbernya sebelum menginstal.'; + + @override + String get author => 'Penulis'; + + @override + String get this_plugin_can_do_following => + 'Plugin ini dapat melakukan hal berikut'; + + @override + String get install => 'Instal'; + + @override + String get install_a_metadata_provider => 'Instal Penyedia Metadata'; + + @override + String get no_tracks_playing => 'Tidak ada Lagu yang sedang diputar saat ini'; + + @override + String get synced_lyrics_not_available => + 'Lirik tersinkronisasi tidak tersedia untuk lagu ini. Silakan gunakan tab'; + + @override + String get plain_lyrics => 'Lirik Polos'; + + @override + String get tab_instead => 'sebagai gantinya.'; + + @override + String get disclaimer => 'Penafian'; + + @override + String get third_party_plugin_dmca_notice => + 'Tim Spotube tidak bertanggung jawab (termasuk hukum) atas plugin \"Pihak ketiga\" mana pun.\nSilakan gunakan dengan risiko Anda sendiri. Untuk bug/masalah apa pun, silakan laporkan ke repositori plugin.\n\nJika ada plugin \"Pihak ketiga\" yang melanggar ToS/DMCA dari layanan/entitas hukum mana pun, silakan minta penulis plugin \"Pihak ketiga\" atau platform hosting, mis. GitHub/Codeberg, untuk mengambil tindakan. Yang tercantum di atas (berlabel \"Pihak ketiga\") adalah semua plugin publik/yang dikelola oleh komunitas. Kami tidak mengkurasi mereka, jadi kami tidak dapat mengambil tindakan apa pun terhadap mereka.\n\n'; + + @override + String get input_does_not_match_format => + 'Masukan tidak cocok dengan format yang diperlukan'; + + @override + String get metadata_provider_plugins => 'Plugin Penyedia Metadata'; + + @override + String get paste_plugin_download_url => + 'Tempel url unduhan atau url repo GitHub/Codeberg atau tautan langsung ke file .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Unduh dan instal plugin dari url'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Gagal menambahkan plugin: $error'; + } + + @override + String get upload_plugin_from_file => 'Unggah plugin dari file'; + + @override + String get installed => 'Terinstal'; + + @override + String get available_plugins => 'Plugin yang tersedia'; + + @override + String get configure_your_own_metadata_plugin => + 'Konfigurasi penyedia metadata playlist/album/artis/feed Anda sendiri'; + + @override + String get audio_scrobblers => 'Scrobblers Audio'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 8039552f..dc8ed9cd 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -1204,7 +1204,7 @@ class AppLocalizationsIt extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Questo è calcolato in base al pagamento medio per stream delle piattaforme di streaming musicale online, che va da \$0.003 a \$0.005. Si tratta di un calcolo ipotetico per dare all\'utente un\'idea di quanto avrebbe pagato agli artisti se avesse ascoltato la loro canzone su diverse piattaforme di streaming musicale.'; @override String count_mins(Object minutes) { @@ -1384,4 +1384,163 @@ class AppLocalizationsIt extends AppLocalizations { @override String get connection_request_denied => 'Connessione negata. L\'utente ha negato l\'accesso.'; + + @override + String get an_error_occurred => 'Si è verificato un errore'; + + @override + String get copy_to_clipboard => 'Copia negli appunti'; + + @override + String get view_logs => 'Visualizza log'; + + @override + String get retry => 'Riprova'; + + @override + String get no_default_metadata_provider_selected => + 'Non hai impostato alcun provider di metadati predefinito'; + + @override + String get manage_metadata_providers => 'Gestisci provider di metadati'; + + @override + String get open_link_in_browser => 'Aprire il link nel browser?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Vuoi aprire il seguente link'; + + @override + String get unsafe_url_warning => + 'Potrebbe essere pericoloso aprire link da fonti non attendibili. Sii cauto!\nPuoi anche copiare il link negli appunti.'; + + @override + String get copy_link => 'Copia link'; + + @override + String get building_your_timeline => + 'Creazione della tua cronologia in base ai tuoi ascolti...'; + + @override + String get official => 'Ufficiale'; + + @override + String author_name(Object author) { + return 'Autore: $author'; + } + + @override + String get third_party => 'Terze parti'; + + @override + String get plugin_requires_authentication => + 'Il plugin richiede l\'autenticazione'; + + @override + String get update_available => 'Aggiornamento disponibile'; + + @override + String get supports_scrobbling => 'Supporta lo scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Questo plugin scrobbla la tua musica per generare la tua cronologia di ascolti.'; + + @override + String get default_plugin => 'Predefinito'; + + @override + String get set_default => 'Imposta come predefinito'; + + @override + String get support => 'Supporto'; + + @override + String get support_plugin_development => 'Sostieni lo sviluppo del plugin'; + + @override + String can_access_name_api(Object name) { + return '- Può accedere all\'API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Vuoi installare questo plugin?'; + + @override + String get third_party_plugin_warning => + 'Questo plugin proviene da un repository di terze parti. Assicurati di fidarti della fonte prima di installarlo.'; + + @override + String get author => 'Autore'; + + @override + String get this_plugin_can_do_following => + 'Questo plugin può fare quanto segue'; + + @override + String get install => 'Installa'; + + @override + String get install_a_metadata_provider => 'Installa un provider di metadati'; + + @override + String get no_tracks_playing => 'Nessun brano in riproduzione al momento'; + + @override + String get synced_lyrics_not_available => + 'Testi sincronizzati non disponibili per questa canzone. Si prega di utilizzare la scheda'; + + @override + String get plain_lyrics => 'Testi semplici'; + + @override + String get tab_instead => 'invece.'; + + @override + String get disclaimer => 'Disclaimer'; + + @override + String get third_party_plugin_dmca_notice => + 'Il team di Spotube non si assume alcuna responsabilità (anche legale) per i plugin di \"terze parti\".\nUsali a tuo rischio e pericolo. Per eventuali bug/problemi, segnalali al repository del plugin.\n\nSe un plugin di \"terze parti\" sta violando i ToS/DMCA di un servizio/entità legale, per favore chiedi all\'autore del plugin \"terzo\" o alla piattaforma di hosting, ad esempio GitHub/Codeberg, di agire. Quelli elencati sopra (etichettati come \"terze parti\") sono tutti plugin pubblici/mantenuti dalla comunità. Non li curiamo, quindi non possiamo intraprendere alcuna azione su di essi.\n\n'; + + @override + String get input_does_not_match_format => + 'L\'input non corrisponde al formato richiesto'; + + @override + String get metadata_provider_plugins => 'Plugin del provider di metadati'; + + @override + String get paste_plugin_download_url => + 'Incolla l\'URL di download o l\'URL del repository GitHub/Codeberg o il link diretto al file .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Scarica e installa il plugin da URL'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Impossibile aggiungere il plugin: $error'; + } + + @override + String get upload_plugin_from_file => 'Carica plugin da file'; + + @override + String get installed => 'Installato'; + + @override + String get available_plugins => 'Plugin disponibili'; + + @override + String get configure_your_own_metadata_plugin => + 'Configura il tuo provider di metadati per playlist/album/artista/feed'; + + @override + String get audio_scrobblers => 'Scrobbler audio'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index a4b6795d..7ce62161 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -82,13 +82,13 @@ class AppLocalizationsJa extends AppLocalizations { String get liked_tracks_description => 'いいねしたすべての曲'; @override - String get playlist => 'プレイリスト'; + String get playlist => '再生リスト'; @override String get create_a_playlist => '再生リストの作成'; @override - String get update_playlist => 'プレイリストを更新'; + String get update_playlist => '再生リストを更新'; @override String get create => '作成'; @@ -139,14 +139,14 @@ class AppLocalizationsJa extends AppLocalizations { String get sort_album => 'アルバム順に並び替え'; @override - String get sort_duration => '時間で並べ替え'; + String get sort_duration => '長さ順に並べ替え'; @override String get sort_tracks => '曲の並び替え'; @override String currently_downloading(Object tracks_length) { - return 'いまダウンロード中 ($tracks_length) 曲'; + return 'ダウンロード中 ($tracks_length) 曲'; } @override @@ -368,7 +368,7 @@ class AppLocalizationsJa extends AppLocalizations { String get download_location => 'ダウンロード先'; @override - String get local_library => 'ローカルライブラリ'; + String get local_library => '端末内ライブラリ'; @override String get add_library_location => 'ライブラリに追加'; @@ -395,10 +395,10 @@ class AppLocalizationsJa extends AppLocalizations { String get system_default => 'システムの既定値'; @override - String get market_place_region => '市場の地域'; + String get market_place_region => '音楽市場の地域'; @override - String get recommendation_country => '推薦先の国'; + String get recommendation_country => 'おすすめの国'; @override String get appearance => '外観'; @@ -588,7 +588,7 @@ class AppLocalizationsJa extends AppLocalizations { String get invidious_instance => 'Invidiousサーバーインスタンス'; @override - String get invidious_description => 'トラックマッチングに使用するInvidiousサーバーインスタンス'; + String get invidious_description => '曲の一致に使用するInvidiousサーバーインスタンス'; @override String get invidious_warning => '一部はうまく機能しない可能性があります。自己責任で使用してください'; @@ -804,17 +804,17 @@ class AppLocalizationsJa extends AppLocalizations { String get search_mode => '検索モード'; @override - String get audio_source => '音声ソース'; + String get audio_source => '音声の提供元'; @override - String get ok => '分かりました'; + String get ok => 'OK'; @override String get failed_to_encrypt => '暗号化に失敗しました'; @override String get encryption_failed_warning => - 'Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください'; + 'SpoTubeはデータを安全に保存するために暗号化を用いますが、暗号化に失敗しました。このため、安全でない保存領域への保存に切り替えます\nOSがLinuxなら、gnome-keyring、kde-wallet、keepassxcなどの管理ツールがインストールされていることを確認してください'; @override String get querying_info => '情報を取得中...'; @@ -824,7 +824,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String piped_down_error_instructions(Object pipedInstance) { - return 'Pipedインスタンス$pipedInstanceは現在ダウンしています\n\nインスタンスを変更するか、\'APIタイプ\'を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください'; + return 'Pipedインスタンス $pipedInstance は現在ダウンしています\n\nインスタンスを変更するか、「APIの種類」を公式のYouTube APIに変更してください\n\n変更後にアプリを再起動してください'; } @override @@ -834,7 +834,7 @@ class AppLocalizationsJa extends AppLocalizations { String get connection_restored => 'インターネット接続が復旧しました'; @override - String get use_system_title_bar => 'システムタイトルバーを使用する'; + String get use_system_title_bar => 'システムのタイトルバーを使う'; @override String get crunching_results => '結果を処理中...'; @@ -843,40 +843,40 @@ class AppLocalizationsJa extends AppLocalizations { String get search_to_get_results => '結果を取得するために検索'; @override - String get use_amoled_mode => 'AMOLEDモードを使用する'; + String get use_amoled_mode => 'AMOLEDモードを使用'; @override - String get pitch_dark_theme => 'ピッチブラックダートテーマ'; + String get pitch_dark_theme => 'ピッチブラック ダークテーマ'; @override - String get normalize_audio => 'オーディオを正規化する'; + String get normalize_audio => '音声を正規化'; @override - String get change_cover => 'カバーを変更する'; + String get change_cover => 'カバーを変更'; @override - String get add_cover => 'カバーを追加する'; + String get add_cover => 'カバーを追加'; @override - String get restore_defaults => 'デフォルト値に戻す'; + String get restore_defaults => '設定を初期化'; @override - String get download_music_codec => '音楽コーデックをダウンロードする'; + String get download_music_codec => 'ダウンロード用の音声コーデック'; @override - String get streaming_music_codec => 'ストリーミング音楽コーデック'; + String get streaming_music_codec => 'ストリーミング用の音声コーデック'; @override - String get login_with_lastfm => 'Last.fmでログインする'; + String get login_with_lastfm => 'Last.fmでログイン'; @override - String get connect => '接続する'; + String get connect => '接続'; @override - String get disconnect_lastfm => 'Last.fmから切断する'; + String get disconnect_lastfm => 'Last.fmから切断'; @override - String get disconnect => '切断する'; + String get disconnect => '切断'; @override String get username => 'ユーザー名'; @@ -885,10 +885,10 @@ class AppLocalizationsJa extends AppLocalizations { String get password => 'パスワード'; @override - String get login => 'ログインする'; + String get login => 'ログイン'; @override - String get login_with_your_lastfm => 'あなたのLast.fmアカウントでログインする'; + String get login_with_your_lastfm => 'Last.fmアカウントでログイン'; @override String get scrobble_to_lastfm => 'Last.fmにスクロブルする'; @@ -897,7 +897,7 @@ class AppLocalizationsJa extends AppLocalizations { String get go_to_album => 'アルバムに移動'; @override - String get discord_rich_presence => 'ディスコードリッチプレゼンス'; + String get discord_rich_presence => 'Discord リッチプレゼンス'; @override String get browse_all => 'すべてを閲覧'; @@ -912,7 +912,7 @@ class AppLocalizationsJa extends AppLocalizations { String get friends => '友達'; @override - String get no_lyrics_available => '申し訳ありませんが、このトラックの歌詞を見つけることができません'; + String get no_lyrics_available => 'すみません、この曲の歌詞が見つかりません'; @override String get start_a_radio => 'ラジオを開始'; @@ -927,28 +927,28 @@ class AppLocalizationsJa extends AppLocalizations { String get endless_playback => 'エンドレス再生'; @override - String get delete_playlist => 'プレイリストを削除'; + String get delete_playlist => '再生リストを削除'; @override - String get delete_playlist_confirmation => 'このプレイリストを削除してもよろしいですか?'; + String get delete_playlist_confirmation => 'この再生リストを削除しますか?'; @override - String get local_tracks => 'ローカルトラック'; + String get local_tracks => '端末内の曲'; @override - String get local_tab => 'ローカル'; + String get local_tab => '端末内'; @override String get song_link => '曲のリンク'; @override - String get skip_this_nonsense => 'この愚かなことをスキップ'; + String get skip_this_nonsense => 'こんなことはスキップ'; @override String get freedom_of_music => '“音楽の自由”'; @override - String get freedom_of_music_palm => '“手のひらの中の音楽の自由”'; + String get freedom_of_music_palm => '“音楽の自由を思いのままに”'; @override String get get_started => 'さあ始めましょう'; @@ -957,13 +957,13 @@ class AppLocalizationsJa extends AppLocalizations { String get youtube_source_description => '推奨され、最適に機能します。'; @override - String get piped_source_description => '自由に感じますか? YouTubeと同じですが、はるかに無料です。'; + String get piped_source_description => '自由を感じる?YouTubeと同じだけど、はるかに自由です。'; @override - String get jiosaavn_source_description => '南アジア地域向けの最適です。'; + String get jiosaavn_source_description => '南アジア地域では最適です。'; @override - String get invidious_source_description => 'Pipedに似ていますが、より高い可用性があります。'; + String get invidious_source_description => 'Pipedに似ていますが、より利用性があります。'; @override String highest_quality(Object quality) { @@ -971,56 +971,55 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get select_audio_source => 'オーディオソースを選択'; + String get select_audio_source => '音声の提供元を選択'; @override - String get endless_playback_description => '新しい曲をキューの最後に自動的に追加'; + String get endless_playback_description => 'キューの最後に新しい曲を自動で追加'; @override String get choose_your_region => '地域を選択'; @override - String get choose_your_region_description => - 'これにより、Spotubeがあなたの場所に適したコンテンツを表示できます。'; + String get choose_your_region_description => 'Spotubeがあなたの地域に適したコンテンツを表示します。'; @override String get choose_your_language => '言語を選択してください'; @override - String get help_project_grow => 'このプロジェクトの成長を支援する'; + String get help_project_grow => 'プロジェクトの成長を支援する'; @override String get help_project_grow_description => - 'Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグを報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。'; + 'SpoTubeはオープンソースプロジェクトです。貢献したり、バグ報告したり、新機能を提案することで、プロジェクトの成長に貢献できます。'; @override - String get contribute_on_github => 'GitHubで貢献する'; + String get contribute_on_github => 'GitHubで貢献'; @override - String get donate_on_open_collective => 'Open Collectiveで寄付する'; + String get donate_on_open_collective => 'Open Collectiveで寄付'; @override String get browse_anonymously => '匿名で閲覧する'; @override - String get enable_connect => '接続を有効にする'; + String get enable_connect => '接続する'; @override - String get enable_connect_description => '他のデバイスからSpotubeを制御する'; + String get enable_connect_description => '他の端末からSpotubeを制御する'; @override - String get devices => 'デバイス'; + String get devices => '機器'; @override - String get select => '選択する'; + String get select => '選択'; @override String connect_client_alert(Object client) { - return '$client によって操作されています'; + return '$client から操作されています'; } @override - String get this_device => 'このデバイス'; + String get this_device => 'この端末'; @override String get remote => 'リモート'; @@ -1030,23 +1029,23 @@ class AppLocalizationsJa extends AppLocalizations { @override String and_n_more(Object count) { - return 'そして $count つのアイテム'; + return 'さらに $count 項目'; } @override - String get recently_played => '最近再生された'; + String get recently_played => '最近聴いた曲'; @override - String get browse_more => 'もっと見る'; + String get browse_more => 'もっと表示'; @override String get no_title => 'タイトルなし'; @override - String get not_playing => '再生中ではありません'; + String get not_playing => '再生なし'; @override - String get epic_failure => '壮大な失敗!'; + String get epic_failure => '壮大なエラー!'; @override String added_num_tracks_to_queue(Object tracks_length) { @@ -1054,7 +1053,7 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get spotube_has_an_update => 'Spotube にアップデートがあります'; + String get spotube_has_an_update => 'Spotube の最新版あり'; @override String get download_now => '今すぐダウンロード'; @@ -1073,20 +1072,19 @@ class AppLocalizationsJa extends AppLocalizations { String get read_the_latest => '最新の '; @override - String get release_notes => 'リリースノート'; + String get release_notes => '更新情報を読む'; @override - String get pick_color_scheme => 'カラースキームを選択'; + String get pick_color_scheme => 'カラーテーマを選択'; @override String get save => '保存'; @override - String get choose_the_device => 'デバイスを選択:'; + String get choose_the_device => '端末を選択:'; @override - String get multiple_device_connected => - '複数のデバイスが接続されています。\nこのアクションを実行するデバイスを選択してください'; + String get multiple_device_connected => '複数の端末が接続されています。\nこの操作を実行する端末を選択'; @override String get nothing_found => '何も見つかりませんでした'; @@ -1133,7 +1131,7 @@ class AppLocalizationsJa extends AppLocalizations { String get birthday => '誕生日'; @override - String get subscription => 'サブスクリプション'; + String get subscription => '登録'; @override String get not_born => '未出生'; @@ -1159,11 +1157,10 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get streaming_fees_hypothetical => - '*これは Spotify のストリームあたりの支払い\nが \$0.003 から \$0.005 であると仮定して計算されています。\nこれは、Spotify でその曲を聴いた場合にアーティストにいくら支払ったかの\n洞察を得るための仮定の計算です。'; + String get streaming_fees_hypothetical => 'ストリーミング料金 (概算)'; @override - String get minutes_listened => 'リスニング時間'; + String get minutes_listened => '視聴時間'; @override String get streamed_songs => 'ストリーミングされた曲'; @@ -1183,7 +1180,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*これは、オンライン音楽ストリーミングプラットフォームの1ストリームあたりの平均支払い額である\$0.003〜\$0.005に基づいて計算されています。これは、ユーザーが異なる音楽ストリーミングプラットフォームで曲を聴いた場合に、アーティストにどれだけ支払ったかを把握するための仮説的な計算です。'; @override String count_mins(Object minutes) { @@ -1200,32 +1197,32 @@ class AppLocalizationsJa extends AppLocalizations { String get summary_songs => '曲'; @override - String get summary_streamed_overall => '全体のストリーミング'; + String get summary_streamed_overall => 'まるごと聴いた'; @override - String get summary_owed_to_artists => '今月アーティストに支払うべき額'; + String get summary_owed_to_artists => '今月アーティストに払う\nべき額'; @override - String get summary_artists => 'アーティストの'; + String get summary_artists => 'アーティスト'; @override - String get summary_music_reached_you => '音楽があなたに届いた'; + String get summary_music_reached_you => 'の音楽が届いた'; @override String get summary_full_albums => 'フルアルバム'; @override - String get summary_got_your_love => 'あなたの愛を受け取った'; + String get summary_got_your_love => 'があなたの愛を受け取った'; @override - String get summary_playlists => 'プレイリスト'; + String get summary_playlists => '再生リスト'; @override - String get summary_were_on_repeat => 'リピートしていた'; + String get summary_were_on_repeat => 'をリピートしました'; @override String total_money(Object money) { - return '合計 $money'; + return '計 $money'; } @override @@ -1233,10 +1230,10 @@ class AppLocalizationsJa extends AppLocalizations { @override String get webview_not_found_description => - 'デバイスにWebviewランタイムがインストールされていません。\nインストールされている場合は、environment PATHにあることを確認してください\n\nインストール後、アプリを再起動してください'; + '端末にWebviewランタイムがインストールされていません。\nインストールされている場合は、環境変数のパスにあるか確認してください\n\nインストール後、アプリを再起動してください'; @override - String get unsupported_platform => 'サポートされていないプラットフォーム'; + String get unsupported_platform => '未対応のプラットフォーム'; @override String get cache_music => '音楽をキャッシュ'; @@ -1276,22 +1273,22 @@ class AppLocalizationsJa extends AppLocalizations { String get undo => '元に戻す'; @override - String get download_all => 'すべてをダウンロード'; + String get download_all => 'すべてダウンロード'; @override - String get add_all_to_playlist => 'すべてをプレイリストに追加'; + String get add_all_to_playlist => 'すべて再生リストに追加'; @override - String get add_all_to_queue => 'すべてをキューに追加'; + String get add_all_to_queue => 'すべてキューに追加'; @override - String get play_all_next => '次にすべてを再生'; + String get play_all_next => 'すべてを次に再生'; @override String get pause => '一時停止'; @override - String get view_all => 'すべてを見る'; + String get view_all => 'すべて表示'; @override String get no_tracks_added_yet => 'まだ曲を追加していないようです'; @@ -1309,7 +1306,7 @@ class AppLocalizationsJa extends AppLocalizations { String get no_favorite_albums_yet => 'まだお気に入りのアルバムを追加していないようです'; @override - String get no_logs_found => 'ログが見つかりませんでした'; + String get no_logs_found => 'ログなし'; @override String get youtube_engine => 'YouTubeエンジン'; @@ -1340,17 +1337,17 @@ class AppLocalizationsJa extends AppLocalizations { String get file_not_found => 'ファイルが見つかりません'; @override - String get custom => 'カスタム'; + String get custom => '独自'; @override - String get add_custom_url => 'カスタムURLを追加'; + String get add_custom_url => '独自にURLを追加'; @override String get edit_port => 'ポートを編集'; @override String get port_helper_msg => - 'デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。'; + '初期設定は-1で、ランダムな番号を示します。ファイアウォールを設定している場合に設定することを推奨します。'; @override String connect_request(Object client) { @@ -1359,4 +1356,155 @@ class AppLocalizationsJa extends AppLocalizations { @override String get connection_request_denied => '接続が拒否されました。ユーザーがアクセスを拒否しました。'; + + @override + String get an_error_occurred => 'エラーが発生しました'; + + @override + String get copy_to_clipboard => 'クリップボードにコピー'; + + @override + String get view_logs => 'ログを表示'; + + @override + String get retry => '再試行'; + + @override + String get no_default_metadata_provider_selected => + 'デフォルトのメタデータプロバイダーが設定されていません'; + + @override + String get manage_metadata_providers => 'メタデータプロバイダーを管理'; + + @override + String get open_link_in_browser => 'リンクをブラウザで開きますか?'; + + @override + String get do_you_want_to_open_the_following_link => '次のリンクを開きますか'; + + @override + String get unsafe_url_warning => + '信頼できないソースからのリンクを開くのは安全ではない場合があります。注意してください!\nリンクをクリップボードにコピーすることもできます。'; + + @override + String get copy_link => 'リンクをコピー'; + + @override + String get building_your_timeline => 'あなたの視聴履歴に基づいてタイムラインを作成しています...'; + + @override + String get official => '公式'; + + @override + String author_name(Object author) { + return '作者: $author'; + } + + @override + String get third_party => 'サードパーティ'; + + @override + String get plugin_requires_authentication => 'プラグインには認証が必要です'; + + @override + String get update_available => 'アップデートが利用可能です'; + + @override + String get supports_scrobbling => 'scrobblingに対応'; + + @override + String get plugin_scrobbling_info => 'このプラグインは、あなたの音楽をscrobbleして視聴履歴を生成します。'; + + @override + String get default_plugin => 'デフォルト'; + + @override + String get set_default => 'デフォルトに設定'; + + @override + String get support => 'サポート'; + + @override + String get support_plugin_development => 'プラグイン開発をサポート'; + + @override + String can_access_name_api(Object name) { + return '- **$name** APIにアクセスできます'; + } + + @override + String get do_you_want_to_install_this_plugin => 'このプラグインをインストールしますか?'; + + @override + String get third_party_plugin_warning => + 'このプラグインはサードパーティのリポジトリからのものです。インストールする前にソースを信頼できるか確認してください。'; + + @override + String get author => '作者'; + + @override + String get this_plugin_can_do_following => 'このプラグインは以下のことができます'; + + @override + String get install => 'インストール'; + + @override + String get install_a_metadata_provider => 'メタデータプロバイダーをインストール'; + + @override + String get no_tracks_playing => '現在再生中のトラックはありません'; + + @override + String get synced_lyrics_not_available => 'この曲の同期歌詞は利用できません。代わりに'; + + @override + String get plain_lyrics => 'シンプルな歌詞'; + + @override + String get tab_instead => 'タブを使用してください。'; + + @override + String get disclaimer => '免責事項'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotubeチームは、いかなる「サードパーティ」プラグインについても責任(法的責任を含む)を負いません。\nご自身の責任でご使用ください。バグや問題については、プラグインリポジトリに報告してください。\n\n「サードパーティ」プラグインが何らかのサービス/法人のToS/DMCAを侵害している場合、その「サードパーティ」プラグインの作者またはホスティングプラットフォーム(例:GitHub/Codeberg)に措置を講じるよう依頼してください。上記に記載されている(「サードパーティ」とラベル付けされた)ものはすべて、パブリック/コミュニティによって維持されているプラグインです。私たちはそれらをキュレーションしていないため、それらに対して措置を講じることはできません。\n\n'; + + @override + String get input_does_not_match_format => '入力が必須フォーマットと一致しません'; + + @override + String get metadata_provider_plugins => 'メタデータプロバイダープラグイン'; + + @override + String get paste_plugin_download_url => + 'ダウンロードURL、GitHub/CodebergリポジトリURL、または.smplugファイルへの直接リンクを貼り付けます'; + + @override + String get download_and_install_plugin_from_url => + 'URLからプラグインをダウンロードしてインストール'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'プラグインの追加に失敗しました: $error'; + } + + @override + String get upload_plugin_from_file => 'ファイルからプラグインをアップロード'; + + @override + String get installed => 'インストール済み'; + + @override + String get available_plugins => '利用可能なプラグイン'; + + @override + String get configure_your_own_metadata_plugin => + '独自のプレイリスト/アルバム/アーティスト/フィードのメタデータプロバイダーを構成'; + + @override + String get audio_scrobblers => 'オーディオスクロッブラー'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_ka.dart b/lib/l10n/generated/app_localizations_ka.dart index 1dbbeb20..a28bd02d 100644 --- a/lib/l10n/generated/app_localizations_ka.dart +++ b/lib/l10n/generated/app_localizations_ka.dart @@ -1203,7 +1203,7 @@ class AppLocalizationsKa extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*ეს გამოითვლება ონლაინ მუსიკალური სტრიმინგის პლატფორმების საშუალო ანაზღაურების საფუძველზე, რომელიც შეადგენს \$0.003-დან \$0.005-მდე. ეს არის ჰიპოთეტური გაანგარიშება, რომელიც მომხმარებელს აძლევს წარმოდგენას, თუ რამდენს გადაუხდიდნენ ისინი არტისტებს, თუ მათ სიმღერებს მოუსმენდნენ სხვადასხვა მუსიკალურ სტრიმინგ პლატფორმაზე.'; @override String count_mins(Object minutes) { @@ -1383,4 +1383,166 @@ class AppLocalizationsKa extends AppLocalizations { @override String get connection_request_denied => 'კავშირი უარყოფილია. მომხმარებელმა უარყო წვდომა.'; + + @override + String get an_error_occurred => 'მოხდა შეცდომა'; + + @override + String get copy_to_clipboard => 'კოპირება ბუფერში'; + + @override + String get view_logs => 'იხილეთ ჟურნალები'; + + @override + String get retry => 'ხელახლა ცდა'; + + @override + String get no_default_metadata_provider_selected => + 'თქვენ არ გაქვთ დაყენებული ნაგულისხმევი მეტამონაცემების პროვაიდერი'; + + @override + String get manage_metadata_providers => + 'მეტამონაცემების პროვაიდერების მართვა'; + + @override + String get open_link_in_browser => 'ბმულის გახსნა ბრაუზერში?'; + + @override + String get do_you_want_to_open_the_following_link => + 'გსურთ გახსნათ შემდეგი ბმული'; + + @override + String get unsafe_url_warning => + 'შეიძლება სახიფათო იყოს ბმულების გახსნა უნდობელი წყაროებიდან. იყავით ფრთხილად!\nასევე შეგიძლიათ დააკოპიროთ ბმული თქვენს ბუფერში.'; + + @override + String get copy_link => 'ბმულის კოპირება'; + + @override + String get building_your_timeline => + 'თქვენი დროის ხაზის აგება თქვენი მოსმენების საფუძველზე...'; + + @override + String get official => 'ოფიციალური'; + + @override + String author_name(Object author) { + return 'ავტორი: $author'; + } + + @override + String get third_party => 'მესამე მხარის'; + + @override + String get plugin_requires_authentication => + 'პლაგინი საჭიროებს ავთენტიფიკაციას'; + + @override + String get update_available => 'განახლება ხელმისაწვდომია'; + + @override + String get supports_scrobbling => 'მხარს უჭერს სქრობლინგს'; + + @override + String get plugin_scrobbling_info => + 'ეს პლაგინი აწარმოებს თქვენი მუსიკის სქრობლინგს, რათა შექმნას თქვენი მოსმენის ისტორია.'; + + @override + String get default_plugin => 'ნაგულისხმევი'; + + @override + String get set_default => 'ნაგულისხმევად დაყენება'; + + @override + String get support => 'მხარდაჭერა'; + + @override + String get support_plugin_development => 'პლაგინის განვითარების მხარდაჭერა'; + + @override + String can_access_name_api(Object name) { + return '- შეუძლია წვდომა **$name** API-ზე'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'გსურთ ამ პლაგინის დაყენება?'; + + @override + String get third_party_plugin_warning => + 'ეს პლაგინი არის მესამე მხარის საცავიდან. გთხოვთ, დარწმუნდეთ, რომ ენდობით წყაროს დაყენებამდე.'; + + @override + String get author => 'ავტორი'; + + @override + String get this_plugin_can_do_following => + 'ამ პლაგინს შეუძლია შემდეგის გაკეთება'; + + @override + String get install => 'დაყენება'; + + @override + String get install_a_metadata_provider => + 'დააყენეთ მეტამონაცემების პროვაიდერი'; + + @override + String get no_tracks_playing => 'ამჟამად არ უკრავს არცერთი ტრეკი'; + + @override + String get synced_lyrics_not_available => + 'ამ სიმღერისთვის სინქრონიზებული ტექსტები არ არის ხელმისაწვდომი. გთხოვთ, გამოიყენოთ'; + + @override + String get plain_lyrics => 'მარტივი ტექსტები'; + + @override + String get tab_instead => 'ჩანართი, სანაცვლოდ.'; + + @override + String get disclaimer => 'პასუხისმგებლობის უარყოფა'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube-ის გუნდი არ იღებს პასუხისმგებლობას (მათ შორის, იურიდიულს) არცერთ \"მესამე მხარის\" პლაგინზე.\nგთხოვთ, გამოიყენოთ ისინი თქვენი რისკის ქვეშ. ნებისმიერი ხარვეზის/პრობლემის შესახებ შეატყობინეთ პლაგინის საცავს.\n\nთუ რომელიმე \"მესამე მხარის\" პლაგინი არღვევს რაიმე სერვისის/იურიდიული პირის ToS/DMCA-ს, გთხოვთ, სთხოვეთ \"მესამე მხარის\" პლაგინის ავტორს ან ჰოსტინგის პლატფორმას, მაგალითად GitHub/Codeberg, მიიღოს ზომები. ზემოთ ჩამოთვლილი (\"მესამე მხარის\" ეტიკეტის მქონე) ყველა არის საჯარო/საზოგადოების მიერ შენარჩუნებული პლაგინები. ჩვენ მათ არ ვაკონტროლებთ, ამიტომ არ შეგვიძლია მათზე რაიმე ზომების მიღება.\n\n'; + + @override + String get input_does_not_match_format => + 'შეყვანა არ ემთხვევა საჭირო ფორმატს'; + + @override + String get metadata_provider_plugins => + 'მეტამონაცემების პროვაიდერების პლაგინები'; + + @override + String get paste_plugin_download_url => + 'ჩასვით ჩამოტვირთვის url ან GitHub/Codeberg-ის რეპოს url ან პირდაპირი ბმული .smplug ფაილზე'; + + @override + String get download_and_install_plugin_from_url => + 'პლაგინის ჩამოტვირთვა და დაყენება url-დან'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'პლაგინის დამატება ვერ მოხერხდა: $error'; + } + + @override + String get upload_plugin_from_file => 'პლაგინის ატვირთვა ფაილიდან'; + + @override + String get installed => 'დაინსტალირებული'; + + @override + String get available_plugins => 'ხელმისაწვდომი პლაგინები'; + + @override + String get configure_your_own_metadata_plugin => + 'დააყენეთ თქვენი საკუთარი პლეილისტის/ალბომის/არტისტის/ფიდის მეტამონაცემების პროვაიდერი'; + + @override + String get audio_scrobblers => 'აუდიო სქრობლერები'; + + @override + String get scrobbling => 'სქრობლინგი'; } diff --git a/lib/l10n/generated/app_localizations_ko.dart b/lib/l10n/generated/app_localizations_ko.dart index dbe0c06f..40104b52 100644 --- a/lib/l10n/generated/app_localizations_ko.dart +++ b/lib/l10n/generated/app_localizations_ko.dart @@ -1185,7 +1185,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*이것은 온라인 음악 스트리밍 플랫폼의 스트림당 평균 지불액인 \$0.003에서 \$0.005를 기준으로 계산됩니다. 이것은 사용자가 다른 음악 스트리밍 플랫폼에서 노래를 들었다면 아티스트에게 얼마를 지불했을지에 대한 통찰력을 제공하기 위한 가상 계산입니다.'; @override String count_mins(Object minutes) { @@ -1361,4 +1361,154 @@ class AppLocalizationsKo extends AppLocalizations { @override String get connection_request_denied => '연결이 거부되었습니다. 사용자가 액세스를 거부했습니다.'; + + @override + String get an_error_occurred => '오류가 발생했습니다'; + + @override + String get copy_to_clipboard => '클립보드에 복사'; + + @override + String get view_logs => '로그 보기'; + + @override + String get retry => '다시 시도'; + + @override + String get no_default_metadata_provider_selected => + '기본 메타데이터 제공자가 설정되지 않았습니다'; + + @override + String get manage_metadata_providers => '메타데이터 제공자 관리'; + + @override + String get open_link_in_browser => '브라우저에서 링크를 여시겠습니까?'; + + @override + String get do_you_want_to_open_the_following_link => '다음 링크를 여시겠습니까'; + + @override + String get unsafe_url_warning => + '신뢰할 수 없는 출처의 링크를 여는 것은 안전하지 않을 수 있습니다. 주의하세요!\n링크를 클립보드에 복사할 수도 있습니다.'; + + @override + String get copy_link => '링크 복사'; + + @override + String get building_your_timeline => '청취 기록을 기반으로 타임라인을 구축하고 있습니다...'; + + @override + String get official => '공식'; + + @override + String author_name(Object author) { + return '저자: $author'; + } + + @override + String get third_party => '타사'; + + @override + String get plugin_requires_authentication => '플러그인에 인증이 필요합니다'; + + @override + String get update_available => '업데이트 사용 가능'; + + @override + String get supports_scrobbling => '스크로블링 지원'; + + @override + String get plugin_scrobbling_info => '이 플러그인은 음악을 스크로블하여 청취 기록을 생성합니다.'; + + @override + String get default_plugin => '기본'; + + @override + String get set_default => '기본값으로 설정'; + + @override + String get support => '지원'; + + @override + String get support_plugin_development => '플러그인 개발 지원'; + + @override + String can_access_name_api(Object name) { + return '- **$name** API에 액세스할 수 있습니다'; + } + + @override + String get do_you_want_to_install_this_plugin => '이 플러그인을 설치하시겠습니까?'; + + @override + String get third_party_plugin_warning => + '이 플러그인은 타사 리포지토리에서 제공됩니다. 설치하기 전에 출처를 신뢰하는지 확인하세요.'; + + @override + String get author => '저자'; + + @override + String get this_plugin_can_do_following => '이 플러그인은 다음을 수행할 수 있습니다'; + + @override + String get install => '설치'; + + @override + String get install_a_metadata_provider => '메타데이터 제공자 설치'; + + @override + String get no_tracks_playing => '현재 재생 중인 트랙이 없습니다'; + + @override + String get synced_lyrics_not_available => '이 노래에 대한 동기화된 가사를 사용할 수 없습니다. 대신'; + + @override + String get plain_lyrics => '일반 가사'; + + @override + String get tab_instead => '탭을 사용하세요.'; + + @override + String get disclaimer => '면책 조항'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube 팀은 어떠한 \"타사\" 플러그인에 대해서도 (법적 포함) 어떠한 책임도 지지 않습니다.\n사용자 자신의 책임하에 사용하시기 바랍니다. 버그/문제에 대해서는 플러그인 리포지토리에 보고해 주세요.\n\n만약 \"타사\" 플러그인이 서비스/법인의 ToS/DMCA를 위반하는 경우, \"타사\" 플러그인 저자 또는 호스팅 플랫폼(예: GitHub/Codeberg)에 조치를 취하도록 요청해 주세요. 위에 나열된 (\"타사\"로 표시된) 플러그인은 모두 공개/커뮤니티에서 유지 관리하는 플러그인입니다. 저희는 이를 큐레이션하지 않으므로 어떠한 조치도 취할 수 없습니다.\n\n'; + + @override + String get input_does_not_match_format => '입력이 필요한 형식과 일치하지 않습니다'; + + @override + String get metadata_provider_plugins => '메타데이터 제공자 플러그인'; + + @override + String get paste_plugin_download_url => + '다운로드 URL, GitHub/Codeberg 리포지토리 URL 또는 .smplug 파일에 대한 직접 링크를 붙여넣으세요'; + + @override + String get download_and_install_plugin_from_url => 'URL에서 플러그인 다운로드 및 설치'; + + @override + String failed_to_add_plugin_error(Object error) { + return '플러그인 추가 실패: $error'; + } + + @override + String get upload_plugin_from_file => '파일에서 플러그인 업로드'; + + @override + String get installed => '설치됨'; + + @override + String get available_plugins => '사용 가능한 플러그인'; + + @override + String get configure_your_own_metadata_plugin => + '자신만의 플레이리스트/앨범/아티스트/피드 메타데이터 제공자 구성'; + + @override + String get audio_scrobblers => '오디오 스크로블러'; + + @override + String get scrobbling => '스크로블링'; } diff --git a/lib/l10n/generated/app_localizations_ne.dart b/lib/l10n/generated/app_localizations_ne.dart index f0aa95ad..18d155fe 100644 --- a/lib/l10n/generated/app_localizations_ne.dart +++ b/lib/l10n/generated/app_localizations_ne.dart @@ -1210,7 +1210,7 @@ class AppLocalizationsNe extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*यो अनलाइन संगीत स्ट्रिमिङ प्लेटफर्मको प्रति स्ट्रिम भुक्तानी \$0.003 देखि \$0.005 को औसतमा आधारित छ। यो एक काल्पनिक गणना हो जुन प्रयोगकर्तालाई उनीहरूले विभिन्न संगीत स्ट्रिमिङ प्लेटफर्ममा आफ्ना गीतहरू सुनेमा कलाकारहरूलाई कति भुक्तानी गर्ने थिए भन्ने बारेमा अन्तरदृष्टि दिनको लागि हो।'; @override String count_mins(Object minutes) { @@ -1390,4 +1390,162 @@ class AppLocalizationsNe extends AppLocalizations { @override String get connection_request_denied => 'जडान अस्वीकृत। प्रयोगकर्ताले पहुँच अस्वीकृत गर्यो।'; + + @override + String get an_error_occurred => 'त्रुटि भयो'; + + @override + String get copy_to_clipboard => 'क्लिपबोर्डमा प्रतिलिपि गर्नुहोस्'; + + @override + String get view_logs => 'लगहरू हेर्नुहोस्'; + + @override + String get retry => 'पुनः प्रयास गर्नुहोस्'; + + @override + String get no_default_metadata_provider_selected => + 'तपाईंले कुनै पूर्वनिर्धारित मेटाडेटा प्रदायक सेट गर्नुभएको छैन'; + + @override + String get manage_metadata_providers => + 'मेटाडेटा प्रदायकहरू प्रबन्ध गर्नुहोस्'; + + @override + String get open_link_in_browser => 'ब्राउजरमा लिङ्क खोल्ने?'; + + @override + String get do_you_want_to_open_the_following_link => + 'के तपाईं निम्न लिङ्क खोल्न चाहनुहुन्छ'; + + @override + String get unsafe_url_warning => + 'अविश्वसनीय स्रोतहरूबाट लिङ्कहरू खोल्नु असुरक्षित हुन सक्छ। सावधान रहनुहोस्!\nतपाईं लिङ्कलाई आफ्नो क्लिपबोर्डमा पनि प्रतिलिपि गर्न सक्नुहुन्छ।'; + + @override + String get copy_link => 'लिङ्क प्रतिलिपि गर्नुहोस्'; + + @override + String get building_your_timeline => + 'तपाईंको सुन्ने आधारमा तपाईंको समयरेखा निर्माण गर्दै...'; + + @override + String get official => 'आधिकारिक'; + + @override + String author_name(Object author) { + return 'लेखक: $author'; + } + + @override + String get third_party => 'तेस्रो-पक्ष'; + + @override + String get plugin_requires_authentication => 'प्लगइनलाई प्रमाणीकरण चाहिन्छ'; + + @override + String get update_available => 'अपडेट उपलब्ध छ'; + + @override + String get supports_scrobbling => 'स्क्रब्बलिंगलाई समर्थन गर्दछ'; + + @override + String get plugin_scrobbling_info => + 'यो प्लगइनले तपाईंको सुन्ने इतिहास उत्पन्न गर्न तपाईंको संगीतलाई स्क्रब्बल गर्दछ।'; + + @override + String get default_plugin => 'पूर्वनिर्धारित'; + + @override + String get set_default => 'पूर्वनिर्धारित सेट गर्नुहोस्'; + + @override + String get support => 'समर्थन'; + + @override + String get support_plugin_development => 'प्लगइन विकासलाई समर्थन गर्नुहोस्'; + + @override + String can_access_name_api(Object name) { + return '- **$name** API मा पहुँच गर्न सक्छ'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'के तपाईं यो प्लगइन स्थापना गर्न चाहनुहुन्छ?'; + + @override + String get third_party_plugin_warning => + 'यो प्लगइन तेस्रो-पक्ष रिपोसिटरीबाट हो। कृपया स्थापना गर्नु अघि तपाईंले स्रोतमा विश्वास गर्नुहुन्छ भनी सुनिश्चित गर्नुहोस्।'; + + @override + String get author => 'लेखक'; + + @override + String get this_plugin_can_do_following => 'यो प्लगइनले निम्न गर्न सक्छ'; + + @override + String get install => 'स्थापना गर्नुहोस्'; + + @override + String get install_a_metadata_provider => + 'मेटाडेटा प्रदायक स्थापना गर्नुहोस्'; + + @override + String get no_tracks_playing => 'हाल कुनै ट्र्याक बजिरहेको छैन'; + + @override + String get synced_lyrics_not_available => + 'यो गीतको लागि सिङ्क गरिएका बोलहरू उपलब्ध छैनन्। कृपया यसको सट्टा'; + + @override + String get plain_lyrics => 'सादा बोलहरू'; + + @override + String get tab_instead => 'ट्याब प्रयोग गर्नुहोस्।'; + + @override + String get disclaimer => 'अस्वीकरण'; + + @override + String get third_party_plugin_dmca_notice => + 'स्पोट्यूब टोलीले कुनै पनि \"तेस्रो-पक्ष\" प्लगइनहरूको लागि कुनै जिम्मेवारी (कानुनी सहित) लिँदैन।\nकृपया तिनीहरूलाई आफ्नो जोखिममा प्रयोग गर्नुहोस्। कुनै पनि बग/समस्याहरूको लागि, कृपया तिनीहरूलाई प्लगइन रिपोसिटरीमा रिपोर्ट गर्नुहोस्।\n\nयदि कुनै \"तेस्रो-पक्ष\" प्लगइनले कुनै सेवा/कानुनी संस्थाको ToS/DMCA तोडिरहेको छ भने, कृपया \"तेस्रो-पक्ष\" प्लगइन लेखक वा होस्टिङ प्लेटफर्म e.g. GitHub/Codeberg लाई कारबाही गर्न अनुरोध गर्नुहोस्। माथि सूचीबद्ध (\"तेस्रो-पक्ष\" लेबल गरिएका) सबै सार्वजनिक/सामुदायिक रूपमा राखिएका प्लगइनहरू हुन्। हामी तिनीहरूलाई क्युरेट गरिरहेका छैनौं, त्यसैले हामी तिनीहरूमा कुनै कारबाही गर्न सक्दैनौं।\n\n'; + + @override + String get input_does_not_match_format => 'इनपुट आवश्यक ढाँचासँग मेल खाँदैन'; + + @override + String get metadata_provider_plugins => 'मेटाडेटा प्रदायक प्लगइनहरू'; + + @override + String get paste_plugin_download_url => + 'डाउनलोड url वा GitHub/Codeberg repo url वा .smplug फाइलमा सिधा लिङ्क टाँस्नुहोस्'; + + @override + String get download_and_install_plugin_from_url => + 'url बाट प्लगइन डाउनलोड र स्थापना गर्नुहोस्'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'प्लगइन थप्न असफल: $error'; + } + + @override + String get upload_plugin_from_file => 'फाइलबाट प्लगइन अपलोड गर्नुहोस्'; + + @override + String get installed => 'स्थापित'; + + @override + String get available_plugins => 'उपलब्ध प्लगइनहरू'; + + @override + String get configure_your_own_metadata_plugin => + 'तपाईंको आफ्नै प्लेलिस्ट/एल्बम/कलाकार/फिड मेटाडेटा प्रदायक कन्फिगर गर्नुहोस्'; + + @override + String get audio_scrobblers => 'अडियो स्क्रब्बलरहरू'; + + @override + String get scrobbling => 'स्क्रब्बलिंग'; } diff --git a/lib/l10n/generated/app_localizations_nl.dart b/lib/l10n/generated/app_localizations_nl.dart index 072daff7..3074e958 100644 --- a/lib/l10n/generated/app_localizations_nl.dart +++ b/lib/l10n/generated/app_localizations_nl.dart @@ -39,7 +39,7 @@ class AppLocalizationsNl extends AppLocalizations { String get featured => 'Aanbevolen'; @override - String get new_releases => 'Nieuwe uitgaves'; + String get new_releases => 'Nieuwe uitgaven'; @override String get songs => 'Liedjes'; @@ -139,7 +139,7 @@ class AppLocalizationsNl extends AppLocalizations { String get sort_album => 'Sorteren op album'; @override - String get sort_duration => 'Sorteer op Duur'; + String get sort_duration => 'Sorteren op lengte'; @override String get sort_tracks => 'Nummers sorteren'; @@ -150,7 +150,7 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get cancel_all => 'Alle annuleren'; + String get cancel_all => 'Alles annuleren'; @override String get filter_artist => 'Artiesten filteren…'; @@ -164,10 +164,10 @@ class AppLocalizationsNl extends AppLocalizations { String get add_artist_to_blacklist => 'Artiest toevoegen aan zwarte lijst'; @override - String get top_tracks => 'Topsporen'; + String get top_tracks => 'Topnummers'; @override - String get fans_also_like => 'Liefhebbers willen ook'; + String get fans_also_like => 'Fans luisteren ook'; @override String get loading => 'Laden…'; @@ -312,10 +312,10 @@ class AppLocalizationsNl extends AppLocalizations { String get slide_to_seek => 'Schuiven om vooruit of achteruit te zoeken'; @override - String get shuffle_playlist => 'Afspeellijst schuifelen'; + String get shuffle_playlist => 'Afspeellijst willekeurig'; @override - String get unshuffle_playlist => 'Afspeellijst onschuifelen'; + String get unshuffle_playlist => 'Afspeellijst op volgorde'; @override String get previous_track => 'Vorige nummer'; @@ -342,7 +342,7 @@ class AppLocalizationsNl extends AppLocalizations { String get queue => 'Wachtrij'; @override - String get alternative_track_sources => 'Alternatieve nummerbronnen'; + String get alternative_track_sources => 'Alternatieve bronnen voor nummers'; @override String get download_track => 'Nummer downloaden'; @@ -776,10 +776,10 @@ class AppLocalizationsNl extends AppLocalizations { String get stop => 'Stoppen'; @override - String get sort_newest => 'Sorteren op nieuwste toegevoegd'; + String get sort_newest => 'Sorteren op recent toegevoegd'; @override - String get sort_oldest => 'Sorteren op oudste toegevoegd'; + String get sort_oldest => 'Sorteren op langst toegevoegd'; @override String get sleep_timer => 'Slaaptimer'; @@ -815,7 +815,7 @@ class AppLocalizationsNl extends AppLocalizations { String get search_mode => 'Zoekmodus'; @override - String get audio_source => 'Audiobron'; + String get audio_source => 'Audio Source'; @override String get ok => 'Oké'; @@ -927,57 +927,56 @@ class AppLocalizationsNl extends AppLocalizations { 'Sorry, geen teksten gevonden voor dit nummer'; @override - String get start_a_radio => 'Start een Radio'; + String get start_a_radio => 'Een radio starten'; @override - String get how_to_start_radio => 'Hoe wilt u de radio starten?'; + String get how_to_start_radio => 'Hoe wil je de radio starten?'; @override String get replace_queue_question => - 'Wilt u de huidige wachtrij vervangen of eraan toevoegen?'; + 'Wil je de huidige wachtrij vervangen of eraan toevoegen?'; @override - String get endless_playback => 'Eindeloze Afspelen'; + String get endless_playback => 'Oneindig afspelen'; @override - String get delete_playlist => 'Verwijder Afspeellijst'; + String get delete_playlist => 'Afspeellijst verwijderen'; @override String get delete_playlist_confirmation => - 'Weet u zeker dat u deze afspeellijst wilt verwijderen?'; + 'Weet je zeker dat je deze afspeellijst wilt verwijderen?'; @override - String get local_tracks => 'Lokale Nummers'; + String get local_tracks => 'Lokale nummers'; @override String get local_tab => 'Lokaal'; @override - String get song_link => 'Nummer Link'; + String get song_link => 'Song-link'; @override - String get skip_this_nonsense => 'Sla deze onzin over'; + String get skip_this_nonsense => 'Deze onzin overslaan'; @override - String get freedom_of_music => '“Vrijheid van Muziek”'; + String get freedom_of_music => '“Vrijheid van muziek”'; @override - String get freedom_of_music_palm => - '“Vrijheid van Muziek in de palm van je hand”'; + String get freedom_of_music_palm => '“Vrijheid van muziek in je hand”'; @override String get get_started => 'Laten we beginnen'; @override - String get youtube_source_description => 'Aanbevolen en werkt het beste.'; + String get youtube_source_description => 'Aangeraden en werkt het best.'; @override String get piped_source_description => - 'Voel je vrij? Hetzelfde als YouTube maar veel gratis.'; + 'Voel je je vrij? Net als YouTube, maar meer vrij.'; @override String get jiosaavn_source_description => - 'Het beste voor de Zuid-Aziatische regio.'; + 'Het beste voor de regio Zuid-Azië.'; @override String get invidious_source_description => @@ -985,41 +984,41 @@ class AppLocalizationsNl extends AppLocalizations { @override String highest_quality(Object quality) { - return 'Hoogste Kwaliteit: $quality'; + return 'Hoogste kwaliteit: $quality'; } @override - String get select_audio_source => 'Selecteer Audiobron'; + String get select_audio_source => 'Audiobron kiezen'; @override String get endless_playback_description => - 'Voeg automatisch nieuwe nummers toe aan het einde van de wachtrij'; + 'Nieuwe nummers automatisch achteraan de wachtrij toevoegen'; @override - String get choose_your_region => 'Kies uw regio'; + String get choose_your_region => 'Kies je regio'; @override String get choose_your_region_description => - 'Dit zal Spotube helpen om de juiste inhoud voor uw locatie te tonen.'; + 'Dit helpt Spotube om de juiste inhoud\nvoor jouw locatie te tonen.'; @override - String get choose_your_language => 'Kies uw taal'; + String get choose_your_language => 'Kies je taal'; @override - String get help_project_grow => 'Help dit project groeien'; + String get help_project_grow => 'Help dit project met groeien'; @override String get help_project_grow_description => - 'Spotube is een open-source project. U kunt dit project helpen groeien door bij te dragen aan het project, bugs te melden of nieuwe functies voor te stellen.'; + 'Spotube is een open-source project. Je kunt dit project helpen groeien door eraan bij te dragen, problemen te melden of nieuwe functies voor te stellen.'; @override - String get contribute_on_github => 'Bijdragen op GitHub'; + String get contribute_on_github => 'Bijdragen on GitHub'; @override - String get donate_on_open_collective => 'Doneren op Open Collective'; + String get donate_on_open_collective => 'Doneren on Open Collective'; @override - String get browse_anonymously => 'Anoniem Bladeren'; + String get browse_anonymously => 'Anoniem browsen'; @override String get enable_connect => 'Verbinding inschakelen'; @@ -1203,7 +1202,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Dit is berekend op basis van de gemiddelde uitbetaling per stream van online muziekstreamingplatforms van \$0,003 tot \$0,005. Dit is een hypothetische berekening om de gebruiker inzicht te geven in hoeveel ze aan de artiesten zouden hebben betaald als ze hun nummer op een ander muziekstreamingplatform zouden beluisteren.'; @override String count_mins(Object minutes) { @@ -1384,4 +1383,163 @@ class AppLocalizationsNl extends AppLocalizations { @override String get connection_request_denied => 'Verbinding geweigerd. Gebruiker heeft toegang geweigerd.'; + + @override + String get an_error_occurred => 'Er is een fout opgetreden'; + + @override + String get copy_to_clipboard => 'Kopiëren naar klembord'; + + @override + String get view_logs => 'Logboeken bekijken'; + + @override + String get retry => 'Opnieuw proberen'; + + @override + String get no_default_metadata_provider_selected => + 'U heeft geen standaard metadata-aanbieder ingesteld'; + + @override + String get manage_metadata_providers => 'Metadata-aanbieders beheren'; + + @override + String get open_link_in_browser => 'Link openen in browser?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Wilt u de volgende link openen'; + + @override + String get unsafe_url_warning => + 'Het kan onveilig zijn om links van onbetrouwbare bronnen te openen. Wees voorzichtig!\nU kunt de link ook naar uw klembord kopiëren.'; + + @override + String get copy_link => 'Link kopiëren'; + + @override + String get building_your_timeline => + 'Uw tijdlijn wordt opgebouwd op basis van uw luistergedrag...'; + + @override + String get official => 'Officieel'; + + @override + String author_name(Object author) { + return 'Auteur: $author'; + } + + @override + String get third_party => 'Derden'; + + @override + String get plugin_requires_authentication => 'Plugin vereist authenticatie'; + + @override + String get update_available => 'Update beschikbaar'; + + @override + String get supports_scrobbling => 'Ondersteunt scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Deze plugin scrobblet uw muziek om uw luistergeschiedenis te genereren.'; + + @override + String get default_plugin => 'Standaard'; + + @override + String get set_default => 'Instellen als standaard'; + + @override + String get support => 'Ondersteuning'; + + @override + String get support_plugin_development => 'Ondersteun plugin-ontwikkeling'; + + @override + String can_access_name_api(Object name) { + return '- Kan de **$name** API benaderen'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Wilt u deze plugin installeren?'; + + @override + String get third_party_plugin_warning => + 'Deze plugin is afkomstig van een repository van derden. Zorg ervoor dat u de bron vertrouwt voordat u installeert.'; + + @override + String get author => 'Auteur'; + + @override + String get this_plugin_can_do_following => + 'Deze plugin kan het volgende doen'; + + @override + String get install => 'Installeren'; + + @override + String get install_a_metadata_provider => + 'Een metadata-aanbieder installeren'; + + @override + String get no_tracks_playing => 'Er wordt momenteel geen nummer afgespeeld'; + + @override + String get synced_lyrics_not_available => + 'Gesynchroniseerde songteksten zijn niet beschikbaar voor dit nummer. Gebruik in plaats daarvan het tabblad'; + + @override + String get plain_lyrics => 'Eenvoudige songteksten'; + + @override + String get tab_instead => 'in plaats daarvan.'; + + @override + String get disclaimer => 'Disclaimer'; + + @override + String get third_party_plugin_dmca_notice => + 'Het Spotube-team draagt geen enkele verantwoordelijkheid (inclusief juridische) voor \"derden\" plugins.\nGebruik ze op eigen risico. Voor bugs/problemen kunt u deze melden bij de plugin-repository.\n\nAls een \"derden\" plugin de ToS/DMCA van een service/juridische entiteit schendt, vraag dan de auteur van de \"derden\" plugin of het hostingplatform, bijvoorbeeld GitHub/Codeberg, om actie te ondernemen. De hierboven vermelde (gelabelde \"derden\") plugins zijn allemaal openbare/door de gemeenschap onderhouden plugins. We beheren ze niet, dus we kunnen geen actie tegen ze ondernemen.\n\n'; + + @override + String get input_does_not_match_format => + 'Invoer komt niet overeen met het vereiste formaat'; + + @override + String get metadata_provider_plugins => 'Metadata-aanbieder Plugins'; + + @override + String get paste_plugin_download_url => + 'Plak de download-URL of de URL van de GitHub/Codeberg-repository of een directe link naar het .smplug-bestand'; + + @override + String get download_and_install_plugin_from_url => + 'Download en installeer de plugin via URL'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Kon de plugin niet toevoegen: $error'; + } + + @override + String get upload_plugin_from_file => 'Plugin uploaden vanuit bestand'; + + @override + String get installed => 'Geïnstalleerd'; + + @override + String get available_plugins => 'Beschikbare plugins'; + + @override + String get configure_your_own_metadata_plugin => + 'Configureer uw eigen metadata-aanbieder voor afspeellijst/album/artiest/feed'; + + @override + String get audio_scrobblers => 'Audioscrobblers'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_pl.dart b/lib/l10n/generated/app_localizations_pl.dart index 51bd0283..969204da 100644 --- a/lib/l10n/generated/app_localizations_pl.dart +++ b/lib/l10n/generated/app_localizations_pl.dart @@ -1203,7 +1203,7 @@ class AppLocalizationsPl extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Jest to obliczone na podstawie średniej wypłaty z internetowych platform streamingowych za jeden stream w wysokości 0,003 do 0,005 USD. Jest to hipotetyczne obliczenie, które ma na celu dać użytkownikowi wgląd w to, ile zapłaciłby artystom, gdyby słuchał ich piosenek na różnych platformach streamingowych.'; @override String count_mins(Object minutes) { @@ -1385,4 +1385,163 @@ class AppLocalizationsPl extends AppLocalizations { @override String get connection_request_denied => 'Połączenie odrzucone. Użytkownik odmówił dostępu.'; + + @override + String get an_error_occurred => 'Wystąpił błąd'; + + @override + String get copy_to_clipboard => 'Kopiuj do schowka'; + + @override + String get view_logs => 'Wyświetl logi'; + + @override + String get retry => 'Ponów'; + + @override + String get no_default_metadata_provider_selected => + 'Nie masz ustawionego domyślnego dostawcy metadanych'; + + @override + String get manage_metadata_providers => 'Zarządzaj dostawcami metadanych'; + + @override + String get open_link_in_browser => 'Otworzyć link w przeglądarce?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Czy chcesz otworzyć następujący link'; + + @override + String get unsafe_url_warning => + 'Otwieranie linków z niezaufanych źródeł może być niebezpieczne. Zachowaj ostrożność!\nMożesz również skopiować link do schowka.'; + + @override + String get copy_link => 'Kopiuj link'; + + @override + String get building_your_timeline => + 'Budowanie Twojej osi czasu na podstawie Twoich odsłuchań...'; + + @override + String get official => 'Oficjalny'; + + @override + String author_name(Object author) { + return 'Autor: $author'; + } + + @override + String get third_party => 'Zewnętrzny'; + + @override + String get plugin_requires_authentication => + 'Wtyczka wymaga uwierzytelnienia'; + + @override + String get update_available => 'Dostępna aktualizacja'; + + @override + String get supports_scrobbling => 'Obsługuje scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Ta wtyczka scrobbluje Twoją muzykę, aby wygenerować historię odsłuchań.'; + + @override + String get default_plugin => 'Domyślna'; + + @override + String get set_default => 'Ustaw jako domyślną'; + + @override + String get support => 'Wsparcie'; + + @override + String get support_plugin_development => 'Wspieraj rozwój wtyczki'; + + @override + String can_access_name_api(Object name) { + return '- Może uzyskać dostęp do API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Czy chcesz zainstalować tę wtyczkę?'; + + @override + String get third_party_plugin_warning => + 'Ta wtyczka pochodzi z zewnętrznego repozytorium. Upewnij się, że ufasz źródłu przed instalacją.'; + + @override + String get author => 'Autor'; + + @override + String get this_plugin_can_do_following => + 'Ta wtyczka może wykonywać następujące czynności'; + + @override + String get install => 'Instaluj'; + + @override + String get install_a_metadata_provider => 'Zainstaluj dostawcę metadanych'; + + @override + String get no_tracks_playing => 'Obecnie nie odtwarzany jest żaden utwór'; + + @override + String get synced_lyrics_not_available => + 'Zsynchronizowane teksty nie są dostępne dla tego utworu. Zamiast tego użyj zakładki'; + + @override + String get plain_lyrics => 'Zwykłe teksty'; + + @override + String get tab_instead => 'zamiast tego.'; + + @override + String get disclaimer => 'Zastrzeżenie'; + + @override + String get third_party_plugin_dmca_notice => + 'Zespół Spotube nie ponosi żadnej odpowiedzialności (w tym prawnej) za żadne wtyczki \"zewnętrzne\".\nUżywaj ich na własne ryzyko. Wszelkie błędy/problemy prosimy zgłaszać w repozytorium wtyczki.\n\nJeśli jakakolwiek wtyczka \"zewnętrzna\" narusza ToS/DMCA jakiejkolwiek usługi/podmiotu prawnego, prosimy o kontakt z autorem wtyczki \"zewnętrznej\" lub platformą hostingową, np. GitHub/Codeberg, w celu podjęcia działań. Wymienione powyżej (oznaczone jako \"zewnętrzne\") są publicznymi wtyczkami utrzymywanymi przez społeczność. Nie kuratujemy ich, więc nie możemy podjąć żadnych działań w ich sprawie.\n\n'; + + @override + String get input_does_not_match_format => + 'Wprowadzony tekst nie pasuje do wymaganego formatu'; + + @override + String get metadata_provider_plugins => 'Wtyczki dostawców metadanych'; + + @override + String get paste_plugin_download_url => + 'Wklej adres URL do pobrania lub adres URL repozytorium GitHub/Codeberg lub bezpośredni link do pliku .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Pobierz i zainstaluj wtyczkę z adresu URL'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Nie udało się dodać wtyczki: $error'; + } + + @override + String get upload_plugin_from_file => 'Prześlij wtyczkę z pliku'; + + @override + String get installed => 'Zainstalowane'; + + @override + String get available_plugins => 'Dostępne wtyczki'; + + @override + String get configure_your_own_metadata_plugin => + 'Skonfiguruj własnego dostawcę metadanych dla playlisty/albumu/artysty/kanału'; + + @override + String get audio_scrobblers => 'Scrobblery audio'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index f4d7d57a..35d9881d 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -1204,7 +1204,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Isso é calculado com base no pagamento médio por stream de plataformas de streaming de música online de US\$ 0,003 a US\$ 0,005. Esta é uma estimativa hipotética para dar ao usuário uma ideia de quanto ele teria pago aos artistas se ouvisse sua música em diferentes plataformas de streaming de música.'; @override String count_mins(Object minutes) { @@ -1383,4 +1383,162 @@ class AppLocalizationsPt extends AppLocalizations { @override String get connection_request_denied => 'Conexão negada. O usuário negou o acesso .'; + + @override + String get an_error_occurred => 'Ocorreu um erro'; + + @override + String get copy_to_clipboard => 'Copiar para a área de transferência'; + + @override + String get view_logs => 'Ver logs'; + + @override + String get retry => 'Tentar novamente'; + + @override + String get no_default_metadata_provider_selected => + 'Você não tem um provedor de metadados padrão definido'; + + @override + String get manage_metadata_providers => 'Gerenciar provedores de metadados'; + + @override + String get open_link_in_browser => 'Abrir link no navegador?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Você deseja abrir o seguinte link'; + + @override + String get unsafe_url_warning => + 'Pode ser inseguro abrir links de fontes não confiáveis. Tenha cautela!\nVocê também pode copiar o link para sua área de transferência.'; + + @override + String get copy_link => 'Copiar link'; + + @override + String get building_your_timeline => + 'Construindo sua linha do tempo com base em suas audições...'; + + @override + String get official => 'Oficial'; + + @override + String author_name(Object author) { + return 'Autor: $author'; + } + + @override + String get third_party => 'Terceiros'; + + @override + String get plugin_requires_authentication => 'Plugin requer autenticação'; + + @override + String get update_available => 'Atualização disponível'; + + @override + String get supports_scrobbling => 'Suporta scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Este plugin faz o scrobbling de sua música para gerar seu histórico de audição.'; + + @override + String get default_plugin => 'Padrão'; + + @override + String get set_default => 'Definir como padrão'; + + @override + String get support => 'Suporte'; + + @override + String get support_plugin_development => 'Apoiar o desenvolvimento do plugin'; + + @override + String can_access_name_api(Object name) { + return '- Pode acessar a API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Você deseja instalar este plugin?'; + + @override + String get third_party_plugin_warning => + 'Este plugin é de um repositório de terceiros. Certifique-se de que você confia na fonte antes de instalá-lo.'; + + @override + String get author => 'Autor'; + + @override + String get this_plugin_can_do_following => + 'Este plugin pode fazer o seguinte'; + + @override + String get install => 'Instalar'; + + @override + String get install_a_metadata_provider => 'Instalar um provedor de metadados'; + + @override + String get no_tracks_playing => 'Nenhuma música sendo reproduzida no momento'; + + @override + String get synced_lyrics_not_available => + 'As letras sincronizadas não estão disponíveis para esta música. Por favor, use a aba'; + + @override + String get plain_lyrics => 'Letras simples'; + + @override + String get tab_instead => 'em vez disso.'; + + @override + String get disclaimer => 'Aviso'; + + @override + String get third_party_plugin_dmca_notice => + 'A equipe Spotube não se responsabiliza (incluindo legalmente) por quaisquer plugins de \"terceiros\".\nUse-os por sua conta e risco. Para quaisquer bugs/problemas, por favor, relate-os ao repositório do plugin.\n\nSe algum plugin de \"terceiros\" estiver violando os Termos de Serviço/DMCA de qualquer serviço/entidade legal, por favor, peça ao autor do plugin \"terceiro\" ou à plataforma de hospedagem, por exemplo, GitHub/Codeberg, para tomar medidas. Os plugins listados acima (rotulados como \"terceiros\") são todos plugins públicos/mantidos pela comunidade. Não os estamos curando, então não podemos tomar nenhuma medida sobre eles.\n\n'; + + @override + String get input_does_not_match_format => + 'A entrada não corresponde ao formato exigido'; + + @override + String get metadata_provider_plugins => 'Plugins do provedor de metadados'; + + @override + String get paste_plugin_download_url => + 'Cole a url de download ou a url do repositório GitHub/Codeberg ou o link direto para o arquivo .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Baixar e instalar o plugin a partir da url'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Falha ao adicionar plugin: $error'; + } + + @override + String get upload_plugin_from_file => 'Carregar plugin a partir de arquivo'; + + @override + String get installed => 'Instalado'; + + @override + String get available_plugins => 'Plugins disponíveis'; + + @override + String get configure_your_own_metadata_plugin => + 'Configure seu próprio provedor de metadados de playlist/álbum/artista/feed'; + + @override + String get audio_scrobblers => 'Scrobblers de áudio'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index 7403a674..e4cd090b 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -1205,7 +1205,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Это рассчитано на основе средней выплаты за прослушивание на онлайн-платформах для потоковой передачи музыки в размере от 0,003 до 0,005 долларов США. Это гипотетический расчет, чтобы дать пользователю представление о том, сколько бы они заплатили артистам, если бы слушали их песни на разных музыкальных стриминговых платформах.'; @override String count_mins(Object minutes) { @@ -1385,4 +1385,163 @@ class AppLocalizationsRu extends AppLocalizations { @override String get connection_request_denied => 'Подключение отклонено. Пользователь отказал в доступе.'; + + @override + String get an_error_occurred => 'Произошла ошибка'; + + @override + String get copy_to_clipboard => 'Скопировать в буфер обмена'; + + @override + String get view_logs => 'Просмотреть журналы'; + + @override + String get retry => 'Повторить'; + + @override + String get no_default_metadata_provider_selected => + 'Вы не выбрали поставщика метаданных по умолчанию'; + + @override + String get manage_metadata_providers => 'Управление поставщиками метаданных'; + + @override + String get open_link_in_browser => 'Открыть ссылку в браузере?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Вы хотите открыть следующую ссылку'; + + @override + String get unsafe_url_warning => + 'Открытие ссылок из ненадежных источников может быть небезопасным. Будьте осторожны!\nВы также можете скопировать ссылку в буфер обмена.'; + + @override + String get copy_link => 'Копировать ссылку'; + + @override + String get building_your_timeline => + 'Создание вашей временной шкалы на основе ваших прослушиваний...'; + + @override + String get official => 'Официальный'; + + @override + String author_name(Object author) { + return 'Автор: $author'; + } + + @override + String get third_party => 'Сторонний'; + + @override + String get plugin_requires_authentication => 'Плагин требует аутентификации'; + + @override + String get update_available => 'Доступно обновление'; + + @override + String get supports_scrobbling => 'Поддерживает скробблинг'; + + @override + String get plugin_scrobbling_info => + 'Этот плагин скробблит вашу музыку для создания вашей истории прослушиваний.'; + + @override + String get default_plugin => 'По умолчанию'; + + @override + String get set_default => 'Установить по умолчанию'; + + @override + String get support => 'Поддержка'; + + @override + String get support_plugin_development => 'Поддержать разработку плагина'; + + @override + String can_access_name_api(Object name) { + return '- Может получить доступ к API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Вы хотите установить этот плагин?'; + + @override + String get third_party_plugin_warning => + 'Этот плагин из стороннего репозитория. Пожалуйста, убедитесь, что вы доверяете источнику перед установкой.'; + + @override + String get author => 'Автор'; + + @override + String get this_plugin_can_do_following => + 'Этот плагин может выполнять следующее'; + + @override + String get install => 'Установить'; + + @override + String get install_a_metadata_provider => 'Установить поставщика метаданных'; + + @override + String get no_tracks_playing => + 'В настоящее время не воспроизводится ни один трек'; + + @override + String get synced_lyrics_not_available => + 'Синхронизированные тексты недоступны для этой песни. Пожалуйста, используйте вкладку'; + + @override + String get plain_lyrics => 'Простые тексты'; + + @override + String get tab_instead => 'вместо этого.'; + + @override + String get disclaimer => 'Отказ от ответственности'; + + @override + String get third_party_plugin_dmca_notice => + 'Команда Spotube не несет никакой ответственности (в том числе юридической) за какие-либо \"сторонние\" плагины.\nПожалуйста, используйте их на свой страх и риск. О любых ошибках/проблемах сообщайте в репозиторий плагина.\n\nЕсли какой-либо \"сторонний\" плагин нарушает ToS/DMCA какого-либо сервиса/юридического лица, пожалуйста, попросите автора плагина \"стороннего\" или хостинговую платформу, например, GitHub/Codeberg, принять меры. Перечисленные выше (помеченные как \"сторонние\") являются общедоступными/поддерживаемыми сообществом плагинами. Мы не курируем их, поэтому не можем принимать по ним никаких мер.\n\n'; + + @override + String get input_does_not_match_format => + 'Введенные данные не соответствуют требуемому формату'; + + @override + String get metadata_provider_plugins => 'Плагины поставщика метаданных'; + + @override + String get paste_plugin_download_url => + 'Вставьте URL-адрес для загрузки или URL-адрес репозитория GitHub/Codeberg или прямую ссылку на файл .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Загрузить и установить плагин по URL-адресу'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Не удалось добавить плагин: $error'; + } + + @override + String get upload_plugin_from_file => 'Загрузить плагин из файла'; + + @override + String get installed => 'Установлено'; + + @override + String get available_plugins => 'Доступные плагины'; + + @override + String get configure_your_own_metadata_plugin => + 'Настройте свой собственный поставщик метаданных для плейлиста/альбома/артиста/ленты'; + + @override + String get audio_scrobblers => 'Аудио скробблеры'; + + @override + String get scrobbling => 'Скробблинг'; } diff --git a/lib/l10n/generated/app_localizations_ta.dart b/lib/l10n/generated/app_localizations_ta.dart index 22507186..0a131edd 100644 --- a/lib/l10n/generated/app_localizations_ta.dart +++ b/lib/l10n/generated/app_localizations_ta.dart @@ -1210,7 +1210,7 @@ class AppLocalizationsTa extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*இது சராசரி ஆன்லைன் இசை ஸ்ட்ரீமிங் தளத்தின் ஒரு ஸ்ட்ரீமிற்கான \$0.003 முதல் \$0.005 வரையிலான கட்டணத்தின் அடிப்படையில் கணக்கிடப்படுகிறது. இது ஒரு கற்பனையான கணக்கீடு ஆகும், இது பயனர்கள் வெவ்வேறு இசை ஸ்ட்ரீமிங் தளங்களில் தங்கள் பாடல்களைக் கேட்டால் கலைஞர்களுக்கு எவ்வளவு பணம் செலுத்தியிருப்பார்கள் என்பது குறித்த நுண்ணறிவை வழங்குகிறது.'; @override String count_mins(Object minutes) { @@ -1390,4 +1390,164 @@ class AppLocalizationsTa extends AppLocalizations { @override String get connection_request_denied => 'இணைப்பு மறுக்கப்பட்டது. பயனர் அணுகலை மறுத்தார்.'; + + @override + String get an_error_occurred => 'ஒரு பிழை ஏற்பட்டது'; + + @override + String get copy_to_clipboard => 'கிளிப்போர்டுக்கு நகலெடுக்கவும்'; + + @override + String get view_logs => 'பதிவுகளைப் பார்க்கவும்'; + + @override + String get retry => 'மீண்டும் முயற்சிக்கவும்'; + + @override + String get no_default_metadata_provider_selected => + 'நீங்கள் எந்த இயல்புநிலை மெட்டாடேட்டா வழங்குநரையும் அமைக்கவில்லை'; + + @override + String get manage_metadata_providers => + 'மெட்டாடேட்டா வழங்குநர்களை நிர்வகிக்கவும்'; + + @override + String get open_link_in_browser => 'இணைப்பை உலாவியில் திறக்கவா?'; + + @override + String get do_you_want_to_open_the_following_link => + 'பின்வரும் இணைப்பை நீங்கள் திறக்க விரும்புகிறீர்களா'; + + @override + String get unsafe_url_warning => + 'நம்பத்தகாத மூலங்களிலிருந்து இணைப்புகளைத் திறப்பது பாதுகாப்பற்றதாக இருக்கலாம். எச்சரிக்கையாக இருங்கள்!\nநீங்கள் இணைப்பை உங்கள் கிளிப்போர்டுக்கு நகலெடுக்கலாம்.'; + + @override + String get copy_link => 'இணைப்பை நகலெடுக்கவும்'; + + @override + String get building_your_timeline => + 'உங்கள் கேட்டலின் அடிப்படையில் உங்கள் காலவரிசையை உருவாக்குகிறது...'; + + @override + String get official => 'அதிகாரபூர்வமானது'; + + @override + String author_name(Object author) { + return 'ஆசிரியர்: $author'; + } + + @override + String get third_party => 'மூன்றாம் தரப்பு'; + + @override + String get plugin_requires_authentication => + 'பிளகின் அங்கீகாரத்தைக் கோருகிறது'; + + @override + String get update_available => 'புதுப்பிப்பு உள்ளது'; + + @override + String get supports_scrobbling => 'ஸ்க்ரோப்ளிங்கை ஆதரிக்கிறது'; + + @override + String get plugin_scrobbling_info => + 'இந்த பிளகின் உங்கள் கேட்பதின் வரலாற்றை உருவாக்க உங்கள் இசையை ஸ்க்ரோப்ள் செய்கிறது.'; + + @override + String get default_plugin => 'இயல்புநிலை'; + + @override + String get set_default => 'இயல்புநிலையாக அமைக்கவும்'; + + @override + String get support => 'ஆதரவு'; + + @override + String get support_plugin_development => 'பிளகின் வளர்ச்சிக்கு ஆதரவு'; + + @override + String can_access_name_api(Object name) { + return '- **$name** API ஐ அணுக முடியும்'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'இந்த பிளகினை நீங்கள் நிறுவ விரும்புகிறீர்களா?'; + + @override + String get third_party_plugin_warning => + 'இந்த பிளகின் மூன்றாம் தரப்பு களஞ்சியத்திலிருந்து வருகிறது. நிறுவும் முன் மூலத்தை நீங்கள் நம்புகிறீர்கள் என்பதை உறுதிப்படுத்தவும்.'; + + @override + String get author => 'ஆசிரியர்'; + + @override + String get this_plugin_can_do_following => + 'இந்த பிளகின் பின்வருவனவற்றைச் செய்ய முடியும்'; + + @override + String get install => 'நிறுவவும்'; + + @override + String get install_a_metadata_provider => 'மெட்டாடேட்டா வழங்குநரை நிறுவவும்'; + + @override + String get no_tracks_playing => 'தற்போது எந்த பாடலும் இயங்கவில்லை'; + + @override + String get synced_lyrics_not_available => + 'இந்த பாடலுக்கு ஒத்திசைக்கப்பட்ட வரிகள் கிடைக்கவில்லை. தயவுசெய்து'; + + @override + String get plain_lyrics => 'சாதாரண வரிகள்'; + + @override + String get tab_instead => 'தாவலை அதற்கு பதிலாக பயன்படுத்தவும்.'; + + @override + String get disclaimer => 'துறப்பு'; + + @override + String get third_party_plugin_dmca_notice => + 'ஸ்பாட்யூப் குழு எந்த \"மூன்றாம் தரப்பு\" பிளகின்களுக்கும் எந்தப் பொறுப்பையும் (சட்டரீதியான உட்பட) ஏற்காது.\nதயவுசெய்து உங்கள் சொந்த ஆபத்தில் அவற்றைப் பயன்படுத்தவும். ஏதேனும் பிழைகள்/சிக்கல்களுக்கு, பிளகின் களஞ்சியத்தில் அவற்றைப் புகாரளிக்கவும்.\n\nஏதேனும் ஒரு \"மூன்றாம் தரப்பு\" பிளகின் ஒரு சேவை/சட்ட நிறுவனத்தின் ToS/DMCA ஐ மீறினால், தயவுசெய்து \"மூன்றாம் தரப்பு\" பிளகின் ஆசிரியரையோ அல்லது ஹோஸ்டிங் தளத்தையோ, எ.கா. GitHub/Codeberg, நடவடிக்கை எடுக்கக் கோரவும். மேலே பட்டியலிடப்பட்ட (\"மூன்றாம் தரப்பு\" என பெயரிடப்பட்ட) அனைத்து பொதுவான/சமூகத்தால் பராமரிக்கப்படும் பிளகின்கள். நாங்கள் அவற்றை க்யூரேட் செய்யவில்லை, எனவே அவற்றின் மீது எந்த நடவடிக்கையும் எடுக்க முடியாது.\n\n'; + + @override + String get input_does_not_match_format => + 'உள்ளீடு தேவையான வடிவத்துடன் பொருந்தவில்லை'; + + @override + String get metadata_provider_plugins => 'மெட்டாடேட்டா வழங்குநர் பிளகின்கள்'; + + @override + String get paste_plugin_download_url => + 'பதிவிறக்க url அல்லது GitHub/Codeberg repo url அல்லது .smplug கோப்பிற்கான நேரடி இணைப்பை ஒட்டவும்'; + + @override + String get download_and_install_plugin_from_url => + 'url இலிருந்து பிளகினைப் பதிவிறக்கி நிறுவவும்'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'பிளகினைச் சேர்க்கத் தவறிவிட்டது: $error'; + } + + @override + String get upload_plugin_from_file => 'கோப்பிலிருந்து பிளகினைப் பதிவேற்றவும்'; + + @override + String get installed => 'நிறுவப்பட்டது'; + + @override + String get available_plugins => 'கிடைக்கக்கூடிய பிளகின்கள்'; + + @override + String get configure_your_own_metadata_plugin => + 'உங்கள் சொந்த பிளேலிஸ்ட்/ஆல்பம்/கலைஞர்/ஊட்ட மெட்டாடேட்டா வழங்குநரை உள்ளமைக்கவும்'; + + @override + String get audio_scrobblers => 'ஆடியோ ஸ்க்ரோப்ளர்கள்'; + + @override + String get scrobbling => 'ஸ்க்ரோப்ளிங்'; } diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index 69c08b0d..85230bfd 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -1198,7 +1198,7 @@ class AppLocalizationsTh extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*การคำนวณนี้อิงจากค่าเฉลี่ยการจ่ายเงินต่อสตรีมของแพลตฟอร์มสตรีมมิ่งเพลงออนไลน์ที่ \$0.003 ถึง \$0.005 นี่เป็นการคำนวณสมมติฐานเพื่อให้ผู้ใช้เข้าใจว่าพวกเขาจะต้องจ่ายเงินให้ศิลปินเท่าไหร่หากพวกเขาฟังเพลงบนแพลตฟอร์มสตรีมมิ่งเพลงที่แตกต่างกัน'; @override String count_mins(Object minutes) { @@ -1376,4 +1376,161 @@ class AppLocalizationsTh extends AppLocalizations { @override String get connection_request_denied => 'การเชื่อมต่อล้มเหลว ผู้ใช้ปฏิเสธการเข้าถึง'; + + @override + String get an_error_occurred => 'เกิดข้อผิดพลาด'; + + @override + String get copy_to_clipboard => 'คัดลอกไปยังคลิปบอร์ด'; + + @override + String get view_logs => 'ดูบันทึก'; + + @override + String get retry => 'ลองใหม่'; + + @override + String get no_default_metadata_provider_selected => + 'คุณไม่ได้ตั้งค่าผู้ให้บริการเมตาดาต้าเริ่มต้น'; + + @override + String get manage_metadata_providers => 'จัดการผู้ให้บริการเมตาดาต้า'; + + @override + String get open_link_in_browser => 'เปิดลิงก์ในเบราว์เซอร์หรือไม่?'; + + @override + String get do_you_want_to_open_the_following_link => + 'คุณต้องการเปิดลิงก์ต่อไปนี้หรือไม่'; + + @override + String get unsafe_url_warning => + 'การเปิดลิงก์จากแหล่งที่ไม่น่าเชื่อถืออาจไม่ปลอดภัย โปรดระมัดระวัง!\nคุณยังสามารถคัดลอกลิงก์ไปยังคลิปบอร์ดของคุณได้'; + + @override + String get copy_link => 'คัดลอกลิงก์'; + + @override + String get building_your_timeline => + 'กำลังสร้างไทม์ไลน์ของคุณตามการฟังของคุณ...'; + + @override + String get official => 'อย่างเป็นทางการ'; + + @override + String author_name(Object author) { + return 'ผู้เขียน: $author'; + } + + @override + String get third_party => 'บุคคลที่สาม'; + + @override + String get plugin_requires_authentication => + 'ปลั๊กอินต้องมีการรับรองความถูกต้อง'; + + @override + String get update_available => 'มีการอัปเดต'; + + @override + String get supports_scrobbling => 'รองรับการ scrobbling'; + + @override + String get plugin_scrobbling_info => + 'ปลั๊กอินนี้จะ scrobble เพลงของคุณเพื่อสร้างประวัติการฟังของคุณ'; + + @override + String get default_plugin => 'ค่าเริ่มต้น'; + + @override + String get set_default => 'ตั้งค่าเริ่มต้น'; + + @override + String get support => 'สนับสนุน'; + + @override + String get support_plugin_development => 'สนับสนุนการพัฒนาปลั๊กอิน'; + + @override + String can_access_name_api(Object name) { + return '- สามารถเข้าถึง API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'คุณต้องการติดตั้งปลั๊กอินนี้หรือไม่?'; + + @override + String get third_party_plugin_warning => + 'ปลั๊กอินนี้มาจากที่เก็บของบุคคลที่สาม โปรดตรวจสอบให้แน่ใจว่าคุณเชื่อถือแหล่งที่มาก่อนทำการติดตั้ง'; + + @override + String get author => 'ผู้เขียน'; + + @override + String get this_plugin_can_do_following => 'ปลั๊กอินนี้สามารถทำสิ่งต่อไปนี้'; + + @override + String get install => 'ติดตั้ง'; + + @override + String get install_a_metadata_provider => 'ติดตั้งผู้ให้บริการเมตาดาต้า'; + + @override + String get no_tracks_playing => 'ขณะนี้ไม่มีเพลงที่กำลังเล่นอยู่'; + + @override + String get synced_lyrics_not_available => + 'ไม่มีเนื้อเพลงที่ซิงค์สำหรับเพลงนี้ กรุณาใช้แท็บ'; + + @override + String get plain_lyrics => 'เนื้อเพลงธรรมดา'; + + @override + String get tab_instead => 'แทน'; + + @override + String get disclaimer => 'ข้อสงวนสิทธิ์'; + + @override + String get third_party_plugin_dmca_notice => + 'ทีม Spotube ไม่รับผิดชอบใดๆ (รวมถึงทางกฎหมาย) สำหรับปลั๊กอิน \"บุคคลที่สาม\" ใดๆ\nโปรดใช้งานด้วยความเสี่ยงของคุณเอง สำหรับข้อบกพร่อง/ปัญหาใดๆ โปรดรายงานไปยังที่เก็บปลั๊กอิน\n\nหากปลั๊กอิน \"บุคคลที่สาม\" ใดๆ ละเมิด ToS/DMCA ของบริการ/นิติบุคคลใดๆ โปรดขอให้ผู้เขียนปลั๊กอิน \"บุคคลที่สาม\" หรือแพลตฟอร์มโฮสติ้ง เช่น GitHub/Codeberg ดำเนินการ ที่ระบุไว้ข้างต้น (ที่ติดป้าย \"บุคคลที่สาม\") เป็นปลั๊กอินสาธารณะ/ที่ดูแลโดยชุมชนทั้งหมด เราไม่ได้จัดการดูแล ดังนั้นเราจึงไม่สามารถดำเนินการใดๆ กับพวกเขาได้\n\n'; + + @override + String get input_does_not_match_format => 'อินพุตไม่ตรงกับรูปแบบที่ต้องการ'; + + @override + String get metadata_provider_plugins => 'ปลั๊กอินผู้ให้บริการเมตาดาต้า'; + + @override + String get paste_plugin_download_url => + 'วาง url ดาวน์โหลดหรือ url ที่เก็บ GitHub/Codeberg หรือลิงก์โดยตรงไปยังไฟล์ .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'ดาวน์โหลดและติดตั้งปลั๊กอินจาก url'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'ไม่สามารถเพิ่มปลั๊กอินได้: $error'; + } + + @override + String get upload_plugin_from_file => 'อัปโหลดปลั๊กอินจากไฟล์'; + + @override + String get installed => 'ติดตั้งแล้ว'; + + @override + String get available_plugins => 'ปลั๊กอินที่มีอยู่'; + + @override + String get configure_your_own_metadata_plugin => + 'กำหนดค่าผู้ให้บริการเมตาดาต้าเพลย์ลิสต์/อัลบั้ม/ศิลปิน/ฟีดของคุณเอง'; + + @override + String get audio_scrobblers => 'เครื่อง scrobbler เสียง'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_tl.dart b/lib/l10n/generated/app_localizations_tl.dart index 3c4de958..361a7bf0 100644 --- a/lib/l10n/generated/app_localizations_tl.dart +++ b/lib/l10n/generated/app_localizations_tl.dart @@ -1210,7 +1210,7 @@ class AppLocalizationsTl extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Ito ay kinakalkula batay sa average na payout ng online music streaming platform na \$0.003 hanggang \$0.005 kada stream. Ito ay isang hypothetical na kalkulasyon upang bigyan ang user ng insight kung magkano ang babayaran nila sa mga artist kung sakaling makinig sila ng kanilang kanta sa iba\'t ibang music streaming platform.'; @override String count_mins(Object minutes) { @@ -1391,4 +1391,164 @@ class AppLocalizationsTl extends AppLocalizations { @override String get connection_request_denied => 'Tanggihan ang koneksyon. Tinanggihan ng gumagamit ang pag-access.'; + + @override + String get an_error_occurred => 'May naganap na error'; + + @override + String get copy_to_clipboard => 'Kopyahin sa clipboard'; + + @override + String get view_logs => 'Tingnan ang mga log'; + + @override + String get retry => 'Subukang muli'; + + @override + String get no_default_metadata_provider_selected => + 'Wala kang nakatakdang default na metadata provider'; + + @override + String get manage_metadata_providers => + 'Pamahalaan ang mga metadata provider'; + + @override + String get open_link_in_browser => 'Buksan ang Link sa Browser?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Gusto mo bang buksan ang sumusunod na link'; + + @override + String get unsafe_url_warning => + 'Maaaring hindi ligtas ang pagbukas ng mga link mula sa hindi pinagkakatiwalaang pinagmulan. Mag-ingat!\nMaaari mo ring kopyahin ang link sa iyong clipboard.'; + + @override + String get copy_link => 'Kopyahin ang Link'; + + @override + String get building_your_timeline => + 'Binubuo ang iyong timeline batay sa iyong mga pinakinggan...'; + + @override + String get official => 'Opisyal'; + + @override + String author_name(Object author) { + return 'May-akda: $author'; + } + + @override + String get third_party => 'Third-party'; + + @override + String get plugin_requires_authentication => + 'Nangangailangan ng authentication ang plugin'; + + @override + String get update_available => 'May available na update'; + + @override + String get supports_scrobbling => 'Sinusuportahan ang scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Sinis-scrobble ng plugin na ito ang iyong musika upang mabuo ang iyong kasaysayan ng pakikinig.'; + + @override + String get default_plugin => 'Default'; + + @override + String get set_default => 'Itakda bilang default'; + + @override + String get support => 'Suporta'; + + @override + String get support_plugin_development => 'Suportahan ang pagbuo ng plugin'; + + @override + String can_access_name_api(Object name) { + return '- Maaaring i-access ang **$name** API'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Gusto mo bang i-install ang plugin na ito?'; + + @override + String get third_party_plugin_warning => + 'Ang plugin na ito ay mula sa third-party na repository. Mangyaring tiyakin na pinagkakatiwalaan mo ang pinagmulan bago mag-install.'; + + @override + String get author => 'May-akda'; + + @override + String get this_plugin_can_do_following => + 'Maaaring gawin ng plugin na ito ang sumusunod'; + + @override + String get install => 'I-install'; + + @override + String get install_a_metadata_provider => 'Mag-install ng Metadata Provider'; + + @override + String get no_tracks_playing => 'Walang Track na kasalukuyang tumutugtog'; + + @override + String get synced_lyrics_not_available => + 'Hindi available ang mga naka-sync na lyrics para sa kantang ito. Mangyaring gamitin ang'; + + @override + String get plain_lyrics => 'Simpleng Lyrics'; + + @override + String get tab_instead => 'na tab sa halip.'; + + @override + String get disclaimer => 'Disclaimer'; + + @override + String get third_party_plugin_dmca_notice => + 'Ang Spotube team ay walang hawak na anumang responsibilidad (kabilang ang legal) para sa anumang \"Third-party\" plugins.\nMangyaring gamitin ang mga ito sa iyong sariling peligro. Para sa anumang mga bug/isyu, mangyaring iulat ang mga ito sa repository ng plugin.\n\nKung ang anumang \"Third-party\" plugin ay lumalabag sa ToS/DMCA ng anumang serbisyo/legal na entity, mangyaring hilingin sa \"Third-party\" plugin author o sa hosting platform e.g. GitHub/Codeberg na gumawa ng aksyon. Ang nakalista sa itaas (\"Third-party\" na may label) ay lahat ng pampubliko/komunidad na pinananatiling mga plugin. Hindi namin sila kinukurusado, kaya hindi kami makakagawa ng anumang aksyon sa kanila.\n\n'; + + @override + String get input_does_not_match_format => + 'Ang input ay hindi tumutugma sa kinakailangang format'; + + @override + String get metadata_provider_plugins => 'Mga Plugin ng Metadata Provider'; + + @override + String get paste_plugin_download_url => + 'I-paste ang download url o GitHub/Codeberg repo url o direktang link sa .smplug file'; + + @override + String get download_and_install_plugin_from_url => + 'I-download at i-install ang plugin mula sa url'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Nabigo ang pagdagdag ng plugin: $error'; + } + + @override + String get upload_plugin_from_file => 'I-upload ang plugin mula sa file'; + + @override + String get installed => 'Naka-install'; + + @override + String get available_plugins => 'Mga available na plugin'; + + @override + String get configure_your_own_metadata_plugin => + 'I-configure ang iyong sariling playlist/album/artist/feed metadata provider'; + + @override + String get audio_scrobblers => 'Mga Audio Scrobbler'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index 93ec14fb..4dc65bbc 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -1204,7 +1204,7 @@ class AppLocalizationsTr extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Bu, çevrimiçi müzik akışı platformlarının ortalama akış başına \$0,003 ile \$0,005 arasındaki ödemesine göre hesaplanmıştır. Bu, kullanıcının farklı müzik akışı platformlarında şarkılarını dinleselerdi sanatçılara ne kadar ödeme yapacaklarına dair fikir vermek için yapılan varsayımsal bir hesaplamadır.'; @override String count_mins(Object minutes) { @@ -1386,4 +1386,162 @@ class AppLocalizationsTr extends AppLocalizations { @override String get connection_request_denied => 'Bağlantı reddedildi. Kullanıcı erişimi reddetti.'; + + @override + String get an_error_occurred => 'Bir hata oluştu'; + + @override + String get copy_to_clipboard => 'Panoya kopyala'; + + @override + String get view_logs => 'Günlükleri görüntüle'; + + @override + String get retry => 'Tekrar dene'; + + @override + String get no_default_metadata_provider_selected => + 'Varsayılan bir meta veri sağlayıcısı ayarlanmadı'; + + @override + String get manage_metadata_providers => 'Meta veri sağlayıcılarını yönet'; + + @override + String get open_link_in_browser => 'Bağlantıyı Tarayıcıda Aç?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Aşağıdaki bağlantıyı açmak istiyor musunuz'; + + @override + String get unsafe_url_warning => + 'Güvenilmeyen kaynaklardan bağlantı açmak güvensiz olabilir. Dikkatli olun!\nBağlantıyı panonuza da kopyalayabilirsiniz.'; + + @override + String get copy_link => 'Bağlantıyı Kopyala'; + + @override + String get building_your_timeline => + 'Dinlemelerinize göre zaman çizelgeniz oluşturuluyor...'; + + @override + String get official => 'Resmi'; + + @override + String author_name(Object author) { + return 'Yazar: $author'; + } + + @override + String get third_party => 'Üçüncü taraf'; + + @override + String get plugin_requires_authentication => + 'Eklenti kimlik doğrulama gerektirir'; + + @override + String get update_available => 'Güncelleme mevcut'; + + @override + String get supports_scrobbling => 'Scrobbling\'i destekler'; + + @override + String get plugin_scrobbling_info => + 'Bu eklenti, dinleme geçmişinizi oluşturmak için müziğinizi scrobble eder.'; + + @override + String get default_plugin => 'Varsayılan'; + + @override + String get set_default => 'Varsayılan olarak ayarla'; + + @override + String get support => 'Destek'; + + @override + String get support_plugin_development => 'Eklenti geliştirmeyi destekle'; + + @override + String can_access_name_api(Object name) { + return '- **$name** API\'ye erişebilir'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Bu eklentiyi yüklemek istiyor musunuz?'; + + @override + String get third_party_plugin_warning => + 'Bu eklenti üçüncü taraf bir depodan gelmektedir. Lütfen yüklemeden önce kaynağa güvendiğinizden emin olun.'; + + @override + String get author => 'Yazar'; + + @override + String get this_plugin_can_do_following => + 'Bu eklenti aşağıdakileri yapabilir'; + + @override + String get install => 'Yükle'; + + @override + String get install_a_metadata_provider => 'Bir Meta Veri Sağlayıcısı Yükle'; + + @override + String get no_tracks_playing => 'Şu anda çalınan bir Parça yok'; + + @override + String get synced_lyrics_not_available => + 'Bu şarkı için senkronize şarkı sözleri mevcut değil. Lütfen'; + + @override + String get plain_lyrics => 'Düz Şarkı Sözleri'; + + @override + String get tab_instead => 'sekmesini kullanın.'; + + @override + String get disclaimer => 'Sorumluluk Reddi'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube ekibi, herhangi bir \"Üçüncü taraf\" eklentisi için herhangi bir sorumluluk (yasal olanlar dahil) kabul etmez.\nLütfen bunları kendi riskinizde kullanın. Herhangi bir hata/sorun için lütfen bunları eklenti deposuna bildirin.\n\nHerhangi bir \"Üçüncü taraf\" eklentisi bir hizmetin/yasal varlığın ToS/DMCA\'sını ihlal ediyorsa, lütfen \"Üçüncü taraf\" eklenti yazarından veya barındırma platformundan, örneğin GitHub/Codeberg\'den harekete geçmesini isteyin. Yukarıda listelenen (\"Üçüncü taraf\" olarak etiketlenen) eklentilerin tümü genel/topluluk tarafından sürdürülen eklentilerdir. Biz bunları küratörlüğünü yapmıyoruz, bu yüzden onlar üzerinde herhangi bir işlem yapamayız.\n\n'; + + @override + String get input_does_not_match_format => 'Girdi, gerekli biçimle eşleşmiyor'; + + @override + String get metadata_provider_plugins => 'Meta Veri Sağlayıcısı Eklentileri'; + + @override + String get paste_plugin_download_url => + 'İndirme url\'sini veya GitHub/Codeberg repo url\'sini veya .smplug dosyasına doğrudan bağlantıyı yapıştırın'; + + @override + String get download_and_install_plugin_from_url => + 'url\'den eklentiyi indir ve yükle'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Eklenti eklenemedi: $error'; + } + + @override + String get upload_plugin_from_file => 'Dosyadan eklenti yükle'; + + @override + String get installed => 'Yüklü'; + + @override + String get available_plugins => 'Mevcut eklentiler'; + + @override + String get configure_your_own_metadata_plugin => + 'Kendi çalma listenizi/albümünüzü/sanatçınızı/akış meta veri sağlayıcınızı yapılandırın'; + + @override + String get audio_scrobblers => 'Ses Scrobbler\'lar'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_uk.dart b/lib/l10n/generated/app_localizations_uk.dart index 51ab48d5..35a18d55 100644 --- a/lib/l10n/generated/app_localizations_uk.dart +++ b/lib/l10n/generated/app_localizations_uk.dart @@ -1205,7 +1205,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Це розраховано на основі середньої виплати за стрім онлайн-платформ для потокового відтворення музики, що становить від \$0,003 до \$0,005. Це гіпотетичний розрахунок, щоб дати користувачеві уявлення про те, скільки б вони заплатили артистам, якщо б слухали їхні пісні на різних музичних стрімінгових платформах.'; @override String count_mins(Object minutes) { @@ -1383,4 +1383,161 @@ class AppLocalizationsUk extends AppLocalizations { @override String get connection_request_denied => 'Підключення відхилено. Користувач відмовив у доступі.'; + + @override + String get an_error_occurred => 'Сталася помилка'; + + @override + String get copy_to_clipboard => 'Копіювати в буфер обміну'; + + @override + String get view_logs => 'Переглянути логи'; + + @override + String get retry => 'Повторити'; + + @override + String get no_default_metadata_provider_selected => + 'Ви не встановили провайдера метаданих за замовчуванням'; + + @override + String get manage_metadata_providers => 'Керувати провайдерами метаданих'; + + @override + String get open_link_in_browser => 'Відкрити посилання в браузері?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Ви хочете відкрити наступне посилання'; + + @override + String get unsafe_url_warning => + 'Відкриття посилань з ненадійних джерел може бути небезпечним. Будьте обережні!\nВи також можете скопіювати посилання в буфер обміну.'; + + @override + String get copy_link => 'Копіювати посилання'; + + @override + String get building_your_timeline => + 'Створення вашої часової шкали на основі ваших прослуховувань...'; + + @override + String get official => 'Офіційний'; + + @override + String author_name(Object author) { + return 'Автор: $author'; + } + + @override + String get third_party => 'Сторонній'; + + @override + String get plugin_requires_authentication => 'Плагін вимагає автентифікації'; + + @override + String get update_available => 'Доступне оновлення'; + + @override + String get supports_scrobbling => 'Підтримує скроблінг'; + + @override + String get plugin_scrobbling_info => + 'Цей плагін скроббить вашу музику, щоб створити вашу історію прослуховувань.'; + + @override + String get default_plugin => 'За замовчуванням'; + + @override + String get set_default => 'Встановити за замовчуванням'; + + @override + String get support => 'Підтримка'; + + @override + String get support_plugin_development => 'Підтримати розробку плагіна'; + + @override + String can_access_name_api(Object name) { + return '- Може отримати доступ до **$name** API'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Ви хочете встановити цей плагін?'; + + @override + String get third_party_plugin_warning => + 'Цей плагін із стороннього репозиторію. Будь ласка, переконайтеся, що ви довіряєте джерелу перед встановленням.'; + + @override + String get author => 'Автор'; + + @override + String get this_plugin_can_do_following => 'Цей плагін може робити наступне'; + + @override + String get install => 'Встановити'; + + @override + String get install_a_metadata_provider => 'Встановити провайдера метаданих'; + + @override + String get no_tracks_playing => 'Наразі не відтворюється жоден трек'; + + @override + String get synced_lyrics_not_available => + 'Синхронізовані тексти недоступні для цієї пісні. Будь ласка, використовуйте вкладку'; + + @override + String get plain_lyrics => 'Звичайні тексти'; + + @override + String get tab_instead => 'замість цього.'; + + @override + String get disclaimer => 'Відмова від відповідальності'; + + @override + String get third_party_plugin_dmca_notice => + 'Команда Spotube не несе жодної відповідальності (включно з юридичною) за будь-які плагіни \"третіх сторін\".\nБудь ласка, використовуйте їх на свій страх і ризик. Про будь-які помилки/проблеми повідомляйте в репозиторій плагіна.\n\nЯкщо якийсь плагін \"третьої сторони\" порушує ToS/DMCA будь-якої служби/юридичної особи, будь ласка, попросіть автора плагіна \"третьої сторони\" або хостингову платформу, наприклад, GitHub/Codeberg, вжити заходів. Усі перераховані вище (позначені як \"треті сторони\") є плагінами, які підтримуються публічно/спільнотою. Ми не куруємо їх, тому не можемо вжити жодних заходів щодо них.\n\n'; + + @override + String get input_does_not_match_format => + 'Введені дані не відповідають необхідному формату'; + + @override + String get metadata_provider_plugins => 'Плагіни провайдера метаданих'; + + @override + String get paste_plugin_download_url => + 'Вставте URL-адресу для завантаження або URL-адресу репозиторію GitHub/Codeberg або пряме посилання на файл .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Завантажити та встановити плагін з URL-адреси'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Не вдалося додати плагін: $error'; + } + + @override + String get upload_plugin_from_file => 'Завантажити плагін з файлу'; + + @override + String get installed => 'Встановлено'; + + @override + String get available_plugins => 'Доступні плагіни'; + + @override + String get configure_your_own_metadata_plugin => + 'Налаштуйте свій власний провайдер метаданих для плейлиста/альбому/виконавця/стрічки'; + + @override + String get audio_scrobblers => 'Аудіо скробблери'; + + @override + String get scrobbling => 'Скроблінг'; } diff --git a/lib/l10n/generated/app_localizations_vi.dart b/lib/l10n/generated/app_localizations_vi.dart index c7f339bc..6015931e 100644 --- a/lib/l10n/generated/app_localizations_vi.dart +++ b/lib/l10n/generated/app_localizations_vi.dart @@ -1208,7 +1208,7 @@ class AppLocalizationsVi extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*Điều này được tính toán dựa trên khoản thanh toán trung bình mỗi luồng của nền tảng phát nhạc trực tuyến là \$0,003 đến \$0,005. Đây là một phép tính giả định để cung cấp cho người dùng cái nhìn sâu sắc về số tiền họ đã trả cho các nghệ sĩ nếu họ nghe bài hát của họ trên các nền tảng phát nhạc trực tuyến khác nhau.'; @override String count_mins(Object minutes) { @@ -1387,4 +1387,163 @@ class AppLocalizationsVi extends AppLocalizations { @override String get connection_request_denied => 'Kết nối bị từ chối. Người dùng đã từ chối quyền truy cập.'; + + @override + String get an_error_occurred => 'Đã xảy ra lỗi'; + + @override + String get copy_to_clipboard => 'Sao chép vào khay nhớ tạm'; + + @override + String get view_logs => 'Xem nhật ký'; + + @override + String get retry => 'Thử lại'; + + @override + String get no_default_metadata_provider_selected => + 'Bạn chưa đặt nhà cung cấp siêu dữ liệu mặc định nào'; + + @override + String get manage_metadata_providers => 'Quản lý nhà cung cấp siêu dữ liệu'; + + @override + String get open_link_in_browser => 'Mở liên kết trong Trình duyệt?'; + + @override + String get do_you_want_to_open_the_following_link => + 'Bạn có muốn mở liên kết sau không'; + + @override + String get unsafe_url_warning => + 'Việc mở các liên kết từ các nguồn không đáng tin cậy có thể không an toàn. Hãy thận trọng!\nBạn cũng có thể sao chép liên kết vào khay nhớ tạm của mình.'; + + @override + String get copy_link => 'Sao chép liên kết'; + + @override + String get building_your_timeline => + 'Đang xây dựng dòng thời gian của bạn dựa trên những gì bạn đã nghe...'; + + @override + String get official => 'Chính thức'; + + @override + String author_name(Object author) { + return 'Tác giả: $author'; + } + + @override + String get third_party => 'Bên thứ ba'; + + @override + String get plugin_requires_authentication => 'Plugin yêu cầu xác thực'; + + @override + String get update_available => 'Có bản cập nhật'; + + @override + String get supports_scrobbling => 'Hỗ trợ scrobbling'; + + @override + String get plugin_scrobbling_info => + 'Plugin này scrobble nhạc của bạn để tạo lịch sử nghe của bạn.'; + + @override + String get default_plugin => 'Mặc định'; + + @override + String get set_default => 'Đặt làm mặc định'; + + @override + String get support => 'Hỗ trợ'; + + @override + String get support_plugin_development => 'Hỗ trợ phát triển plugin'; + + @override + String can_access_name_api(Object name) { + return '- Có thể truy cập API **$name**'; + } + + @override + String get do_you_want_to_install_this_plugin => + 'Bạn có muốn cài đặt plugin này không?'; + + @override + String get third_party_plugin_warning => + 'Plugin này đến từ một kho lưu trữ của bên thứ ba. Vui lòng đảm bảo rằng bạn tin tưởng nguồn trước khi cài đặt.'; + + @override + String get author => 'Tác giả'; + + @override + String get this_plugin_can_do_following => + 'Plugin này có thể làm những việc sau'; + + @override + String get install => 'Cài đặt'; + + @override + String get install_a_metadata_provider => + 'Cài đặt một Nhà cung cấp siêu dữ liệu'; + + @override + String get no_tracks_playing => 'Hiện không có bản nhạc nào đang phát'; + + @override + String get synced_lyrics_not_available => + 'Lời bài hát được đồng bộ hóa không có sẵn cho bài hát này. Vui lòng sử dụng'; + + @override + String get plain_lyrics => 'Lời bài hát thuần túy'; + + @override + String get tab_instead => 'thay thế.'; + + @override + String get disclaimer => 'Miễn trừ trách nhiệm'; + + @override + String get third_party_plugin_dmca_notice => + 'Nhóm Spotube không chịu bất kỳ trách nhiệm nào (bao gồm cả pháp lý) đối với bất kỳ plugin \"Bên thứ ba\" nào.\nVui lòng sử dụng chúng với rủi ro của riêng bạn. Đối với bất kỳ lỗi/vấn đề nào, vui lòng báo cáo chúng cho kho lưu trữ plugin.\n\nNếu bất kỳ plugin \"Bên thứ ba\" nào vi phạm ToS/DMCA của bất kỳ dịch vụ/thực thể pháp lý nào, vui lòng yêu cầu tác giả plugin \"Bên thứ ba\" hoặc nền tảng lưu trữ, ví dụ: GitHub/Codeberg, thực hiện hành động. Tất cả các plugin được liệt kê ở trên (được gắn nhãn \"Bên thứ ba\") đều là các plugin công cộng/do cộng đồng duy trì. Chúng tôi không quản lý chúng, vì vậy chúng tôi không thể thực hiện bất kỳ hành động nào đối với chúng.\n\n'; + + @override + String get input_does_not_match_format => + 'Đầu vào không khớp với định dạng yêu cầu'; + + @override + String get metadata_provider_plugins => 'Plugin Nhà cung cấp siêu dữ liệu'; + + @override + String get paste_plugin_download_url => + 'Dán url tải xuống hoặc url kho lưu trữ GitHub/Codeberg hoặc liên kết trực tiếp đến tệp .smplug'; + + @override + String get download_and_install_plugin_from_url => + 'Tải xuống và cài đặt plugin từ url'; + + @override + String failed_to_add_plugin_error(Object error) { + return 'Không thể thêm plugin: $error'; + } + + @override + String get upload_plugin_from_file => 'Tải lên plugin từ tệp'; + + @override + String get installed => 'Đã cài đặt'; + + @override + String get available_plugins => 'Các plugin có sẵn'; + + @override + String get configure_your_own_metadata_plugin => + 'Cấu hình nhà cung cấp siêu dữ liệu danh sách phát/album/nghệ sĩ/nguồn cấp dữ liệu của riêng bạn'; + + @override + String get audio_scrobblers => 'Bộ scrobbler âm thanh'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index 53a6c3dc..e42b6994 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -574,10 +574,10 @@ class AppLocalizationsZh extends AppLocalizations { String get something_went_wrong => '某些地方出现了问题'; @override - String get piped_instance => '管道服务器实例'; + String get piped_instance => 'Piped 服务器实例'; @override - String get piped_description => '管道服务器实例用于匹配歌曲'; + String get piped_description => 'Piped 服务器实例用于匹配歌曲'; @override String get piped_warning => '它们中的一部分可能并不能正常工作。使用时请自行承担风险'; @@ -1179,7 +1179,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get hipotetical_calculation => - '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + '*这是根据在线音乐流媒体平台每流平均支付0.003美元至0.005美元计算得出的。这是一个假设性的计算,旨在让用户了解如果他们在不同的音乐流媒体平台上收听歌曲,他们将需要向艺人支付多少费用。'; @override String count_mins(Object minutes) { @@ -1354,4 +1354,1648 @@ class AppLocalizationsZh extends AppLocalizations { @override String get connection_request_denied => '连接被拒绝。用户拒绝访问。'; + + @override + String get an_error_occurred => '发生错误'; + + @override + String get copy_to_clipboard => '复制到剪贴板'; + + @override + String get view_logs => '查看日志'; + + @override + String get retry => '重试'; + + @override + String get no_default_metadata_provider_selected => '您未设置默认元数据提供者'; + + @override + String get manage_metadata_providers => '管理元数据提供者'; + + @override + String get open_link_in_browser => '在浏览器中打开链接?'; + + @override + String get do_you_want_to_open_the_following_link => '您想打开以下链接吗'; + + @override + String get unsafe_url_warning => '从不受信任的来源打开链接可能不安全。请谨慎!\n您也可以将链接复制到剪贴板。'; + + @override + String get copy_link => '复制链接'; + + @override + String get building_your_timeline => '正在根据您的收听记录构建您的时间线...'; + + @override + String get official => '官方'; + + @override + String author_name(Object author) { + return '作者:$author'; + } + + @override + String get third_party => '第三方'; + + @override + String get plugin_requires_authentication => '插件需要身份验证'; + + @override + String get update_available => '有可用更新'; + + @override + String get supports_scrobbling => '支持 Scrobbling'; + + @override + String get plugin_scrobbling_info => '此插件会 scrobble 您的音乐以生成您的收听历史记录。'; + + @override + String get default_plugin => '默认'; + + @override + String get set_default => '设为默认'; + + @override + String get support => '支持'; + + @override + String get support_plugin_development => '支持插件开发'; + + @override + String can_access_name_api(Object name) { + return '- 可以访问 **$name** API'; + } + + @override + String get do_you_want_to_install_this_plugin => '您想安装此插件吗?'; + + @override + String get third_party_plugin_warning => '此插件来自第三方存储库。请在安装前确保您信任此来源。'; + + @override + String get author => '作者'; + + @override + String get this_plugin_can_do_following => '此插件可以执行以下操作'; + + @override + String get install => '安装'; + + @override + String get install_a_metadata_provider => '安装元数据提供者'; + + @override + String get no_tracks_playing => '当前没有播放任何曲目'; + + @override + String get synced_lyrics_not_available => '此歌曲的同步歌词不可用。请使用'; + + @override + String get plain_lyrics => '纯歌词'; + + @override + String get tab_instead => '选项卡。'; + + @override + String get disclaimer => '免责声明'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube 团队对任何“第三方”插件不承担任何责任(包括法律责任)。\n请自行承担风险使用。对于任何错误/问题,请向插件存储库报告。\n\n如果任何“第三方”插件违反了任何服务/法律实体的服务条款/DMCA,请要求该“第三方”插件作者或托管平台(例如 GitHub/Codeberg)采取行动。上面列出的(标记为“第三方”)都是公共/社区维护的插件。我们不对此类插件进行管理,因此无法对其采取任何行动。\n\n'; + + @override + String get input_does_not_match_format => '输入与所需格式不匹配'; + + @override + String get metadata_provider_plugins => '元数据提供者插件'; + + @override + String get paste_plugin_download_url => + '粘贴下载 URL、GitHub/Codeberg 存储库 URL 或 .smplug 文件的直接链接'; + + @override + String get download_and_install_plugin_from_url => '从 URL 下载并安装插件'; + + @override + String failed_to_add_plugin_error(Object error) { + return '添加插件失败:$error'; + } + + @override + String get upload_plugin_from_file => '从文件上传插件'; + + @override + String get installed => '已安装'; + + @override + String get available_plugins => '可用插件'; + + @override + String get configure_your_own_metadata_plugin => '配置您自己的播放列表/专辑/艺人/订阅元数据提供者'; + + @override + String get audio_scrobblers => '音频 Scrobblers'; + + @override + String get scrobbling => 'Scrobbling'; +} + +/// The translations for Chinese, as used in Taiwan (`zh_TW`). +class AppLocalizationsZhTw extends AppLocalizationsZh { + AppLocalizationsZhTw() : super('zh_TW'); + + @override + String get guest => '訪客'; + + @override + String get browse => '瀏覽'; + + @override + String get search => '搜尋'; + + @override + String get library => '音樂庫'; + + @override + String get lyrics => '歌詞'; + + @override + String get settings => '設定'; + + @override + String get genre_categories_filter => '過濾分類...'; + + @override + String get genre => '探索歌單'; + + @override + String get personalized => '為你打造'; + + @override + String get featured => '推薦'; + + @override + String get new_releases => '新歌熱播'; + + @override + String get songs => '歌曲'; + + @override + String playing_track(Object track) { + return '播放 $track'; + } + + @override + String queue_clear_alert(Object track_length) { + return '這將清空目前的播放清單。$track_length 首歌曲將被移除\n你確定要繼續嗎?'; + } + + @override + String get load_more => '載入更多'; + + @override + String get playlists => '歌單'; + + @override + String get artists => '藝人'; + + @override + String get albums => '專輯'; + + @override + String get tracks => '歌曲'; + + @override + String get downloads => '下載'; + + @override + String get filter_playlists => '過濾歌單...'; + + @override + String get liked_tracks => '已按讚的歌曲'; + + @override + String get liked_tracks_description => '你按過讚的所有歌曲'; + + @override + String get playlist => '播放清單'; + + @override + String get create_a_playlist => '建立一個歌單'; + + @override + String get update_playlist => '更新播放清單'; + + @override + String get create => '建立'; + + @override + String get cancel => '取消'; + + @override + String get update => '更新'; + + @override + String get playlist_name => '歌單名稱'; + + @override + String get name_of_playlist => '歌單的名稱'; + + @override + String get description => '說明'; + + @override + String get public => '公開'; + + @override + String get collaborative => '共享協作'; + + @override + String get search_local_tracks => '搜尋本地歌曲...'; + + @override + String get play => '播放'; + + @override + String get delete => '刪除'; + + @override + String get none => '無'; + + @override + String get sort_a_z => '依字母順序'; + + @override + String get sort_z_a => '依字母倒序'; + + @override + String get sort_artist => '按藝人'; + + @override + String get sort_album => '按專輯'; + + @override + String get sort_duration => '依長度排序'; + + @override + String get sort_tracks => '排序方式'; + + @override + String currently_downloading(Object tracks_length) { + return '正在下載 ($tracks_length)'; + } + + @override + String get cancel_all => '取消全部'; + + @override + String get filter_artist => '過濾藝人...'; + + @override + String followers(Object followers) { + return '$followers 名追蹤者'; + } + + @override + String get add_artist_to_blacklist => '封鎖該藝人'; + + @override + String get top_tracks => '熱門歌曲'; + + @override + String get fans_also_like => '粉絲也喜歡'; + + @override + String get loading => '載入中...'; + + @override + String get artist => '藝人'; + + @override + String get blacklisted => '已封鎖'; + + @override + String get following => '關注中'; + + @override + String get follow => '關注'; + + @override + String get artist_url_copied => '此名藝人的分享連結已複製至剪貼簿'; + + @override + String added_to_queue(Object tracks) { + return '已新增 $tracks 首歌曲到播放清單'; + } + + @override + String get filter_albums => '過濾專輯...'; + + @override + String get synced => '同步'; + + @override + String get plain => '未同步'; + + @override + String get shuffle => '隨機播放'; + + @override + String get search_tracks => '搜尋歌曲...'; + + @override + String get released => '發表時間'; + + @override + String error(Object error) { + return '發生錯誤: $error'; + } + + @override + String get title => '標題'; + + @override + String get time => '時長'; + + @override + String get more_actions => '更多動作'; + + @override + String download_count(Object count) { + return '下載 ($count) 首歌曲'; + } + + @override + String add_count_to_playlist(Object count) { + return '將 ($count) 首歌曲新增到歌單中'; + } + + @override + String add_count_to_queue(Object count) { + return '新增 ($count) 首歌曲到播放清單'; + } + + @override + String play_count_next(Object count) { + return '接下來將播放 ($count) 首歌曲'; + } + + @override + String get album => '專輯'; + + @override + String copied_to_clipboard(Object data) { + return '已將 $data 複製至剪貼簿'; + } + + @override + String add_to_following_playlists(Object track) { + return '新增 $track 到以下播放清單'; + } + + @override + String get add => '新增'; + + @override + String added_track_to_queue(Object track) { + return '新增 $track 到播放清單'; + } + + @override + String get add_to_queue => '新增至播放清單'; + + @override + String track_will_play_next(Object track) { + return '$track 將在下一首播放'; + } + + @override + String get play_next => '下一首播放'; + + @override + String removed_track_from_queue(Object track) { + return '將 $track 從播放清單移除'; + } + + @override + String get remove_from_queue => '從播放清單移除'; + + @override + String get remove_from_favorites => '取消按讚'; + + @override + String get save_as_favorite => '按讚'; + + @override + String get add_to_playlist => '新增到歌單'; + + @override + String get remove_from_playlist => '從歌單移除'; + + @override + String get add_to_blacklist => '新增到已封鎖清單'; + + @override + String get remove_from_blacklist => '從已封鎖清單移除'; + + @override + String get share => '分享'; + + @override + String get mini_player => '小窗模式'; + + @override + String get slide_to_seek => '滑動以前進或後退'; + + @override + String get shuffle_playlist => '隨機播放歌單'; + + @override + String get unshuffle_playlist => '取消隨機播放歌單'; + + @override + String get previous_track => '上一首歌曲'; + + @override + String get next_track => '下一首歌'; + + @override + String get pause_playback => '暫停播放'; + + @override + String get resume_playback => '恢復播放'; + + @override + String get loop_track => '單曲循環'; + + @override + String get no_loop => '無循環'; + + @override + String get repeat_playlist => '歌單循環'; + + @override + String get queue => '播放清單'; + + @override + String get alternative_track_sources => '其它音源'; + + @override + String get download_track => '下載歌曲'; + + @override + String tracks_in_queue(Object tracks) { + return '$tracks 首歌曲在播放清單中'; + } + + @override + String get clear_all => '清除全部'; + + @override + String get show_hide_ui_on_hover => '游標暫留時顯示 / 隱藏控制列'; + + @override + String get always_on_top => '置頂'; + + @override + String get exit_mini_player => '退出小窗模式'; + + @override + String get download_location => '下載路徑'; + + @override + String get local_library => '本地媒體庫'; + + @override + String get add_library_location => '新增至媒體庫'; + + @override + String get remove_library_location => '從媒體庫移除'; + + @override + String get account => '帳戶'; + + @override + String get logout => '退出'; + + @override + String get logout_of_this_account => '退出該帳戶'; + + @override + String get language_region => '語言與地區'; + + @override + String get language => '語言'; + + @override + String get system_default => '系統預設'; + + @override + String get market_place_region => '市集地區'; + + @override + String get recommendation_country => '請選擇國家與地區以取得對應的音樂推薦'; + + @override + String get appearance => '外觀'; + + @override + String get layout_mode => '佈局類型'; + + @override + String get override_layout_settings => '將覆寫響應式佈局設定'; + + @override + String get adaptive => '響應式'; + + @override + String get compact => '緊湊'; + + @override + String get extended => '寬闊'; + + @override + String get theme => '主題'; + + @override + String get dark => '深色'; + + @override + String get light => '淺色'; + + @override + String get system => '依循系統'; + + @override + String get accent_color => '主色調'; + + @override + String get sync_album_color => '符合封面顏色'; + + @override + String get sync_album_color_description => '選取專輯封面主題色為主色調'; + + @override + String get playback => '播放'; + + @override + String get audio_quality => '音質'; + + @override + String get high => '高'; + + @override + String get low => '低'; + + @override + String get pre_download_play => '下載後播放'; + + @override + String get pre_download_play_description => '先下載歌曲後再播放而非串流播放(建議頻寬較高使用者使用)'; + + @override + String get skip_non_music => '跳過非音樂片段(跳過贊助商廣告)'; + + @override + String get blacklist_description => '已封鎖的歌曲與藝人'; + + @override + String get wait_for_download_to_finish => '請等待目前下載工作完成'; + + @override + String get desktop => '桌面版設定'; + + @override + String get close_behavior => '點選關閉按鈕行為'; + + @override + String get close => '關閉'; + + @override + String get minimize_to_tray => '最小化到工作列'; + + @override + String get show_tray_icon => '顯示工作列圖示'; + + @override + String get about => '關於'; + + @override + String get u_love_spotube => '我們明白你喜歡 Spotube'; + + @override + String get check_for_updates => '檢查更新'; + + @override + String get about_spotube => '關於 Spotube'; + + @override + String get blacklist => '黑名單'; + + @override + String get please_sponsor => '請考慮贊助或捐款'; + + @override + String get spotube_description => 'Spotube,一款輕量、跨平台且完全免費的 Spotify 用戶端。'; + + @override + String get version => '版本'; + + @override + String get build_number => '建置編號'; + + @override + String get founder => '發起人'; + + @override + String get repository => '專案儲存庫'; + + @override + String get bug_issues => '缺陷與問題報告'; + + @override + String get made_with => '於孟加拉🇧🇩用 ❤️ 發電'; + + @override + String get kingkor_roy_tirtho => 'Kingkor Roy Tirtho'; + + @override + String copyright(Object current_year) { + return '© 2021-$current_year Kingkor Roy Tirtho'; + } + + @override + String get license => '授權'; + + @override + String get credentials_will_not_be_shared_disclaimer => + '您大可放心,軟體不會收集或分享任何個人資料給第三方'; + + @override + String get know_how_to_login => '不知道該怎麼辦?'; + + @override + String get follow_step_by_step_guide => '請依照以下說明進行'; + + @override + String cookie_name_cookie(Object name) { + return '$name Cookie'; + } + + @override + String get fill_in_all_fields => '請填入所有欄位'; + + @override + String get submit => '提交'; + + @override + String get exit => '退出'; + + @override + String get previous => '上一步'; + + @override + String get next => '下一步'; + + @override + String get done => '完成'; + + @override + String get step_1 => '步驟 1'; + + @override + String get first_go_to => '首先,前往'; + + @override + String get something_went_wrong => '某些地方出現了問題'; + + @override + String get piped_instance => 'Piped 伺服器實例'; + + @override + String get piped_description => 'Piped 伺服器實例用於匹配歌曲'; + + @override + String get piped_warning => '它們之中的一部分可能無法正常運作。使用時請自行承擔風險'; + + @override + String get invidious_instance => 'Invidious 伺服器實例'; + + @override + String get invidious_description => '用於音軌匹配的 Invidious 伺服器實例'; + + @override + String get invidious_warning => '有些可能無法正常運作。請自行承擔風險'; + + @override + String get generate => '生成'; + + @override + String track_exists(Object track) { + return '曲目 $track 已存在'; + } + + @override + String get replace_downloaded_tracks => '替換已下載的歌曲'; + + @override + String get skip_download_tracks => '下載時跳過已下載的歌曲'; + + @override + String get do_you_want_to_replace => '你確定要取代已下載的歌曲嗎??'; + + @override + String get replace => '取代'; + + @override + String get skip => '跳過'; + + @override + String select_up_to_count_type(Object count, Object type) { + return '選擇最多 $count 種的類型 $type'; + } + + @override + String get select_genres => '選擇曲風'; + + @override + String get add_genres => '新增曲風'; + + @override + String get country => '國家和地區'; + + @override + String get number_of_tracks_generate => '產生歌曲的數目'; + + @override + String get acousticness => '原聲程度'; + + @override + String get danceability => '律動感'; + + @override + String get energy => '衝擊感'; + + @override + String get instrumentalness => '歌唱部分佔比'; + + @override + String get liveness => '現場感'; + + @override + String get loudness => '響度'; + + @override + String get speechiness => '朗誦比例'; + + @override + String get valence => '心理感受'; + + @override + String get popularity => '流行度'; + + @override + String get key => '曲調'; + + @override + String get duration => '歌曲長度 (s)'; + + @override + String get tempo => '每分鐘拍數 (BPM)'; + + @override + String get mode => '旋律重複度'; + + @override + String get time_signature => '音符時值'; + + @override + String get short => '短'; + + @override + String get medium => '中'; + + @override + String get long => '長'; + + @override + String get min => '最低'; + + @override + String get max => '最高'; + + @override + String get target => '目標'; + + @override + String get moderate => '中'; + + @override + String get deselect_all => '取消全選'; + + @override + String get select_all => '全選'; + + @override + String get are_you_sure => '你確定嗎?'; + + @override + String get generating_playlist => '正在產生你的自訂歌單...'; + + @override + String selected_count_tracks(Object count) { + return '已選取 $count 首歌曲'; + } + + @override + String get download_warning => + '如果你大量下載這些歌曲,你顯然在侵犯音樂的版權並對音樂創作社區造成了傷害。我希望你能意識到這一點。永遠要尊重並支持藝術家們的辛勤工作'; + + @override + String get download_ip_ban_warning => + '小心,如果出現超出正常的下載請求,那你的 IP 可能會被 YouTube 封鎖,這意味著你的裝置將在長達 2-3 個月的時間內無法使用該 IP 訪問 YouTube(即使你沒登入)。Spotube 不會因而承擔任何責任'; + + @override + String get by_clicking_accept_terms => '點擊 \'同意\' 代表你同意以下的條款'; + + @override + String get download_agreement_1 => '我明白侵害音樂版權是一件不好的事'; + + @override + String get download_agreement_2 => '我將盡可能支持藝術家的工作。我現在之所以做不到是因為缺乏資金來購買正版'; + + @override + String get download_agreement_3 => + '我完全了解我的 IP 存在被 YouTube 封鎖的風險。並且我明白 Spotube 的擁有者與貢獻者們無須對我目前的行為所導致的任何後果負責'; + + @override + String get decline => '拒絕'; + + @override + String get accept => '同意'; + + @override + String get details => '詳細資訊'; + + @override + String get youtube => 'YouTube'; + + @override + String get channel => '頻道'; + + @override + String get likes => '讚'; + + @override + String get dislikes => '倒讚'; + + @override + String get views => '瀏覽次數'; + + @override + String get streamUrl => '播放串流 URL'; + + @override + String get stop => '停止'; + + @override + String get sort_newest => '依新增日期順序'; + + @override + String get sort_oldest => '依新增日期倒序'; + + @override + String get sleep_timer => '睡眠計時器'; + + @override + String mins(Object minutes) { + return '$minutes 分'; + } + + @override + String hours(Object hours) { + return '$hours 時'; + } + + @override + String hour(Object hours) { + return '$hours 時'; + } + + @override + String get custom_hours => '自訂時長'; + + @override + String get logs => '記錄檔(Log)'; + + @override + String get developers => '開發者'; + + @override + String get not_logged_in => '你尚未登入'; + + @override + String get search_mode => '搜尋模式'; + + @override + String get audio_source => '音訊來源'; + + @override + String get ok => '確定'; + + @override + String get failed_to_encrypt => '加密失敗'; + + @override + String get encryption_failed_warning => + 'Spotube使用加密來安全地儲存您的資料。但是失敗了。因此,它將回退到不安全的儲存空間\n如果您使用Linux,請確保已安裝gnome-keyring、kde-wallet和keepassxc等加密服務'; + + @override + String get querying_info => '正在查詢資訊...'; + + @override + String get piped_api_down => 'Piped API 無法使用'; + + @override + String piped_down_error_instructions(Object pipedInstance) { + return '當前Piped實例 $pipedInstance 不可用\n\n請更改實例或將\'API類型\'更改為官方YouTube API\n\n更改後請確保重新啟動應用程式'; + } + + @override + String get you_are_offline => '您目前處於離線狀態'; + + @override + String get connection_restored => '您的網路連線已恢復'; + + @override + String get use_system_title_bar => '使用作業系統的預設視窗標題列'; + + @override + String get crunching_results => '處理結果中...'; + + @override + String get search_to_get_results => '搜尋以取得結果'; + + @override + String get use_amoled_mode => '使用 AMOLED 模式'; + + @override + String get pitch_dark_theme => '漆黑主題'; + + @override + String get normalize_audio => '標準化音訊'; + + @override + String get change_cover => '更改封面'; + + @override + String get add_cover => '新增封面'; + + @override + String get restore_defaults => '恢復預設值'; + + @override + String get download_music_codec => '下載音樂編解碼器'; + + @override + String get streaming_music_codec => '串流音樂編解碼器'; + + @override + String get login_with_lastfm => '使用 Last.fm 登入'; + + @override + String get connect => '連線'; + + @override + String get disconnect_lastfm => '切斷 Last.fm 連線'; + + @override + String get disconnect => '斷開連線'; + + @override + String get username => '帳號'; + + @override + String get password => '密碼'; + + @override + String get login => '登入'; + + @override + String get login_with_your_lastfm => '使用您的 Last.fm 帳號登入'; + + @override + String get scrobble_to_lastfm => '在 Last.fm 上記錄你的播放'; + + @override + String get go_to_album => '前往專輯'; + + @override + String get discord_rich_presence => 'Discord Rick Presence(Discord 狀態)'; + + @override + String get browse_all => '瀏覽全部'; + + @override + String get genres => '音樂類型'; + + @override + String get explore_genres => '探索音樂類型'; + + @override + String get friends => '好友'; + + @override + String get no_lyrics_available => '抱歉,無法找到這首歌的歌詞'; + + @override + String get start_a_radio => '開始收聽電台'; + + @override + String get how_to_start_radio => '您想如何開始收聽電台?'; + + @override + String get replace_queue_question => '您想要取代目前清單還是追加到清單?'; + + @override + String get endless_playback => '無限播放'; + + @override + String get delete_playlist => '刪除播放清單'; + + @override + String get delete_playlist_confirmation => '您確定要刪除此播放清單嗎?'; + + @override + String get local_tracks => '本地音訊'; + + @override + String get local_tab => '本地'; + + @override + String get song_link => '歌曲連結'; + + @override + String get skip_this_nonsense => '跳過這個無聊內容'; + + @override + String get freedom_of_music => '“音樂的自由”'; + + @override + String get freedom_of_music_palm => '「音樂的自由掌握在您手中」'; + + @override + String get get_started => '我們開始吧'; + + @override + String get youtube_source_description => '建議且效果最佳。'; + + @override + String get piped_source_description => '感覺自由?與 YouTube 一樣,但更自由。'; + + @override + String get jiosaavn_source_description => '最適合南亞地區。'; + + @override + String get invidious_source_description => '類似 Piped,但可用性更高。'; + + @override + String highest_quality(Object quality) { + return '最高音質:$quality'; + } + + @override + String get select_audio_source => '選擇音訊來源'; + + @override + String get endless_playback_description => '自動將新歌曲加入清單的結尾'; + + @override + String get choose_your_region => '選擇您的所在地區'; + + @override + String get choose_your_region_description => '這能幫助 Spotube 為您的所在位置顯示正確的內容。'; + + @override + String get choose_your_language => '選擇您的語言'; + + @override + String get help_project_grow => '幫助這個專案成長'; + + @override + String get help_project_grow_description => + 'Spotube是一個開源專案。您可以透過為專案做出貢獻、回報錯誤或建議新功能來幫助專案成長。'; + + @override + String get contribute_on_github => '在GitHub上做出貢獻'; + + @override + String get donate_on_open_collective => '在Open Collective上捐款'; + + @override + String get browse_anonymously => '匿名瀏覽'; + + @override + String get enable_connect => '啟用連線'; + + @override + String get enable_connect_description => '從其他裝置控制Spotube'; + + @override + String get devices => '裝置'; + + @override + String get select => '選擇'; + + @override + String connect_client_alert(Object client) { + return '您正在被 $client 控制'; + } + + @override + String get this_device => '此裝置'; + + @override + String get remote => '遠端'; + + @override + String get stats => '統計'; + + @override + String and_n_more(Object count) { + return '還有 $count 個'; + } + + @override + String get recently_played => '最近播放'; + + @override + String get browse_more => '瀏覽更多'; + + @override + String get no_title => '無標題'; + + @override + String get not_playing => '未播放'; + + @override + String get epic_failure => '史詩級的失敗!'; + + @override + String added_num_tracks_to_queue(Object tracks_length) { + return '已將 $tracks_length 首曲目新增至清單'; + } + + @override + String get spotube_has_an_update => 'Spotube 有更新版本'; + + @override + String get download_now => '立即下載'; + + @override + String nightly_version(Object nightlyBuildNum) { + return 'Spotube Nightly $nightlyBuildNum 已發佈'; + } + + @override + String release_version(Object version) { + return 'Spotube v$version 已發布'; + } + + @override + String get read_the_latest => '閱讀最新'; + + @override + String get release_notes => '版本說明'; + + @override + String get pick_color_scheme => '選擇配色方案'; + + @override + String get save => '儲存'; + + @override + String get choose_the_device => '選擇裝置:'; + + @override + String get multiple_device_connected => '已連接多個裝置。\n選擇您希望執行此操作的裝置'; + + @override + String get nothing_found => '未找到任何內容'; + + @override + String get the_box_is_empty => '箱子為空'; + + @override + String get top_artists => '熱門藝人'; + + @override + String get top_albums => '熱門專輯'; + + @override + String get this_week => '本週'; + + @override + String get this_month => '本月'; + + @override + String get last_6_months => '過去6個月'; + + @override + String get this_year => '今年'; + + @override + String get last_2_years => '過去2年'; + + @override + String get all_time => '所有時間'; + + @override + String powered_by_provider(Object providerName) { + return '由 $providerName 提供支援'; + } + + @override + String get email => '電子郵件'; + + @override + String get profile_followers => '追蹤者'; + + @override + String get birthday => '生日'; + + @override + String get subscription => '訂閱'; + + @override + String get not_born => '尚未建立'; + + @override + String get hacker => '駭客'; + + @override + String get profile => '個人資訊'; + + @override + String get no_name => '沒有名字'; + + @override + String get edit => '編輯'; + + @override + String get user_profile => '使用者資料'; + + @override + String count_plays(Object count) { + return '$count 次播放'; + } + + @override + String get streaming_fees_hypothetical => + '*基於 Spotify 每次播放的支付金額\n從 \$0.003 到 \$0.005 計算。這是一個假設性的\n計算,旨在讓用戶了解如果他們在 Spotify 上收聽\n這些歌曲,可能會付給作者的金額。'; + + @override + String get minutes_listened => '聽的分鐘數'; + + @override + String get streamed_songs => '已串流歌曲'; + + @override + String count_streams(Object count) { + return '$count 次串流'; + } + + @override + String get owned_by_you => '由您所有'; + + @override + String copied_shareurl_to_clipboard(Object shareUrl) { + return '$shareUrl 已複製到剪貼簿'; + } + + @override + String get hipotetical_calculation => + '*此為根據線上音樂串流平台平均每次播放 \$0.003 至 \$0.005 的收益所計算的假設值。此為一個假設性計算,旨在讓使用者了解若他們在不同的音樂串流平台上收聽同一首歌曲,他們將會支付給藝人多少費用。'; + + @override + String count_mins(Object minutes) { + return '$minutes 分鐘'; + } + + @override + String get summary_minutes => '分鐘'; + + @override + String get summary_listened_to_music => '聽音樂'; + + @override + String get summary_songs => '歌曲'; + + @override + String get summary_streamed_overall => '整體串流媒體'; + + @override + String get summary_owed_to_artists => '本月欠藝術家的'; + + @override + String get summary_artists => '藝術家的'; + + @override + String get summary_music_reached_you => '音樂接觸到你'; + + @override + String get summary_full_albums => '完整專輯'; + + @override + String get summary_got_your_love => '獲得了你的愛心'; + + @override + String get summary_playlists => '播放清單'; + + @override + String get summary_were_on_repeat => '已經重複播放'; + + @override + String total_money(Object money) { + return '總計 $money'; + } + + @override + String get webview_not_found => '未找到 Webview 框架'; + + @override + String get webview_not_found_description => + '您的裝置中未安裝 Webview Runtime。\n如果已安裝,請確保它的位置在系統環境變數(PATH)中\n\n安裝後,重新啟動應用程式'; + + @override + String get unsupported_platform => '不支援的平台'; + + @override + String get cache_music => '快取音樂'; + + @override + String get open => '開啟'; + + @override + String get cache_folder => '快取資料夾'; + + @override + String get export => '導出'; + + @override + String get clear_cache => '清除快取'; + + @override + String get clear_cache_confirmation => '您要清除快取嗎?'; + + @override + String get export_cache_files => '匯出快取檔案'; + + @override + String found_n_files(Object count) { + return '找到 $count 個檔案'; + } + + @override + String get export_cache_confirmation => '您要匯出這些檔案到'; + + @override + String exported_n_out_of_m_files(Object files, Object filesExported) { + return '匯出了 $filesExported / $files 個檔案'; + } + + @override + String get undo => '取消'; + + @override + String get download_all => '下載全部'; + + @override + String get add_all_to_playlist => '全部加入到播放清單'; + + @override + String get add_all_to_queue => '全部加入清單'; + + @override + String get play_all_next => '播放全部下一首'; + + @override + String get pause => '暫停'; + + @override + String get view_all => '檢視全部'; + + @override + String get no_tracks_added_yet => '看起來你還沒有加入任何歌曲'; + + @override + String get no_tracks => '看起來這裡沒有任何歌曲'; + + @override + String get no_tracks_listened_yet => '看起來你還沒聽任何歌曲'; + + @override + String get not_following_artists => '你沒有關注任何藝術家'; + + @override + String get no_favorite_albums_yet => '看起來你還沒有將任何專輯加入到收藏夾'; + + @override + String get no_logs_found => '未找到日誌'; + + @override + String get youtube_engine => 'YouTube 引擎'; + + @override + String youtube_engine_not_installed_title(Object engine) { + return '$engine 未安裝'; + } + + @override + String youtube_engine_not_installed_message(Object engine) { + return '$engine 未在您的系統中安裝。'; + } + + @override + String youtube_engine_set_path(Object engine) { + return '確保它可用在 PATH 變數中,或\n設定 $engine 執行檔的絕對路徑'; + } + + @override + String get youtube_engine_unix_issue_message => + '在類 Unix 作業系統(如 macOS/Linux/Unix)中,請在 .zshrc/.bashrc/.bash_profile 等檔案中設定路徑無效。\n您需要在 shell 設定檔中設定路徑'; + + @override + String get download => '下載'; + + @override + String get file_not_found => '找不到檔案'; + + @override + String get custom => '自訂'; + + @override + String get add_custom_url => '新增自訂 URL'; + + @override + String get edit_port => '編輯端口'; + + @override + String get port_helper_msg => '預設值為 -1,表示隨機數。如果您已配置防火牆,建議設定此項目。'; + + @override + String connect_request(Object client) { + return '允許 $client 連線嗎?'; + } + + @override + String get connection_request_denied => '連線被拒絕。請求被使用者拒絕。'; + + @override + String get an_error_occurred => '發生錯誤'; + + @override + String get copy_to_clipboard => '複製到剪貼簿'; + + @override + String get view_logs => '檢視日誌'; + + @override + String get retry => '重試'; + + @override + String get no_default_metadata_provider_selected => '您沒有設定預設的中繼資料供應商'; + + @override + String get manage_metadata_providers => '管理中繼資料供應商'; + + @override + String get open_link_in_browser => '要在瀏覽器中開啟連結嗎?'; + + @override + String get do_you_want_to_open_the_following_link => '您想開啟以下連結嗎'; + + @override + String get unsafe_url_warning => '從不受信任的來源開啟連結可能不安全。請務必小心!\n您也可以將連結複製到剪貼簿。'; + + @override + String get copy_link => '複製連結'; + + @override + String get building_your_timeline => '正在根據您的收聽記錄建立您的時間軸...'; + + @override + String get official => '官方'; + + @override + String author_name(Object author) { + return '作者:$author'; + } + + @override + String get third_party => '第三方'; + + @override + String get plugin_requires_authentication => '此外掛程式需要驗證'; + + @override + String get update_available => '有可用的更新'; + + @override + String get supports_scrobbling => '支援 Scrobbling'; + + @override + String get plugin_scrobbling_info => '此外掛程式會 Scrobble 您的音樂以產生您的收聽記錄。'; + + @override + String get default_plugin => '預設'; + + @override + String get set_default => '設為預設'; + + @override + String get support => '支援'; + + @override + String get support_plugin_development => '支援外掛程式開發'; + + @override + String can_access_name_api(Object name) { + return '- 可以存取 **$name** API'; + } + + @override + String get do_you_want_to_install_this_plugin => '您想安裝此外掛程式嗎?'; + + @override + String get third_party_plugin_warning => '此外掛程式來自第三方儲存庫。請在安裝前確認您信任該來源。'; + + @override + String get author => '作者'; + + @override + String get this_plugin_can_do_following => '此外掛程式可以執行以下操作'; + + @override + String get install => '安裝'; + + @override + String get install_a_metadata_provider => '安裝中繼資料供應商'; + + @override + String get no_tracks_playing => '目前沒有正在播放的曲目'; + + @override + String get synced_lyrics_not_available => '此歌曲沒有同步歌詞。請改用'; + + @override + String get plain_lyrics => '純歌詞'; + + @override + String get tab_instead => '分頁。'; + + @override + String get disclaimer => '免責聲明'; + + @override + String get third_party_plugin_dmca_notice => + 'Spotube 團隊對任何「第三方」外掛程式不負任何責任(包括法律責任)。\n請自行承擔使用風險。如有任何錯誤/問題,請向該外掛程式的儲存庫回報。\n\n若有任何「第三方」外掛程式違反任何服務/法律實體的服務條款/DMCA,請向「第三方」外掛程式作者或託管平台(如 GitHub/Codeberg)要求採取行動。以上列出的(標記為「第三方」)外掛程式均為公開/社群維護的外掛程式。我們沒有對其進行審核,因此無法對其採取任何行動。\n\n'; + + @override + String get input_does_not_match_format => '輸入不符合所需格式'; + + @override + String get metadata_provider_plugins => '中繼資料供應商外掛程式'; + + @override + String get paste_plugin_download_url => + '貼上下載網址、GitHub/Codeberg 儲存庫網址或 .smplug 檔案的直接連結'; + + @override + String get download_and_install_plugin_from_url => '從網址下載並安裝外掛程式'; + + @override + String failed_to_add_plugin_error(Object error) { + return '新增外掛程式失敗:$error'; + } + + @override + String get upload_plugin_from_file => '從檔案上傳外掛程式'; + + @override + String get installed => '已安裝'; + + @override + String get available_plugins => '可用的外掛程式'; + + @override + String get configure_your_own_metadata_plugin => '設定您自己的播放清單/專輯/藝人/動態中繼資料供應商'; + + @override + String get audio_scrobblers => '音訊 Scrobblers'; + + @override + String get scrobbling => 'Scrobbling'; } diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 0b974641..d0aeccc4 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -14,6 +14,7 @@ /// watchakorn-18k@github => Thai /// llama3, vishnumur777@github => Tamil /// Microsoft Copilot, Tutislav@github => Czech +/// 510208@github => Traditional Chinese library l10n; @@ -49,6 +50,7 @@ class L10n { const Locale('ta', 'IN'), const Locale('tr', 'TR'), const Locale('zh', 'CN'), + const Locale('zh', 'TW'), const Locale('vi', 'VN'), const Locale('eu', 'ES'), ]; diff --git a/lib/main.dart b/lib/main.dart index 8f02371d..b5789d6f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -217,7 +217,7 @@ class Spotube extends HookConsumerWidget { iconTheme: const IconThemeProperties(), colorScheme: colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.light) ?? - ColorSchemes.lightOrange(), + LegacyColorSchemes.lightSlate(), surfaceOpacity: .8, surfaceBlur: 10, ), @@ -226,7 +226,7 @@ class Spotube extends HookConsumerWidget { iconTheme: const IconThemeProperties(), colorScheme: colorSchemeMap[accentMaterialColor.name]?.call(ThemeMode.dark) ?? - ColorSchemes.darkOrange(), + LegacyColorSchemes.darkSlate(), surfaceOpacity: .8, surfaceBlur: 10, ), diff --git a/lib/models/connect/connect.freezed.dart b/lib/models/connect/connect.freezed.dart index 157d0911..9f9b558b 100644 --- a/lib/models/connect/connect.freezed.dart +++ b/lib/models/connect/connect.freezed.dart @@ -112,13 +112,8 @@ mixin _$WebSocketLoadEventData { required TResult orElse(), }) => throw _privateConstructorUsedError; - - /// Serializes this WebSocketLoadEventData to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $WebSocketLoadEventDataCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -147,8 +142,6 @@ class _$WebSocketLoadEventDataCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -197,8 +190,6 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res> $Res Function(_$WebSocketLoadEventDataPlaylistImpl) _then) : super(_value, _then); - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -222,8 +213,6 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res> )); } - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SpotubeSimplePlaylistObjectCopyWith<$Res>? get collection { @@ -292,14 +281,12 @@ class _$WebSocketLoadEventDataPlaylistImpl other.initialIndex == initialIndex)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_tracks), collection, initialIndex); - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$WebSocketLoadEventDataPlaylistImplCopyWith< @@ -433,11 +420,8 @@ abstract class WebSocketLoadEventDataPlaylist extends WebSocketLoadEventData { SpotubeSimplePlaylistObject? get collection; @override int? get initialIndex; - - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$WebSocketLoadEventDataPlaylistImplCopyWith< _$WebSocketLoadEventDataPlaylistImpl> get copyWith => throw _privateConstructorUsedError; @@ -472,8 +456,6 @@ class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res> $Res Function(_$WebSocketLoadEventDataAlbumImpl) _then) : super(_value, _then); - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -497,8 +479,6 @@ class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res> )); } - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SpotubeSimpleAlbumObjectCopyWith<$Res>? get collection { @@ -565,14 +545,12 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { other.initialIndex == initialIndex)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_tracks), collection, initialIndex); - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl> @@ -705,11 +683,8 @@ abstract class WebSocketLoadEventDataAlbum extends WebSocketLoadEventData { SpotubeSimpleAlbumObject? get collection; @override int? get initialIndex; - - /// Create a copy of WebSocketLoadEventData - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index d12fec14..bc30627d 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -65,7 +65,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 7; + int get schemaVersion => 8; @override MigrationStrategy get migration { @@ -102,7 +102,7 @@ class AppDatabase extends _$AppDatabase { ); await customStatement( "ALTER TABLE $tableName " - "ADD COLUMN $columnName TEXT NOT NULL DEFAULT 'Orange:0xFFf97315'", + "ADD COLUMN $columnName TEXT NOT NULL DEFAULT 'Slate:0xff64748b'", ); await customStatement( "UPDATE $tableName " @@ -114,7 +114,7 @@ class AppDatabase extends _$AppDatabase { ); await customStatement( "UPDATE $tableName " - "SET $columnName = 'Orange:0xFFf97315' WHERE $columnName = 'Blue:0xFF2196F3'", + "SET $columnName = 'Slate:0xff64748b' WHERE $columnName = 'Blue:0xFF2196F3'", ); }, from5To6: (m, schema) async { @@ -142,6 +142,63 @@ class AppDatabase extends _$AppDatabase { schema.audioPlayerStateTable.tracks, ); }, + from7To8: (m, schema) async { + await m + .addColumn( + schema.metadataPluginsTable, + schema.metadataPluginsTable.entryPoint, + ) + .catchError((error, stackTrace) { + // If the column already exists, ignore the error + if (!error.toString().contains('duplicate column name')) { + throw error; + } + }); + await m + .addColumn( + schema.metadataPluginsTable, + schema.metadataPluginsTable.apis, + ) + .catchError((error, stackTrace) { + // If the column already exists, ignore the error + if (!error.toString().contains('duplicate column name')) { + throw error; + } + }); + await m + .addColumn( + schema.metadataPluginsTable, + schema.metadataPluginsTable.abilities, + ) + .catchError((error, stackTrace) { + // If the column already exists, ignore the error + if (!error.toString().contains('duplicate column name')) { + throw error; + } + }); + await m + .addColumn( + schema.metadataPluginsTable, + schema.metadataPluginsTable.repository, + ) + .catchError((error, stackTrace) { + // If the column already exists, ignore the error + if (!error.toString().contains('duplicate column name')) { + throw error; + } + }); + await m + .addColumn( + schema.metadataPluginsTable, + schema.metadataPluginsTable.pluginApiVersion, + ) + .catchError((error, stackTrace) { + // If the column already exists, ignore the error + if (!error.toString().contains('duplicate column name')) { + throw error; + } + }); + }, ), ); } diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index c96666b6..ba24c037 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -666,7 +666,7 @@ class $PreferencesTableTable extends PreferencesTable 'accent_color_scheme', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: false, - defaultValue: const Constant("Orange:0xFFf97315")) + defaultValue: const Constant("Slate:0xff64748b")) .withConverter( $PreferencesTableTable.$converteraccentColorScheme); static const VerificationMeta _layoutModeMeta = @@ -3889,7 +3889,9 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable @override late final GeneratedColumn pluginApiVersion = GeneratedColumn( 'plugin_api_version', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('1.0.0')); @override List get $columns => [ id, @@ -3969,8 +3971,6 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable _pluginApiVersionMeta, pluginApiVersion.isAcceptableOrUnknown( data['plugin_api_version']!, _pluginApiVersionMeta)); - } else if (isInserting) { - context.missing(_pluginApiVersionMeta); } return context; } @@ -4245,15 +4245,14 @@ class MetadataPluginsTableCompanion required List abilities, this.selected = const Value.absent(), this.repository = const Value.absent(), - required String pluginApiVersion, + this.pluginApiVersion = const Value.absent(), }) : name = Value(name), description = Value(description), version = Value(version), author = Value(author), entryPoint = Value(entryPoint), apis = Value(apis), - abilities = Value(abilities), - pluginApiVersion = Value(pluginApiVersion); + abilities = Value(abilities); static Insertable custom({ Expression? id, Expression? name, @@ -6365,7 +6364,7 @@ typedef $$MetadataPluginsTableTableCreateCompanionBuilder required List abilities, Value selected, Value repository, - required String pluginApiVersion, + Value pluginApiVersion, }); typedef $$MetadataPluginsTableTableUpdateCompanionBuilder = MetadataPluginsTableCompanion Function({ @@ -6583,7 +6582,7 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< required List abilities, Value selected = const Value.absent(), Value repository = const Value.absent(), - required String pluginApiVersion, + Value pluginApiVersion = const Value.absent(), }) => MetadataPluginsTableCompanion.insert( id: id, diff --git a/lib/models/database/database.steps.dart b/lib/models/database/database.steps.dart index 60efd63d..a228f5a7 100644 --- a/lib/models/database/database.steps.dart +++ b/lib/models/database/database.steps.dart @@ -1,4 +1,3 @@ -// dart format width=80 import 'package:drift/internal/versioned_schema.dart' as i0; import 'package:drift/drift.dart' as i1; import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import @@ -1408,7 +1407,7 @@ final class Schema5 extends i0.VersionedSchema { i1.GeneratedColumn _column_55(String aliasedName) => i1.GeneratedColumn('accent_color_scheme', aliasedName, false, type: i1.DriftSqlType.string, - defaultValue: const Constant("Orange:0xFFf97315")); + defaultValue: const Constant("Slate:0xff64748b")); final class Schema6 extends i0.VersionedSchema { Schema6({required super.database}) : super(version: 6); @@ -1989,6 +1988,220 @@ i1.GeneratedColumn _column_67(String aliasedName) => i1.GeneratedColumn _column_68(String aliasedName) => i1.GeneratedColumn('plugin_api_version', aliasedName, false, type: i1.DriftSqlType.string); + +final class Schema8 extends i0.VersionedSchema { + Schema8({required super.database}) : super(version: 8); + @override + late final List entities = [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + historyTable, + lyricsTable, + metadataPluginsTable, + uniqueBlacklist, + uniqTrackMatch, + ]; + late final Shape0 authenticationTable = Shape0( + source: i0.VersionedTable( + entityName: 'authentication_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape1 blacklistTable = Shape1( + source: i0.VersionedTable( + entityName: 'blacklist_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_4, + _column_5, + _column_6, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape13 preferencesTable = Shape13( + source: i0.VersionedTable( + entityName: 'preferences_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_13, + _column_14, + _column_15, + _column_55, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + _column_24, + _column_25, + _column_26, + _column_54, + _column_27, + _column_28, + _column_29, + _column_30, + _column_31, + _column_56, + _column_53, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape3 scrobblerTable = Shape3( + source: i0.VersionedTable( + entityName: 'scrobbler_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_32, + _column_33, + _column_34, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape4 skipSegmentTable = Shape4( + source: i0.VersionedTable( + entityName: 'skip_segment_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_35, + _column_36, + _column_37, + _column_32, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape5 sourceMatchTable = Shape5( + source: i0.VersionedTable( + entityName: 'source_match_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_37, + _column_38, + _column_39, + _column_32, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape14 audioPlayerStateTable = Shape14( + source: i0.VersionedTable( + entityName: 'audio_player_state_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_40, + _column_41, + _column_42, + _column_43, + _column_57, + _column_58, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape9 historyTable = Shape9( + source: i0.VersionedTable( + entityName: 'history_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_32, + _column_50, + _column_51, + _column_52, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape10 lyricsTable = Shape10( + source: i0.VersionedTable( + entityName: 'lyrics_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_37, + _column_52, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape15 metadataPluginsTable = Shape15( + source: i0.VersionedTable( + entityName: 'metadata_plugins_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_59, + _column_60, + _column_61, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + _column_69, + ], + attachedDatabase: database, + ), + alias: null); + final i1.Index uniqueBlacklist = i1.Index('unique_blacklist', + 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); + final i1.Index uniqTrackMatch = i1.Index('uniq_track_match', + 'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)'); +} + +i1.GeneratedColumn _column_69(String aliasedName) => + i1.GeneratedColumn('plugin_api_version', aliasedName, false, + type: i1.DriftSqlType.string, defaultValue: const Constant('1.0.0')); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -1996,6 +2209,7 @@ i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema5 schema) from4To5, required Future Function(i1.Migrator m, Schema6 schema) from5To6, required Future Function(i1.Migrator m, Schema7 schema) from6To7, + required Future Function(i1.Migrator m, Schema8 schema) from7To8, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -2029,6 +2243,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from6To7(migrator, schema); return 7; + case 7: + final schema = Schema8(database: database); + final migrator = i1.Migrator(database, schema); + await from7To8(migrator, schema); + return 8; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -2042,6 +2261,7 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema5 schema) from4To5, required Future Function(i1.Migrator m, Schema6 schema) from5To6, required Future Function(i1.Migrator m, Schema7 schema) from6To7, + required Future Function(i1.Migrator m, Schema8 schema) from7To8, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( @@ -2051,4 +2271,5 @@ i1.OnUpgrade stepByStep({ from4To5: from4To5, from5To6: from5To6, from6To7: from6To7, + from7To8: from7To8, )); diff --git a/lib/models/database/tables/metadata_plugins.dart b/lib/models/database/tables/metadata_plugins.dart index ef7a2fb1..8fa3b064 100644 --- a/lib/models/database/tables/metadata_plugins.dart +++ b/lib/models/database/tables/metadata_plugins.dart @@ -11,5 +11,6 @@ class MetadataPluginsTable extends Table { TextColumn get abilities => text().map(const StringListConverter())(); BoolColumn get selected => boolean().withDefault(const Constant(false))(); TextColumn get repository => text().nullable()(); - TextColumn get pluginApiVersion => text()(); + TextColumn get pluginApiVersion => + text().withDefault(const Constant('1.0.0'))(); } diff --git a/lib/models/database/tables/preferences.dart b/lib/models/database/tables/preferences.dart index 377f288d..85014920 100644 --- a/lib/models/database/tables/preferences.dart +++ b/lib/models/database/tables/preferences.dart @@ -79,7 +79,7 @@ class PreferencesTable extends Table { TextColumn get closeBehavior => textEnum() .withDefault(Constant(CloseBehavior.close.name))(); TextColumn get accentColorScheme => text() - .withDefault(const Constant("Orange:0xFFf97315")) + .withDefault(const Constant("Slate:0xff64748b")) .map(const SpotubeColorConverter())(); TextColumn get layoutMode => textEnum().withDefault(Constant(LayoutMode.adaptive.name))(); @@ -131,7 +131,7 @@ class PreferencesTable extends Table { systemTitleBar: false, skipNonMusic: false, closeBehavior: CloseBehavior.close, - accentColorScheme: SpotubeColor(Colors.orange.value, name: "Orange"), + accentColorScheme: SpotubeColor(Colors.slate.value, name: "Slate"), layoutMode: LayoutMode.adaptive, locale: const Locale("system", "system"), market: Market.US, diff --git a/lib/models/metadata/image.dart b/lib/models/metadata/image.dart index 0467dfd6..2ee0f748 100644 --- a/lib/models/metadata/image.dart +++ b/lib/models/metadata/image.dart @@ -20,9 +20,9 @@ enum ImagePlaceholder { } final placeholderUrlMap = { - ImagePlaceholder.albumArt: Assets.albumPlaceholder.path, - ImagePlaceholder.artist: Assets.userPlaceholder.path, - ImagePlaceholder.collection: Assets.placeholder.path, + ImagePlaceholder.albumArt: Assets.images.albumPlaceholder.path, + ImagePlaceholder.artist: Assets.images.userPlaceholder.path, + ImagePlaceholder.collection: Assets.images.placeholder.path, ImagePlaceholder.online: "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png", }; @@ -42,6 +42,17 @@ extension SpotubeImageExtensions on List? { : placeholderUrlMap[placeholder]!; } + Uri asUri({ + int index = 1, + required ImagePlaceholder placeholder, + }) { + final url = asUrlString(placeholder: placeholder, index: index); + if (url.startsWith("http")) { + return Uri.parse(url); + } + return Uri.file(url); + } + String smallest(ImagePlaceholder placeholder) { final sortedImage = this?.sorted((a, b) { final widthComparison = (a.width ?? 0).compareTo(b.width ?? 0); diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index bb4cf3f8..54fd452a 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -33,12 +33,8 @@ mixin _$SpotubeFullAlbumObject { String? get recordLabel => throw _privateConstructorUsedError; List? get genres => throw _privateConstructorUsedError; - /// Serializes this SpotubeFullAlbumObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeFullAlbumObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeFullAlbumObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -73,8 +69,6 @@ class _$SpotubeFullAlbumObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeFullAlbumObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -166,8 +160,6 @@ class __$$SpotubeFullAlbumObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeFullAlbumObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeFullAlbumObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -316,7 +308,7 @@ class _$SpotubeFullAlbumObjectImpl implements _SpotubeFullAlbumObject { const DeepCollectionEquality().equals(other._genres, _genres)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -331,9 +323,7 @@ class _$SpotubeFullAlbumObjectImpl implements _SpotubeFullAlbumObject { recordLabel, const DeepCollectionEquality().hash(_genres)); - /// Create a copy of SpotubeFullAlbumObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeFullAlbumObjectImplCopyWith<_$SpotubeFullAlbumObjectImpl> @@ -384,11 +374,8 @@ abstract class _SpotubeFullAlbumObject implements SpotubeFullAlbumObject { String? get recordLabel; @override List? get genres; - - /// Create a copy of SpotubeFullAlbumObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeFullAlbumObjectImplCopyWith<_$SpotubeFullAlbumObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -409,12 +396,8 @@ mixin _$SpotubeSimpleAlbumObject { SpotubeAlbumType get albumType => throw _privateConstructorUsedError; String? get releaseDate => throw _privateConstructorUsedError; - /// Serializes this SpotubeSimpleAlbumObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeSimpleAlbumObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeSimpleAlbumObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -446,8 +429,6 @@ class _$SpotubeSimpleAlbumObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeSimpleAlbumObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -521,8 +502,6 @@ class __$$SpotubeSimpleAlbumObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeSimpleAlbumObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeSimpleAlbumObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -634,7 +613,7 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { other.releaseDate == releaseDate)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -646,9 +625,7 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { albumType, releaseDate); - /// Create a copy of SpotubeSimpleAlbumObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeSimpleAlbumObjectImplCopyWith<_$SpotubeSimpleAlbumObjectImpl> @@ -690,11 +667,8 @@ abstract class _SpotubeSimpleAlbumObject implements SpotubeSimpleAlbumObject { SpotubeAlbumType get albumType; @override String? get releaseDate; - - /// Create a copy of SpotubeSimpleAlbumObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeSimpleAlbumObjectImplCopyWith<_$SpotubeSimpleAlbumObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -713,12 +687,8 @@ mixin _$SpotubeFullArtistObject { List? get genres => throw _privateConstructorUsedError; int? get followers => throw _privateConstructorUsedError; - /// Serializes this SpotubeFullArtistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeFullArtistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeFullArtistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -749,8 +719,6 @@ class _$SpotubeFullArtistObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeFullArtistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -818,8 +786,6 @@ class __$$SpotubeFullArtistObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeFullArtistObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeFullArtistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -923,7 +889,7 @@ class _$SpotubeFullArtistObjectImpl implements _SpotubeFullArtistObject { other.followers == followers)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -934,9 +900,7 @@ class _$SpotubeFullArtistObjectImpl implements _SpotubeFullArtistObject { const DeepCollectionEquality().hash(_genres), followers); - /// Create a copy of SpotubeFullArtistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeFullArtistObjectImplCopyWith<_$SpotubeFullArtistObjectImpl> @@ -975,11 +939,8 @@ abstract class _SpotubeFullArtistObject implements SpotubeFullArtistObject { List? get genres; @override int? get followers; - - /// Create a copy of SpotubeFullArtistObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeFullArtistObjectImplCopyWith<_$SpotubeFullArtistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -996,12 +957,8 @@ mixin _$SpotubeSimpleArtistObject { String get externalUri => throw _privateConstructorUsedError; List? get images => throw _privateConstructorUsedError; - /// Serializes this SpotubeSimpleArtistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeSimpleArtistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeSimpleArtistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -1030,8 +987,6 @@ class _$SpotubeSimpleArtistObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeSimpleArtistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1087,8 +1042,6 @@ class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeSimpleArtistObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeSimpleArtistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1164,14 +1117,12 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject { const DeepCollectionEquality().equals(other._images, _images)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, id, name, externalUri, const DeepCollectionEquality().hash(_images)); - /// Create a copy of SpotubeSimpleArtistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeSimpleArtistObjectImplCopyWith<_$SpotubeSimpleArtistObjectImpl> @@ -1205,11 +1156,8 @@ abstract class _SpotubeSimpleArtistObject implements SpotubeSimpleArtistObject { String get externalUri; @override List? get images; - - /// Create a copy of SpotubeSimpleArtistObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeSimpleArtistObjectImplCopyWith<_$SpotubeSimpleArtistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1227,13 +1175,9 @@ mixin _$SpotubeBrowseSectionObject { bool get browseMore => throw _privateConstructorUsedError; List get items => throw _privateConstructorUsedError; - /// Serializes this SpotubeBrowseSectionObject to a JSON map. Map toJson(Object? Function(T) toJsonT) => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeBrowseSectionObjectCopyWith> get copyWith => throw _privateConstructorUsedError; } @@ -1265,8 +1209,6 @@ class _$SpotubeBrowseSectionObjectCopyWithImpl $Res Function(_$SpotubeBrowseSectionObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1415,14 +1355,12 @@ class _$SpotubeBrowseSectionObjectImpl const DeepCollectionEquality().equals(other._items, _items)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, id, title, externalUri, browseMore, const DeepCollectionEquality().hash(_items)); - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeBrowseSectionObjectImplCopyWith bool get browseMore; @override List get items; - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeBrowseSectionObjectImplCopyWith> get copyWith => throw _privateConstructorUsedError; @@ -1551,13 +1486,8 @@ mixin _$MetadataFormFieldObject { required TResult orElse(), }) => throw _privateConstructorUsedError; - - /// Serializes this MetadataFormFieldObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $MetadataFormFieldObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -1582,8 +1512,6 @@ class _$MetadataFormFieldObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1627,8 +1555,6 @@ class __$$MetadataFormFieldInputObjectImplCopyWithImpl<$Res> $Res Function(_$MetadataFormFieldInputObjectImpl) _then) : super(_value, _then); - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1729,14 +1655,12 @@ class _$MetadataFormFieldInputObjectImpl (identical(other.regex, regex) || other.regex == regex)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, objectType, id, variant, placeholder, defaultValue, required, regex); - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$MetadataFormFieldInputObjectImplCopyWith< @@ -1862,11 +1786,8 @@ abstract class MetadataFormFieldInputObject implements MetadataFormFieldObject { String? get defaultValue; bool? get required; String? get regex; - - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$MetadataFormFieldInputObjectImplCopyWith< _$MetadataFormFieldInputObjectImpl> get copyWith => throw _privateConstructorUsedError; @@ -1894,8 +1815,6 @@ class __$$MetadataFormFieldTextObjectImplCopyWithImpl<$Res> $Res Function(_$MetadataFormFieldTextObjectImpl) _then) : super(_value, _then); - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1945,13 +1864,11 @@ class _$MetadataFormFieldTextObjectImpl implements MetadataFormFieldTextObject { (identical(other.text, text) || other.text == text)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, objectType, text); - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$MetadataFormFieldTextObjectImplCopyWith<_$MetadataFormFieldTextObjectImpl> @@ -2063,11 +1980,8 @@ abstract class MetadataFormFieldTextObject implements MetadataFormFieldObject { @override String get objectType; String get text; - - /// Create a copy of MetadataFormFieldObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$MetadataFormFieldTextObjectImplCopyWith<_$MetadataFormFieldTextObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -2082,12 +1996,8 @@ mixin _$SpotubeImageObject { int? get width => throw _privateConstructorUsedError; int? get height => throw _privateConstructorUsedError; - /// Serializes this SpotubeImageObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeImageObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeImageObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -2111,8 +2021,6 @@ class _$SpotubeImageObjectCopyWithImpl<$Res, $Val extends SpotubeImageObject> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeImageObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2156,8 +2064,6 @@ class __$$SpotubeImageObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeImageObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeImageObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2212,13 +2118,11 @@ class _$SpotubeImageObjectImpl implements _SpotubeImageObject { (identical(other.height, height) || other.height == height)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, url, width, height); - /// Create a copy of SpotubeImageObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeImageObjectImplCopyWith<_$SpotubeImageObjectImpl> get copyWith => @@ -2248,11 +2152,8 @@ abstract class _SpotubeImageObject implements SpotubeImageObject { int? get width; @override int? get height; - - /// Create a copy of SpotubeImageObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeImageObjectImplCopyWith<_$SpotubeImageObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -2270,13 +2171,9 @@ mixin _$SpotubePaginationResponseObject { bool get hasMore => throw _privateConstructorUsedError; List get items => throw _privateConstructorUsedError; - /// Serializes this SpotubePaginationResponseObject to a JSON map. Map toJson(Object? Function(T) toJsonT) => throw _privateConstructorUsedError; - - /// Create a copy of SpotubePaginationResponseObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubePaginationResponseObjectCopyWith> get copyWith => throw _privateConstructorUsedError; @@ -2305,8 +2202,6 @@ class _$SpotubePaginationResponseObjectCopyWithImpl $Res Function(_$SpotubePaginationResponseObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubePaginationResponseObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2450,14 +2343,12 @@ class _$SpotubePaginationResponseObjectImpl const DeepCollectionEquality().equals(other._items, _items)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, limit, nextOffset, total, hasMore, const DeepCollectionEquality().hash(_items)); - /// Create a copy of SpotubePaginationResponseObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubePaginationResponseObjectImplCopyWith bool get hasMore; @override List get items; - - /// Create a copy of SpotubePaginationResponseObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubePaginationResponseObjectImplCopyWith> get copyWith => throw _privateConstructorUsedError; @@ -2522,12 +2410,8 @@ mixin _$SpotubeFullPlaylistObject { bool get collaborative => throw _privateConstructorUsedError; bool get public => throw _privateConstructorUsedError; - /// Serializes this SpotubeFullPlaylistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeFullPlaylistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeFullPlaylistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -2563,8 +2447,6 @@ class _$SpotubeFullPlaylistObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeFullPlaylistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2618,8 +2500,6 @@ class _$SpotubeFullPlaylistObjectCopyWithImpl<$Res, ) as $Val); } - /// Create a copy of SpotubeFullPlaylistObject - /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SpotubeUserObjectCopyWith<$Res> get owner { @@ -2663,8 +2543,6 @@ class __$$SpotubeFullPlaylistObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeFullPlaylistObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeFullPlaylistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2798,7 +2676,7 @@ class _$SpotubeFullPlaylistObjectImpl implements _SpotubeFullPlaylistObject { (identical(other.public, public) || other.public == public)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -2812,9 +2690,7 @@ class _$SpotubeFullPlaylistObjectImpl implements _SpotubeFullPlaylistObject { collaborative, public); - /// Create a copy of SpotubeFullPlaylistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeFullPlaylistObjectImplCopyWith<_$SpotubeFullPlaylistObjectImpl> @@ -2862,11 +2738,8 @@ abstract class _SpotubeFullPlaylistObject implements SpotubeFullPlaylistObject { bool get collaborative; @override bool get public; - - /// Create a copy of SpotubeFullPlaylistObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeFullPlaylistObjectImplCopyWith<_$SpotubeFullPlaylistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -2885,12 +2758,8 @@ mixin _$SpotubeSimplePlaylistObject { SpotubeUserObject get owner => throw _privateConstructorUsedError; List get images => throw _privateConstructorUsedError; - /// Serializes this SpotubeSimplePlaylistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeSimplePlaylistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeSimplePlaylistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -2925,8 +2794,6 @@ class _$SpotubeSimplePlaylistObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeSimplePlaylistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -2965,8 +2832,6 @@ class _$SpotubeSimplePlaylistObjectCopyWithImpl<$Res, ) as $Val); } - /// Create a copy of SpotubeSimplePlaylistObject - /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SpotubeUserObjectCopyWith<$Res> get owner { @@ -3007,8 +2872,6 @@ class __$$SpotubeSimplePlaylistObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeSimplePlaylistObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeSimplePlaylistObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -3104,14 +2967,12 @@ class _$SpotubeSimplePlaylistObjectImpl const DeepCollectionEquality().equals(other._images, _images)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, id, name, description, externalUri, owner, const DeepCollectionEquality().hash(_images)); - /// Create a copy of SpotubeSimplePlaylistObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeSimplePlaylistObjectImplCopyWith<_$SpotubeSimplePlaylistObjectImpl> @@ -3152,11 +3013,8 @@ abstract class _SpotubeSimplePlaylistObject SpotubeUserObject get owner; @override List get images; - - /// Create a copy of SpotubeSimplePlaylistObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeSimplePlaylistObjectImplCopyWith<_$SpotubeSimplePlaylistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -3176,12 +3034,8 @@ mixin _$SpotubeSearchResponseObject { throw _privateConstructorUsedError; List get tracks => throw _privateConstructorUsedError; - /// Serializes this SpotubeSearchResponseObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeSearchResponseObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeSearchResponseObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -3212,8 +3066,6 @@ class _$SpotubeSearchResponseObjectCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeSearchResponseObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -3269,8 +3121,6 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeSearchResponseObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeSearchResponseObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -3367,7 +3217,7 @@ class _$SpotubeSearchResponseObjectImpl const DeepCollectionEquality().equals(other._tracks, _tracks)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -3376,9 +3226,7 @@ class _$SpotubeSearchResponseObjectImpl const DeepCollectionEquality().hash(_playlists), const DeepCollectionEquality().hash(_tracks)); - /// Create a copy of SpotubeSearchResponseObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeSearchResponseObjectImplCopyWith<_$SpotubeSearchResponseObjectImpl> @@ -3413,11 +3261,8 @@ abstract class _SpotubeSearchResponseObject List get playlists; @override List get tracks; - - /// Create a copy of SpotubeSearchResponseObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeSearchResponseObjectImplCopyWith<_$SpotubeSearchResponseObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -3533,13 +3378,8 @@ mixin _$SpotubeTrackObject { required TResult orElse(), }) => throw _privateConstructorUsedError; - - /// Serializes this SpotubeTrackObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeTrackObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -3571,8 +3411,6 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -3611,8 +3449,6 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> ) as $Val); } - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SpotubeSimpleAlbumObjectCopyWith<$Res> get album { @@ -3654,8 +3490,6 @@ class __$$SpotubeLocalTrackObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeLocalTrackObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -3764,14 +3598,12 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { (identical(other.path, path) || other.path == path)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, id, name, externalUri, const DeepCollectionEquality().hash(_artists), album, durationMs, path); - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeLocalTrackObjectImplCopyWith<_$SpotubeLocalTrackObjectImpl> @@ -3925,11 +3757,8 @@ abstract class SpotubeLocalTrackObject implements SpotubeTrackObject { @override int get durationMs; String get path; - - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeLocalTrackObjectImplCopyWith<_$SpotubeLocalTrackObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -3966,8 +3795,6 @@ class __$$SpotubeFullTrackObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeFullTrackObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -4086,7 +3913,7 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { other.explicit == explicit)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -4099,9 +3926,7 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { isrc, explicit); - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeFullTrackObjectImplCopyWith<_$SpotubeFullTrackObjectImpl> @@ -4260,11 +4085,8 @@ abstract class SpotubeFullTrackObject implements SpotubeTrackObject { int get durationMs; String get isrc; bool get explicit; - - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeFullTrackObjectImplCopyWith<_$SpotubeFullTrackObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -4280,12 +4102,8 @@ mixin _$SpotubeUserObject { List get images => throw _privateConstructorUsedError; String get externalUri => throw _privateConstructorUsedError; - /// Serializes this SpotubeUserObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotubeUserObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SpotubeUserObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -4313,8 +4131,6 @@ class _$SpotubeUserObjectCopyWithImpl<$Res, $Val extends SpotubeUserObject> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeUserObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -4367,8 +4183,6 @@ class __$$SpotubeUserObjectImplCopyWithImpl<$Res> $Res Function(_$SpotubeUserObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeUserObject - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -4444,14 +4258,12 @@ class _$SpotubeUserObjectImpl implements _SpotubeUserObject { other.externalUri == externalUri)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, id, name, const DeepCollectionEquality().hash(_images), externalUri); - /// Create a copy of SpotubeUserObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith => @@ -4484,11 +4296,8 @@ abstract class _SpotubeUserObject implements SpotubeUserObject { List get images; @override String get externalUri; - - /// Create a copy of SpotubeUserObject - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -4510,12 +4319,8 @@ mixin _$PluginConfiguration { List get abilities => throw _privateConstructorUsedError; String? get repository => throw _privateConstructorUsedError; - /// Serializes this PluginConfiguration to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of PluginConfiguration - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $PluginConfigurationCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -4549,8 +4354,6 @@ class _$PluginConfigurationCopyWithImpl<$Res, $Val extends PluginConfiguration> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of PluginConfiguration - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -4639,8 +4442,6 @@ class __$$PluginConfigurationImplCopyWithImpl<$Res> $Res Function(_$PluginConfigurationImpl) _then) : super(_value, _then); - /// Create a copy of PluginConfiguration - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -4783,7 +4584,7 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { other.repository == repository)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -4798,9 +4599,7 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { const DeepCollectionEquality().hash(_abilities), repository); - /// Create a copy of PluginConfiguration - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith => @@ -4852,11 +4651,8 @@ abstract class _PluginConfiguration extends PluginConfiguration { List get abilities; @override String? get repository; - - /// Create a copy of PluginConfiguration - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith => throw _privateConstructorUsedError; } @@ -4872,12 +4668,8 @@ mixin _$PluginUpdateAvailable { String get version => throw _privateConstructorUsedError; String? get changelog => throw _privateConstructorUsedError; - /// Serializes this PluginUpdateAvailable to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of PluginUpdateAvailable - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $PluginUpdateAvailableCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -4902,8 +4694,6 @@ class _$PluginUpdateAvailableCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of PluginUpdateAvailable - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -4949,8 +4739,6 @@ class __$$PluginUpdateAvailableImplCopyWithImpl<$Res> $Res Function(_$PluginUpdateAvailableImpl) _then) : super(_value, _then); - /// Create a copy of PluginUpdateAvailable - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -5008,13 +4796,11 @@ class _$PluginUpdateAvailableImpl implements _PluginUpdateAvailable { other.changelog == changelog)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, downloadUrl, version, changelog); - /// Create a copy of PluginUpdateAvailable - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$PluginUpdateAvailableImplCopyWith<_$PluginUpdateAvailableImpl> @@ -5044,11 +4830,8 @@ abstract class _PluginUpdateAvailable implements PluginUpdateAvailable { String get version; @override String? get changelog; - - /// Create a copy of PluginUpdateAvailable - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$PluginUpdateAvailableImplCopyWith<_$PluginUpdateAvailableImpl> get copyWith => throw _privateConstructorUsedError; } @@ -5065,12 +4848,8 @@ mixin _$MetadataPluginRepository { String get description => throw _privateConstructorUsedError; String get repoUrl => throw _privateConstructorUsedError; - /// Serializes this MetadataPluginRepository to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of MetadataPluginRepository - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $MetadataPluginRepositoryCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -5095,8 +4874,6 @@ class _$MetadataPluginRepositoryCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of MetadataPluginRepository - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -5148,8 +4925,6 @@ class __$$MetadataPluginRepositoryImplCopyWithImpl<$Res> $Res Function(_$MetadataPluginRepositoryImpl) _then) : super(_value, _then); - /// Create a copy of MetadataPluginRepository - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -5217,14 +4992,12 @@ class _$MetadataPluginRepositoryImpl implements _MetadataPluginRepository { (identical(other.repoUrl, repoUrl) || other.repoUrl == repoUrl)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, name, owner, description, repoUrl); - /// Create a copy of MetadataPluginRepository - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$MetadataPluginRepositoryImplCopyWith<_$MetadataPluginRepositoryImpl> @@ -5257,11 +5030,8 @@ abstract class _MetadataPluginRepository implements MetadataPluginRepository { String get description; @override String get repoUrl; - - /// Create a copy of MetadataPluginRepository - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$MetadataPluginRepositoryImplCopyWith<_$MetadataPluginRepositoryImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/metadata/track.dart b/lib/models/metadata/track.dart index 84af429a..ecf7f0a2 100644 --- a/lib/models/metadata/track.dart +++ b/lib/models/metadata/track.dart @@ -60,6 +60,14 @@ class SpotubeTrackObject with _$SpotubeTrackObject { ], releaseDate: metadata?.year != null ? "${metadata!.year}-01-01" : "1970-01-01", + images: [ + if (art != null) + SpotubeImageObject( + url: art, + width: 300, + height: 300, + ), + ], ), durationMs: metadata?.durationMs?.toInt() ?? 0, path: file.path, @@ -93,7 +101,9 @@ extension ToMetadataSpotubeFullTrackObject on SpotubeFullTrackObject { albumArtist: artists.map((a) => a.name).join(", "), year: album.releaseDate == null ? 1970 - : DateTime.parse(album.releaseDate!).year, + : DateTime.tryParse(album.releaseDate!)?.year ?? + int.tryParse(album.releaseDate!) ?? + 1970, durationMs: durationMs.toDouble(), fileSize: BigInt.from(fileLength), picture: imageBytes != null diff --git a/lib/models/playback/track_sources.dart b/lib/models/playback/track_sources.dart index 6900e323..c9d089a6 100644 --- a/lib/models/playback/track_sources.dart +++ b/lib/models/playback/track_sources.dart @@ -2,6 +2,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/sourced_track/enums.dart'; part 'track_sources.freezed.dart'; @@ -38,6 +39,27 @@ class TrackSourceQuery with _$TrackSourceQuery { /// Parses [SpotubeMedia]'s [uri] property to create a [TrackSourceQuery]. factory TrackSourceQuery.parseUri(String url) { + final isLocal = !url.startsWith("http"); + + if (isLocal) { + try { + return TrackSourceQuery( + id: url, + title: '', + artists: [], + album: '', + durationMs: 0, + isrc: '', + explicit: false, + ); + } catch (e, stackTrace) { + AppLogger.log.e( + "Failed to parse local track URI: $url\n$e", + stackTrace: stackTrace, + ); + } + } + final uri = Uri.parse(url); return TrackSourceQuery( id: uri.pathSegments.last, diff --git a/lib/models/playback/track_sources.freezed.dart b/lib/models/playback/track_sources.freezed.dart index 28130873..760037d8 100644 --- a/lib/models/playback/track_sources.freezed.dart +++ b/lib/models/playback/track_sources.freezed.dart @@ -28,12 +28,8 @@ mixin _$TrackSourceQuery { String get isrc => throw _privateConstructorUsedError; bool get explicit => throw _privateConstructorUsedError; - /// Serializes this TrackSourceQuery to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of TrackSourceQuery - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $TrackSourceQueryCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -64,8 +60,6 @@ class _$TrackSourceQueryCopyWithImpl<$Res, $Val extends TrackSourceQuery> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of TrackSourceQuery - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -136,8 +130,6 @@ class __$$TrackSourceQueryImplCopyWithImpl<$Res> $Res Function(_$TrackSourceQueryImpl) _then) : super(_value, _then); - /// Create a copy of TrackSourceQuery - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -241,7 +233,7 @@ class _$TrackSourceQueryImpl extends _TrackSourceQuery { other.explicit == explicit)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -253,9 +245,7 @@ class _$TrackSourceQueryImpl extends _TrackSourceQuery { isrc, explicit); - /// Create a copy of TrackSourceQuery - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => @@ -298,11 +288,8 @@ abstract class _TrackSourceQuery extends TrackSourceQuery { String get isrc; @override bool get explicit; - - /// Create a copy of TrackSourceQuery - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => throw _privateConstructorUsedError; } @@ -320,12 +307,8 @@ mixin _$TrackSourceInfo { String get pageUrl => throw _privateConstructorUsedError; int get durationMs => throw _privateConstructorUsedError; - /// Serializes this TrackSourceInfo to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of TrackSourceInfo - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $TrackSourceInfoCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -355,8 +338,6 @@ class _$TrackSourceInfoCopyWithImpl<$Res, $Val extends TrackSourceInfo> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of TrackSourceInfo - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -421,8 +402,6 @@ class __$$TrackSourceInfoImplCopyWithImpl<$Res> _$TrackSourceInfoImpl _value, $Res Function(_$TrackSourceInfoImpl) _then) : super(_value, _then); - /// Create a copy of TrackSourceInfo - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -509,14 +488,12 @@ class _$TrackSourceInfoImpl implements _TrackSourceInfo { other.durationMs == durationMs)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, id, title, artists, thumbnail, pageUrl, durationMs); - /// Create a copy of TrackSourceInfo - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => @@ -555,11 +532,8 @@ abstract class _TrackSourceInfo implements TrackSourceInfo { String get pageUrl; @override int get durationMs; - - /// Create a copy of TrackSourceInfo - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => throw _privateConstructorUsedError; } @@ -575,12 +549,8 @@ mixin _$TrackSource { SourceCodecs get codec => throw _privateConstructorUsedError; String get bitrate => throw _privateConstructorUsedError; - /// Serializes this TrackSource to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of TrackSource - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $TrackSourceCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -608,8 +578,6 @@ class _$TrackSourceCopyWithImpl<$Res, $Val extends TrackSource> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of TrackSource - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -662,8 +630,6 @@ class __$$TrackSourceImplCopyWithImpl<$Res> _$TrackSourceImpl _value, $Res Function(_$TrackSourceImpl) _then) : super(_value, _then); - /// Create a copy of TrackSource - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -730,13 +696,11 @@ class _$TrackSourceImpl implements _TrackSource { (identical(other.bitrate, bitrate) || other.bitrate == bitrate)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, url, quality, codec, bitrate); - /// Create a copy of TrackSource - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith => @@ -768,11 +732,8 @@ abstract class _TrackSource implements TrackSource { SourceCodecs get codec; @override String get bitrate; - - /// Create a copy of TrackSource - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/modules/home/sections/new_releases.dart b/lib/modules/home/sections/new_releases.dart index b2f46b10..2a1f2f91 100644 --- a/lib/modules/home/sections/new_releases.dart +++ b/lib/modules/home/sections/new_releases.dart @@ -1,11 +1,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/album/releases.dart'; import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; class HomeNewReleasesSection extends HookConsumerWidget { const HomeNewReleasesSection({super.key}); @@ -24,12 +26,30 @@ class HomeNewReleasesSection extends HookConsumerWidget { return const SizedBox.shrink(); } + if (newReleases.error + case MetadataPluginException( + errorCode: MetadataPluginErrorCode.noDefaultPlugin, + message: _, + )) { + return const SizedBox.shrink(); + } + return HorizontalPlaybuttonCardView( items: newReleases.asData?.value.items ?? [], title: Text(context.l10n.new_releases), isLoadingNextPage: newReleases.isLoadingNextPage, hasNextPage: newReleases.asData?.value.hasMore ?? false, onFetchMore: newReleasesNotifier.fetchMore, + error: newReleases.hasError + ? Center( + child: ErrorBox( + error: newReleases.error!, + onRetry: () { + ref.invalidate(metadataPluginAlbumReleasesProvider); + }, + ), + ) + : null, ); } } diff --git a/lib/modules/home/sections/sections.dart b/lib/modules/home/sections/sections.dart index cffbaf9e..b04e7a9e 100644 --- a/lib/modules/home/sections/sections.dart +++ b/lib/modules/home/sections/sections.dart @@ -2,10 +2,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/routes.gr.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; +import 'package:spotube/components/fallbacks/no_default_metadata_plugin.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/metadata_plugin/browse/sections.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:flutter_undraw/flutter_undraw.dart'; @@ -34,8 +37,7 @@ class HomePageBrowseSection extends HookConsumerWidget { spacing: 8, children: [ const CircularProgressIndicator(), - const Text("Building your timeline based on your listenings...") - .muted, + Text(context.l10n.building_your_timeline).muted, ], ), const Gap(16), @@ -44,6 +46,29 @@ class HomePageBrowseSection extends HookConsumerWidget { ); } + if (browseSections.error + case MetadataPluginException( + errorCode: MetadataPluginErrorCode.noDefaultPlugin, + message: _, + )) { + return const SliverFillRemaining( + child: Center(child: NoDefaultMetadataPlugin()), + ); + } + + if (browseSections.hasError) { + return SliverFillRemaining( + child: Center( + child: ErrorBox( + error: browseSections.error!, + onRetry: () { + ref.invalidate(metadataPluginBrowseSectionsProvider); + }, + ), + ), + ); + } + return SliverInfiniteList( hasReachedMax: browseSections.asData?.value.hasMore == false, isLoading: !browseSections.isLoading && browseSections.isLoadingNextPage, diff --git a/lib/modules/library/local_folder/local_folder_item.dart b/lib/modules/library/local_folder/local_folder_item.dart index 12dcdbbe..8bed5f7f 100644 --- a/lib/modules/library/local_folder/local_folder_item.dart +++ b/lib/modules/library/local_folder/local_folder_item.dart @@ -32,23 +32,6 @@ class LocalFolderItem extends HookConsumerWidget { final isDownloadFolder = folder == downloadFolder; final isCacheFolder = folder == cacheFolder.data; - final Uri(:pathSegments) = Uri.parse( - folder - .replaceFirst(RegExp(r'^/Volumes/[^/]+/Users/'), "") - .replaceFirst(r'C:\Users\', "") - .replaceFirst(r'/home/', ""), - ); - - // if length > 5, we ... all the middle segments after 2 and the last 2 - final segments = pathSegments.length > 5 - ? [ - ...pathSegments.take(2), - "...", - ...pathSegments.skip(pathSegments.length - 3).toList() - ..removeLast(), - ] - : pathSegments.take(max(pathSegments.length - 1, 0)).toList(); - final trackSnapshot = ref.watch( localTracksProvider.select( (s) => s.whenData((tracks) => tracks[folder]?.take(4).toList()), @@ -111,40 +94,18 @@ class LocalFolderItem extends HookConsumerWidget { const Gap(8), Stack( children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Center( - child: Text( - isDownloadFolder - ? context.l10n.downloads - : isCacheFolder - ? context.l10n.cache_folder.capitalize() - : basename(folder), - style: const TextStyle(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - Wrap( - spacing: 2, - runSpacing: 2, - children: [ - for (final MapEntry(key: index, value: segment) - in segments.asMap().entries) - Text.rich( - TextSpan( - children: [ - if (index != 0) const TextSpan(text: "/ "), - TextSpan(text: segment), - ], - ), - maxLines: 2, - ).xSmall().muted(), - ], - ), - ], + Center( + child: Text( + isDownloadFolder + ? context.l10n.downloads + : isCacheFolder + ? context.l10n.cache_folder.capitalize() + : basename(folder), + style: const TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), if (!isDownloadFolder && !isCacheFolder) Align( diff --git a/lib/modules/metadata_plugins/installed_plugin.dart b/lib/modules/metadata_plugins/installed_plugin.dart index 9692c38c..4d050afc 100644 --- a/lib/modules/metadata_plugins/installed_plugin.dart +++ b/lib/modules/metadata_plugins/installed_plugin.dart @@ -1,9 +1,9 @@ -import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.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/components/markdown/markdown.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/metadata_plugins/plugin_update_available_dialog.dart'; import 'package:spotube/provider/metadata_plugin/core/auth.dart'; @@ -85,15 +85,15 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { runSpacing: 8, children: [ if (isOfficial) - const PrimaryBadge( - leading: Icon(SpotubeIcons.done), - child: Text("Official"), + PrimaryBadge( + leading: const Icon(SpotubeIcons.done), + child: Text(context.l10n.official), ) else ...[ - Text("Author: ${plugin.author}"), - const DestructiveBadge( - leading: Icon(SpotubeIcons.warning), - child: Text("Third-party"), + Text(context.l10n.author_name(plugin.author)), + DestructiveBadge( + leading: const Icon(SpotubeIcons.warning), + child: Text(context.l10n.third_party), ) ], SecondaryBadge( @@ -103,6 +103,14 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { launchUrl(repoUrl); }, ), + SecondaryBadge( + child: Padding( + padding: const EdgeInsets.all(1), + child: Text( + "${context.l10n.version}: ${plugin.version}", + ), + ), + ), ], ) ], @@ -132,11 +140,11 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { spacing: 12, children: [ if (requiresAuth && !isAuthenticated) - const Row( + Row( spacing: 8, children: [ - Icon(SpotubeIcons.warning, color: Colors.yellow), - Text("Plugin requires authentication"), + const Icon(SpotubeIcons.warning, color: Colors.yellow), + Text(context.l10n.plugin_requires_authentication), ], ), if (hasUpdate) @@ -144,7 +152,7 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { width: double.infinity, child: Basic( leading: const Icon(SpotubeIcons.update), - title: const Text("Update available"), + title: Text(context.l10n.update_available), subtitle: Text( updateAvailable!.asData!.value!.version, ), @@ -159,19 +167,17 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { ), ); }, - child: const Text("Update"), + child: Text(context.l10n.update), ), ), ), if (supportsScrobbling) - const SizedBox( + SizedBox( width: double.infinity, child: Basic( - leading: Icon(SpotubeIcons.info), - title: Text("Supports scrobbling"), - subtitle: Text( - "This plugin scrobbles your music to generate your listening history.", - ), + leading: const Icon(SpotubeIcons.info), + title: Text(context.l10n.supports_scrobbling), + subtitle: Text(context.l10n.plugin_scrobbling_info), ), ) ], @@ -185,9 +191,11 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { onPressed: () async { await pluginsNotifier.setDefaultPlugin(plugin); }, - child: isDefault - ? const Text("Default") - : const Text("Set default"), + child: Text( + isDefault + ? context.l10n.default_plugin + : context.l10n.set_default, + ), ), if (isDefault) Consumer(builder: (context, ref, _) { @@ -227,13 +235,14 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { }, ), leading: const Icon(SpotubeIcons.heartFilled), - child: const Text("Support"), + child: Text(context.l10n.support), onPressed: () { showDialog( context: context, builder: (context) { return AlertDialog( - title: const Text("Support plugin development"), + title: + Text(context.l10n.support_plugin_development), content: ConstrainedBox( constraints: BoxConstraints( maxHeight: mediaQuery.height * 0.8, @@ -253,7 +262,7 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { onPressed: () { Navigator.of(context).pop(); }, - child: const Text("Close"), + child: Text(context.l10n.close), ), ], ); @@ -269,7 +278,7 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { await metadataPlugin.asData?.value?.auth.authenticate(); }, leading: const Icon(SpotubeIcons.login), - child: const Text("Login"), + child: Text(context.l10n.login), ) else if (isDefault && requiresAuth && isAuthenticated) Button.destructive( @@ -277,7 +286,7 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { await metadataPlugin.asData?.value?.auth.logout(); }, leading: const Icon(SpotubeIcons.logout), - child: const Text("Logout"), + child: Text(context.l10n.logout), ) ], ) diff --git a/lib/modules/metadata_plugins/plugin_repository.dart b/lib/modules/metadata_plugins/plugin_repository.dart index 3a093741..f140b9ee 100644 --- a/lib/modules/metadata_plugins/plugin_repository.dart +++ b/lib/modules/metadata_plugins/plugin_repository.dart @@ -4,6 +4,7 @@ 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/components/markdown/markdown.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -26,7 +27,8 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { return Card( child: Basic( - title: Text(pluginRepo.name), + title: Text( + "${pluginRepo.owner == "KRTirtho" ? "" : "${pluginRepo.owner}/"}${pluginRepo.name}"), subtitle: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -37,9 +39,9 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { spacing: 8, children: [ if (pluginRepo.owner == "KRTirtho") ...[ - const PrimaryBadge( + PrimaryBadge( leading: Icon(SpotubeIcons.done), - child: Text("Official"), + child: Text(context.l10n.official), ), SecondaryBadge( leading: host == "github.com" @@ -51,10 +53,10 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { }, ), ] else ...[ - Text("Author: ${pluginRepo.owner}"), - const DestructiveBadge( - leading: Icon(SpotubeIcons.warning), - child: Text("Third-party"), + Text(context.l10n.author_name(pluginRepo.owner)), + DestructiveBadge( + leading: const Icon(SpotubeIcons.warning), + child: Text(context.l10n.third_party), ) ] ], @@ -78,21 +80,19 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { context: context, builder: (context) { final pluginAbilities = pluginConfig.apis - .map((e) => "- Can access **${e.name}** API") + .map( + (e) => context.l10n.can_access_name_api(e.name)) .join("\n\n"); return AlertDialog( - title: - const Text("Do you want to install this plugin?"), + title: Text( + context.l10n.do_you_want_to_install_this_plugin), content: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - "This plugin is from a third-party repository. " - "Please ensure you trust the source before installing.", - ), + Text(context.l10n.third_party_plugin_warning), const Gap(8), FutureBuilder( future: @@ -125,9 +125,10 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { ), const Gap(8), AppMarkdown( - data: "**Author**: ${pluginConfig.author}\n\n" - "**Repository**: [${pluginConfig.repository ?? 'N/A'}](${pluginConfig.repository})\n\n\n\n" - "This plugin can do following:\n\n" + data: + "**${context.l10n.author}**: ${pluginConfig.author}\n\n" + "**${context.l10n.repository}**: [${pluginConfig.repository ?? 'N/A'}](${pluginConfig.repository})\n\n\n\n" + "${context.l10n.this_plugin_can_do_following}:\n\n" "$pluginAbilities", ), ], @@ -137,13 +138,13 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { onPressed: () { Navigator.of(context).pop(false); }, - child: const Text("Deny"), + child: Text(context.l10n.decline), ), Button.primary( onPressed: () { Navigator.of(context).pop(true); }, - child: const Text("Allow"), + child: Text(context.l10n.accept), ), ], ); @@ -161,7 +162,7 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { leading: isInstalling.value ? const CircularProgressIndicator() : const Icon(SpotubeIcons.add), - child: const Text("Install"), + child: Text(context.l10n.install), ), ), ); diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index f96c52df..ec903aab 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:spotube/collections/assets.gen.dart'; @@ -21,11 +21,9 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; -import 'package:spotube/utils/platform.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -41,7 +39,6 @@ class PlayerView extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); - final authenticated = ref.watch(metadataPluginAuthenticatedProvider); final sourcedCurrentTrack = ref.watch(activeTrackSourcesProvider); final currentActiveTrack = ref.watch(audioPlayerProvider.select((s) => s.activeTrack)); @@ -100,22 +97,22 @@ class PlayerView extends HookConsumerWidget { backgroundColor: Colors.transparent, headers: [ SafeArea( - minimum: - kIsMobile ? const EdgeInsets.only(top: 80) : EdgeInsets.zero, bottom: false, child: TitleBar( surfaceOpacity: 0, surfaceBlur: 0, leading: [ IconButton.ghost( - icon: const Icon(SpotubeIcons.angleDown, size: 18), + size: const ButtonSize(1.2), + icon: const Icon(SpotubeIcons.angleDown), onPressed: panelController.close, ) ], trailing: [ if (currentActiveTrackSource is YoutubeSourcedTrack) TextButton( - leading: Assets.logos.songlinkTransparent.image( + size: const ButtonSize(1.2), + leading: Assets.images.logos.songlinkTransparent.image( width: 20, height: 20, color: theme.colorScheme.foreground, @@ -134,7 +131,8 @@ class PlayerView extends HookConsumerWidget { child: Text(context.l10n.details), ).call, child: IconButton.ghost( - icon: const Icon(SpotubeIcons.info, size: 18), + size: const ButtonSize(1.2), + icon: const Icon(SpotubeIcons.info), onPressed: currentActiveTrackSource == null ? null : () { @@ -178,7 +176,7 @@ class PlayerView extends HookConsumerWidget { borderRadius: BorderRadius.circular(20), child: UniversalImage( path: albumArt, - placeholder: Assets.albumPlaceholder.path, + placeholder: Assets.images.albumPlaceholder.path, fit: BoxFit.cover, ), ), @@ -242,18 +240,16 @@ class PlayerView extends HookConsumerWidget { }, ), ), - if (authenticated.asData?.value == true) - const SizedBox(width: 10), - if (authenticated.asData?.value == true) - Expanded( - child: OutlineButton( - leading: const Icon(SpotubeIcons.music), - child: Text(context.l10n.lyrics), - onPressed: () { - context.pushRoute(const PlayerLyricsRoute()); - }, - ), + const SizedBox(width: 10), + Expanded( + child: OutlineButton( + leading: const Icon(SpotubeIcons.music), + child: Text(context.l10n.lyrics), + onPressed: () { + context.pushRoute(const PlayerLyricsRoute()); + }, ), + ), const SizedBox(width: 10), ], ), diff --git a/lib/modules/player/player_controls.dart b/lib/modules/player/player_controls.dart index 14b9cf43..3da36bf8 100644 --- a/lib/modules/player/player_controls.dart +++ b/lib/modules/player/player_controls.dart @@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:media_kit/media_kit.dart'; import 'package:palette_generator/palette_generator.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/intents.dart'; @@ -14,6 +14,7 @@ import 'package:spotube/modules/player/use_progress.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/utils/platform.dart'; class PlayerControls extends HookConsumerWidget { final PaletteGenerator? palette; @@ -48,6 +49,9 @@ class PlayerControls extends HookConsumerWidget { useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; final theme = Theme.of(context); + final buttonSize = + kIsMobile ? const ButtonSize(1.5) : const ButtonSize(1.2); + return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { @@ -149,9 +153,11 @@ class PlayerControls extends HookConsumerWidget { ), ).call, child: IconButton( + size: buttonSize, icon: Icon( SpotubeIcons.shuffle, color: shuffled ? theme.colorScheme.primary : null, + size: 22, ), variance: shuffled ? ButtonVariance.secondary @@ -170,8 +176,10 @@ class PlayerControls extends HookConsumerWidget { }), Tooltip( tooltip: TooltipContainer( - child: Text(context.l10n.previous_track)).call, + child: Text(context.l10n.previous_track), + ).call, child: IconButton.ghost( + size: buttonSize, enabled: !isFetchingActiveTrack, icon: const Icon(SpotubeIcons.skipBack), onPressed: audioPlayer.skipToPrevious, @@ -186,6 +194,7 @@ class PlayerControls extends HookConsumerWidget { ), ).call, child: IconButton.primary( + size: buttonSize, shape: ButtonShape.circle, icon: isFetchingActiveTrack ? const SizedBox( @@ -206,8 +215,10 @@ class PlayerControls extends HookConsumerWidget { ), Tooltip( tooltip: - TooltipContainer(child: Text(context.l10n.next_track)).call, + TooltipContainer(child: Text(context.l10n.next_track)) + .call, child: IconButton.ghost( + size: buttonSize, icon: const Icon(SpotubeIcons.skipForward), onPressed: isFetchingActiveTrack ? null : audioPlayer.skipToNext, @@ -228,6 +239,7 @@ class PlayerControls extends HookConsumerWidget { ), ).call, child: IconButton( + size: buttonSize, icon: Icon( loopMode == PlaylistMode.single ? SpotubeIcons.repeatOne diff --git a/lib/modules/player/player_overlay_collapsed.dart b/lib/modules/player/player_overlay_collapsed.dart index aa5a3b38..d0961ade 100644 --- a/lib/modules/player/player_overlay_collapsed.dart +++ b/lib/modules/player/player_overlay_collapsed.dart @@ -1,6 +1,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:spotube/collections/intents.dart'; import 'package:spotube/collections/spotube_icons.dart'; diff --git a/lib/modules/player/player_track_details.dart b/lib/modules/player/player_track_details.dart index 995ed4ab..c158aed3 100644 --- a/lib/modules/player/player_track_details.dart +++ b/lib/modules/player/player_track_details.dart @@ -37,7 +37,7 @@ class PlayerTrackDetails extends HookConsumerWidget { child: UniversalImage( path: (track?.album.images) .asUrlString(placeholder: ImagePlaceholder.albumArt), - placeholder: Assets.albumPlaceholder.path, + placeholder: Assets.images.albumPlaceholder.path, ), ), ), @@ -47,10 +47,8 @@ class PlayerTrackDetails extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), - LinkText( + Text( playback.activeTrack?.name ?? "", - TrackRoute(trackId: playback.activeTrack?.id ?? ""), - push: true, overflow: TextOverflow.ellipsis, style: theme.typography.normal.copyWith( color: color, diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index 4e36aa09..a6c3ae32 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -18,6 +18,7 @@ import 'package:spotube/hooks/utils/use_debounce.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/playback/track_sources.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -36,7 +37,7 @@ final sourceInfoToIconMap = { decoration: BoxDecoration( borderRadius: BorderRadius.circular(90), image: DecorationImage( - image: Assets.jiosaavn.provider(), + image: Assets.images.logos.jiosaavn.provider(), fit: BoxFit.cover, ), ), @@ -48,7 +49,7 @@ final sourceInfoToIconMap = { decoration: BoxDecoration( borderRadius: BorderRadius.circular(90), image: DecorationImage( - image: Assets.invidious.provider(), + image: Assets.images.logos.invidious.provider(), fit: BoxFit.cover, ), ), @@ -69,6 +70,7 @@ class SiblingTracksSheet extends HookConsumerWidget { final preferences = ref.watch(userPreferencesProvider); final youtubeEngine = ref.watch(youtubeEngineProvider); + final isLoading = useState(false); final isSearching = useState(false); final searchMode = useState(preferences.searchMode); final activeTrackSources = ref.watch(activeTrackSourcesProvider); @@ -95,7 +97,7 @@ class SiblingTracksSheet extends HookConsumerWidget { final controller = useScrollController(); final searchRequest = useMemoized(() async { - if (searchTerm.trim().isEmpty) { + if (searchTerm.trim().isEmpty || activeTrackSource == null) { return []; } if (preferences.audioSource == AudioSource.jiosaavn) { @@ -107,7 +109,7 @@ class SiblingTracksSheet extends HookConsumerWidget { return siblingType.info; })); - final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; + final activeSourceInfo = activeTrackSource.info; return results ..removeWhere((element) => element.id == activeSourceInfo.id) @@ -122,18 +124,18 @@ class SiblingTracksSheet extends HookConsumerWidget { resultsYt .map(YoutubeVideoInfo.fromVideo) .mapIndexed((i, video) async { - final siblingType = - await YoutubeSourcedTrack.toSiblingType(i, video, ref); - return siblingType.info; - }), + if (!context.mounted) return null; + final siblingType = + await YoutubeSourcedTrack.toSiblingType(i, video, ref); + return siblingType.info; + }) + .whereType>() + .toList(), ); - final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; + final activeSourceInfo = activeTrackSource.info; return searchResults ..removeWhere((element) => element.id == activeSourceInfo.id) - ..insert( - 0, - activeSourceInfo, - ); + ..insert(0, activeSourceInfo); } }, [ searchTerm, @@ -165,8 +167,8 @@ class SiblingTracksSheet extends HookConsumerWidget { }, [activeTrack, previousActiveTrack]); final itemBuilder = useCallback( - (TrackSourceInfo sourceInfo) { - final icon = sourceInfoToIconMap[sourceInfo.runtimeType]; + (TrackSourceInfo sourceInfo, AudioSource source) { + final icon = sourceInfoToIconMap[source]; return ButtonTile( style: ButtonVariance.ghost, padding: const EdgeInsets.symmetric(horizontal: 8), @@ -194,23 +196,40 @@ class SiblingTracksSheet extends HookConsumerWidget { ), ], ), - enabled: !isFetchingActiveTrack, + enabled: !isFetchingActiveTrack && !isLoading.value, selected: !isFetchingActiveTrack && sourceInfo.id == activeTrackSource?.info.id, - onPressed: () { + onPressed: () async { if (!isFetchingActiveTrack && sourceInfo.id != activeTrackSource?.info.id) { - activeTrackNotifier?.swapWithSibling(sourceInfo); - if (MediaQuery.sizeOf(context).mdAndUp) { - closeOverlay(context); - } else { - closeDrawer(context); + try { + isLoading.value = true; + await activeTrackNotifier?.swapWithSibling(sourceInfo); + await ref.read(audioPlayerProvider.notifier).swapActiveSource(); + + if (context.mounted) { + if (MediaQuery.sizeOf(context).mdAndUp) { + closeOverlay(context); + } else { + closeDrawer(context); + } + } + } finally { + if (context.mounted) { + isLoading.value = false; + } } } }, ); }, - [activeTrackSource, activeTrackNotifier, siblings], + [ + activeTrackSource, + activeTrackNotifier, + siblings, + isFetchingActiveTrack, + isLoading.value, + ], ); final scale = context.theme.scaling; @@ -288,6 +307,15 @@ class SiblingTracksSheet extends HookConsumerWidget { ], ), ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: isLoading.value + ? const SizedBox( + width: double.infinity, + child: LinearProgressIndicator(), + ) + : const SizedBox.shrink(), + ), Expanded( child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), @@ -301,8 +329,10 @@ class SiblingTracksSheet extends HookConsumerWidget { controller: controller, itemCount: siblings.length, separatorBuilder: (context, index) => const Gap(8), - itemBuilder: (context, index) => - itemBuilder(siblings[index]), + itemBuilder: (context, index) => itemBuilder( + siblings[index], + activeTrackSource!.source, + ), ), true => FutureBuilder( future: searchRequest, @@ -321,8 +351,10 @@ class SiblingTracksSheet extends HookConsumerWidget { controller: controller, itemCount: snapshot.data!.length, separatorBuilder: (context, index) => const Gap(8), - itemBuilder: (context, index) => - itemBuilder(snapshot.data![index]), + itemBuilder: (context, index) => itemBuilder( + snapshot.data![index], + preferences.audioSource, + ), ); }, ), diff --git a/lib/modules/root/bottom_player.dart b/lib/modules/root/bottom_player.dart index 5d9fd35b..33497d8d 100644 --- a/lib/modules/root/bottom_player.dart +++ b/lib/modules/root/bottom_player.dart @@ -40,7 +40,7 @@ class BottomPlayer extends HookConsumerWidget { index: (playlist.activeTrack?.album.images.length ?? 1) - 1, placeholder: ImagePlaceholder.albumArt, ) - : Assets.albumPlaceholder.path, + : Assets.images.albumPlaceholder.path, [playlist.activeTrack?.album.images], ); diff --git a/lib/modules/root/sidebar/sidebar.dart b/lib/modules/root/sidebar/sidebar.dart index 74ff4a7f..e4e7db3d 100644 --- a/lib/modules/root/sidebar/sidebar.dart +++ b/lib/modules/root/sidebar/sidebar.dart @@ -3,7 +3,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/side_bar_tiles.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/extensions/constrains.dart'; @@ -20,19 +19,6 @@ class Sidebar extends HookConsumerWidget { super.key, }); - static Widget brandLogo(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: Colors.black, - borderRadius: BorderRadius.circular(50), - ), - child: Assets.spotubeLogoPng.image( - height: 50, - cacheHeight: (100 * MediaQuery.devicePixelRatioOf(context)).toInt(), - ), - ); - } - @override Widget build(BuildContext context, WidgetRef ref) { final ThemeData(:colorScheme) = Theme.of(context); @@ -80,6 +66,9 @@ class Sidebar extends HookConsumerWidget { ), for (final tile in sidebarTileList) NavigationButton( + style: router.currentPath.startsWith(tile.pathPrefix) + ? const ButtonStyle.secondary() + : null, label: mediaQuery.lgAndUp ? Text(tile.title) : null, child: Tooltip( tooltip: TooltipContainer(child: Text(tile.title)).call, @@ -94,6 +83,9 @@ class Sidebar extends HookConsumerWidget { NavigationLabel(child: Text(context.l10n.library)), for (final tile in sidebarLibraryTileList) NavigationButton( + style: router.currentPath.startsWith(tile.pathPrefix) + ? const ButtonStyle.secondary() + : null, label: mediaQuery.lgAndUp ? Text(tile.title) : null, onPressed: () { context.navigateTo(tile.route); diff --git a/lib/modules/settings/color_scheme_picker_dialog.dart b/lib/modules/settings/color_scheme_picker_dialog.dart index 3fb05e72..9469ff00 100644 --- a/lib/modules/settings/color_scheme_picker_dialog.dart +++ b/lib/modules/settings/color_scheme_picker_dialog.dart @@ -41,18 +41,18 @@ final Set colorsMap = { }; final colorSchemeMap = { - "slate": ColorSchemes.slate, - "gray": ColorSchemes.gray, - "zinc": ColorSchemes.zinc, - "neutral": ColorSchemes.neutral, - "stone": ColorSchemes.stone, - "red": ColorSchemes.red, - "orange": ColorSchemes.orange, - "yellow": ColorSchemes.yellow, - "green": ColorSchemes.green, - "blue": ColorSchemes.blue, - "violet": ColorSchemes.violet, - "rose": ColorSchemes.rose, + "slate": LegacyColorSchemes.slate, + "gray": LegacyColorSchemes.gray, + "zinc": LegacyColorSchemes.zinc, + "neutral": LegacyColorSchemes.neutral, + "stone": LegacyColorSchemes.stone, + "red": LegacyColorSchemes.red, + "orange": LegacyColorSchemes.orange, + "yellow": LegacyColorSchemes.yellow, + "green": LegacyColorSchemes.green, + "blue": LegacyColorSchemes.blue, + "violet": LegacyColorSchemes.violet, + "rose": LegacyColorSchemes.rose, }; class ColorSchemePickerDialog extends HookConsumerWidget { diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index 57b81cea..049d8023 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -48,6 +48,7 @@ class AlbumPage extends HookConsumerWidget { description: "${context.l10n.released} • ${album.releaseDate} • ${album.artists.first.name}", tracks: tracks.asData?.value.items ?? [], + error: tracks.error, pagination: PaginationProps( hasNextPage: tracks.asData?.value.hasMore ?? false, isLoading: tracks.isLoading || tracks.isLoadingNextPage, diff --git a/lib/pages/artist/section/top_tracks.dart b/lib/pages/artist/section/top_tracks.dart index 9ec7314b..30745a01 100644 --- a/lib/pages/artist/section/top_tracks.dart +++ b/lib/pages/artist/section/top_tracks.dart @@ -1,5 +1,7 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; 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/collections/spotube_icons.dart'; @@ -19,6 +21,7 @@ class ArtistPageTopTracks extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); + final isLoading = useState(false); final playlist = ref.watch(audioPlayerProvider); final playlistNotifier = ref.watch(audioPlayerProvider.notifier); @@ -40,46 +43,54 @@ class ArtistPageTopTracks extends HookConsumerWidget { final topTracks = topTracksQuery.asData?.value.items ?? List.generate(10, (index) => FakeData.track); - void playPlaylist(List tracks, - {SpotubeTrackObject? currentTrack}) async { + void playPlaylist( + List tracks, { + SpotubeTrackObject? currentTrack, + }) async { + isLoading.value = true; + currentTrack ??= tracks.first; + try { + final isRemoteDevice = await showSelectDeviceDialog(context, ref); - final isRemoteDevice = await showSelectDeviceDialog(context, ref); + if (isRemoteDevice == null) return; - if (isRemoteDevice == null) return; + if (isRemoteDevice) { + final remotePlayback = ref.read(connectProvider.notifier); + final remotePlaylist = ref.read(queueProvider); - if (isRemoteDevice) { - final remotePlayback = ref.read(connectProvider.notifier); - final remotePlaylist = ref.read(queueProvider); + final isPlaylistPlaying = remotePlaylist.containsTracks(tracks); - final isPlaylistPlaying = remotePlaylist.containsTracks(tracks); - - if (!isPlaylistPlaying) { - await remotePlayback.load( - WebSocketLoadEventData.playlist( - tracks: tracks, - collection: null, + if (!isPlaylistPlaying) { + await remotePlayback.load( + WebSocketLoadEventData.playlist( + tracks: tracks, + collection: null, + initialIndex: + tracks.indexWhere((s) => s.id == currentTrack?.id), + ), + ); + } else if (isPlaylistPlaying && + currentTrack.id != remotePlaylist.activeTrack?.id) { + final index = playlist.tracks + .toList() + .indexWhere((s) => s.id == currentTrack!.id); + await remotePlayback.jumpTo(index); + } + } else { + if (!isPlaylistPlaying) { + playlistNotifier.load( + tracks, initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id), - ), - ); - } else if (isPlaylistPlaying && - currentTrack.id != remotePlaylist.activeTrack?.id) { - final index = playlist.tracks - .toList() - .indexWhere((s) => s.id == currentTrack!.id); - await remotePlayback.jumpTo(index); - } - } else { - if (!isPlaylistPlaying) { - playlistNotifier.load( - tracks, - initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id), - autoPlay: true, - ); - } else if (isPlaylistPlaying && - currentTrack.id != playlist.activeTrack?.id) { - await playlistNotifier.jumpToTrack(currentTrack); + autoPlay: true, + ); + } else if (isPlaylistPlaying && + currentTrack.id != playlist.activeTrack?.id) { + await playlistNotifier.jumpToTrack(currentTrack); + } } + } finally { + isLoading.value = false; } } @@ -120,12 +131,19 @@ class ArtistPageTopTracks extends HookConsumerWidget { const SizedBox(width: 5), IconButton.primary( shape: ButtonShape.circle, - enabled: !isPlaylistPlaying, - icon: Skeleton.keep( - child: Icon( - isPlaylistPlaying ? SpotubeIcons.pause : SpotubeIcons.play, - ), - ), + enabled: !isPlaylistPlaying && !isLoading.value, + icon: isLoading.value + ? CircularProgressIndicator( + size: 20 * context.theme.scaling, + color: theme.colorScheme.primaryForeground, + ) + : Skeleton.keep( + child: Icon( + isPlaylistPlaying + ? SpotubeIcons.pause + : SpotubeIcons.play, + ), + ), onPressed: () => playPlaylist(topTracks.toList()), ) ], diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index 6abb11eb..164e5d43 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +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'; diff --git a/lib/pages/getting_started/getting_started.dart b/lib/pages/getting_started/getting_started.dart index a576ed09..1662624c 100644 --- a/lib/pages/getting_started/getting_started.dart +++ b/lib/pages/getting_started/getting_started.dart @@ -71,7 +71,7 @@ class GettingStartedPage extends HookConsumerWidget { child: DecoratedBox( decoration: BoxDecoration( image: DecorationImage( - image: Assets.bengaliPatternsBg.provider(), + image: Assets.images.bengaliPatternsBg.provider(), fit: BoxFit.cover, ), ), diff --git a/lib/pages/getting_started/sections/greeting.dart b/lib/pages/getting_started/sections/greeting.dart index 4b9c0a89..68903e07 100644 --- a/lib/pages/getting_started/sections/greeting.dart +++ b/lib/pages/getting_started/sections/greeting.dart @@ -17,7 +17,7 @@ class GettingStartedPageGreetingSection extends HookConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Assets.spotubeLogoPng.image(height: 200), + Assets.branding.spotubeLogoPng.image(height: 200), const Gap(24), const Text("Spotube").semiBold().h4(), const Gap(4), diff --git a/lib/pages/getting_started/sections/playback.dart b/lib/pages/getting_started/sections/playback.dart index f122dbcd..43ff2c8e 100644 --- a/lib/pages/getting_started/sections/playback.dart +++ b/lib/pages/getting_started/sections/playback.dart @@ -19,9 +19,10 @@ final audioSourceToIconMap = { AudioSource.piped: const Icon(SpotubeIcons.piped, size: 20), AudioSource.invidious: ClipRRect( borderRadius: BorderRadius.circular(26), - child: Assets.invidious.image(width: 26, height: 26), + child: Assets.images.logos.invidious.image(width: 26, height: 26), ), - AudioSource.jiosaavn: Assets.jiosaavn.image(width: 20, height: 20), + AudioSource.jiosaavn: + Assets.images.logos.jiosaavn.image(width: 20, height: 20), }; class GettingStartedPagePlaybackSection extends HookConsumerWidget { diff --git a/lib/pages/getting_started/sections/region.dart b/lib/pages/getting_started/sections/region.dart index 7bfe545d..917cc41e 100644 --- a/lib/pages/getting_started/sections/region.dart +++ b/lib/pages/getting_started/sections/region.dart @@ -21,8 +21,10 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { } bool filterLocale(Locale locale, String query) { - final language = - LanguageLocals.getDisplayLanguage(locale.languageCode).toString(); + final language = LanguageLocals.getDisplayLanguage( + locale.languageCode, + locale.countryCode, + ).toString(); return language.toLowerCase().contains(query.toLowerCase()); } @@ -124,8 +126,9 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { ? Text(context.l10n.system_default) : Text( LanguageLocals.getDisplayLanguage( - value.languageCode) - .toString(), + value.languageCode, + value.countryCode, + ).toString(), ), popup: SelectPopup.builder( searchPlaceholder: Text(context.l10n.search), @@ -161,6 +164,7 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { child: Text( LanguageLocals.getDisplayLanguage( locale.languageCode, + locale.countryCode, ).toString(), ), ); diff --git a/lib/pages/getting_started/sections/support.dart b/lib/pages/getting_started/sections/support.dart index 1b50f839..ef549296 100644 --- a/lib/pages/getting_started/sections/support.dart +++ b/lib/pages/getting_started/sections/support.dart @@ -104,15 +104,15 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Button.secondary( - leading: const Icon(SpotubeIcons.anonymous), + Button.primary( + leading: const Icon(SpotubeIcons.extensions), onPressed: () async { await KVStoreService.setDoneGettingStarted(true); if (context.mounted) { - context.navigateTo(const HomeRoute()); + context.pushRoute(const SettingsMetadataProviderRoute()); } }, - child: Text(context.l10n.browse_anonymously), + child: Text(context.l10n.install_a_metadata_provider), ), ], ), diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 89f12f45..a92c776e 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -1,12 +1,8 @@ -import 'dart:ui'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/database/database.dart'; @@ -45,19 +41,17 @@ class HomePage extends HookConsumerWidget { if (mediaQuery.smAndDown || layoutMode == LayoutMode.compact) SliverAppBar( floating: true, - title: Image.asset( - theme.brightness == Brightness.dark - ? Assets.spotubeLogoPng.path - : Assets.spotubeLogoLight.path, - height: 45, - width: 45, - color: theme.colorScheme.background, - colorBlendMode: BlendMode.saturation, - cacheHeight: - (100 * MediaQuery.devicePixelRatioOf(context)).toInt(), + title: DefaultTextStyle( + style: TextStyle( + fontFamily: "Cookie", + fontSize: 30, + letterSpacing: 1.8, + color: theme.colorScheme.foreground, + ), + child: const Text("Spotube"), ), - backgroundColor: context.theme.colorScheme.background, - foregroundColor: context.theme.colorScheme.foreground, + backgroundColor: theme.colorScheme.background, + foregroundColor: theme.colorScheme.foreground, actions: [ const ConnectDeviceButton(), const Gap(10), diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index 42c6af7c..4f183346 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -9,6 +9,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; +import 'package:spotube/components/fallbacks/no_default_metadata_plugin.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; @@ -17,6 +19,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/albums.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; @RoutePage() class UserAlbumsPage extends HookConsumerWidget { @@ -50,10 +53,27 @@ class UserAlbumsPage extends HookConsumerWidget { []; }, [albumsQuery.asData?.value, searchText.value]); + if (albumsQuery.error + case MetadataPluginException( + errorCode: MetadataPluginErrorCode.noDefaultPlugin, + message: _, + )) { + return const Center(child: NoDefaultMetadataPlugin()); + } + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } + if (albumsQuery.hasError) { + return ErrorBox( + error: albumsQuery.error!, + onRetry: () { + ref.invalidate(metadataPluginSavedAlbumsProvider); + }, + ); + } + return SafeArea( bottom: false, child: Scaffold( @@ -79,7 +99,7 @@ class UserAlbumsPage extends HookConsumerWidget { features: const [ InputFeature.leading(Icon(SpotubeIcons.filter)) ], - placeholder: Text(context.l10n.filter_artist), + placeholder: Text(context.l10n.filter_albums), ), ), ), @@ -101,7 +121,7 @@ class UserAlbumsPage extends HookConsumerWidget { color: Theme.of(context).colorScheme.primary, ), Text( - context.l10n.not_following_artists, + context.l10n.no_favorite_albums_yet, textAlign: TextAlign.center, ).muted().small() ], diff --git a/lib/pages/library/user_artists.dart b/lib/pages/library/user_artists.dart index 097dff4f..d5df13e5 100644 --- a/lib/pages/library/user_artists.dart +++ b/lib/pages/library/user_artists.dart @@ -12,6 +12,8 @@ import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; +import 'package:spotube/components/fallbacks/no_default_metadata_plugin.dart'; import 'package:spotube/modules/artist/artist_card.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/waypoint.dart'; @@ -20,6 +22,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/artists.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; @RoutePage() class UserArtistsPage extends HookConsumerWidget { @@ -55,10 +58,27 @@ class UserArtistsPage extends HookConsumerWidget { final controller = useScrollController(); + if (artistQuery.error + case MetadataPluginException( + errorCode: MetadataPluginErrorCode.noDefaultPlugin, + message: _, + )) { + return const Center(child: NoDefaultMetadataPlugin()); + } + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } + if (artistQuery.hasError) { + return ErrorBox( + error: artistQuery.error!, + onRetry: () { + ref.invalidate(metadataPluginSavedArtistsProvider); + }, + ); + } + return SafeArea( bottom: false, child: Scaffold( diff --git a/lib/pages/library/user_local_tracks/local_folder.dart b/lib/pages/library/user_local_tracks/local_folder.dart index c256af7f..27af0f57 100644 --- a/lib/pages/library/user_local_tracks/local_folder.dart +++ b/lib/pages/library/user_local_tracks/local_folder.dart @@ -98,315 +98,311 @@ class LocalLibraryPage extends HookConsumerWidget { return SafeArea( bottom: false, child: Scaffold( - headers: [ - TitleBar( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 0, - ), - surfaceBlur: 0, - leading: const [BackButton()], - title: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - isDownloads - ? context.l10n.downloads - : isCache - ? context.l10n.cache_folder.capitalize() - : location, - ), - FutureBuilder( - future: directorySize, - builder: (context, snapshot) { - return Text( - "${(snapshot.data ?? 0)} GB", - ).xSmall().muted(); - }, - ) - ], - ), - backgroundColor: Colors.transparent, - trailingGap: 10, - trailing: [ - if (isCache) ...[ - IconButton.outline( - size: ButtonSize.small, - icon: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(SpotubeIcons.delete), - Text(context.l10n.clear_cache) - ], - ).xSmall().iconSmall(), - onPressed: () async { - final accepted = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(context.l10n.clear_cache_confirmation), - actions: [ - Button.outline( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text(context.l10n.decline), - ), - Button.destructive( - onPressed: () async { - Navigator.of(context).pop(true); - }, - child: Text(context.l10n.accept), - ), - ], - ), - ); - - if (accepted ?? false) return; - - final cacheDir = Directory( - await UserPreferencesNotifier.getMusicCacheDir(), - ); - - if (cacheDir.existsSync()) { - await cacheDir.delete(recursive: true); - } - }, - ), - IconButton.outline( - size: ButtonSize.small, - icon: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(SpotubeIcons.export), - Text( - context.l10n.export, - ) - ], - ).xSmall().iconSmall(), - onPressed: () async { - final exportPath = - await FilePicker.platform.getDirectoryPath(); - - if (exportPath == null) return; - final exportDirectory = Directory(exportPath); - - if (!exportDirectory.existsSync()) { - await exportDirectory.create(recursive: true); - } - - final cacheDir = Directory( - await UserPreferencesNotifier.getMusicCacheDir()); - - if (!context.mounted) return; - await showDialog( - context: context, - builder: (context) { - return LocalFolderCacheExportDialog( - cacheDir: cacheDir, - exportDir: exportDirectory, - ); - }, - ); - }, - ), - ] + headers: [ + TitleBar( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 0, + ), + surfaceBlur: 0, + leading: const [BackButton()], + title: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isDownloads + ? context.l10n.downloads + : isCache + ? context.l10n.cache_folder.capitalize() + : location, + ), + FutureBuilder( + future: directorySize, + builder: (context, snapshot) { + return Text( + "${(snapshot.data ?? 0)} GB", + ).xSmall().muted(); + }, + ) ], ), - ], - child: LayoutBuilder( - builder: (context, constraints) => Column( + backgroundColor: Colors.transparent, + trailingGap: 10, + trailing: [ + if (isCache) ...[ + IconButton.outline( + size: ButtonSize.small, + icon: Column( + mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - const Gap(5), - Button.primary( - onPressed: trackSnapshot.asData?.value != null - ? () async { - if (trackSnapshot - .asData?.value.isNotEmpty == - true) { - if (!isPlaylistPlaying) { - await playLocalTracks( - ref, - trackSnapshot - .asData!.value[location] ?? - [], - ); - } - } - } - : null, - leading: Icon( - isPlaylistPlaying - ? SpotubeIcons.stop - : SpotubeIcons.play, - ), - child: Text(context.l10n.play), - ), - const Spacer(), - if (constraints.smAndDown) - ExpandableSearchButton( - isFiltering: isFiltering.value, - onPressed: (value) => isFiltering.value = value, - searchFocus: searchFocus, - ) - else - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 300 * scale, - maxHeight: 38 * scale, - ), - child: ExpandableSearchField( - isFiltering: true, - onChangeFiltering: (value) {}, - searchController: searchController, - searchFocus: searchFocus, - ), - ), - const Gap(5), - SortTracksDropdown( - value: sortBy.value, - onChanged: (value) { - sortBy.value = value; - }, - ), - const Gap(5), - IconButton.outline( - icon: const Icon(SpotubeIcons.refresh), - onPressed: () { - ref.invalidate(localTracksProvider); - }, - ) - ], + const Icon(SpotubeIcons.delete), + Text(context.l10n.clear_cache) + ], + ).xSmall().iconSmall(), + onPressed: () async { + final accepted = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(context.l10n.clear_cache_confirmation), + actions: [ + Button.outline( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(context.l10n.decline), + ), + Button.destructive( + onPressed: () async { + Navigator.of(context).pop(true); + }, + child: Text(context.l10n.accept), + ), + ], + ), + ); + + if (accepted ?? false) return; + + final cacheDir = Directory( + await UserPreferencesNotifier.getMusicCacheDir(), + ); + + if (cacheDir.existsSync()) { + await cacheDir.delete(recursive: true); + } + }, + ), + IconButton.outline( + size: ButtonSize.small, + icon: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(SpotubeIcons.export), + Text( + context.l10n.export, + ) + ], + ).xSmall().iconSmall(), + onPressed: () async { + final exportPath = + await FilePicker.platform.getDirectoryPath(); + + if (exportPath == null) return; + final exportDirectory = Directory(exportPath); + + if (!exportDirectory.existsSync()) { + await exportDirectory.create(recursive: true); + } + + final cacheDir = Directory( + await UserPreferencesNotifier.getMusicCacheDir()); + + if (!context.mounted) return; + await showDialog( + context: context, + builder: (context) { + return LocalFolderCacheExportDialog( + cacheDir: cacheDir, + exportDir: exportDirectory, + ); + }, + ); + }, + ), + ] + ], + ), + ], + child: LayoutBuilder( + builder: (context, constraints) => Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const Gap(5), + Button.primary( + onPressed: trackSnapshot.asData?.value != null + ? () async { + if (trackSnapshot.asData?.value.isNotEmpty == + true) { + if (!isPlaylistPlaying) { + await playLocalTracks( + ref, + trackSnapshot.asData!.value[location] ?? [], + ); + } + } + } + : null, + leading: Icon( + isPlaylistPlaying + ? SpotubeIcons.stop + : SpotubeIcons.play, + ), + child: Text(context.l10n.play), + ), + const Spacer(), + if (constraints.smAndDown) + ExpandableSearchButton( + isFiltering: isFiltering.value, + onPressed: (value) => isFiltering.value = value, + searchFocus: searchFocus, + ) + else + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 300 * scale, + maxHeight: 38 * scale, + ), + child: ExpandableSearchField( + isFiltering: true, + onChangeFiltering: (value) {}, + searchController: searchController, + searchFocus: searchFocus, ), ), - ExpandableSearchField( - searchController: searchController, - searchFocus: searchFocus, - isFiltering: isFiltering.value, - onChangeFiltering: (value) => isFiltering.value = value, - ), - HookBuilder(builder: (context) { - return trackSnapshot.when( - data: (tracks) { - final sortedTracks = useMemoized(() { - return ServiceUtils.sortTracks( - tracks[location] ?? - [], - sortBy.value); - }, [sortBy.value, tracks]); + const Gap(5), + SortTracksDropdown( + value: sortBy.value, + onChanged: (value) { + sortBy.value = value; + }, + ), + const Gap(5), + IconButton.outline( + icon: const Icon(SpotubeIcons.refresh), + onPressed: () { + ref.invalidate(localTracksProvider); + }, + ) + ], + ), + ), + ExpandableSearchField( + searchController: searchController, + searchFocus: searchFocus, + isFiltering: isFiltering.value, + onChangeFiltering: (value) => isFiltering.value = value, + ), + HookBuilder(builder: (context) { + return trackSnapshot.when( + data: (tracks) { + final sortedTracks = useMemoized(() { + return ServiceUtils.sortTracks( + tracks[location] ?? [], + sortBy.value); + }, [sortBy.value, tracks]); - final filteredTracks = useMemoized(() { - if (searchController.text.isEmpty) { - return sortedTracks; - } - return sortedTracks - .map((e) => ( - weightedRatio( - "${e.name} - ${e.artists.asString()}", - searchController.text, - ), - e, - )) - .toList() - .sorted( - (a, b) => b.$1.compareTo(a.$1), - ) - .where((e) => e.$1 > 50) - .map((e) => e.$2) - .toList() - .toList(); - }, [searchController.text, sortedTracks]); - - if (!trackSnapshot.isLoading && - filteredTracks.isEmpty) { - return Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Undraw( - illustration: UndrawIllustration.empty, - height: 200 * scale, - color: context.theme.colorScheme.primary, - ), - const Gap(10), - Text( - context.l10n.nothing_found, - textAlign: TextAlign.center, - ).muted().small() - ], + final filteredTracks = useMemoized(() { + if (searchController.text.isEmpty) { + return sortedTracks; + } + return sortedTracks + .map((e) => ( + weightedRatio( + "${e.name} - ${e.artists.asString()}", + searchController.text, ), - ); - } + e, + )) + .toList() + .sorted( + (a, b) => b.$1.compareTo(a.$1), + ) + .where((e) => e.$1 > 50) + .map((e) => e.$2) + .toList() + .toList(); + }, [searchController.text, sortedTracks]); - return Expanded( - child: material.RefreshIndicator.adaptive( - onRefresh: () async { - ref.invalidate(localTracksProvider); - }, - child: InterScrollbar( - controller: controller, - child: Skeletonizer( - enabled: trackSnapshot.isLoading, - child: ListView.builder( - controller: controller, - physics: - const AlwaysScrollableScrollPhysics(), - itemCount: trackSnapshot.isLoading - ? 5 - : filteredTracks.length, - itemBuilder: (context, index) { - if (trackSnapshot.isLoading) { - return TrackTile( - playlist: playlist, - track: FakeData.track, - index: index, - ); - } + if (!trackSnapshot.isLoading && filteredTracks.isEmpty) { + return Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Undraw( + illustration: UndrawIllustration.empty, + height: 200 * scale, + color: context.theme.colorScheme.primary, + ), + const Gap(10), + Text( + context.l10n.nothing_found, + textAlign: TextAlign.center, + ).muted().small() + ], + ), + ); + } - final track = filteredTracks[index]; - return TrackTile( - index: index, - playlist: playlist, - track: track, - userPlaylist: false, - onTap: () async { - await playLocalTracks( - ref, - sortedTracks, - currentTrack: track, - ); - }, - ); - }, - ), - ), - ), - ), - ); - }, - loading: () => Expanded( - child: Skeletonizer( - enabled: true, - child: ListView.builder( - itemCount: 5, - itemBuilder: (context, index) => TrackTile( - track: FakeData.track, + return Expanded( + child: material.RefreshIndicator.adaptive( + onRefresh: () async { + ref.invalidate(localTracksProvider); + }, + child: InterScrollbar( + controller: controller, + child: Skeletonizer( + enabled: trackSnapshot.isLoading, + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: trackSnapshot.isLoading + ? 5 + : filteredTracks.length, + itemBuilder: (context, index) { + if (trackSnapshot.isLoading) { + return TrackTile( + playlist: playlist, + track: FakeData.track, + index: index, + ); + } + + final track = filteredTracks[index]; + return TrackTile( index: index, playlist: playlist, - ), - ), + track: track, + userPlaylist: false, + onTap: () async { + await playLocalTracks( + ref, + sortedTracks, + currentTrack: track, + ); + }, + ); + }, ), ), - error: (error, stackTrace) => - Text(error.toString() + stackTrace.toString()), - ); - }) - ], - ))), + ), + ), + ); + }, + loading: () => Expanded( + child: Skeletonizer( + enabled: true, + child: ListView.builder( + itemCount: 5, + itemBuilder: (context, index) => TrackTile( + track: FakeData.track, + index: index, + playlist: playlist, + ), + ), + ), + ), + error: (error, stackTrace) => + Text(error.toString() + stackTrace.toString()), + ); + }) + ], + ), + ), + ), ); } } diff --git a/lib/pages/library/user_local_tracks/user_local_tracks.dart b/lib/pages/library/user_local_tracks/user_local_tracks.dart index 67e02b0b..43fa3cc9 100644 --- a/lib/pages/library/user_local_tracks/user_local_tracks.dart +++ b/lib/pages/library/user_local_tracks/user_local_tracks.dart @@ -4,6 +4,7 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.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/modules/library/local_folder/local_folder_item.dart'; @@ -85,10 +86,10 @@ class UserLocalLibraryPage extends HookConsumerWidget { gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, mainAxisExtent: constrains.isXs - ? 210 + ? 230 * context.theme.scaling : constrains.mdAndDown - ? 280 - : 250, + ? 280 * context.theme.scaling + : 250 * context.theme.scaling, crossAxisSpacing: 10, mainAxisSpacing: 10, ), diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index c7493ec3..9020d463 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -8,6 +8,8 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; +import 'package:spotube/components/fallbacks/no_default_metadata_plugin.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; @@ -19,6 +21,7 @@ import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; import 'package:spotube/provider/metadata_plugin/core/user.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; @RoutePage() class UserPlaylistsPage extends HookConsumerWidget { @@ -47,7 +50,7 @@ class UserPlaylistsPage extends HookConsumerWidget { owner: me.asData!.value!, images: [ SpotubeImageObject( - url: Assets.likedTracks.path, + url: Assets.images.likedTracks.path, width: 300, height: 300, ) @@ -78,10 +81,27 @@ class UserPlaylistsPage extends HookConsumerWidget { final controller = useScrollController(); + if (playlistsQuery.error + case MetadataPluginException( + errorCode: MetadataPluginErrorCode.noDefaultPlugin, + message: _, + )) { + return const Center(child: NoDefaultMetadataPlugin()); + } + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } + if (playlistsQuery.hasError) { + return ErrorBox( + error: playlistsQuery.error!, + onRetry: () { + ref.invalidate(metadataPluginSavedPlaylistsProvider); + }, + ); + } + return material.RefreshIndicator.adaptive( onRefresh: () async { ref.invalidate(metadataPluginSavedPlaylistsProvider); diff --git a/lib/pages/lyrics/plain_lyrics.dart b/lib/pages/lyrics/plain_lyrics.dart index 69f71cf4..3f0d7d1b 100644 --- a/lib/pages/lyrics/plain_lyrics.dart +++ b/lib/pages/lyrics/plain_lyrics.dart @@ -118,7 +118,7 @@ class PlainLyrics extends HookConsumerWidget { ), child: SelectableText( lyrics == null && playlist.activeTrack == null - ? "No Track being played currently" + ? context.l10n.no_tracks_playing : lyrics ?? "", textAlign: TextAlign.center, ), diff --git a/lib/pages/lyrics/synced_lyrics.dart b/lib/pages/lyrics/synced_lyrics.dart index 5319d7ad..cb331724 100644 --- a/lib/pages/lyrics/synced_lyrics.dart +++ b/lib/pages/lyrics/synced_lyrics.dart @@ -18,8 +18,6 @@ import 'package:spotube/provider/lyrics/synced.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; -import 'package:stroke_text/stroke_text.dart'; - class SyncedLyrics extends HookConsumerWidget { final PaletteColor palette; final bool? isModal; @@ -160,6 +158,9 @@ class SyncedLyrics extends HookConsumerWidget { child: AnimatedDefaultTextStyle( duration: const Duration(milliseconds: 250), style: TextStyle( + color: isActive + ? theme.colorScheme.foreground + : theme.colorScheme.mutedForeground, fontWeight: isActive ? FontWeight.w500 : FontWeight.normal, @@ -181,25 +182,7 @@ class SyncedLyrics extends HookConsumerWidget { } audioPlayer.seek(time); }, - child: Builder(builder: (context) { - return StrokeText( - text: lyricSlice.text, - textStyle: - DefaultTextStyle.of(context).style, - textColor: switch (( - isActive, - isModal == true - )) { - (true, _) => Colors.white, - (_, true) => - theme.colorScheme.mutedForeground, - (_, _) => palette.bodyTextColor, - }, - strokeColor: isActive - ? Colors.black - : Colors.transparent, - ); - }), + child: Text(lyricSlice.text), ), ), ), @@ -236,18 +219,17 @@ class SyncedLyrics extends HookConsumerWidget { text: TextSpan( style: bodyTextTheme, children: [ - const TextSpan( - text: - "Synced lyrics are not available for this song. Please use the", + TextSpan( + text: context.l10n.synced_lyrics_not_available, ), TextSpan( - text: " Plain Lyrics ", + text: " ${context.l10n.plain_lyrics} ", style: typography.large.copyWith( color: palette.bodyTextColor, fontWeight: FontWeight.bold, ), ), - const TextSpan(text: "tab instead."), + TextSpan(text: context.l10n.tab_instead), ], ), ), diff --git a/lib/pages/player/lyrics.dart b/lib/pages/player/lyrics.dart index e1aad553..093b0aa2 100644 --- a/lib/pages/player/lyrics.dart +++ b/lib/pages/player/lyrics.dart @@ -28,20 +28,18 @@ class PlayerLyricsPage extends HookConsumerWidget { final selectedIndex = useState(0); final palette = usePaletteColor(albumArt, ref); - final tabbar = Padding( - padding: const EdgeInsets.all(10), - child: TabList( - index: selectedIndex.value, - onChanged: (index) => selectedIndex.value = index, - children: [ - TabItem( - child: Text(context.l10n.synced), - ), - TabItem( - child: Text(context.l10n.plain), - ), - ], - )); + final tabbar = TabList( + index: selectedIndex.value, + onChanged: (index) => selectedIndex.value = index, + children: [ + TabItem( + child: Text(context.l10n.synced), + ), + TabItem( + child: Text(context.l10n.plain), + ), + ], + ); return Scaffold( headers: [ diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 7c9a7fec..3897acef 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart' as material; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; @RoutePage() class LikedPlaylistPage extends HookConsumerWidget { @@ -21,6 +23,8 @@ class LikedPlaylistPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final likedTracks = ref.watch(metadataPluginSavedTracksProvider); + final likedTracksNotifier = + ref.watch(metadataPluginSavedTracksProvider.notifier); final tracks = likedTracks.asData?.value.items ?? []; return material.RefreshIndicator.adaptive( @@ -30,13 +34,15 @@ class LikedPlaylistPage extends HookConsumerWidget { child: TrackPresentation( options: TrackPresentationOptions( collection: playlist, - image: "assets/liked-tracks.jpg", + image: Assets.images.likedTracks.path, pagination: PaginationProps( - hasNextPage: false, - isLoading: likedTracks.isLoading, - onFetchMore: () {}, + hasNextPage: likedTracks.asData?.value.hasMore ?? false, + isLoading: likedTracks.isLoadingNextPage && !likedTracks.isLoading, + onFetchMore: () async { + await likedTracksNotifier.fetchMore(); + }, onFetchAll: () async { - return tracks.toList(); + return await likedTracksNotifier.fetchAll(); }, onRefresh: () async { ref.invalidate(metadataPluginSavedTracksProvider); @@ -45,6 +51,7 @@ class LikedPlaylistPage extends HookConsumerWidget { title: playlist.name, description: playlist.description, tracks: tracks, + error: likedTracks.error, routePath: '/playlist/${playlist.id}', isLiked: false, shareUrl: null, diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index b50bf147..4aca5945 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -79,6 +79,7 @@ class PlaylistPage extends HookConsumerWidget { owner: playlist.owner.name, ownerImage: playlist.owner.images.lastOrNull?.url, tracks: tracks.asData?.value.items ?? [], + error: tracks.error, routePath: '/playlist/${playlist.id}', isLiked: isFavoritePlaylist.asData?.value ?? false, shareUrl: playlist.externalUri, diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 7dec4b04..c6118a97 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -7,7 +7,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; +import 'package:spotube/components/fallbacks/no_default_metadata_plugin.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/string.dart'; @@ -17,10 +18,10 @@ import 'package:spotube/pages/search/tabs/all.dart'; import 'package:spotube/pages/search/tabs/artists.dart'; import 'package:spotube/pages/search/tabs/playlists.dart'; import 'package:spotube/pages/search/tabs/tracks.dart'; -import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; final searchTermStateProvider = StateProvider((ref) { return ""; @@ -37,8 +38,6 @@ class SearchPage extends HookConsumerWidget { final controller = useShadcnTextEditingController(); final focusNode = useFocusNode(); - final authenticated = ref.watch(metadataPluginAuthenticatedProvider); - final searchTerm = ref.watch(searchTermStateProvider); final searchChipSnapshot = ref.watch(metadataPluginSearchChipsProvider); final selectedChip = useState( @@ -83,147 +82,163 @@ class SearchPage extends HookConsumerWidget { if (kTitlebarVisible) const TitleBar(automaticallyImplyLeading: false, height: 30) ], - child: authenticated.asData?.value != true - ? const AnonymousFallback() - : Column( + child: Builder(builder: (context) { + if (searchChipSnapshot.error + case MetadataPluginException( + errorCode: MetadataPluginErrorCode.noDefaultPlugin, + message: _ + )) { + return const NoDefaultMetadataPlugin(); + } + + if (searchChipSnapshot.hasError) { + return ErrorBox( + error: searchChipSnapshot.error!, + onRetry: () { + ref.invalidate(metadataPluginSearchChipsProvider); + }, + ); + } + + return Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 10, - ), - child: ListenableBuilder( - listenable: controller, - builder: (context, _) { - final suggestions = controller.text.isEmpty - ? KVStoreService.recentSearches - : KVStoreService.recentSearches - .where( - (s) => - weightedRatio( - s.toLowerCase(), - controller.text.toLowerCase(), - ) > - 50, - ) - .toList(); + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + child: ListenableBuilder( + listenable: controller, + builder: (context, _) { + final suggestions = controller.text.isEmpty + ? KVStoreService.recentSearches + : KVStoreService.recentSearches + .where( + (s) => + weightedRatio( + s.toLowerCase(), + controller.text.toLowerCase(), + ) > + 50, + ) + .toList(); - return KeyboardListener( - focusNode: focusNode, + return KeyboardListener( + focusNode: focusNode, + autofocus: true, + onKeyEvent: (value) { + final isEnter = value.logicalKey == + LogicalKeyboardKey.enter; + + if (isEnter) { + onSubmitted(controller.text); + focusNode.unfocus(); + } + }, + child: AutoComplete( + suggestions: suggestions.length <= 2 + ? [ + ...suggestions, + "Twenty One Pilots", + "Linkin Park", + "d4vd" + ] + : suggestions, + completer: (suggestion) => suggestion, + mode: AutoCompleteMode.replaceAll, + child: TextField( autofocus: true, - onKeyEvent: (value) { - final isEnter = value.logicalKey == - LogicalKeyboardKey.enter; - - if (isEnter) { - onSubmitted(controller.text); - focusNode.unfocus(); - } - }, - child: AutoComplete( - suggestions: suggestions.length <= 2 - ? [ - ...suggestions, - "Twenty One Pilots", - "Linkin Park", - "d4vd" - ] - : suggestions, - completer: (suggestion) => suggestion, - mode: AutoCompleteMode.replaceAll, - child: TextField( - autofocus: true, - controller: controller, - features: [ - const InputFeature.leading( - Icon(SpotubeIcons.search), - ), - InputFeature.trailing( - AnimatedCrossFade( - duration: const Duration( - milliseconds: 300), - crossFadeState: controller - .text.isNotEmpty + controller: controller, + features: [ + const InputFeature.leading( + Icon(SpotubeIcons.search), + ), + InputFeature.trailing( + AnimatedCrossFade( + duration: + const Duration(milliseconds: 300), + crossFadeState: + controller.text.isNotEmpty ? CrossFadeState.showFirst : CrossFadeState.showSecond, - firstChild: IconButton.ghost( - size: ButtonSize.small, - icon: const Icon( - SpotubeIcons.close), - onPressed: () { - controller.clear(); - }, - ), - secondChild: - const SizedBox.square( - dimension: 28), - ), - ) - ], - textInputAction: TextInputAction.search, - placeholder: Text(context.l10n.search), - onSubmitted: onSubmitted, - ), - ), - ); - }), - ), - ), - ], - ), - Row( - spacing: 8, - children: [ - const Gap(12), - if (searchChipSnapshot.asData?.value != null) - for (final chip in searchChipSnapshot.asData!.value) - Chip( - style: selectedChip.value == chip - ? ButtonVariance.primary.copyWith( - decoration: (context, states, value) { - return ButtonVariance.primary - .decoration(context, states) - .copyWithIfBoxDecoration( - borderRadius: - BorderRadius.circular(100), - ); - }, - ) - : ButtonVariance.secondary.copyWith( - decoration: (context, states, value) { - return ButtonVariance.secondary - .decoration(context, states) - .copyWithIfBoxDecoration( - borderRadius: - BorderRadius.circular(100), - ); - }, - ), - child: Text(chip.capitalize()), - onPressed: () { - selectedChip.value = chip; - }, - ), - ], - ), - Expanded( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: switch (selectedChip.value) { - "tracks" => const SearchPageTracksTab(), - "albums" => const SearchPageAlbumsTab(), - "artists" => const SearchPageArtistsTab(), - "playlists" => const SearchPagePlaylistsTab(), - _ => const SearchPageAllTab(), - }, + firstChild: IconButton.ghost( + size: ButtonSize.small, + icon: + const Icon(SpotubeIcons.close), + onPressed: () { + controller.clear(); + }, + ), + secondChild: const SizedBox.square( + dimension: 28), + ), + ) + ], + textInputAction: TextInputAction.search, + placeholder: Text(context.l10n.search), + onSubmitted: onSubmitted, + ), + ), + ); + }), ), ), ], ), + Row( + spacing: 8, + children: [ + const Gap(12), + if (searchChipSnapshot.asData?.value != null) + for (final chip in searchChipSnapshot.asData!.value) + Chip( + style: selectedChip.value == chip + ? ButtonVariance.primary.copyWith( + decoration: (context, states, value) { + return ButtonVariance.primary + .decoration(context, states) + .copyWithIfBoxDecoration( + borderRadius: + BorderRadius.circular(100), + ); + }, + ) + : ButtonVariance.secondary.copyWith( + decoration: (context, states, value) { + return ButtonVariance.secondary + .decoration(context, states) + .copyWithIfBoxDecoration( + borderRadius: + BorderRadius.circular(100), + ); + }, + ), + child: Text(chip.capitalize()), + onPressed: () { + selectedChip.value = chip; + }, + ), + ], + ), + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: switch (selectedChip.value) { + "tracks" => const SearchPageTracksTab(), + "albums" => const SearchPageAlbumsTab(), + "artists" => const SearchPageArtistsTab(), + "playlists" => const SearchPagePlaylistsTab(), + _ => const SearchPageAllTab(), + }, + ), + ), + ], + ); + }), ), ), ); diff --git a/lib/pages/search/tabs/albums.dart b/lib/pages/search/tabs/albums.dart index 19781c05..e27772c6 100644 --- a/lib/pages/search/tabs/albums.dart +++ b/lib/pages/search/tabs/albums.dart @@ -2,6 +2,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/modules/search/loading.dart'; @@ -23,6 +24,15 @@ class SearchPageAlbumsTab extends HookConsumerWidget { final searchAlbums = searchAlbumsSnapshot.asData?.value.items ?? [FakeData.albumSimple]; + if (searchAlbumsSnapshot.hasError) { + return ErrorBox( + error: searchAlbumsSnapshot.error!, + onRetry: () { + ref.invalidate(metadataPluginSearchAlbumsProvider(searchTerm)); + }, + ); + } + return SearchPlaceholder( snapshot: searchAlbumsSnapshot, child: Padding( diff --git a/lib/pages/search/tabs/all.dart b/lib/pages/search/tabs/all.dart index 42ff1e69..306bdfce 100644 --- a/lib/pages/search/tabs/all.dart +++ b/lib/pages/search/tabs/all.dart @@ -1,5 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/modules/search/loading.dart'; import 'package:spotube/pages/search/search.dart'; @@ -19,6 +20,15 @@ class SearchPageAllTab extends HookConsumerWidget { final searchSnapshot = ref.watch(metadataPluginSearchAllProvider(searchTerm)); + if (searchSnapshot.hasError) { + return ErrorBox( + error: searchSnapshot.error!, + onRetry: () { + ref.invalidate(metadataPluginSearchAllProvider(searchTerm)); + }, + ); + } + return SearchPlaceholder( snapshot: searchSnapshot, child: InterScrollbar( diff --git a/lib/pages/search/tabs/artists.dart b/lib/pages/search/tabs/artists.dart index 59c77a70..8cea7b58 100644 --- a/lib/pages/search/tabs/artists.dart +++ b/lib/pages/search/tabs/artists.dart @@ -5,6 +5,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/waypoint.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; @@ -27,6 +28,15 @@ class SearchPageArtistsTab extends HookConsumerWidget { ref.read(metadataPluginSearchArtistsProvider(searchTerm).notifier); final searchArtists = searchArtistsSnapshot.asData?.value.items ?? []; + if (searchArtistsSnapshot.hasError) { + return ErrorBox( + error: searchArtistsSnapshot.error!, + onRetry: () { + ref.invalidate(metadataPluginSearchArtistsProvider(searchTerm)); + }, + ); + } + return SearchPlaceholder( snapshot: searchArtistsSnapshot, child: AnimatedSwitcher( diff --git a/lib/pages/search/tabs/playlists.dart b/lib/pages/search/tabs/playlists.dart index 2ea9d430..f00153cb 100644 --- a/lib/pages/search/tabs/playlists.dart +++ b/lib/pages/search/tabs/playlists.dart @@ -2,6 +2,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/modules/search/loading.dart'; @@ -23,6 +24,15 @@ class SearchPagePlaylistsTab extends HookConsumerWidget { final searchPlaylists = searchPlaylistsSnapshot.asData?.value.items ?? [FakeData.playlistSimple]; + if (searchPlaylistsSnapshot.hasError) { + return ErrorBox( + error: searchPlaylistsSnapshot.error!, + onRetry: () { + ref.invalidate(metadataPluginSearchPlaylistsProvider(searchTerm)); + }, + ); + } + return SearchPlaceholder( snapshot: searchPlaylistsSnapshot, child: Padding( diff --git a/lib/pages/search/tabs/tracks.dart b/lib/pages/search/tabs/tracks.dart index 2212c010..e4c56891 100644 --- a/lib/pages/search/tabs/tracks.dart +++ b/lib/pages/search/tabs/tracks.dart @@ -4,6 +4,7 @@ import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; import 'package:spotube/components/dialogs/prompt_dialog.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; +import 'package:spotube/components/fallbacks/error_box.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; @@ -31,6 +32,15 @@ class SearchPageTracksTab extends HookConsumerWidget { final playlist = ref.watch(audioPlayerProvider); final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + if (searchTracksSnapshot.hasError) { + return ErrorBox( + error: searchTracksSnapshot.error!, + onRetry: () { + ref.invalidate(metadataPluginSearchTracksProvider(searchTerm)); + }, + ); + } + return SearchPlaceholder( snapshot: searchTracksSnapshot, child: InfiniteList( diff --git a/lib/pages/settings/about.dart b/lib/pages/settings/about.dart index 27775f3c..5a95c0eb 100644 --- a/lib/pages/settings/about.dart +++ b/lib/pages/settings/about.dart @@ -45,7 +45,7 @@ class AboutSpotubePage extends HookConsumerWidget { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ - Assets.spotubeLogoPng.image( + Assets.branding.spotubeLogoPng.image( height: 200, width: 200, ), diff --git a/lib/pages/settings/logs.dart b/lib/pages/settings/logs.dart index 4985b57a..61269456 100644 --- a/lib/pages/settings/logs.dart +++ b/lib/pages/settings/logs.dart @@ -71,32 +71,34 @@ class LogsPage extends HookConsumerWidget { ), ) ], - child: switch (logsQuery) { - AsyncData(:final value) => InterScrollbar( - controller: controller, - child: SingleChildScrollView( - padding: const EdgeInsets.all(8.0), + child: SafeArea( + child: switch (logsQuery) { + AsyncData(:final value) => InterScrollbar( controller: controller, - child: Card(child: SelectableText(value)), - ), - ), - AsyncError(:final error) => switch (error) { - StateError() => Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Undraw( - illustration: UndrawIllustration.noData, - height: 200 * context.theme.scaling, - width: 200 * context.theme.scaling, - color: context.theme.colorScheme.primary, - ), - Text(context.l10n.no_logs_found).muted().small(), - ], + child: SingleChildScrollView( + padding: const EdgeInsets.all(8.0), + controller: controller, + child: Card(child: SelectableText(value)), ), - _ => Center(child: Text(error.toString())), - }, - _ => const Center(child: CircularProgressIndicator()), - }, + ), + AsyncError(:final error) => switch (error) { + StateError() => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Undraw( + illustration: UndrawIllustration.noData, + height: 200 * context.theme.scaling, + width: 200 * context.theme.scaling, + color: context.theme.colorScheme.primary, + ), + Text(context.l10n.no_logs_found).muted().small(), + ], + ), + _ => Center(child: Text(error.toString())), + }, + _ => const Center(child: CircularProgressIndicator()), + }, + ), ); } } diff --git a/lib/pages/settings/metadata/metadata_form.dart b/lib/pages/settings/metadata/metadata_form.dart index a82d405c..b0aeb8bb 100644 --- a/lib/pages/settings/metadata/metadata_form.dart +++ b/lib/pages/settings/metadata/metadata_form.dart @@ -73,7 +73,7 @@ class SettingsMetadataProviderFormPage extends HookConsumerWidget { FormBuilderValidators.match( RegExp(field.regex!), errorText: - "Input doesn't match the required format", + context.l10n.input_does_not_match_format, ), ]), builder: (formField) { diff --git a/lib/pages/settings/metadata_plugins.dart b/lib/pages/settings/metadata_plugins.dart index f0655019..6698a67f 100644 --- a/lib/pages/settings/metadata_plugins.dart +++ b/lib/pages/settings/metadata_plugins.dart @@ -1,5 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; @@ -9,6 +11,7 @@ import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/form/text_form_field.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/metadata_plugins/installed_plugin.dart'; import 'package:spotube/modules/metadata_plugins/plugin_repository.dart'; @@ -16,6 +19,7 @@ import 'package:spotube/provider/metadata_plugin/core/repositories.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:file_picker/file_picker.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/utils/platform.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:sliver_tools/sliver_tools.dart'; @@ -55,9 +59,9 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { return SafeArea( bottom: false, child: Scaffold( - headers: const [ + headers: [ TitleBar( - title: Text("Metadata provider plugin"), + title: Text(context.l10n.metadata_provider_plugins), ) ], child: Padding( @@ -75,56 +79,112 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { name: "plugin_url", validator: FormBuilderValidators.url( protocols: ["http", "https"]), - placeholder: const Text( - "Paste download url or GitHub/Codeberg repo url" - "or direct link to .smplug file", - ), + placeholder: + Text(context.l10n.paste_plugin_download_url), ), ), ), - Tooltip( - tooltip: const TooltipContainer( - child: Text("Download and install plugin from url"), - ).call, - child: IconButton.secondary( - icon: const Icon(SpotubeIcons.download), - onPressed: () async { - if (formKey.currentState?.saveAndValidate() ?? - false) { - final url = formKey.currentState - ?.fields["plugin_url"]?.value as String; + HookBuilder(builder: (context) { + final isLoading = useState(false); - if (url.isNotEmpty) { - final pluginConfig = await pluginsNotifier - .downloadAndCachePlugin(url); + return Tooltip( + tooltip: TooltipContainer( + child: Text(context + .l10n.download_and_install_plugin_from_url), + ).call, + child: IconButton.secondary( + icon: isLoading.value + ? const SizedBox.square( + dimension: 22, + child: + CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(SpotubeIcons.download), + enabled: !isLoading.value, + onPressed: () async { + try { + if (formKey.currentState?.saveAndValidate() ?? + false) { + final url = formKey.currentState + ?.fields["plugin_url"]?.value as String; - await pluginsNotifier.addPlugin(pluginConfig); + if (url.isNotEmpty) { + isLoading.value = true; + final pluginConfig = await pluginsNotifier + .downloadAndCachePlugin(url); + + await pluginsNotifier.addPlugin(pluginConfig); + + formKey.currentState?.fields["plugin_url"] + ?.reset(); + } + } + } catch (e, stackTrace) { + AppLogger.reportError(e, stackTrace); + if (context.mounted) { + showToast( + showDuration: const Duration(seconds: 5), + context: context, + builder: (context, overlay) { + return SurfaceCard( + child: Basic( + leading: const Icon( + SpotubeIcons.error, + color: Colors.red, + ), + title: Text( + context.l10n + .failed_to_add_plugin_error( + e.toString()), + ), + ), + ); + }, + ); + } + } finally { + isLoading.value = false; } - } - }, - ), - ), + }, + ), + ); + }), Tooltip( - tooltip: const TooltipContainer( - child: Text("Upload plugin from file"), + tooltip: TooltipContainer( + child: Text(context.l10n.upload_plugin_from_file), ).call, child: IconButton.primary( icon: const Icon(SpotubeIcons.upload), onPressed: () async { - final result = await FilePicker.platform.pickFiles( - type: kIsAndroid ? FileType.any : FileType.custom, - allowedExtensions: kIsAndroid ? [] : ["smplug"], - withData: true, - ); + Uint8List bytes; - if (result == null) return; + if (kIsFlatpak) { + final result = await openFile( + acceptedTypeGroups: [ + const XTypeGroup( + label: 'Spotube Metadata Plugin', + extensions: ['smplug'], + ), + ], + ); + if (result == null) return; + bytes = await result.readAsBytes(); + } else { + final result = await FilePicker.platform.pickFiles( + type: kIsAndroid ? FileType.any : FileType.custom, + allowedExtensions: kIsAndroid ? [] : ["smplug"], + withData: true, + ); - final file = result.files.first; + if (result == null) return; - if (file.bytes == null) return; + final file = result.files.first; + if (file.bytes == null) return; + bytes = file.bytes!; + } - final pluginConfig = await pluginsNotifier - .extractPluginArchive(file.bytes!); + final pluginConfig = + await pluginsNotifier.extractPluginArchive(bytes); await pluginsNotifier.addPlugin(pluginConfig); }, ), @@ -138,7 +198,7 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { child: Row( children: [ const Gap(8), - const Text("Installed").h4, + Text(context.l10n.installed).h4, const Gap(8), const Expanded(child: Divider()), const Gap(8), @@ -164,7 +224,7 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { child: Row( children: [ const Gap(8), - const Text("Available plugins").h4, + Text(context.l10n.available_plugins).h4, const Gap(8), const Expanded(child: Divider()), const Gap(8), @@ -177,6 +237,9 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { !pluginReposSnapshot.isLoadingNextPage, itemCount: pluginRepos.length, onFetchData: pluginReposNotifier.fetchMore, + separatorBuilder: (context, index) { + return const Gap(12); + }, loadingBuilder: (context) { return Skeletonizer( enabled: true, @@ -217,21 +280,16 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { spacing: 8, children: [ const Icon(SpotubeIcons.warning, size: 16), - const Text( - "Disclaimer", - style: TextStyle(fontWeight: FontWeight.bold), + Text( + context.l10n.disclaimer, + style: const TextStyle( + fontWeight: FontWeight.bold), ).bold, ], ), - const Text( - "The Spotube team does not hold any responsibility (including legal) for any \"Third-party\" plugins.\n" - "Please use them at your own risk. For any bugs/issues, please report them to the plugin repository." - "\n\n" - "If 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", - ).muted.xSmall, + Text(context.l10n.third_party_plugin_dmca_notice) + .muted + .xSmall, ], ), ), diff --git a/lib/pages/settings/scrobbling/scrobbling.dart b/lib/pages/settings/scrobbling/scrobbling.dart new file mode 100644 index 00000000..9c7f3296 --- /dev/null +++ b/lib/pages/settings/scrobbling/scrobbling.dart @@ -0,0 +1,67 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart' + show ListTile, ListTileTheme, ListTileThemeData, Material, MaterialType; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +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/components/titlebar/titlebar.dart'; +import 'package:spotube/extensions/context.dart'; + +@RoutePage() +class SettingsScrobblingPage extends HookConsumerWidget { + static const name = "settings_scrobbling"; + + const SettingsScrobblingPage({super.key}); + + @override + Widget build(BuildContext context, ref) { + return Material( + type: MaterialType.transparency, + child: ListTileTheme( + data: ListTileThemeData( + contentPadding: EdgeInsets.zero, + minVerticalPadding: 0, + shape: RoundedRectangleBorder( + borderRadius: context.theme.borderRadiusLg, + side: BorderSide( + color: context.theme.colorScheme.border, + width: .5, + ), + ), + textColor: context.theme.colorScheme.foreground, + iconColor: context.theme.colorScheme.foreground, + selectedColor: context.theme.colorScheme.accent, + subtitleTextStyle: context.theme.typography.xSmall, + ), + child: SafeArea( + bottom: false, + child: Scaffold( + headers: [TitleBar(title: Text(context.l10n.scrobbling))], + child: ListView( + padding: const EdgeInsets.all(8), + children: [ + Card( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ListTile( + leading: const Icon(SpotubeIcons.lastFm, color: Colors.red), + title: Text(context.l10n.login_with_lastfm), + subtitle: Text(context.l10n.scrobble_to_lastfm), + trailing: Button.secondary( + leading: const Icon(SpotubeIcons.lastFm), + onPressed: () { + context.navigateTo(const LastFMLoginRoute()); + }, + child: Text(context.l10n.connect), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index c9051470..af8e1b80 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -21,9 +21,8 @@ class SettingsAccountSection extends HookConsumerWidget { children: [ ListTile( leading: const Icon(SpotubeIcons.extensions), - title: const Text("Metadata provider plugins"), - subtitle: const Text( - "Configure your own playlist/album/artist/feed metadata provider"), + title: Text(context.l10n.metadata_provider_plugins), + subtitle: Text(context.l10n.configure_your_own_metadata_plugin), onTap: () { context.pushRoute(const SettingsMetadataProviderRoute()); }, @@ -31,16 +30,12 @@ class SettingsAccountSection extends HookConsumerWidget { ), if (scrobbler.asData?.value == null) ListTile( - leading: const Icon(SpotubeIcons.lastFm), - title: Text(context.l10n.login_with_lastfm), - subtitle: Text(context.l10n.scrobble_to_lastfm), - trailing: Button.secondary( - leading: const Icon(SpotubeIcons.lastFm), - onPressed: () { - context.navigateTo(const LastFMLoginRoute()); - }, - child: Text(context.l10n.connect), - ), + leading: const Icon(SpotubeIcons.music), + title: Text(context.l10n.audio_scrobblers), + onTap: () { + context.pushRoute(const SettingsScrobblingRoute()); + }, + trailing: const Icon(SpotubeIcons.angleRight), ) else ListTile( diff --git a/lib/pages/settings/sections/language_region.dart b/lib/pages/settings/sections/language_region.dart index ba4af41d..920b0df7 100644 --- a/lib/pages/settings/sections/language_region.dart +++ b/lib/pages/settings/sections/language_region.dart @@ -13,7 +13,8 @@ import 'package:spotube/l10n/l10n.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; final localWithName = L10n.all.map((e) { - final isoCodeName = LanguageLocals.getDisplayLanguage(e.languageCode); + final isoCodeName = + LanguageLocals.getDisplayLanguage(e.languageCode, e.countryCode); return ( locale: e, name: "${isoCodeName.name} (${isoCodeName.nativeName})", diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index 1787a0de..cb53ca4f 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -147,21 +147,38 @@ class AudioPlayerNotifier extends Notifier { try { // Playlist and state has to be in sync. This is only meant for // the shuffle/re-ordering indices to be in sync - if (playlist.medias.length != state.tracks.length) return; - - final queries = playlist.medias - .map((media) => TrackSourceQuery.parseUri(media.uri)) - .toList(); + if (playlist.medias.length != state.tracks.length) { + AppLogger.log.w( + "Playlist length does not match state tracks length. Ignoring... " + "Playlist length: ${playlist.medias.length}, " + "State tracks length: ${state.tracks.length}", + ); + return; + } final trackGroupedById = groupBy( state.tracks, (query) => query.id, ); - final tracks = queries - .map((query) => trackGroupedById[query.id]?.firstOrNull) - .nonNulls - .toList(); + final tracks = []; + + for (final media in playlist.medias) { + final trackQuery = TrackSourceQuery.parseUri(media.uri); + final track = trackGroupedById[trackQuery.id]?.firstOrNull; + if (track != null) { + tracks.add(track); + } + } + + if (tracks.length != state.tracks.length) { + AppLogger.log.w("Mismatch in tracks after reordering/shuffling."); + final missingTracks = + state.tracks.where((track) => !tracks.contains(track)).toList(); + AppLogger.log.w( + "Missing tracks: ${missingTracks.map((e) => e.id).join(", ")}", + ); + } state = state.copyWith( tracks: tracks, @@ -412,6 +429,20 @@ class AudioPlayerNotifier extends Notifier { ); } + Future swapActiveSource() async { + if (state.tracks.isEmpty || state.activeTrack is! SpotubeFullTrackObject) { + return; + } + + final currentIndex = state.currentIndex; + final currentTrack = state.activeTrack as SpotubeFullTrackObject; + final swappedMedia = SpotubeMedia(currentTrack); + + await audioPlayer.addTrackAt(swappedMedia, currentIndex + 1); + await audioPlayer.skipToNext(); + await audioPlayer.removeTrack(currentIndex); + } + Future jumpToTrack(SpotubeTrackObject track) async { final index = state.tracks.toList().indexWhere((element) => element.id == track.id); diff --git a/lib/provider/audio_player/querying_track_info.dart b/lib/provider/audio_player/querying_track_info.dart index d7e271ae..ce99b261 100644 --- a/lib/provider/audio_player/querying_track_info.dart +++ b/lib/provider/audio_player/querying_track_info.dart @@ -11,10 +11,15 @@ final queryingTrackInfoProvider = Provider((ref) { return false; } + if (audioPlayer.activeTrack is! SpotubeFullTrackObject) { + return false; + } + return ref .watch(trackSourcesProvider( TrackSourceQuery.fromTrack( - audioPlayer.activeTrack! as SpotubeFullTrackObject), + audioPlayer.activeTrack! as SpotubeFullTrackObject, + ), )) .isLoading; }); diff --git a/lib/provider/audio_player/state.freezed.dart b/lib/provider/audio_player/state.freezed.dart index 0299cd2f..146b0541 100644 --- a/lib/provider/audio_player/state.freezed.dart +++ b/lib/provider/audio_player/state.freezed.dart @@ -27,12 +27,8 @@ mixin _$AudioPlayerState { int get currentIndex => throw _privateConstructorUsedError; List get tracks => throw _privateConstructorUsedError; - /// Serializes this AudioPlayerState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of AudioPlayerState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $AudioPlayerStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -62,8 +58,6 @@ class _$AudioPlayerStateCopyWithImpl<$Res, $Val extends AudioPlayerState> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of AudioPlayerState - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -128,8 +122,6 @@ class __$$AudioPlayerStateImplCopyWithImpl<$Res> $Res Function(_$AudioPlayerStateImpl) _then) : super(_value, _then); - /// Create a copy of AudioPlayerState - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -234,7 +226,7 @@ class _$AudioPlayerStateImpl extends _AudioPlayerState { const DeepCollectionEquality().equals(other._tracks, _tracks)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash( runtimeType, @@ -245,9 +237,7 @@ class _$AudioPlayerStateImpl extends _AudioPlayerState { currentIndex, const DeepCollectionEquality().hash(_tracks)); - /// Create a copy of AudioPlayerState - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => @@ -287,11 +277,8 @@ abstract class _AudioPlayerState extends AudioPlayerState { int get currentIndex; @override List get tracks; - - /// Create a copy of AudioPlayerState - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/provider/download_manager_provider.dart b/lib/provider/download_manager_provider.dart index 2a79f60d..d7f28b67 100644 --- a/lib/provider/download_manager_provider.dart +++ b/lib/provider/download_manager_provider.dart @@ -130,7 +130,7 @@ class DownloadManagerProvider extends ChangeNotifier { download.status.value == DownloadStatus.queued, ) .map((e) => e.request.url) - .contains(sourcedTrack.getUrlOfCodec(downloadCodec)); + .contains(sourcedTrack.getUrlOfCodec(downloadCodec)!); } /// For singular downloads @@ -152,7 +152,9 @@ class DownloadManagerProvider extends ChangeNotifier { if (sourcedTrack.codec == downloadCodec) { final downloadTask = await dl.addDownload( - sourcedTrack.getUrlOfCodec(downloadCodec), savePath); + sourcedTrack.getUrlOfCodec(downloadCodec)!, + savePath, + ); if (downloadTask != null) { $history.add(sourcedTrack); } @@ -169,7 +171,7 @@ class DownloadManagerProvider extends ChangeNotifier { return d; }); final downloadTask = await dl.addDownload( - sourcedTrack.getUrlOfCodec(downloadCodec), + sourcedTrack.getUrlOfCodec(downloadCodec)!, savePath, ); if (downloadTask != null) { @@ -202,18 +204,18 @@ class DownloadManagerProvider extends ChangeNotifier { Future removeFromQueue(SpotubeFullTrackObject track) async { final sourcedTrack = await mapToSourcedTrack(track); - await dl.removeDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); + await dl.removeDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!); $history.remove(sourcedTrack); } Future pause(SpotubeFullTrackObject track) async { final sourcedTrack = await mapToSourcedTrack(track); - return dl.pauseDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); + return dl.pauseDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!); } Future resume(SpotubeFullTrackObject track) async { final sourcedTrack = await mapToSourcedTrack(track); - return dl.resumeDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); + return dl.resumeDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!); } Future retry(SpotubeFullTrackObject track) { @@ -222,7 +224,7 @@ class DownloadManagerProvider extends ChangeNotifier { void cancel(SpotubeFullTrackObject track) async { final sourcedTrack = await mapToSourcedTrack(track); - return dl.cancelDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); + return dl.cancelDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!); } void cancelAll() { @@ -256,7 +258,7 @@ class DownloadManagerProvider extends ChangeNotifier { if (sourcedTrack == null) { return null; } - return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec))?.status; + return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!)?.status; } ValueNotifier? getProgressNotifier(SpotubeFullTrackObject track) { @@ -266,7 +268,7 @@ class DownloadManagerProvider extends ChangeNotifier { if (sourcedTrack == null) { return null; } - return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec))?.progress; + return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!)?.progress; } } diff --git a/lib/provider/glance/glance.dart b/lib/provider/glance/glance.dart index 8afeda11..00b6bc38 100644 --- a/lib/provider/glance/glance.dart +++ b/lib/provider/glance/glance.dart @@ -80,17 +80,22 @@ Future _sendActiveTrack(SpotubeTrackObject? track) async { final jsonTrack = track.toJson(); - final image = track.album?.images.first; - final cachedImage = await DefaultCacheManager().getSingleFile(image!.url); + final image = track.album.images.firstOrNull; + final cachedImage = image == null + ? null + : image.url.startsWith("http") + ? (await DefaultCacheManager().getSingleFile(image.url)).path + : image.url; final data = { ...jsonTrack, "album": { ...jsonTrack["album"], "images": [ - { - ...image.toJson(), - "path": cachedImage.path, - } + if (cachedImage != null && image != null) + { + ...image.toJson(), + "path": cachedImage, + } ] } }; diff --git a/lib/provider/local_tracks/local_tracks_provider.dart b/lib/provider/local_tracks/local_tracks_provider.dart index 89ea6d57..8d44b607 100644 --- a/lib/provider/local_tracks/local_tracks_provider.dart +++ b/lib/provider/local_tracks/local_tracks_provider.dart @@ -35,6 +35,12 @@ const imgMimeToExt = { "image/gif": ".gif", }; +typedef MetadataFile = ({ + Metadata? metadata, + File file, + String? art, +}); + final localTracksProvider = FutureProvider>>((ref) async { try { @@ -89,7 +95,7 @@ final localTracksProvider = } } - final List> filesWithMetadata = await Future.wait( + final List filesWithMetadata = await Future.wait( entities.map((file) async { try { final metadata = await MetadataGod.readMetadata(file: file.path); @@ -111,10 +117,10 @@ final localTracksProvider = ); } - return {"metadata": metadata, "file": file, "art": imageFile.path}; + return (metadata: metadata, file: file, art: imageFile.path); } catch (e, stack) { if (e case FrbException() || TimeoutException()) { - return {"file": file}; + return (file: file, metadata: null, art: null); } AppLogger.reportError(e, stack); return null; @@ -125,9 +131,9 @@ final localTracksProvider = final tracksFromMetadata = filesWithMetadata .map( (fileWithMetadata) => SpotubeTrackObject.localTrackFromFile( - fileWithMetadata["file"] as File, - metadata: fileWithMetadata["metadata"] as Metadata?, - art: fileWithMetadata["art"] as String?, + fileWithMetadata.file, + metadata: fileWithMetadata.metadata, + art: fileWithMetadata.art, ) as SpotubeLocalTrackObject, ) .toList(); diff --git a/lib/provider/metadata_plugin/album/album.dart b/lib/provider/metadata_plugin/album/album.dart index 72c62202..3a386236 100644 --- a/lib/provider/metadata_plugin/album/album.dart +++ b/lib/provider/metadata_plugin/album/album.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; -import 'package:spotube/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; final metadataPluginAlbumProvider = FutureProvider.autoDispose.family( @@ -12,9 +12,7 @@ final metadataPluginAlbumProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "No metadata plugin is not set", - ); + throw MetadataPluginException.noDefaultPlugin(); } return metadataPlugin.album.getAlbum(id); diff --git a/lib/provider/metadata_plugin/artist/artist.dart b/lib/provider/metadata_plugin/artist/artist.dart index e55d6103..f1691657 100644 --- a/lib/provider/metadata_plugin/artist/artist.dart +++ b/lib/provider/metadata_plugin/artist/artist.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; -import 'package:spotube/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; final metadataPluginArtistProvider = FutureProvider.autoDispose.family( @@ -12,9 +12,7 @@ final metadataPluginArtistProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "No metadata plugin is not set", - ); + throw MetadataPluginException.noDefaultPlugin(); } return metadataPlugin.artist.getArtist(artistId); diff --git a/lib/provider/metadata_plugin/library/albums.dart b/lib/provider/metadata_plugin/library/albums.dart index daa21151..10438025 100644 --- a/lib/provider/metadata_plugin/library/albums.dart +++ b/lib/provider/metadata_plugin/library/albums.dart @@ -1,6 +1,6 @@ import 'package:riverpod/riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/utils/paginated.dart'; class MetadataPluginSavedAlbumNotifier @@ -18,38 +18,50 @@ class MetadataPluginSavedAlbumNotifier @override build() async { - ref.watch(metadataPluginProvider); + await ref.watch(metadataPluginAuthenticatedProvider.future); return await fetch(0, 20); } Future addFavorite(List albums) async { - await update((state) async { - (await metadataPlugin).album.save(albums.map((e) => e.id).toList()); - return state.copyWith( - items: [...state.items, ...albums], - ); - }); + if (albums.isEmpty || state.value == null) return; + final oldState = state.value; - for (final album in albums) { - ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id)); + state = AsyncData( + state.value!.copyWith( + items: [ + ...albums, + ...state.value!.items, + ], + ), + ); + try { + await (await metadataPlugin).album.save(albums.map((e) => e.id).toList()); + } catch (e) { + state = AsyncData(oldState!); + rethrow; } } Future removeFavorite(List albums) async { - await update((state) async { - final albumIds = albums.map((e) => e.id).toList(); - (await metadataPlugin).album.unsave(albumIds); - return state.copyWith( - items: state.items + if (albums.isEmpty || state.value == null) return; + + final oldState = state.value; + + final albumIds = albums.map((e) => e.id).toList(); + state = AsyncData( + state.value!.copyWith( + items: state.value!.items .where( (e) => albumIds.contains((e).id) == false, ) .toList(), - ); - }); - - for (final album in albums) { - ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id)); + ), + ); + try { + await (await metadataPlugin).album.unsave(albumIds); + } catch (e) { + state = AsyncData(oldState!); + rethrow; } } } @@ -63,9 +75,14 @@ final metadataPluginSavedAlbumsProvider = AsyncNotifierProvider< final metadataPluginIsSavedAlbumProvider = FutureProvider.autoDispose.family( (ref, albumId) async { - final metadataPlugin = await ref.watch(metadataPluginProvider.future); + final savedAlbums = + await ref.watch(metadataPluginSavedAlbumsProvider.future); + final savedAlbumsNotifier = + ref.read(metadataPluginSavedAlbumsProvider.notifier); + final allSavedAlbums = savedAlbums.hasMore + ? await savedAlbumsNotifier.fetchAll() + : savedAlbums.items; - return metadataPlugin!.user - .isSavedAlbums([albumId]).then((value) => value.first); + return allSavedAlbums.any((element) => element.id == albumId); }, ); diff --git a/lib/provider/metadata_plugin/library/artists.dart b/lib/provider/metadata_plugin/library/artists.dart index 30d0f641..31f976e0 100644 --- a/lib/provider/metadata_plugin/library/artists.dart +++ b/lib/provider/metadata_plugin/library/artists.dart @@ -1,6 +1,6 @@ import 'package:riverpod/riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/utils/paginated.dart'; class MetadataPluginSavedArtistNotifier @@ -20,38 +20,53 @@ class MetadataPluginSavedArtistNotifier @override build() async { - ref.watch(metadataPluginProvider); + await ref.watch(metadataPluginAuthenticatedProvider.future); return await fetch(0, 20); } Future addFavorite(List artists) async { - await update((state) async { - (await metadataPlugin).artist.save(artists.map((e) => e.id).toList()); - return state.copyWith( - items: [...state.items, ...artists], - ); - }); + if (artists.isEmpty || state.value == null) return; + final oldState = state.value; - for (final artist in artists) { - ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id)); + state = AsyncData( + state.value!.copyWith( + items: [ + ...artists, + ...state.value!.items, + ], + ), + ); + try { + await (await metadataPlugin) + .artist + .save(artists.map((e) => e.id).toList()); + } catch (e) { + state = AsyncData(oldState!); + rethrow; } } Future removeFavorite(List artists) async { - await update((state) async { - final artistIds = artists.map((e) => e.id).toList(); - (await metadataPlugin).artist.unsave(artistIds); - return state.copyWith( - items: state.items + if (artists.isEmpty || state.value == null) return; + + final oldState = state.value; + + final artistIds = artists.map((e) => e.id).toList(); + state = AsyncData( + state.value!.copyWith( + items: state.value!.items .where( (e) => artistIds.contains((e).id) == false, ) .toList(), - ); - }); + ), + ); - for (final artist in artists) { - ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id)); + try { + await (await metadataPlugin).artist.unsave(artistIds); + } catch (e) { + state = AsyncData(oldState!); + rethrow; } } } @@ -65,9 +80,15 @@ final metadataPluginSavedArtistsProvider = AsyncNotifierProvider< final metadataPluginIsSavedArtistProvider = FutureProvider.autoDispose.family( (ref, artistId) async { - final metadataPlugin = await ref.watch(metadataPluginProvider.future); + final savedArtists = + await ref.watch(metadataPluginSavedArtistsProvider.future); + final savedArtistsNotifier = + ref.read(metadataPluginSavedArtistsProvider.notifier); - return metadataPlugin!.user - .isSavedArtists([artistId]).then((value) => value.first); + final allSavedArtists = savedArtists.hasMore + ? await savedArtistsNotifier.fetchAll() + : savedArtists.items; + + return allSavedArtists.any((element) => element.id == artistId); }, ); diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart index 832e7a04..6350d610 100644 --- a/lib/provider/metadata_plugin/library/playlists.dart +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -1,10 +1,11 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; import 'package:spotube/provider/metadata_plugin/utils/paginated.dart'; -import 'package:spotube/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; class MetadataPluginSavedPlaylistsNotifier extends PaginatedAsyncNotifier { @@ -21,7 +22,8 @@ class MetadataPluginSavedPlaylistsNotifier @override build() async { - ref.watch(metadataPluginProvider); + await ref.watch(metadataPluginAuthenticatedProvider.future); + final playlists = await fetch(0, 20); return playlists; @@ -42,25 +44,43 @@ class MetadataPluginSavedPlaylistsNotifier } Future addFavorite(SpotubeSimplePlaylistObject playlist) async { - await update((state) async { - (await metadataPlugin).playlist.save(playlist.id); - return state.copyWith( - items: [...state.items, playlist], - ); - }); + if (state.value == null) return; - ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); + final oldState = state.value; + + state = AsyncData( + state.value!.copyWith( + items: [ + playlist, + ...state.value!.items, + ], + ), + ); + + try { + await (await metadataPlugin).playlist.save(playlist.id); + } catch (e) { + state = AsyncData(oldState!); + rethrow; + } } Future removeFavorite(SpotubeSimplePlaylistObject playlist) async { - await update((state) async { - (await metadataPlugin).playlist.unsave(playlist.id); - return state.copyWith( - items: state.items.where((e) => (e).id != playlist.id).toList(), - ); - }); + if (state.value == null) return; - ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); + final oldState = state.value; + state = AsyncData( + state.value!.copyWith( + items: state.value!.items.where((e) => (e).id != playlist.id).toList(), + ), + ); + + try { + await (await metadataPlugin).playlist.unsave(playlist.id); + } catch (e) { + state = AsyncData(oldState!); + rethrow; + } } Future delete(String playlistId) async { @@ -111,13 +131,19 @@ final metadataPluginIsSavedPlaylistProvider = final plugin = await ref.watch(metadataPluginProvider.future); if (plugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "Failed to get metadata plugin", - ); + throw MetadataPluginException.noDefaultPlugin(); } - final follows = await plugin.user.isSavedPlaylist(id); + final savedPlaylists = + await ref.watch(metadataPluginSavedPlaylistsProvider.future); - return follows; + final savedPlaylistsNotifier = + ref.read(metadataPluginSavedPlaylistsProvider.notifier); + + final allSavedPlaylists = savedPlaylists.hasMore + ? await savedPlaylistsNotifier.fetchAll() + : savedPlaylists.items; + + return allSavedPlaylists.any((element) => element.id == id); }, ); diff --git a/lib/provider/metadata_plugin/library/tracks.dart b/lib/provider/metadata_plugin/library/tracks.dart index b9747c6c..d19865dd 100644 --- a/lib/provider/metadata_plugin/library/tracks.dart +++ b/lib/provider/metadata_plugin/library/tracks.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:spotube/provider/metadata_plugin/utils/paginated.dart'; @@ -22,20 +22,57 @@ class MetadataPluginSavedTracksNotifier build() async { ref.cacheFor(); - ref.watch(metadataPluginProvider); + await ref.watch(metadataPluginAuthenticatedProvider.future); return await fetch(0, 20); } Future addFavorite(List tracks) async { - await (await metadataPlugin).track.save(tracks.map((e) => e.id).toList()); + if (state.value == null) { + return; + } - ref.invalidateSelf(); + final oldState = state.value; + state = AsyncData( + state.value!.copyWith( + items: [ + ...tracks.whereType(), + ...state.value!.items + ], + ), + ); + + try { + await (await metadataPlugin).track.save(tracks.map((e) => e.id).toList()); + } catch (e) { + state = AsyncData(oldState!); + rethrow; + } } Future removeFavorite(List tracks) async { - await (await metadataPlugin).track.unsave(tracks.map((e) => e.id).toList()); + if (state.value == null) { + return; + } - ref.invalidateSelf(); + final oldState = state.value; + state = AsyncData( + state.value!.copyWith( + items: state.value!.items + .where( + (savedTrack) => !tracks.any((track) => track.id == savedTrack.id), + ) + .toList(), + ), + ); + + try { + await (await metadataPlugin) + .track + .unsave(tracks.map((e) => e.id).toList()); + } catch (e) { + state = AsyncData(oldState!); + rethrow; + } } } @@ -48,9 +85,11 @@ final metadataPluginSavedTracksProvider = AutoDisposeAsyncNotifierProvider< final metadataPluginIsSavedTrackProvider = FutureProvider.autoDispose.family( (ref, trackId) async { - await ref.watch(metadataPluginSavedTracksProvider.future); - final allSavedTracks = - await ref.read(metadataPluginSavedTracksProvider.notifier).fetchAll(); + final savedTracks = + await ref.watch(metadataPluginSavedTracksProvider.future); + final allSavedTracks = savedTracks.hasMore + ? await ref.read(metadataPluginSavedTracksProvider.notifier).fetchAll() + : savedTracks.items; return allSavedTracks.any((track) => track.id == trackId); }, diff --git a/lib/provider/metadata_plugin/metadata_plugin_provider.dart b/lib/provider/metadata_plugin/metadata_plugin_provider.dart index 2a2d9a22..b61c0255 100644 --- a/lib/provider/metadata_plugin/metadata_plugin_provider.dart +++ b/lib/provider/metadata_plugin/metadata_plugin_provider.dart @@ -15,7 +15,6 @@ import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/metadata/errors/exceptions.dart'; import 'package:spotube/services/metadata/metadata.dart'; import 'package:spotube/utils/service_utils.dart'; -import 'package:uuid/uuid.dart'; import 'package:archive/archive.dart'; import 'package:pub_semver/pub_semver.dart'; @@ -89,42 +88,62 @@ class MetadataPluginNotifier extends AsyncNotifier { } Future toStatePlugins( - List plugins) async { + List plugins, + ) async { int defaultPlugin = -1; - final pluginConfigs = plugins.mapIndexed( - (index, plugin) { - if (plugin.selected) { - defaultPlugin = index; - } + final pluginConfigs = []; + + for (int i = 0; i < plugins.length; i++) { + final plugin = plugins[i]; + + final pluginConfig = PluginConfiguration( + type: PluginType.metadata, + name: plugin.name, + author: plugin.author, + description: plugin.description, + version: plugin.version, + entryPoint: plugin.entryPoint, + pluginApiVersion: plugin.pluginApiVersion, + repository: plugin.repository, + apis: plugin.apis + .map( + (e) => PluginApis.values.firstWhereOrNull( + (api) => api.name == e, + ), + ) + .nonNulls + .toList(), + abilities: plugin.abilities + .map( + (e) => PluginAbilities.values.firstWhereOrNull( + (ability) => ability.name == e, + ), + ) + .nonNulls + .toList(), + ); + + final pluginExtractionDir = await _getPluginExtractionDir(pluginConfig); + final pluginJsonFile = + File(join(pluginExtractionDir.path, "plugin.json")); + final pluginBinaryFile = + File(join(pluginExtractionDir.path, "plugin.out")); + + if (!await pluginExtractionDir.exists() || + !await pluginJsonFile.exists() || + !await pluginBinaryFile.exists()) { + // Delete the plugin entry from DB if the plugin files are not there. + await database.metadataPluginsTable.deleteOne(plugin); + continue; + } + + pluginConfigs.add(pluginConfig); + + if (plugin.selected) { + defaultPlugin = pluginConfigs.length - 1; + } + } - return PluginConfiguration( - type: PluginType.metadata, - name: plugin.name, - author: plugin.author, - description: plugin.description, - version: plugin.version, - entryPoint: plugin.entryPoint, - pluginApiVersion: plugin.pluginApiVersion, - repository: plugin.repository, - apis: plugin.apis - .map( - (e) => PluginApis.values.firstWhereOrNull( - (api) => api.name == e, - ), - ) - .nonNulls - .toList(), - abilities: plugin.abilities - .map( - (e) => PluginAbilities.values.firstWhereOrNull( - (ability) => ability.name == e, - ), - ) - .nonNulls - .toList(), - ); - }, - ).toList(); return MetadataPluginState( plugins: pluginConfigs, defaultPlugin: defaultPlugin, @@ -207,7 +226,7 @@ class MetadataPluginNotifier extends AsyncNotifier { final pluginDir = await _getPluginRootDir(); final pluginExtractionDirPath = join( pluginDir.path, - "${ServiceUtils.sanitizeFilename(plugin.name)}-${plugin.version}", + "${ServiceUtils.sanitizeFilename(plugin.author)}-${ServiceUtils.sanitizeFilename(plugin.name)}-${plugin.version}", ); return Directory(pluginExtractionDirPath); } @@ -272,12 +291,8 @@ class MetadataPluginNotifier extends AsyncNotifier { final pluginDir = await _getPluginRootDir(); await pluginDir.create(recursive: true); - final tempPluginName = "${const Uuid().v4()}.smplug"; - final pluginFile = File(join(pluginDir.path, tempPluginName)); - - final pluginRes = await globalDio.download( + final pluginRes = await globalDio.get( pluginDownloadUrl, - pluginFile.path, options: Options( responseType: ResponseType.bytes, followRedirects: true, @@ -289,7 +304,7 @@ class MetadataPluginNotifier extends AsyncNotifier { throw MetadataPluginException.pluginDownloadFailed(); } - return await extractPluginArchive(await pluginFile.readAsBytes()); + return await extractPluginArchive(pluginRes.data); } bool validatePluginApiCompatibility(PluginConfiguration plugin) { @@ -314,7 +329,8 @@ class MetadataPluginNotifier extends AsyncNotifier { final pluginRes = await (database.metadataPluginsTable.select() ..where( - (tbl) => tbl.name.equals(plugin.name), + (tbl) => + tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author), ) ..limit(1)) .get(); @@ -332,7 +348,7 @@ class MetadataPluginNotifier extends AsyncNotifier { entryPoint: plugin.entryPoint, apis: plugin.apis.map((e) => e.name).toList(), abilities: plugin.abilities.map((e) => e.name).toList(), - pluginApiVersion: plugin.pluginApiVersion, + pluginApiVersion: Value(plugin.pluginApiVersion), repository: Value(plugin.repository), ), ); @@ -344,8 +360,8 @@ class MetadataPluginNotifier extends AsyncNotifier { if (pluginExtractionDir.existsSync()) { await pluginExtractionDir.delete(recursive: true); } - await database.metadataPluginsTable - .deleteWhere((tbl) => tbl.name.equals(plugin.name)); + await database.metadataPluginsTable.deleteWhere((tbl) => + tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author)); } Future updatePlugin( @@ -356,7 +372,8 @@ class MetadataPluginNotifier extends AsyncNotifier { final pluginUpdatedConfig = await downloadAndCachePlugin(update.downloadUrl); - if (pluginUpdatedConfig.name != plugin.name) { + if (pluginUpdatedConfig.name != plugin.name && + pluginUpdatedConfig.author != plugin.author) { throw MetadataPluginException.invalidPluginConfiguration(); } _assertPluginApiCompatibility(pluginUpdatedConfig); @@ -375,7 +392,8 @@ class MetadataPluginNotifier extends AsyncNotifier { .write(const MetadataPluginsTableCompanion(selected: Value(false))); await (database.metadataPluginsTable.update() - ..where((tbl) => tbl.name.equals(plugin.name))) + ..where((tbl) => + tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author))) .write( const MetadataPluginsTableCompanion(selected: Value(true)), ); diff --git a/lib/provider/metadata_plugin/playlist/playlist.dart b/lib/provider/metadata_plugin/playlist/playlist.dart index 79ead185..71062b95 100644 --- a/lib/provider/metadata_plugin/playlist/playlist.dart +++ b/lib/provider/metadata_plugin/playlist/playlist.dart @@ -4,7 +4,7 @@ import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/core/user.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; -import 'package:spotube/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; import 'package:spotube/services/metadata/metadata.dart'; class MetadataPluginPlaylistNotifier @@ -13,9 +13,7 @@ class MetadataPluginPlaylistNotifier final metadataPlugin = await ref.read(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "Metadata plugin is not set", - ); + throw MetadataPluginException.noDefaultPlugin(); } return metadataPlugin; diff --git a/lib/provider/metadata_plugin/search/all.dart b/lib/provider/metadata_plugin/search/all.dart index 92f60175..b40ee78a 100644 --- a/lib/provider/metadata_plugin/search/all.dart +++ b/lib/provider/metadata_plugin/search/all.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; -import 'package:spotube/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; final metadataPluginSearchAllProvider = FutureProvider.autoDispose.family( @@ -9,9 +9,7 @@ final metadataPluginSearchAllProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "No default metadata plugin found", - ); + throw MetadataPluginException.noDefaultPlugin(); } return metadataPlugin.search.all(query); @@ -22,9 +20,7 @@ final metadataPluginSearchChipsProvider = FutureProvider((ref) async { final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "No default metadata plugin found", - ); + throw MetadataPluginException.noDefaultPlugin(); } return metadataPlugin.search.chips; }); diff --git a/lib/provider/metadata_plugin/tracks/track.dart b/lib/provider/metadata_plugin/tracks/track.dart index 261e967d..502780e1 100644 --- a/lib/provider/metadata_plugin/tracks/track.dart +++ b/lib/provider/metadata_plugin/tracks/track.dart @@ -1,15 +1,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; -import 'package:spotube/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; final metadataPluginTrackProvider = FutureProvider.family((ref, trackId) async { final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "No metadata plugin is set as default."); + throw MetadataPluginException.noDefaultPlugin(); } return metadataPlugin.track.getTrack(trackId); diff --git a/lib/provider/metadata_plugin/utils/common.dart b/lib/provider/metadata_plugin/utils/common.dart index 98a1f4e4..087b8a1b 100644 --- a/lib/provider/metadata_plugin/utils/common.dart +++ b/lib/provider/metadata_plugin/utils/common.dart @@ -6,7 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; -import 'package:spotube/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; import 'package:spotube/services/metadata/metadata.dart'; extension PaginationExtension on AsyncValue { @@ -20,8 +20,7 @@ mixin MetadataPluginMixin final plugin = await ref.read(metadataPluginProvider.future); if (plugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "Metadata plugin is not set"); + throw MetadataPluginException.noDefaultPlugin(); } return plugin; diff --git a/lib/provider/metadata_plugin/utils/family_paginated.dart b/lib/provider/metadata_plugin/utils/family_paginated.dart index 97717d04..b798dc8e 100644 --- a/lib/provider/metadata_plugin/utils/family_paginated.dart +++ b/lib/provider/metadata_plugin/utils/family_paginated.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:spotube/services/logger/logger.dart'; abstract class FamilyPaginatedAsyncNotifier extends FamilyAsyncNotifier, A> @@ -12,22 +14,25 @@ abstract class FamilyPaginatedAsyncNotifier Future fetchMore() async { if (state.value == null || !state.value!.hasMore) return; - state = AsyncLoadingNext(state.asData!.value); + final oldState = state.value; - state = await AsyncValue.guard( - () async { - final newState = await fetch( - state.value!.nextOffset!, - state.value!.limit, - ); + try { + state = AsyncLoadingNext(state.asData!.value); - final oldItems = - state.value!.items.isEmpty ? [] : state.value!.items.cast(); - final items = newState.items.isEmpty ? [] : newState.items.cast(); + final newState = await fetch( + state.value!.nextOffset!, + state.value!.limit, + ); - return newState.copyWith(items: [...oldItems, ...items]); - }, - ); + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + state = AsyncData(newState.copyWith(items: [...oldItems, ...items])); + } catch (e, stack) { + AppLogger.reportError(e, stack); + state = AsyncData(oldState!); + } } Future> fetchAll() async { @@ -36,17 +41,32 @@ abstract class FamilyPaginatedAsyncNotifier bool hasMore = true; while (hasMore) { - await update((state) async { - final newState = await fetch( - state.nextOffset!, - state.limit, - ); + final newState = await fetch( + state.value!.nextOffset!, + max(state.value!.limit, 100), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, state.value!.limit), + ) + .catchError( + (e) async { + await Future.delayed(const Duration(milliseconds: 500)); + return fetch(state.value!.nextOffset!, state.value!.limit); + }, + ); - hasMore = newState.hasMore; - final oldItems = state.items.isEmpty ? [] : state.items.cast(); - final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]); - }); + hasMore = newState.hasMore; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + state = AsyncData( + newState.copyWith(items: [...oldItems, ...items]), + ); } return state.value!.items.cast(); @@ -60,21 +80,26 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier Future fetchMore() async { if (state.value == null || !state.value!.hasMore) return; + final oldState = state.value; - state = AsyncLoadingNext(state.asData!.value); + try { + state = AsyncLoadingNext(state.value!); - state = await AsyncValue.guard( - () async { - final newState = await fetch( - state.value!.nextOffset!, - state.value!.limit, - ); - return newState.copyWith(items: [ + final newState = await fetch( + state.value!.nextOffset!, + state.value!.limit, + ); + + state = AsyncData( + newState.copyWith(items: [ ...state.value!.items.cast(), ...newState.items.cast(), - ]); - }, - ); + ]), + ); + } catch (e, stack) { + AppLogger.reportError(e, stack); + state = AsyncData(oldState!); + } } Future> fetchAll() async { @@ -83,18 +108,32 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier bool hasMore = true; while (hasMore) { - await update((state) async { - final newState = await fetch( - state.nextOffset!, - state.limit, - ); + final newState = await fetch( + state.value!.nextOffset!, + max(state.value!.limit, 100), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, state.value!.limit), + ) + .catchError( + (e) async { + await Future.delayed(const Duration(milliseconds: 500)); + return fetch(state.value!.nextOffset!, state.value!.limit); + }, + ); - hasMore = newState.hasMore; - return newState.copyWith(items: [ - ...state.items.cast(), - ...newState.items.cast(), - ]); - }); + hasMore = newState.hasMore; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + state = AsyncData( + newState.copyWith(items: [...oldItems, ...items]), + ); } return state.value!.items.cast(); diff --git a/lib/provider/metadata_plugin/utils/paginated.dart b/lib/provider/metadata_plugin/utils/paginated.dart index e1d2bf26..4c77441a 100644 --- a/lib/provider/metadata_plugin/utils/paginated.dart +++ b/lib/provider/metadata_plugin/utils/paginated.dart @@ -1,10 +1,12 @@ import 'dart:async'; +import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; // ignore: implementation_imports import 'package:riverpod/src/async_notifier.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:spotube/services/logger/logger.dart'; mixin PaginatedAsyncNotifierMixin // ignore: invalid_use_of_internal_member @@ -14,22 +16,24 @@ mixin PaginatedAsyncNotifierMixin Future fetchMore() async { if (state.value == null || !state.value!.hasMore) return; - state = AsyncLoadingNext(state.asData!.value); + final oldState = state.value; + try { + state = AsyncLoadingNext(state.asData!.value); - state = await AsyncValue.guard( - () async { - final newState = await fetch( - state.value!.nextOffset!, - state.value!.limit, - ); + final newState = await fetch( + state.value!.nextOffset!, + state.value!.limit, + ); - final oldItems = - state.value!.items.isEmpty ? [] : state.value!.items.cast(); - final items = newState.items.isEmpty ? [] : newState.items.cast(); + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]); - }, - ); + state = AsyncData(newState.copyWith(items: [...oldItems, ...items])); + } catch (e, stack) { + AppLogger.reportError(e, stack); + state = AsyncData(oldState!); + } } Future> fetchAll() async { @@ -38,17 +42,32 @@ mixin PaginatedAsyncNotifierMixin bool hasMore = true; while (hasMore) { - await update((state) async { - final newState = await fetch( - state.nextOffset!, - state.limit, - ); + final newState = await fetch( + state.value!.nextOffset!, + max(state.value!.limit, 100), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)), + ) + .catchError( + (e) => fetch(state.value!.nextOffset!, state.value!.limit), + ) + .catchError( + (e) async { + await Future.delayed(const Duration(milliseconds: 500)); + return fetch(state.value!.nextOffset!, state.value!.limit); + }, + ); - hasMore = newState.hasMore; - final oldItems = state.items.isEmpty ? [] : state.items.cast(); - final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]); - }); + hasMore = newState.hasMore; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + state = AsyncData( + newState.copyWith(items: [...oldItems, ...items]), + ); } return state.value!.items.cast(); diff --git a/lib/provider/server/routes/connect.dart b/lib/provider/server/routes/connect.dart index ccfbf5ba..257c4cb4 100644 --- a/lib/provider/server/routes/connect.dart +++ b/lib/provider/server/routes/connect.dart @@ -161,7 +161,7 @@ class ServerConnectRoutes { event.onLoad((event) async { await audioPlayerNotifier.load( - event.data.tracks as List, + event.data.tracks.cast().toList(), autoPlay: true, initialIndex: event.data.initialIndex ?? 0, ); @@ -186,7 +186,7 @@ class ServerConnectRoutes { }); event.onStop((event) async { - await audioPlayer.stop(); + await ref.read(audioPlayerProvider.notifier).stop(); }); event.onNext((event) async { diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index 25026425..c81d968f 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -48,6 +48,7 @@ class ServerPlaybackRoutes { Future<({dio_lib.Response response, Uint8List? bytes})> streamTrack( + Request request, SourcedTrack track, Map headers, ) async { @@ -59,28 +60,6 @@ class ServerPlaybackRoutes { ), ), ); - final trackPartialCacheFile = File("${trackCacheFile.path}.part"); - - var options = Options( - headers: { - ...headers, - "user-agent": _randomUserAgent, - "Cache-Control": "max-age=3600", - "Connection": "keep-alive", - "host": Uri.parse(track.url).host, - }, - responseType: ResponseType.bytes, - validateStatus: (status) => status! < 400, - ); - - final headersRes = await Future.value( - dio.head( - track.url, - options: options, - ), - ).catchError((_) async => null); - - final contentLength = headersRes?.headers.value("content-length"); if (await trackCacheFile.exists() && userPreferences.cacheMusic) { final bytes = await trackCacheFile.readAsBytes(); @@ -95,12 +74,69 @@ class ServerPlaybackRoutes { "accept-ranges": ["bytes"], "content-range": ["bytes 0-$cachedFileLength/$cachedFileLength"], }), - requestOptions: RequestOptions(path: track.url), + requestOptions: RequestOptions(path: request.requestedUri.toString()), ), bytes: bytes, ); } + final trackPartialCacheFile = File("${trackCacheFile.path}.part"); + + String url = track.url ?? + await ref + .read(trackSourcesProvider(track.query).notifier) + .swapWithNextSibling() + .then((track) => track.url!); + + var options = Options( + headers: { + ...headers, + "user-agent": _randomUserAgent, + "Cache-Control": "max-age=3600", + "Connection": "keep-alive", + "host": Uri.parse(url).host, + }, + responseType: ResponseType.bytes, + validateStatus: (status) => status! < 400, + ); + + final contentLengthRes = await Future.value( + dio.head( + url, + options: options, + ), + ).catchError((e, stack) async { + AppLogger.reportError(e, stack); + + final sourcedTrack = await ref + .read(trackSourcesProvider(track.query).notifier) + .refreshStreamingUrl(); + + url = sourcedTrack.url!; + + return dio.head(url, options: options); + }); + + // Redirect to m3u8 link directly as it handles range requests internally + if (contentLengthRes?.headers.value("content-type") == + "application/vnd.apple.mpegurl") { + return ( + response: dio_lib.Response( + statusCode: 301, + statusMessage: "M3U8 Redirect", + headers: Headers.fromMap({ + "location": [url], + "content-type": ["application/vnd.apple.mpegurl"], + }), + requestOptions: RequestOptions(path: request.requestedUri.toString()), + isRedirect: true, + ), + bytes: null, + ); + } + + final contentLength = contentLengthRes?.headers.value("content-length"); + /// Forcing partial content range as mpv sometimes greedily wants /// everything at one go. Slows down overall streaming. final range = RangeHeader.parse(headers["range"] ?? ""); @@ -116,33 +152,7 @@ class ServerPlaybackRoutes { ); } - final res = await dio - .get( - track.url, - options: options.copyWith(headers: { - ...?options.headers, - "user-agent": _randomUserAgent, - }), - ) - .catchError((e, stack) async { - AppLogger.reportError(e, stack); - final sourcedTrack = await ref - .read(trackSourcesProvider(track.query).notifier) - .refreshStreamingUrl(); - - // It gets updated by itself. - // if (playlist.activeTrack?.id == sourcedTrack.query.id) { - // ref.read(activeTrackSourcesProvider.notifier).update(sourcedTrack); - // } - - return await dio.get( - sourcedTrack.url, - options: options.copyWith(headers: { - ...?options.headers, - "user-agent": _randomUserAgent, - }), - ); - }); + final res = await dio.get(url, options: options); final bytes = res.data; @@ -215,18 +225,17 @@ class ServerPlaybackRoutes { ? activeSourcedTrack?.source : await ref.read( trackSourcesProvider( - TrackSourceQuery.parseUri(request.url.toString()), + //! Use [Request.requestedUri] as it contains full https url. + //! [Request.url] will exclude and starts relatively. (streams/... basically) + TrackSourceQuery.parseUri(request.requestedUri.toString()), ).future, ); - // This will be automatically updated by the notifier. - // if (playlist.activeTrack?.id == sourcedTrack?.query.id && - // sourcedTrack != null) { - // ref.read(activeTrackSourcesProvider.notifier).update(sourcedTrack); - // } - - final (bytes: audioBytes, response: res) = - await streamTrack(sourcedTrack!, request.headers); + final (bytes: audioBytes, response: res) = await streamTrack( + request, + sourcedTrack!, + request.headers, + ); return Response( res.statusCode!, diff --git a/lib/provider/server/track_sources.dart b/lib/provider/server/track_sources.dart index 4a0f29ca..24502471 100644 --- a/lib/provider/server/track_sources.dart +++ b/lib/provider/server/track_sources.dart @@ -34,6 +34,12 @@ class TrackSourcesNotifier return await prev.swapWithSibling(sibling) ?? prev; }); } + + Future swapWithNextSibling() async { + return await update((prev) async { + return await prev.swapWithSibling(prev.siblings.first) as SourcedTrack; + }); + } } final trackSourcesProvider = AsyncNotifierProviderFamily get errorStream => _mkPlayer.stream.error; - Stream get playlistStream => _mkPlayer.stream.playlist.map((s) { - return s; - }); + Stream get playlistStream => _mkPlayer.stream.playlist; } diff --git a/lib/services/audio_player/custom_player.dart b/lib/services/audio_player/custom_player.dart index f0dc8f13..5258696b 100644 --- a/lib/services/audio_player/custom_player.dart +++ b/lib/services/audio_player/custom_player.dart @@ -12,19 +12,15 @@ import 'package:spotube/utils/platform.dart'; /// This class adds a state stream to the [Player] class. class CustomPlayer extends Player { final StreamController _playerStateStream; - final StreamController _shuffleStream; late final List _subscriptions; - bool _shuffled; int _androidAudioSessionId = 0; String _packageName = ""; AndroidAudioManager? _androidAudioManager; CustomPlayer({super.configuration}) - : _playerStateStream = StreamController.broadcast(), - _shuffleStream = StreamController.broadcast(), - _shuffled = false { + : _playerStateStream = StreamController.broadcast() { nativePlayer.setProperty("network-timeout", "120"); _subscriptions = [ @@ -86,10 +82,10 @@ class CustomPlayer extends Player { } } - bool get shuffled => _shuffled; + bool get shuffled => state.shuffle; Stream get playerStateStream => _playerStateStream.stream; - Stream get shuffleStream => _shuffleStream.stream; + Stream get shuffleStream => stream.shuffle; Stream get indexChangeStream { int oldIndex = state.playlist.index; return stream.playlist.map((event) => event.index).where((newIndex) { @@ -103,22 +99,14 @@ class CustomPlayer extends Player { @override Future setShuffle(bool shuffle) async { - _shuffled = shuffle; await super.setShuffle(shuffle); - _shuffleStream.add(shuffle); - await Future.delayed(const Duration(milliseconds: 100)); - if (shuffle) { - await move(state.playlist.index, 0); - } } @override Future stop() async { await super.stop(); - _shuffled = false; _playerStateStream.add(AudioPlaybackState.stopped); - _shuffleStream.add(false); } @override @@ -134,7 +122,8 @@ class CustomPlayer extends Player { Future insert(int index, Media media) async { await add(media); - await move(state.playlist.medias.length, index); + await Future.delayed(const Duration(milliseconds: 100)); + await move(state.playlist.medias.length - 1, index); } Future setAudioNormalization(bool normalize) async { diff --git a/lib/services/audio_services/audio_services.dart b/lib/services/audio_services/audio_services.dart index cb27a346..c511da61 100644 --- a/lib/services/audio_services/audio_services.dart +++ b/lib/services/audio_services/audio_services.dart @@ -53,10 +53,8 @@ class AudioServices with WidgetsBindingObserver { title: track.name, artist: track.artists.asString(), duration: Duration(milliseconds: track.durationMs), - artUri: Uri.parse( - (track.album.images).asUrlString( - placeholder: ImagePlaceholder.albumArt, - ), + artUri: (track.album.images).asUri( + placeholder: ImagePlaceholder.albumArt, ), playable: true, )); diff --git a/lib/services/metadata/endpoints/error.dart b/lib/services/metadata/endpoints/error.dart deleted file mode 100644 index f4c6af94..00000000 --- a/lib/services/metadata/endpoints/error.dart +++ /dev/null @@ -1,12 +0,0 @@ -class MetadataPluginException implements Exception { - final String exceptionType; - final String message; - - MetadataPluginException.noDefaultPlugin(this.message) - : exceptionType = "NoDefault"; - - @override - String toString() { - return "${exceptionType}MetadataPluginException: $message"; - } -} diff --git a/lib/services/metadata/errors/exceptions.dart b/lib/services/metadata/errors/exceptions.dart index be460745..62cc3779 100644 --- a/lib/services/metadata/errors/exceptions.dart +++ b/lib/services/metadata/errors/exceptions.dart @@ -9,6 +9,7 @@ enum MetadataPluginErrorCode { pluginDownloadFailed, duplicatePlugin, pluginByteCodeFileNotFound, + noDefaultPlugin, } class MetadataPluginException implements Exception { @@ -67,6 +68,11 @@ class MetadataPluginException implements Exception { 'Plugin byte code file, plugin.out not found. Please ensure the plugin is correctly packaged.', errorCode: MetadataPluginErrorCode.pluginByteCodeFileNotFound, ); + MetadataPluginException.noDefaultPlugin() + : this._( + 'No default metadata plugin is set. Please set a default plugin in the settings.', + errorCode: MetadataPluginErrorCode.noDefaultPlugin, + ); @override String toString() => 'MetadataPluginException: $message'; diff --git a/lib/services/song_link/song_link.freezed.dart b/lib/services/song_link/song_link.freezed.dart index c704cde3..0a1af8a9 100644 --- a/lib/services/song_link/song_link.freezed.dart +++ b/lib/services/song_link/song_link.freezed.dart @@ -30,12 +30,8 @@ mixin _$SongLink { String? get nativeAppUriMobile => throw _privateConstructorUsedError; String? get nativeAppUriDesktop => throw _privateConstructorUsedError; - /// Serializes this SongLink to a JSON map. Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SongLink - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) $SongLinkCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -67,8 +63,6 @@ class _$SongLinkCopyWithImpl<$Res, $Val extends SongLink> // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SongLink - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -151,8 +145,6 @@ class __$$SongLinkImplCopyWithImpl<$Res> _$SongLinkImpl _value, $Res Function(_$SongLinkImpl) _then) : super(_value, _then); - /// Create a copy of SongLink - /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -269,14 +261,12 @@ class _$SongLinkImpl implements _SongLink { other.nativeAppUriDesktop == nativeAppUriDesktop)); } - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, displayName, linkId, platform, show, uniqueId, country, url, nativeAppUriMobile, nativeAppUriDesktop); - /// Create a copy of SongLink - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) @override @pragma('vm:prefer-inline') _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith => @@ -323,11 +313,8 @@ abstract class _SongLink implements SongLink { String? get nativeAppUriMobile; @override String? get nativeAppUriDesktop; - - /// Create a copy of SongLink - /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(includeFromJson: false, includeToJson: false) + @JsonKey(ignore: true) _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/services/sourced_track/enums.dart b/lib/services/sourced_track/enums.dart index 9ef6c080..d9ea079c 100644 --- a/lib/services/sourced_track/enums.dart +++ b/lib/services/sourced_track/enums.dart @@ -9,9 +9,20 @@ enum SourceCodecs { } enum SourceQualities { - high, - medium, - low, + high(2), + medium(1), + low(0); + + final int priority; + const SourceQualities(this.priority); + + bool operator <(SourceQualities other) { + return priority < other.priority; + } + + operator >(SourceQualities other) { + return priority > other.priority; + } } typedef SiblingType = ({ diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index 3e218bad..d979c007 100644 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -84,6 +84,8 @@ abstract class SourcedTrack extends BasicSourcedTrack { onlyCleanArtist: true, ).trim(); + assert(title.trim().isNotEmpty, "Title should not be empty"); + return "$title - ${track.artists.join(", ")}"; } @@ -139,7 +141,7 @@ abstract class SourcedTrack extends BasicSourcedTrack { } Future refreshStream(); - String get url { + String? get url { final preferences = ref.read(userPreferencesProvider); final codec = preferences.audioSource == AudioSource.jiosaavn @@ -155,7 +157,7 @@ abstract class SourcedTrack extends BasicSourcedTrack { /// /// If no sources match the codec, it will return the first or last source /// based on the user's audio quality preference. - String getUrlOfCodec(SourceCodecs codec) { + String? getUrlOfCodec(SourceCodecs codec) { final preferences = ref.read(userPreferencesProvider); final exactMatch = sources.firstWhereOrNull( @@ -177,7 +179,7 @@ abstract class SourcedTrack extends BasicSourcedTrack { }).toList(); if (sameCodecSources.isNotEmpty) { - return preferences.audioQuality != SourceQualities.low + return preferences.audioQuality > SourceQualities.low ? sameCodecSources.first.url : sameCodecSources.last.url; } @@ -188,9 +190,9 @@ abstract class SourcedTrack extends BasicSourcedTrack { return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index; }); - return preferences.audioQuality != SourceQualities.low - ? fallbackSource.first.url - : fallbackSource.last.url; + return preferences.audioQuality > SourceQualities.low + ? fallbackSource.firstOrNull?.url + : fallbackSource.lastOrNull?.url; } SourceCodecs get codec { diff --git a/lib/services/sourced_track/sources/jiosaavn.dart b/lib/services/sourced_track/sources/jiosaavn.dart index 4b67e717..02e97479 100644 --- a/lib/services/sourced_track/sources/jiosaavn.dart +++ b/lib/services/sourced_track/sources/jiosaavn.dart @@ -216,7 +216,7 @@ class JioSaavnSourcedTrack extends SourcedTrack { ref: ref, siblings: newSiblings, sources: source!, - info: info, + info: newSourceInfo, query: query, source: AudioSource.jiosaavn, ); diff --git a/lib/services/sourced_track/sources/piped.dart b/lib/services/sourced_track/sources/piped.dart index 65d613a6..78beda10 100644 --- a/lib/services/sourced_track/sources/piped.dart +++ b/lib/services/sourced_track/sources/piped.dart @@ -269,7 +269,7 @@ class PipedSourcedTrack extends SourcedTrack { ref: ref, siblings: newSiblings, sources: toSources(manifest), - info: info, + info: newSourceInfo, query: query, source: source, ); diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart index 0e494b89..399d5e10 100644 --- a/lib/services/sourced_track/sources/youtube.dart +++ b/lib/services/sourced_track/sources/youtube.dart @@ -1,4 +1,5 @@ import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/models/database/database.dart'; @@ -6,6 +7,7 @@ import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/youtube_engine/youtube_engine.dart'; +import 'package:spotube/services/dio/dio.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/song_link/song_link.dart'; import 'package:spotube/services/sourced_track/enums.dart'; @@ -118,6 +120,7 @@ class YoutubeSourcedTrack extends SourcedTrack { dynamic ref, ) async { assert(ref is WidgetRef || ref is Ref, "Invalid ref type"); + List? sourceMap; if (index == 0) { final manifest = @@ -201,7 +204,12 @@ class YoutubeSourcedTrack extends SourcedTrack { final searchedVideos = await ref.read(youtubeEngineProvider).searchVideos(isrc.toString()); if (searchedVideos.isNotEmpty) { - isrcResults.addAll(searchedVideos + AppLogger.log + .d("${track.title} ISRC $isrc Total ${searchedVideos.length}"); + + final stringBuffer = StringBuffer(); + + final filteredMatches = searchedVideos .map(YoutubeVideoInfo.fromVideo) .map((YoutubeVideoInfo videoInfo) { final ytWords = videoInfo.title @@ -221,12 +229,20 @@ class YoutubeSourcedTrack extends SourcedTrack { .abs() .inMilliseconds <= 3000) { + stringBuffer.writeln( + "ISRC MATCH: ${videoInfo.id} ${videoInfo.title} by ${videoInfo.channelName} ${videoInfo.duration}", + ); + return videoInfo; } return null; }) - .whereType() - .toList()); + .nonNulls + .toList(); + + AppLogger.log.d(stringBuffer.toString()); + + isrcResults.addAll(filteredMatches); } } return isrcResults; @@ -247,7 +263,21 @@ class YoutubeSourcedTrack extends SourcedTrack { videoResults.addAll(isrcResults); if (isrcResults.isEmpty) { + AppLogger.log.w("No ISRC results found, falling back to SongLink"); + final links = await SongLinkService.links(query.id); + + final stringBuffer = links.fold( + StringBuffer(), + (previousValue, element) { + previousValue.writeln( + "SongLink ${query.id} ${element.platform} ${element.url}"); + return previousValue; + }, + ); + + AppLogger.log.d(stringBuffer.toString()); + final ytLink = links.firstWhereOrNull( (link) => link.platform == "youtube", ); @@ -262,6 +292,8 @@ class YoutubeSourcedTrack extends SourcedTrack { // Ignore this error and continue with the search AppLogger.reportError(e, stack); } + } else { + AppLogger.log.w("No YouTube link found in SongLink results"); } } } @@ -307,6 +339,7 @@ class YoutubeSourcedTrack extends SourcedTrack { final newSourceInfo = isStepSibling ? sibling : siblings.firstWhere((s) => s.id == sibling.id); + final newSiblings = siblings.where((s) => s.id != sibling.id).toList() ..insert(0, info); @@ -333,7 +366,7 @@ class YoutubeSourcedTrack extends SourcedTrack { source: source, siblings: newSiblings, sources: toTrackSources(manifest), - info: info, + info: newSourceInfo, query: query, ); } @@ -360,14 +393,39 @@ class YoutubeSourcedTrack extends SourcedTrack { @override Future refreshStream() async { - final manifest = - await ref.read(youtubeEngineProvider).getStreamManifest(info.id); + List validStreams = []; + + final stringBuffer = StringBuffer(); + for (final source in sources) { + final res = await globalDio.head( + source.url, + options: + Options(validateStatus: (status) => status != null && status < 500), + ); + + stringBuffer.writeln( + "[${query.id}] ${res.statusCode} ${source.quality} ${source.codec} ${source.bitrate}", + ); + + if (res.statusCode! < 400) { + validStreams.add(source); + } + } + + AppLogger.log.d(stringBuffer.toString()); + + if (validStreams.isEmpty) { + final manifest = + await ref.read(youtubeEngineProvider).getStreamManifest(info.id); + + validStreams = toTrackSources(manifest); + } final sourcedTrack = YoutubeSourcedTrack( ref: ref, siblings: siblings, source: source, - sources: toTrackSources(manifest), + sources: validStreams, info: info, query: query, ); diff --git a/lib/services/youtube_engine/youtube_explode_engine.dart b/lib/services/youtube_engine/youtube_explode_engine.dart index 461e01f6..fa58314c 100644 --- a/lib/services/youtube_engine/youtube_explode_engine.dart +++ b/lib/services/youtube_engine/youtube_explode_engine.dart @@ -1,5 +1,6 @@ import 'dart:isolate'; +import 'package:flutter/foundation.dart'; import 'package:spotube/services/youtube_engine/youtube_engine.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; @@ -57,6 +58,7 @@ class IsolatedYoutubeExplode { static void _isolateEntry(SendPort mainSendPort) { final receivePort = ReceivePort(); final youtubeExplode = YoutubeExplode(); + final stopWatch = kDebugMode ? Stopwatch() : null; /// Send the main port to the main isolate mainSendPort.send(receivePort.sendPort); @@ -66,6 +68,19 @@ class IsolatedYoutubeExplode { final String methodName = message[1]; final List arguments = message[2]; + if (stopWatch != null) { + if (stopWatch.isRunning) { + stopWatch.stop(); + final symbol = stopWatch.elapsedMilliseconds < 1000 ? "⚠️" : "⏱️"; + debugPrint( + "$symbol YoutubeExplode operation gap ${stopWatch.elapsedMilliseconds} ms", + ); + stopWatch.reset(); + } else { + stopWatch.start(); + } + } + // Run the requested method on YoutubeExplode var result = switch (methodName) { "search" => youtubeExplode.search @@ -152,9 +167,13 @@ class YouTubeExplodeEngine implements YouTubeEngine { ], ); + final audioStreams = streamManifest.audioOnly.where( + (stream) => stream.bitrate.bitsPerSecond >= 40960, + ); + return StreamManifest( - streamManifest.audioOnly.map((stream) { - return AudioOnlyStreamInfo( + audioStreams.map( + (stream) => AudioOnlyStreamInfo( stream.videoId, stream.tag, stream.url, @@ -170,8 +189,8 @@ class YouTubeExplodeEngine implements YouTubeEngine { stream.fragments, stream.codec, stream.audioTrack, - ); - }), + ), + ), ); } diff --git a/lib/services/youtube_engine/yt_dlp_engine.dart b/lib/services/youtube_engine/yt_dlp_engine.dart index ebabc345..28665fe9 100644 --- a/lib/services/youtube_engine/yt_dlp_engine.dart +++ b/lib/services/youtube_engine/yt_dlp_engine.dart @@ -11,9 +11,7 @@ import 'package:http_parser/http_parser.dart'; class YtDlpEngine implements YouTubeEngine { StreamManifest _parseFormats(List formats, videoId) { final audioOnlyStreams = formats - .where( - (f) => f["resolution"] == "audio only" && f["manifest_url"] == null, - ) + .where((f) => f["resolution"] == "audio only") .sorted((a, b) => a["quality"] > b["quality"] ? 1 : -1) .map((f) { final filesize = f["filesize"] ?? f["filesize_approx"]; @@ -22,13 +20,14 @@ class YtDlpEngine implements YouTubeEngine { 0, Uri.parse(f["url"]), StreamContainer.parse( - f["container"]?.replaceAll("_dash", "").replaceAll("m4a", "mp4"), + f["container"]?.replaceAll("_dash", "").replaceAll("m4a", "mp4") ?? + (f["protocol"] == "m3u8_native" ? "m3u8" : "mp4"), ), filesize != null ? FileSize(filesize) : FileSize.unknown, Bitrate( (((f["abr"] ?? f["tbr"] ?? 0) * 1000) as num).toInt(), ), - f["acodec"] ?? "webm", + f["acodec"] ?? "aac", f["format_note"], [], MediaType.parse( diff --git a/linux/com.github.KRTirtho.Spotube.appdata.xml b/linux/com.github.KRTirtho.Spotube.appdata.xml index ad63e4b0..d8024303 100644 --- a/linux/com.github.KRTirtho.Spotube.appdata.xml +++ b/linux/com.github.KRTirtho.Spotube.appdata.xml @@ -37,7 +37,7 @@ - https://rawcdn.githack.com/KRTirtho/spotube/refs/heads/master/assets/spotube-screenshot.png + https://rawcdn.githack.com/KRTirtho/spotube/refs/heads/master/assets/branding/spotube-screenshot.png Spotube screenshot diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 8a9a2e19..eda2d021 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -11,11 +11,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -37,6 +39,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); + g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); + irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar); g_autoptr(FlPluginRegistrar) local_notifier_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "LocalNotifierPlugin"); local_notifier_plugin_register_with_registrar(local_notifier_registrar); @@ -52,6 +57,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); + g_autoptr(FlPluginRegistrar) super_native_extensions_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin"); + super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar); g_autoptr(FlPluginRegistrar) system_theme_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin"); system_theme_plugin_register_with_registrar(system_theme_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 783f370f..b9ca593f 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,11 +8,13 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux flutter_timezone gtk + irondash_engine_context local_notifier media_kit_libs_linux open_file_linux screen_retriever_linux sqlite3_flutter_libs + super_native_extensions system_theme tray_manager url_launcher_linux @@ -21,7 +23,6 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flutter_discord_rpc - media_kit_native_event_loop metadata_god ) diff --git a/linux/packaging/appimage/make_config.yaml b/linux/packaging/appimage/make_config.yaml index a877028c..b277e20e 100644 --- a/linux/packaging/appimage/make_config.yaml +++ b/linux/packaging/appimage/make_config.yaml @@ -1,5 +1,5 @@ display_name: Spotube -icon: assets/spotube-logo.png +icon: assets/branding/spotube-logo.png keywords: - Music diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml index 12e57e1c..ec28d8ae 100644 --- a/linux/packaging/deb/make_config.yaml +++ b/linux/packaging/deb/make_config.yaml @@ -15,7 +15,7 @@ dependencies: - gir1.2-appindicator3-0.1 | gir1.2-ayatanaappindicator3-0.1 - libsecret-1-0 - libnotify-bin - - libjsoncpp25 + - libjsoncpp1 | libjsoncpp25 | libjsoncpp26 - libmpv1 | libmpv2 - xdg-user-dirs - avahi-daemon @@ -30,7 +30,7 @@ suggested_dependencies: - yt-dlp essential: false -icon: assets/spotube-logo.png +icon: assets/branding/spotube-logo.png keywords: - Music diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml index e709642e..510aedc7 100644 --- a/linux/packaging/rpm/make_config.yaml +++ b/linux/packaging/rpm/make_config.yaml @@ -1,4 +1,4 @@ -icon: assets/spotube-logo.png +icon: assets/branding/spotube-logo.png summary: Lightweight cross-platform music client group: Applications/Multimedia vendor: Kingkor Roy Tirtho diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index bc163169..9385ed14 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,10 +12,12 @@ import bonsoir_darwin import connectivity_plus import desktop_webview_window import device_info_plus +import file_picker import file_selector_macos import flutter_inappwebview_macos import flutter_secure_storage_macos import flutter_timezone +import irondash_engine_context import local_notifier import media_kit_libs_macos_audio import open_file_mac @@ -25,6 +27,7 @@ import screen_retriever_macos import shared_preferences_foundation import sqflite_darwin import sqlite3_flutter_libs +import super_native_extensions import system_theme import tray_manager import url_launcher_macos @@ -38,10 +41,12 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) + IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) @@ -51,6 +56,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) + SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin")) SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin")) TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 35483fae..1d6b3c4c 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,7 +1,8 @@ PODS: - - app_links (1.0.0): + - app_links (6.4.1): - FlutterMacOS - - audio_service (0.14.1): + - audio_service (0.0.1): + - Flutter - FlutterMacOS - audio_session (0.0.1): - FlutterMacOS @@ -9,7 +10,6 @@ PODS: - Flutter - FlutterMacOS - connectivity_plus (0.0.1): - - Flutter - FlutterMacOS - desktop_webview_window (0.0.1): - FlutterMacOS @@ -27,12 +27,12 @@ PODS: - flutter_timezone (0.1.0): - FlutterMacOS - FlutterMacOS (1.0.0) + - irondash_engine_context (0.0.1): + - FlutterMacOS - local_notifier (0.1.0): - FlutterMacOS - media_kit_libs_macos_audio (1.0.4): - FlutterMacOS - - media_kit_native_event_loop (1.0.0): - - FlutterMacOS - metadata_god (0.0.1): - FlutterMacOS - open_file_mac (0.0.1): @@ -51,25 +51,33 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - sqlite3 (3.47.2): - - sqlite3/common (= 3.47.2) - - sqlite3/common (3.47.2) - - sqlite3/dbstatvtab (3.47.2): + - sqlite3 (3.50.4): + - sqlite3/common (= 3.50.4) + - sqlite3/common (3.50.4) + - sqlite3/dbstatvtab (3.50.4): - sqlite3/common - - sqlite3/fts5 (3.47.2): + - sqlite3/fts5 (3.50.4): - sqlite3/common - - sqlite3/perf-threadsafe (3.47.2): + - sqlite3/math (3.50.4): - sqlite3/common - - sqlite3/rtree (3.47.2): + - sqlite3/perf-threadsafe (3.50.4): + - sqlite3/common + - sqlite3/rtree (3.50.4): + - sqlite3/common + - sqlite3/session (3.50.4): - sqlite3/common - sqlite3_flutter_libs (0.0.1): - Flutter - FlutterMacOS - - sqlite3 (~> 3.47.1) + - sqlite3 (~> 3.50.4) - sqlite3/dbstatvtab - sqlite3/fts5 + - sqlite3/math - sqlite3/perf-threadsafe - sqlite3/rtree + - sqlite3/session + - super_native_extensions (0.0.1): + - FlutterMacOS - system_theme (0.0.1): - FlutterMacOS - tray_manager (0.0.1): @@ -81,10 +89,10 @@ PODS: DEPENDENCIES: - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - - audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/macos`) + - audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/darwin`) - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - bonsoir_darwin (from `Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin`) - - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin`) + - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - desktop_webview_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) @@ -93,9 +101,9 @@ DEPENDENCIES: - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`) - local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`) - media_kit_libs_macos_audio (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos`) - - media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`) - metadata_god (from `Flutter/ephemeral/.symlinks/plugins/metadata_god/macos`) - open_file_mac (from `Flutter/ephemeral/.symlinks/plugins/open_file_mac/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) @@ -104,6 +112,7 @@ DEPENDENCIES: - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) + - super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`) - system_theme (from `Flutter/ephemeral/.symlinks/plugins/system_theme/macos`) - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) @@ -118,13 +127,13 @@ EXTERNAL SOURCES: app_links: :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos audio_service: - :path: Flutter/ephemeral/.symlinks/plugins/audio_service/macos + :path: Flutter/ephemeral/.symlinks/plugins/audio_service/darwin audio_session: :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos bonsoir_darwin: :path: Flutter/ephemeral/.symlinks/plugins/bonsoir_darwin/darwin connectivity_plus: - :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/darwin + :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos desktop_webview_window: :path: Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos device_info_plus: @@ -141,12 +150,12 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos FlutterMacOS: :path: Flutter/ephemeral + irondash_engine_context: + :path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos local_notifier: :path: Flutter/ephemeral/.symlinks/plugins/local_notifier/macos media_kit_libs_macos_audio: :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos - media_kit_native_event_loop: - :path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos metadata_god: :path: Flutter/ephemeral/.symlinks/plugins/metadata_god/macos open_file_mac: @@ -163,6 +172,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin sqlite3_flutter_libs: :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin + super_native_extensions: + :path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos system_theme: :path: Flutter/ephemeral/.symlinks/plugins/system_theme/macos tray_manager: @@ -173,11 +184,11 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d - audio_service: 0d9e4e25347bb3efb768f3b9f005911a81e587a7 - audio_session: 48ab6500f7a5e7c64363e206565a5dfe5a0c1441 + app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f + audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd + audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e - connectivity_plus: 2256d3e20624a7749ed21653aafe291a46446fee + connectivity_plus: 4adf20a405e25b42b9c9f87feff8f4b6fde18a4e desktop_webview_window: 2f0cdefecc06e21208a51589bd3d1580a87a703c device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 @@ -185,10 +196,10 @@ SPEC CHECKSUMS: flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54 flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8 - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e media_kit_libs_macos_audio: 06f3cf88d6d89c7c3c87eae57689d1c6adb335b2 - media_kit_native_event_loop: a5833d1e4d5bedb6f691e9909fa57f15f436f2c8 metadata_god: 8029e6ff4b1400ae4f13c38d2c478e8633f0e58b open_file_mac: 01874b6d6a2c1485ac9b126d7105b99102dea2cf OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 @@ -197,8 +208,9 @@ SPEC CHECKSUMS: screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - sqlite3: 7559e33dae4c78538df563795af3a86fc887ee71 - sqlite3_flutter_libs: f0b59f6bb2a18597d0796558725007e5a7428397 + sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b + sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1 + super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 system_theme: ed74293ad07d3a05e3e2d0059ff342360346f1a0 tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166 url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index bf5d70cf..1c3f2973 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -410,7 +410,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -494,7 +494,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -541,7 +541,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/macos/packaging/dmg/make_config.yaml b/macos/packaging/dmg/make_config.yaml index 8292ede1..74e58066 100644 --- a/macos/packaging/dmg/make_config.yaml +++ b/macos/packaging/dmg/make_config.yaml @@ -1,5 +1,5 @@ title: Spotube -icon: assets/spotube-logo.png +icon: assets/branding/spotube-logo.png contents: - x: 448 y: 344 diff --git a/metadata/en-US/images/icon.png b/metadata/en-US/images/icon.png index b24a8c23..d46d9ce5 100644 Binary files a/metadata/en-US/images/icon.png and b/metadata/en-US/images/icon.png differ diff --git a/metadata/en-US/images/phoneScreenshots/android-1.jpg b/metadata/en-US/images/phoneScreenshots/android-1.jpg index 574c86c8..16debbd7 100644 Binary files a/metadata/en-US/images/phoneScreenshots/android-1.jpg and b/metadata/en-US/images/phoneScreenshots/android-1.jpg differ diff --git a/metadata/en-US/images/phoneScreenshots/android-2.jpg b/metadata/en-US/images/phoneScreenshots/android-2.jpg index 62da3c86..d594227e 100644 Binary files a/metadata/en-US/images/phoneScreenshots/android-2.jpg and b/metadata/en-US/images/phoneScreenshots/android-2.jpg differ diff --git a/metadata/en-US/images/phoneScreenshots/android-3.jpg b/metadata/en-US/images/phoneScreenshots/android-3.jpg index ccdbee84..ed2d61f2 100644 Binary files a/metadata/en-US/images/phoneScreenshots/android-3.jpg and b/metadata/en-US/images/phoneScreenshots/android-3.jpg differ diff --git a/metadata/en-US/images/phoneScreenshots/android-4.jpg b/metadata/en-US/images/phoneScreenshots/android-4.jpg index 278aae21..36a3549f 100644 Binary files a/metadata/en-US/images/phoneScreenshots/android-4.jpg and b/metadata/en-US/images/phoneScreenshots/android-4.jpg differ diff --git a/metadata/en-US/images/phoneScreenshots/android-5.jpg b/metadata/en-US/images/phoneScreenshots/android-5.jpg index 563b26c6..29be481b 100644 Binary files a/metadata/en-US/images/phoneScreenshots/android-5.jpg and b/metadata/en-US/images/phoneScreenshots/android-5.jpg differ diff --git a/metadata/en-US/images/phoneScreenshots/android-6.jpg b/metadata/en-US/images/phoneScreenshots/android-6.jpg deleted file mode 100644 index fc8d4750..00000000 Binary files a/metadata/en-US/images/phoneScreenshots/android-6.jpg and /dev/null differ diff --git a/metadata/tr/images/icon.png b/metadata/tr/images/icon.png index b24a8c23..d46d9ce5 100644 Binary files a/metadata/tr/images/icon.png and b/metadata/tr/images/icon.png differ diff --git a/metadata/tr/images/phoneScreenshots/android-1.jpg b/metadata/tr/images/phoneScreenshots/android-1.jpg index 574c86c8..16debbd7 100644 Binary files a/metadata/tr/images/phoneScreenshots/android-1.jpg and b/metadata/tr/images/phoneScreenshots/android-1.jpg differ diff --git a/metadata/tr/images/phoneScreenshots/android-2.jpg b/metadata/tr/images/phoneScreenshots/android-2.jpg index 62da3c86..d594227e 100644 Binary files a/metadata/tr/images/phoneScreenshots/android-2.jpg and b/metadata/tr/images/phoneScreenshots/android-2.jpg differ diff --git a/metadata/tr/images/phoneScreenshots/android-3.jpg b/metadata/tr/images/phoneScreenshots/android-3.jpg index ccdbee84..ed2d61f2 100644 Binary files a/metadata/tr/images/phoneScreenshots/android-3.jpg and b/metadata/tr/images/phoneScreenshots/android-3.jpg differ diff --git a/metadata/tr/images/phoneScreenshots/android-4.jpg b/metadata/tr/images/phoneScreenshots/android-4.jpg index 278aae21..36a3549f 100644 Binary files a/metadata/tr/images/phoneScreenshots/android-4.jpg and b/metadata/tr/images/phoneScreenshots/android-4.jpg differ diff --git a/metadata/tr/images/phoneScreenshots/android-5.jpg b/metadata/tr/images/phoneScreenshots/android-5.jpg index 563b26c6..29be481b 100644 Binary files a/metadata/tr/images/phoneScreenshots/android-5.jpg and b/metadata/tr/images/phoneScreenshots/android-5.jpg differ diff --git a/metadata/tr/images/phoneScreenshots/android-6.jpg b/metadata/tr/images/phoneScreenshots/android-6.jpg deleted file mode 100644 index fc8d4750..00000000 Binary files a/metadata/tr/images/phoneScreenshots/android-6.jpg and /dev/null differ diff --git a/pubspec.lock b/pubspec.lock index f1c65465..a6720c55 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,31 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "76.0.0" - _macros: - dependency: transitive - description: dart - source: sdk - version: "0.3.3" + version: "85.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" url: "https://pub.dev" source: hosted - version: "6.11.0" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" - url: "https://pub.dev" - source: hosted - version: "0.11.3" + version: "7.7.1" ansicolor: dependency: transitive description: @@ -42,10 +29,10 @@ packages: dependency: "direct main" description: name: app_links - sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" + sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.4.1" app_links_linux: dependency: transitive description: @@ -90,10 +77,10 @@ packages: dependency: transitive description: name: asn1lib - sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5" + sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" url: "https://pub.dev" source: hosted - version: "1.5.8" + version: "1.6.5" async: dependency: "direct main" description: @@ -106,10 +93,10 @@ packages: dependency: "direct main" description: name: audio_service - sha256: "9dd5ba7e77567b290c35908b1950d61485b4dfdd3a0ac398e98cfeec04651b75" + sha256: cb122c7c2639d2a992421ef96b67948ad88c5221da3365ccef1031393a76e044 url: "https://pub.dev" source: hosted - version: "0.18.15" + version: "0.18.18" audio_service_mpris: dependency: "direct main" description: @@ -122,26 +109,26 @@ packages: dependency: transitive description: name: audio_service_platform_interface - sha256: "8431a455dac9916cc9ee6f7da5620a666436345c906ad2ebb7fa41d18b3c1bf4" + sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777" url: "https://pub.dev" source: hosted - version: "0.1.1" + version: "0.1.3" audio_service_web: dependency: transitive description: name: audio_service_web - sha256: "4cdc2127cd4562b957fb49227dc58e3303fafb09bde2573bc8241b938cf759d9" + sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.1.4" audio_session: dependency: "direct main" description: name: audio_session - sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" + sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" url: "https://pub.dev" source: hosted - version: "0.1.21" + version: "0.1.25" auto_route: dependency: "direct main" description: @@ -154,10 +141,10 @@ packages: dependency: "direct dev" description: name: auto_route_generator - sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6 + sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46 url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.3.1" auto_size_text: dependency: "direct main" description: @@ -170,18 +157,18 @@ packages: dependency: transitive description: name: base32 - sha256: ddad4ebfedf93d4500818ed8e61443b734ffe7cf8a45c668c9b34ef6adde02e2 + sha256: "37548444aaee8bd5e91db442ce69ee3a79d3652ed47c1fa7568aa3bb9af0aea5" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.2.0" bonsoir: dependency: "direct main" description: name: bonsoir - sha256: b7697a954c772a6ddc68d52b3e4768947cc98613127f7720a05b14ed1e59d68b + sha256: "2e2cf3be580deccad9a48dcaddddf90de092e74b7de2015ef58fb24e11d66496" url: "https://pub.dev" source: hosted - version: "5.1.10" + version: "5.1.11" bonsoir_android: dependency: "direct overridden" description: @@ -235,10 +222,10 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.5.4" build_cli_annotations: dependency: transitive description: @@ -251,42 +238,42 @@ packages: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.4" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.5.4" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" url: "https://pub.dev" source: hosted - version: "7.3.2" + version: "9.1.2" built_collection: dependency: transitive description: @@ -299,10 +286,10 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.12.0" cached_network_image: dependency: "direct main" description: @@ -355,10 +342,18 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -387,10 +382,10 @@ packages: dependency: "direct main" description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" color: dependency: transitive description: @@ -403,10 +398,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "8a68739d3ee113e51ad35583fdf9ab82c55d09d693d3c39da1aebab87c938412" + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5" connectivity_plus_platform_interface: dependency: transitive description: @@ -427,18 +422,18 @@ packages: dependency: transitive description: name: country_flags - sha256: dad797491167a5b8dee465b969cb756795d842fdfc3fc1ff93f22e9c1884b73d + sha256: "78a7bf8aabd7ae1a90087f0c517471ac9ebfe07addc652692f58da0f0f833196" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.3.0" coverage: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.15.0" cross_file: dependency: transitive description: @@ -475,18 +470,18 @@ packages: dependency: transitive description: name: dart_mappable - sha256: f69a961ae8589724ebb542e588f228ae844c5f78028899cbe2cc718977c1b382 + sha256: "15f41a35da8ee690bbfa0059fa241edeeaea73f89a2ba685b354ece07cd8ada6" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.6.0" dart_style: dependency: transitive description: name: dart_style - sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "3.1.1" dartx: dependency: transitive description: @@ -507,16 +502,16 @@ packages: dependency: transitive description: name: dbus - sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.11" desktop_webview_window: dependency: "direct main" description: path: "packages/desktop_webview_window" - ref: "feat/cookies" - resolved-ref: f20e433d4a948515b35089d40069f7dd9bced9e4 + ref: HEAD + resolved-ref: f261ff20e310d05713249b21c199a9fe17a3de6f url: "https://github.com/KRTirtho/flutter-plugins.git" source: git version: "0.2.4" @@ -524,26 +519,26 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431" + sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da" url: "https://pub.dev" source: hosted - version: "11.2.0" + version: "11.3.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.3" dio: dependency: "direct main" description: name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 url: "https://pub.dev" source: hosted - version: "5.8.0+1" + version: "5.9.0" dio_http2_adapter: dependency: "direct main" description: @@ -556,10 +551,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.1" disable_battery_optimization: dependency: "direct main" description: @@ -582,18 +577,18 @@ packages: dependency: "direct main" description: name: drift - sha256: c2d073d35ad441730812f4ea05b5dd031fb81c5f9786a4f5fb77ecd6307b6f74 + sha256: "6aaea757f53bb035e8a3baedf3d1d53a79d6549a6c13d84f7546509da9372c7c" url: "https://pub.dev" source: hosted - version: "2.22.1" + version: "2.28.1" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: f4ab5d6976b1e31551ceb82ff597a505bda7818ff4f7be08a1da9d55eb6e730c + sha256: "68c138e884527d2bd61df2ade276c3a144df84d1adeb0ab8f3196b5afe021bd4" url: "https://pub.dev" source: hosted - version: "2.22.1" + version: "2.28.0" duration: dependency: "direct main" description: @@ -622,18 +617,18 @@ packages: dependency: "direct main" description: name: envied - sha256: "129a0dbf32b90344fa2e9d6943569fdec8f17904e66161e0a1f09ee3416508ae" + sha256: "8c22863e9e8e58e3e987d1a6e2797d604d296999125340410ddc44e03e5941d9" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.2.1" envied_generator: dependency: "direct dev" description: name: envied_generator - sha256: "76aec98907872ce8488f021e68d213bd0d9bf224eb393a094be1708cc3180d41" + sha256: "894f6c5eb624c60a1ce6f642b6fd7ec68bc3440aa6f1881837aa9acbbeade0c8" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.1" equatable: dependency: transitive description: @@ -670,10 +665,10 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -686,10 +681,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "16dc141db5a2ccc6520ebb6a2eb5945b1b09e95085c021d9f914f8ded7f1465c" + sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "10.3.3" file_selector: dependency: "direct main" description: @@ -702,18 +697,18 @@ packages: dependency: transitive description: name: file_selector_android - sha256: "98ac58e878b05ea2fdb204e7f4fc4978d90406c9881874f901428e01d3b18fbc" + sha256: "4be8ae7374c81daf88e49084a1d68dfe68466ef38a6a3d711cc0b83d53e22465" url: "https://pub.dev" source: hosted - version: "0.5.1+12" + version: "0.5.1+16" file_selector_ios: dependency: transitive description: name: file_selector_ios - sha256: "94b98ad950b8d40d96fee8fa88640c2e4bd8afcdd4817993bd04e20310f45420" + sha256: fe9f52123af16bba4ad65bd7e03defbbb4b172a38a8e6aaa2a869a0c56a5f5fb url: "https://pub.dev" source: hosted - version: "0.5.3+1" + version: "0.5.3+2" file_selector_linux: dependency: transitive description: @@ -726,10 +721,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" url: "https://pub.dev" source: hosted - version: "0.9.4+2" + version: "0.9.4+4" file_selector_platform_interface: dependency: transitive description: @@ -750,10 +745,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.3+4" fixnum: dependency: transitive description: @@ -762,14 +757,23 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + fk_user_agent: + dependency: transitive + description: + path: "." + ref: master + resolved-ref: "922f9f9eafd8b501da83dca67d56b2887fa8f916" + url: "https://github.com/TiffApps/fk_user_agent.git" + source: git + version: "2.1.1" fluentui_system_icons: dependency: "direct main" description: name: fluentui_system_icons - sha256: "721a4d2edf31d44f13de917b3098c75faccd98854632619e858164700211184f" + sha256: "1592ae32dae75fb66741ab2c0607bc6554ef086f1c457e27ef937fb4901d33da" url: "https://pub.dev" source: hosted - version: "1.1.266" + version: "1.1.273" flutter: dependency: "direct main" description: flutter @@ -796,10 +800,10 @@ packages: dependency: "direct main" description: name: flutter_discord_rpc - sha256: "9363a803863d56fd89c0a21639c70b126245fcbafaeb0a3d091e7ac06951d03f" + sha256: "7ee12a3ff928e85e5e0a60c7c23417a2f1f4e604e4983fc0789ff6562fb15f2b" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" flutter_displaymode: dependency: "direct main" description: @@ -825,26 +829,26 @@ packages: dependency: "direct main" description: name: flutter_form_builder - sha256: "39aee5a2548df0b3979a83eea38468116a888341fbca8a92c4be18a486a7bb57" + sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876" url: "https://pub.dev" source: hosted - version: "9.6.0" + version: "9.7.0" flutter_gen_core: dependency: transitive description: name: flutter_gen_core - sha256: "46ecf0e317413dd065547887c43f93f55e9653e83eb98dc13dd07d40dd225325" + sha256: eda54fdc5de08e7eeea663eb8442aafc8660b5a13fda4e0c9e572c64e50195fb url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.11.0" flutter_gen_runner: dependency: "direct dev" description: name: flutter_gen_runner - sha256: "77f0a02fc30d9fcf2549fe874eb3fde091435724904bcbb1af60aa40cbfab1f4" + sha256: "669bf8b7a9b4acbdcb7fcc5e12bf638aca19acedf43341714cbca3bf3a219521" url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.11.0" flutter_hooks: dependency: "direct main" description: @@ -921,10 +925,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "31cd0885738e87c72d6f055564d37fabcdacee743b396b78c7636c169cac64f5" + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.14.2" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -942,10 +946,10 @@ packages: dependency: "direct main" description: name: flutter_markdown_plus - sha256: fe74214c5ac2f850d93efda290dcde3f18006e90a87caa9e3e6c13222a5db4de + sha256: "7f349c075157816da399216a4127096108fd08e1ac931e34e72899281db4113c" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.5" flutter_native_splash: dependency: "direct main" description: @@ -959,7 +963,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "2a549b08a91349a6ca334981e956dc2c9519b04c" + resolved-ref: "916bde44cbead75125e8db842eb46bdcf211a79a" url: "https://github.com/KRTirtho/flutter_new_pipe_extractor.git" source: git version: "0.1.0" @@ -967,10 +971,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.30" flutter_riverpod: dependency: "direct main" description: @@ -983,10 +987,10 @@ packages: dependency: transitive description: name: flutter_rust_bridge - sha256: "0ad5079de35d317650fec59b26cb4d0c116ebc2ce703a29f9367513b8a91c287" + sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.11.1" flutter_secure_storage: dependency: "direct main" description: @@ -1048,10 +1052,10 @@ packages: dependency: "direct overridden" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.2.1" flutter_test: dependency: "direct dev" description: flutter @@ -1082,10 +1086,10 @@ packages: dependency: "direct main" description: name: form_builder_validators - sha256: "517fb884183fff7a0ef3db7d375981011da26ee452f20fb3d2e788ad527ad01d" + sha256: "1b03c74d1db740890e6af803b43e5ebe56f8fa1ff5609cbf744e8d980dc5f8c6" url: "https://pub.dev" source: hosted - version: "11.1.1" + version: "11.2.0" form_validator: dependency: "direct main" description: @@ -1098,10 +1102,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" url: "https://pub.dev" source: hosted - version: "2.5.7" + version: "2.5.8" freezed_annotation: dependency: "direct main" description: @@ -1143,26 +1147,26 @@ packages: dependency: "direct main" description: name: get_it - sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 + sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.2.0" glob: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" google_fonts: dependency: "direct main" description: name: google_fonts - sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 + sha256: ebc94ed30fd13cefd397cb1658b593f21571f014b7d1197eeb41fb95f05d899a url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.3.1" graphs: dependency: transitive description: @@ -1217,7 +1221,7 @@ packages: description: path: "." ref: main - resolved-ref: c4895250ee45a59c88770f97abebc9e9bbb62259 + resolved-ref: "01935a75640092af7947bfb21a497240376f0c83" url: "https://github.com/KRTirtho/hetu_spotube_plugin.git" source: git version: "0.0.1" @@ -1226,7 +1230,7 @@ packages: description: path: "." ref: main - resolved-ref: "7e9032c054c547f7900c9c9fe4b76e29c8ac1cd1" + resolved-ref: "577ad115dce0514afc53e2b3ab7b96bcd88d3be3" url: "https://github.com/hetu-community/hetu_std.git" source: git version: "1.0.0" @@ -1234,10 +1238,10 @@ packages: dependency: "direct main" description: name: home_widget - sha256: b313e3304c0429669fddf1286e1fbf61a64b873f38ba30b3eb890ef0d7560b12 + sha256: "7430f7549d42cef2e729bd3c779de748b93f1eb78b1abfe6bca8fffd1cfce3e9" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.0+1" hooks_riverpod: dependency: "direct main" description: @@ -1250,10 +1254,10 @@ packages: dependency: "direct main" description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.6" html_unescape: dependency: "direct main" description: @@ -1266,10 +1270,10 @@ packages: dependency: "direct main" description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.5.0" http2: dependency: transitive description: @@ -1290,10 +1294,10 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: "direct main" description: @@ -1314,74 +1318,74 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: fa8141602fde3f7e2f81dbf043613eb44dfa325fa0bcf93c0f142c9f7a2c193e + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" url: "https://pub.dev" source: hosted - version: "0.8.12+18" + version: "0.8.13+1" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e url: "https://pub.dev" source: hosted - version: "0.8.12+1" + version: "0.8.13" image_picker_linux: dependency: transitive description: name: image_picker_linux - sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" image_picker_macos: dependency: transitive description: name: image_picker_macos - sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" image_picker_windows: dependency: transitive description: name: image_picker_windows - sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" image_size_getter: dependency: transitive description: name: image_size_getter - sha256: "0511799498340b70993d2dfb34b55a2247b5b801d75a6cdd4543acfcafdb12b0" + sha256: "7c26937e0ae341ca558b7556591fd0cc456fcc454583b7cb665d2f03e93e590f" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.1" integration_test: dependency: "direct dev" description: flutter @@ -1391,10 +1395,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.20.2" invidious: dependency: "direct main" description: @@ -1411,6 +1415,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + irondash_engine_context: + dependency: transitive + description: + name: irondash_engine_context + sha256: "2bb0bc13dfda9f5aaef8dde06ecc5feb1379f5bb387d59716d799554f3f305d7" + url: "https://pub.dev" + source: hosted + version: "0.5.5" + irondash_message_channel: + dependency: transitive + description: + name: irondash_message_channel + sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060 + url: "https://pub.dev" + source: hosted + version: "0.7.0" jiosaavn: dependency: "direct main" description: @@ -1423,18 +1443,18 @@ packages: dependency: transitive description: name: jovial_misc - sha256: "4b10a4cac4f492d9692e97699bff775efa84abdba29909124cbccf3126e31cea" + sha256: "4301011027d87b8b919cb862db84071a34448eadbb32cc8d40fe505424dfe69a" url: "https://pub.dev" source: hosted - version: "0.9.0" + version: "0.9.2" jovial_svg: dependency: transitive description: name: jovial_svg - sha256: ca14d42956b9949c36333065c9141f100e930c918f57f4bd8dd59d35581bd3fc + sha256: "6791b1435547bdc0793081a166d41a8a313ebc61e4e5136fb7a3218781fb9e50" url: "https://pub.dev" source: hosted - version: "1.1.24" + version: "1.1.27" js: dependency: transitive description: @@ -1455,34 +1475,34 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c url: "https://pub.dev" source: hosted - version: "6.9.0" + version: "6.9.5" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.1" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1503,10 +1523,10 @@ packages: dependency: "direct main" description: name: logger - sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 + sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.1" logging: dependency: "direct main" description: @@ -1523,14 +1543,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - macros: - dependency: transitive - description: - name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" - url: "https://pub.dev" - source: hosted - version: "0.1.3-main.0" markdown: dependency: transitive description: @@ -1558,66 +1570,65 @@ packages: media_kit: dependency: "direct main" description: - name: media_kit - sha256: "1f1deee148533d75129a6f38251ff8388e33ee05fc2d20a6a80e57d6051b7b62" - url: "https://pub.dev" - source: hosted - version: "1.1.11" + path: media_kit + ref: HEAD + resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + url: "https://github.com/media-kit/media-kit" + source: git + version: "1.2.0" media_kit_libs_android_audio: - dependency: transitive + dependency: "direct overridden" description: - name: media_kit_libs_android_audio - sha256: "3d2df5c09d3f3ff7c55b53bf955e46712f76483e77562a5a017439a3ea85ce88" - url: "https://pub.dev" - source: hosted - version: "1.3.6" + path: "libs/android/media_kit_libs_android_audio" + ref: HEAD + resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + url: "https://github.com/media-kit/media-kit" + source: git + version: "1.3.7" media_kit_libs_audio: dependency: "direct main" description: - name: media_kit_libs_audio - sha256: be40e17c4cb7bd4e14114dce24a36e645f2ac5989dda543deaba2e7873901ba0 - url: "https://pub.dev" - source: hosted - version: "1.0.5" + path: "libs/universal/media_kit_libs_audio" + ref: HEAD + resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + url: "https://github.com/media-kit/media-kit" + source: git + version: "1.0.6" media_kit_libs_ios_audio: - dependency: transitive + dependency: "direct overridden" description: - name: media_kit_libs_ios_audio - sha256: "78ccf04e27d6b4ba00a355578ccb39b772f00d48269a6ac3db076edf2d51934f" - url: "https://pub.dev" - source: hosted + path: "libs/ios/media_kit_libs_ios_audio" + ref: HEAD + resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + url: "https://github.com/media-kit/media-kit" + source: git version: "1.1.4" media_kit_libs_linux: - dependency: transitive + dependency: "direct overridden" description: - name: media_kit_libs_linux - sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310 - url: "https://pub.dev" - source: hosted - version: "1.1.3" + path: "libs/linux/media_kit_libs_linux" + ref: HEAD + resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + url: "https://github.com/media-kit/media-kit" + source: git + version: "1.2.1" media_kit_libs_macos_audio: - dependency: transitive + dependency: "direct overridden" description: - name: media_kit_libs_macos_audio - sha256: "3be21844df98f286de32808592835073cdef2c1a10078bac135da790badca950" - url: "https://pub.dev" - source: hosted + path: "libs/macos/media_kit_libs_macos_audio" + ref: HEAD + resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + url: "https://github.com/media-kit/media-kit" + source: git version: "1.1.4" media_kit_libs_windows_audio: - dependency: transitive + dependency: "direct overridden" description: - name: media_kit_libs_windows_audio - sha256: c2fd558cc87b9d89a801141fcdffe02e338a3b21a41a18fbd63d5b221a1b8e53 - url: "https://pub.dev" - source: hosted - version: "1.0.9" - media_kit_native_event_loop: - dependency: transitive - description: - name: media_kit_native_event_loop - sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40" - url: "https://pub.dev" - source: hosted + path: "libs/windows/media_kit_libs_windows_audio" + ref: HEAD + resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + url: "https://github.com/media-kit/media-kit" + source: git version: "1.0.9" menu_base: dependency: transitive @@ -1639,10 +1650,10 @@ packages: dependency: "direct main" description: name: metadata_god - sha256: "025d8149059f62f44108ab9d74ebd77aa8f0af98b238f3f25121a4711ee3e5d0" + sha256: "2b19b7e88cf5ab7016fe8d1f868bb35a193aaef06fd96cfc7de4ec3eacc16eb0" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" mime: dependency: "direct main" description: @@ -1759,10 +1770,10 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" package_info_plus: dependency: "direct main" description: @@ -1783,10 +1794,10 @@ packages: dependency: "direct main" description: name: palette_generator - sha256: "0b20245c451f14a5ca0818ab7a377765162389f8e8f0db361cceabf0fed9d1ea" + sha256: "4420f7ccc3f0a4a906144e73f8b6267cd940b64f57a7262e95cb8cec3a8ae0ed" url: "https://pub.dev" source: hosted - version: "0.3.3+5" + version: "0.3.3+7" path: dependency: "direct main" description: @@ -1815,18 +1826,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.18" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" path_provider_linux: dependency: transitive description: @@ -1855,26 +1866,26 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" url: "https://pub.dev" source: hosted - version: "11.3.1" + version: "11.4.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc url: "https://pub.dev" source: hosted - version: "12.0.13" + version: "12.1.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 url: "https://pub.dev" source: hosted - version: "9.4.5" + version: "9.4.7" permission_handler_html: dependency: transitive description: @@ -1887,10 +1898,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "4.3.0" permission_handler_windows: dependency: transitive description: @@ -1903,18 +1914,18 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" phonecodes: dependency: transitive description: name: phonecodes - sha256: "094a76b0ba3d8f9c1c83044ae8783d46e6906703c86eb08facd876844c264bf5" + sha256: d963c19d35914cd83620e64125689a0c09047e25046639f2a124142ccf5868bb url: "https://pub.dev" source: hosted - version: "0.0.3" + version: "0.0.4" piped_client: dependency: "direct main" description: @@ -1923,6 +1934,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.2" + pixel_snap: + dependency: transitive + description: + name: pixel_snap + sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0" + url: "https://pub.dev" + source: hosted + version: "0.1.5" platform: dependency: transitive description: @@ -1959,18 +1978,18 @@ packages: dependency: transitive description: name: posix - sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.0.3" process: dependency: transitive description: name: process - sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.0.5" process_run: dependency: "direct dev" description: @@ -1983,10 +2002,10 @@ packages: dependency: "direct dev" description: name: pub_api_client - sha256: d9ced27bb5b933012a5218120f1fbee2a7d3bd234a7d5cc6fe29dedf4df6127a + sha256: b9c0184ce4a562d8cf2ebd7be235a25aa158b7c01bdef863cccadc5894644e26 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.1" pub_semver: dependency: "direct main" description: @@ -1999,10 +2018,10 @@ packages: dependency: "direct dev" description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" quiver: dependency: transitive description: @@ -2011,6 +2030,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" + random_user_agents: + dependency: transitive + description: + name: random_user_agents + sha256: "19facde509a2482dababb454faf2aceff797a6ae08e80f91268c0c8a7420f03b" + url: "https://pub.dev" + source: hosted + version: "1.0.15" recase: dependency: transitive description: @@ -2039,10 +2066,10 @@ packages: dependency: transitive description: name: safe_local_storage - sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440 + sha256: e9a21b6fec7a8aa62cc2585ff4c1b127df42f3185adbd2aca66b47abe2e80236 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "2.0.1" screen_retriever: dependency: transitive description: @@ -2104,34 +2131,34 @@ packages: dependency: "direct main" description: name: shadcn_flutter - sha256: "979a86e203eb1fb139e8b5c84b49b17b28808804cbff189b43052d56ba6854b5" + sha256: af83de199b7c3a965ab24e293cfcafe2764c12b7f911f5b1a427c332029262d9 url: "https://pub.dev" source: hosted - version: "0.0.37" + version: "0.0.44" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "7f172d1b06de5da47b6264c2692ee2ead20bbbc246690427cdb4fc301cd0c549" + sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.4.12" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -2152,10 +2179,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: @@ -2261,26 +2288,26 @@ packages: dependency: "direct main" description: name: smtc_windows - sha256: "80f7c10867da485ffdf87f842bf27e6763589933c18c11af5dc1cd1e158c3154" + sha256: dee279b0ddf663c4c729a88bca4e57fb4861aa1b3d01e230bdbf1277b8bfe664 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" source_gen: dependency: transitive description: name: source_gen - sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "2.0.0" source_helper: dependency: transitive description: name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca url: "https://pub.dev" source: hosted - version: "1.3.4" + version: "1.3.7" source_map_stack_trace: dependency: transitive description: @@ -2317,34 +2344,34 @@ packages: dependency: transitive description: name: sqflite - sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" sqflite_android: dependency: transitive description: name: sqflite_android - sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.2+2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" url: "https://pub.dev" source: hosted - version: "2.5.4+6" + version: "2.5.6" sqflite_darwin: dependency: transitive description: name: sqflite_darwin - sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474" + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" sqflite_platform_interface: dependency: transitive description: @@ -2357,26 +2384,26 @@ packages: dependency: "direct main" description: name: sqlite3 - sha256: cb7f4e9dc1b52b1fa350f7b3d41c662e75fc3d399555fa4e5efcf267e9a4fbb5 + sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.9.0" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: "636b0fe8a2de894e5455572f6cbbc458f4ffecfe9f860b79439e27041ea4f0b9" + sha256: "2b03273e71867a8a4d030861fc21706200debe5c5858a4b9e58f4a1c129586a4" url: "https://pub.dev" source: hosted - version: "0.5.27" + version: "0.5.39" sqlparser: dependency: transitive description: name: sqlparser - sha256: "4cad4b2c5f63dc9ea1a8dcffb58cf762322bea5dd8836870164a65e913bdae41" + sha256: "57090342af1ce32bb499aa641f4ecdd2d6231b9403cea537ac059e803cc20d67" url: "https://pub.dev" source: hosted - version: "0.40.0" + version: "0.41.2" stack_trace: dependency: transitive description: @@ -2405,10 +2432,10 @@ packages: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: @@ -2425,6 +2452,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.3" + super_clipboard: + dependency: transitive + description: + name: super_clipboard + sha256: e73f3bb7e66cc9260efa1dc507f979138e7e106c3521e2dda2d0311f6d728a16 + url: "https://pub.dev" + source: hosted + version: "0.9.1" + super_native_extensions: + dependency: transitive + description: + name: super_native_extensions + sha256: b9611dcb68f1047d6f3ef11af25e4e68a21b1a705bbcc3eb8cb4e9f5c3148569 + url: "https://pub.dev" + source: hosted + version: "0.9.1" sync_http: dependency: transitive description: @@ -2437,18 +2480,18 @@ packages: dependency: transitive description: name: synchronized - sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 url: "https://pub.dev" source: hosted - version: "3.3.0+3" + version: "3.4.0" syntax_highlight: dependency: transitive description: name: syntax_highlight - sha256: ee33b6aa82cc722bb9b40152a792181dee222353b486c0255fde666a3e3a4997 + sha256: "4d3ba40658cadba6ba55d697f29f00b43538ebb6eb4a0ca0e895c568eaced138" url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.5.0" system_theme: dependency: "direct main" description: @@ -2477,26 +2520,26 @@ packages: dependency: "direct main" description: name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.25.15" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.8" + version: "0.6.11" time: dependency: transitive description: @@ -2509,10 +2552,10 @@ packages: dependency: "direct main" description: name: timezone - sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.10.1" timing: dependency: transitive description: @@ -2533,10 +2576,10 @@ packages: dependency: "direct main" description: name: tray_manager - sha256: ad18c4cd73003097d182884bacb0578ad2865f3ab842a0ad00f6d043ed49eaf0 + sha256: "537e539f48cd82d8ee2240d4330158c7b44c7e043e8e18b5811f2f8f6b7df25a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.5.1" type_plus: dependency: transitive description: @@ -2557,10 +2600,10 @@ packages: dependency: transitive description: name: unicode - sha256: "48f8b6c50ed70ba61647f4987d56ec505923041956bbdb651db2838ce7b47b58" + sha256: "0d99edbd2e74726bed2e4989713c8bec02e5581628e334d8c88c0271593fb402" url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "1.1.8" universal_io: dependency: transitive description: @@ -2581,34 +2624,34 @@ packages: dependency: transitive description: name: uri_parser - sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835" + sha256: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "3.0.0" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7" url: "https://pub.dev" source: hosted - version: "6.3.14" + version: "6.3.18" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.4" url_launcher_linux: dependency: transitive description: @@ -2621,10 +2664,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.3" url_launcher_platform_interface: dependency: transitive description: @@ -2637,18 +2680,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.1" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" uuid: dependency: "direct main" description: @@ -2661,34 +2704,34 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 url: "https://pub.dev" source: hosted - version: "1.1.15" + version: "1.1.19" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.12" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.19" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" version: dependency: "direct main" description: @@ -2717,42 +2760,42 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.3" web: dependency: "direct overridden" description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: "direct main" description: name: web_socket_channel - sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.3" webdriver: dependency: transitive description: @@ -2781,10 +2824,10 @@ packages: dependency: transitive description: name: win32 - sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" + sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.14.0" win32_registry: dependency: "direct main" description: @@ -2829,19 +2872,19 @@ packages: dependency: "direct main" description: name: youtube_explode_dart - sha256: "9cd131624135065a6866c1e8ad4f4703e5bcfd1da322d4fddc55c972d04b6b22" + sha256: "9ff345caf8351c59eb1b7560837f761e08d2beaea3b4187637942715a31a6f58" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.5.2" yt_dlp_dart: dependency: "direct main" description: path: "." - ref: e2d82305fab18566408d6f8758361017d1640c3d - resolved-ref: e2d82305fab18566408d6f8758361017d1640c3d + ref: "4e5310e14af74bdbb51e2a4766e66d6c6a2562a8" + resolved-ref: "4e5310e14af74bdbb51e2a4766e66d6c6a2562a8" url: "https://github.com/KRTirtho/yt_dlp_dart.git" source: git version: "1.0.0" sdks: - dart: ">=3.7.2 <4.0.0" - flutter: ">=3.32.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.1" diff --git a/pubspec.yaml b/pubspec.yaml index 4ce97201..c3044aad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Open source extensible music streaming platform and app, based on B publish_to: "none" -version: 4.0.2+41 +version: 5.0.0+42 homepage: https://spotube.krtirtho.dev repository: https://github.com/KRTirtho/spotube @@ -27,7 +27,6 @@ dependencies: desktop_webview_window: git: path: packages/desktop_webview_window - ref: feat/cookies url: https://github.com/KRTirtho/flutter-plugins.git device_info_plus: ^11.1.1 dio: ^5.4.3+1 @@ -42,7 +41,7 @@ dependencies: duration: ^3.0.12 encrypt: ^5.0.3 envied: ^1.0.0 - file_picker: 8.1.4 + file_picker: 10.3.3 file_selector: ^1.0.3 fluentui_system_icons: ^1.1.234 flutter: @@ -52,12 +51,12 @@ dependencies: url: https://github.com/KRTirtho/flutter_broadcasts.git ref: 63931dfe06733d4fb7452e9981e1f0b23414d97a flutter_cache_manager: ^3.3.0 - flutter_discord_rpc: ^1.0.0 + flutter_discord_rpc: ^1.1.0 flutter_displaymode: ^0.6.0 flutter_feather_icons: ^2.0.0+1 flutter_form_builder: ^9.6.0 flutter_hooks: ^0.20.5 - flutter_inappwebview: ^6.1.3 + flutter_inappwebview: ^6.1.5 flutter_localizations: sdk: flutter flutter_native_splash: ^2.4.6 @@ -85,9 +84,15 @@ dependencies: logger: ^2.0.2 logging: ^1.3.0 lrc: ^1.0.2 - media_kit: ^1.1.10+1 - media_kit_libs_audio: ^1.0.4 - metadata_god: ^1.0.0 + media_kit: + git: + url: https://github.com/media-kit/media-kit + path: media_kit + media_kit_libs_audio: + git: + url: https://github.com/media-kit/media-kit + path: libs/universal/media_kit_libs_audio + metadata_god: ^1.1.0 mime: ^2.0.0 open_file: ^3.5.10 package_info_plus: ^6.0.0 @@ -102,7 +107,7 @@ dependencies: ref: dart-3-support url: https://github.com/KRTirtho/scrobblenaut.git scroll_to_index: ^3.0.1 - shadcn_flutter: ^0.0.37 + shadcn_flutter: ^0.0.42 shared_preferences: ^2.2.3 shelf: ^1.4.1 shelf_router: ^1.1.4 @@ -111,7 +116,7 @@ dependencies: skeletonizer: ^2.1.0+1 sliding_up_panel: ^2.0.0+1 sliver_tools: ^0.2.12 - smtc_windows: ^1.0.0 + smtc_windows: ^1.1.0 sqlite3: ^2.4.3 sqlite3_flutter_libs: ^0.5.23 stroke_text: ^0.0.2 @@ -133,7 +138,7 @@ dependencies: yt_dlp_dart: git: url: https://github.com/KRTirtho/yt_dlp_dart.git - ref: e2d82305fab18566408d6f8758361017d1640c3d + ref: 4e5310e14af74bdbb51e2a4766e66d6c6a2562a8 flutter_new_pipe_extractor: git: url: https://github.com/KRTirtho/flutter_new_pipe_extractor.git @@ -195,16 +200,36 @@ dependency_overrides: ref: patch-2 path: flutter_secure_storage_linux flutter_secure_storage_platform_interface: 2.0.0 + media_kit_libs_android_audio: + git: + url: https://github.com/media-kit/media-kit + path: libs/android/media_kit_libs_android_audio + media_kit_libs_ios_audio: + git: + url: https://github.com/media-kit/media-kit + path: libs/ios/media_kit_libs_ios_audio + media_kit_libs_macos_audio: + git: + url: https://github.com/media-kit/media-kit + path: libs/macos/media_kit_libs_macos_audio + media_kit_libs_windows_audio: + git: + url: https://github.com/media-kit/media-kit + path: libs/windows/media_kit_libs_windows_audio + media_kit_libs_linux: + git: + url: https://github.com/media-kit/media-kit + path: libs/linux/media_kit_libs_linux flutter: generate: true uses-material-design: true assets: - - assets/ - - assets/tutorial/ - - assets/logos/ - - assets/backgrounds/ - - assets/patterns/ + - assets/images/ + - assets/images/logos/ + - assets/branding/spotube-logo.png + - assets/branding/spotube-logo-light.png + - assets/branding/spotube-logo.ico - LICENSE - packages/flutter_undraw/assets/undraw/access_denied.svg - packages/flutter_undraw/assets/undraw/fixing_bugs.svg @@ -217,6 +242,7 @@ flutter: - packages/flutter_undraw/assets/undraw/empty.svg - packages/flutter_undraw/assets/undraw/no_data.svg - packages/flutter_undraw/assets/undraw/process.svg + - packages/flutter_undraw/assets/undraw/stars.svg # hetu script bytecode - packages/hetu_std/assets/bytecode/std.out - packages/hetu_otp_util/assets/bytecode/otp_util.out @@ -233,6 +259,20 @@ flutter: - asset: assets/fonts/Cookie-Regular.ttf style: normal weight: 500 + - family: Ubuntu Mono + fonts: + - asset: assets/fonts/Ubuntu_Mono/UbuntuMono-Regular.ttf + style: normal + weight: 400 + - asset: assets/fonts/Ubuntu_Mono/UbuntuMono-Bold.ttf + style: normal + weight: 700 + - asset: assets/fonts/Ubuntu_Mono/UbuntuMono-Italic.ttf + style: italic + weight: 400 + - asset: assets/fonts/Ubuntu_Mono/UbuntuMono-BoldItalic.ttf + style: italic + weight: 700 flutter_gen: output: lib/collections diff --git a/test/drift/app_db/generated/schema.dart b/test/drift/app_db/generated/schema.dart index 87de9194..dfd3edf3 100644 --- a/test/drift/app_db/generated/schema.dart +++ b/test/drift/app_db/generated/schema.dart @@ -1,38 +1,41 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; import 'package:drift/internal/migrations.dart'; -import 'schema_v1.dart' as v1; -import 'schema_v2.dart' as v2; -import 'schema_v3.dart' as v3; -import 'schema_v4.dart' as v4; import 'schema_v5.dart' as v5; -import 'schema_v6.dart' as v6; +import 'schema_v4.dart' as v4; +import 'schema_v8.dart' as v8; +import 'schema_v3.dart' as v3; +import 'schema_v2.dart' as v2; +import 'schema_v1.dart' as v1; import 'schema_v7.dart' as v7; +import 'schema_v6.dart' as v6; class GeneratedHelper implements SchemaInstantiationHelper { @override GeneratedDatabase databaseForVersion(QueryExecutor db, int version) { switch (version) { - case 1: - return v1.DatabaseAtV1(db); - case 2: - return v2.DatabaseAtV2(db); - case 3: - return v3.DatabaseAtV3(db); - case 4: - return v4.DatabaseAtV4(db); case 5: return v5.DatabaseAtV5(db); - case 6: - return v6.DatabaseAtV6(db); + case 4: + return v4.DatabaseAtV4(db); + case 8: + return v8.DatabaseAtV8(db); + case 3: + return v3.DatabaseAtV3(db); + case 2: + return v2.DatabaseAtV2(db); + case 1: + return v1.DatabaseAtV1(db); case 7: return v7.DatabaseAtV7(db); + case 6: + return v6.DatabaseAtV6(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3, 4, 5, 6, 7]; + static const versions = const [1, 2, 3, 4, 5, 6, 7, 8]; } diff --git a/test/drift/app_db/generated/schema_v1.dart b/test/drift/app_db/generated/schema_v1.dart index ca848561..7a849d18 100644 --- a/test/drift/app_db/generated/schema_v1.dart +++ b/test/drift/app_db/generated/schema_v1.dart @@ -1,6 +1,6 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; class AuthenticationTable extends Table diff --git a/test/drift/app_db/generated/schema_v2.dart b/test/drift/app_db/generated/schema_v2.dart index c9642f86..4b28750d 100644 --- a/test/drift/app_db/generated/schema_v2.dart +++ b/test/drift/app_db/generated/schema_v2.dart @@ -1,6 +1,6 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; class AuthenticationTable extends Table diff --git a/test/drift/app_db/generated/schema_v3.dart b/test/drift/app_db/generated/schema_v3.dart index f6416823..7ddf4d2b 100644 --- a/test/drift/app_db/generated/schema_v3.dart +++ b/test/drift/app_db/generated/schema_v3.dart @@ -1,6 +1,6 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; class AuthenticationTable extends Table diff --git a/test/drift/app_db/generated/schema_v4.dart b/test/drift/app_db/generated/schema_v4.dart index 4206abdb..c8f07c6e 100644 --- a/test/drift/app_db/generated/schema_v4.dart +++ b/test/drift/app_db/generated/schema_v4.dart @@ -1,6 +1,6 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; class AuthenticationTable extends Table diff --git a/test/drift/app_db/generated/schema_v5.dart b/test/drift/app_db/generated/schema_v5.dart index 4283aa98..72c48612 100644 --- a/test/drift/app_db/generated/schema_v5.dart +++ b/test/drift/app_db/generated/schema_v5.dart @@ -1,6 +1,6 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; class AuthenticationTable extends Table diff --git a/test/drift/app_db/generated/schema_v6.dart b/test/drift/app_db/generated/schema_v6.dart index c0ef0442..9e556976 100644 --- a/test/drift/app_db/generated/schema_v6.dart +++ b/test/drift/app_db/generated/schema_v6.dart @@ -1,6 +1,6 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; class AuthenticationTable extends Table diff --git a/test/drift/app_db/generated/schema_v7.dart b/test/drift/app_db/generated/schema_v7.dart index b476efbd..b28397ab 100644 --- a/test/drift/app_db/generated/schema_v7.dart +++ b/test/drift/app_db/generated/schema_v7.dart @@ -1,6 +1,6 @@ -// dart format width=80 // GENERATED CODE, DO NOT EDIT BY HAND. // ignore_for_file: type=lint +//@dart=2.12 import 'package:drift/drift.dart'; class AuthenticationTable extends Table diff --git a/test/drift/app_db/generated/schema_v8.dart b/test/drift/app_db/generated/schema_v8.dart new file mode 100644 index 00000000..33fb4dad --- /dev/null +++ b/test/drift/app_db/generated/schema_v8.dart @@ -0,0 +1,3517 @@ +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +//@dart=2.12 +import 'package:drift/drift.dart'; + +class AuthenticationTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthenticationTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn cookie = GeneratedColumn( + 'cookie', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn accessToken = GeneratedColumn( + 'access_token', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn expiration = GeneratedColumn( + 'expiration', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [id, cookie, accessToken, expiration]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'authentication_table'; + @override + Set get $primaryKey => {id}; + @override + AuthenticationTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthenticationTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + cookie: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}cookie'])!, + accessToken: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}access_token'])!, + expiration: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expiration'])!, + ); + } + + @override + AuthenticationTable createAlias(String alias) { + return AuthenticationTable(attachedDatabase, alias); + } +} + +class AuthenticationTableData extends DataClass + implements Insertable { + final int id; + final String cookie; + final String accessToken; + final DateTime expiration; + const AuthenticationTableData( + {required this.id, + required this.cookie, + required this.accessToken, + required this.expiration}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['cookie'] = Variable(cookie); + map['access_token'] = Variable(accessToken); + map['expiration'] = Variable(expiration); + return map; + } + + AuthenticationTableCompanion toCompanion(bool nullToAbsent) { + return AuthenticationTableCompanion( + id: Value(id), + cookie: Value(cookie), + accessToken: Value(accessToken), + expiration: Value(expiration), + ); + } + + factory AuthenticationTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthenticationTableData( + id: serializer.fromJson(json['id']), + cookie: serializer.fromJson(json['cookie']), + accessToken: serializer.fromJson(json['accessToken']), + expiration: serializer.fromJson(json['expiration']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'cookie': serializer.toJson(cookie), + 'accessToken': serializer.toJson(accessToken), + 'expiration': serializer.toJson(expiration), + }; + } + + AuthenticationTableData copyWith( + {int? id, + String? cookie, + String? accessToken, + DateTime? expiration}) => + AuthenticationTableData( + id: id ?? this.id, + cookie: cookie ?? this.cookie, + accessToken: accessToken ?? this.accessToken, + expiration: expiration ?? this.expiration, + ); + AuthenticationTableData copyWithCompanion(AuthenticationTableCompanion data) { + return AuthenticationTableData( + id: data.id.present ? data.id.value : this.id, + cookie: data.cookie.present ? data.cookie.value : this.cookie, + accessToken: + data.accessToken.present ? data.accessToken.value : this.accessToken, + expiration: + data.expiration.present ? data.expiration.value : this.expiration, + ); + } + + @override + String toString() { + return (StringBuffer('AuthenticationTableData(') + ..write('id: $id, ') + ..write('cookie: $cookie, ') + ..write('accessToken: $accessToken, ') + ..write('expiration: $expiration') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, cookie, accessToken, expiration); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthenticationTableData && + other.id == this.id && + other.cookie == this.cookie && + other.accessToken == this.accessToken && + other.expiration == this.expiration); +} + +class AuthenticationTableCompanion + extends UpdateCompanion { + final Value id; + final Value cookie; + final Value accessToken; + final Value expiration; + const AuthenticationTableCompanion({ + this.id = const Value.absent(), + this.cookie = const Value.absent(), + this.accessToken = const Value.absent(), + this.expiration = const Value.absent(), + }); + AuthenticationTableCompanion.insert({ + this.id = const Value.absent(), + required String cookie, + required String accessToken, + required DateTime expiration, + }) : cookie = Value(cookie), + accessToken = Value(accessToken), + expiration = Value(expiration); + static Insertable custom({ + Expression? id, + Expression? cookie, + Expression? accessToken, + Expression? expiration, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (cookie != null) 'cookie': cookie, + if (accessToken != null) 'access_token': accessToken, + if (expiration != null) 'expiration': expiration, + }); + } + + AuthenticationTableCompanion copyWith( + {Value? id, + Value? cookie, + Value? accessToken, + Value? expiration}) { + return AuthenticationTableCompanion( + id: id ?? this.id, + cookie: cookie ?? this.cookie, + accessToken: accessToken ?? this.accessToken, + expiration: expiration ?? this.expiration, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (cookie.present) { + map['cookie'] = Variable(cookie.value); + } + if (accessToken.present) { + map['access_token'] = Variable(accessToken.value); + } + if (expiration.present) { + map['expiration'] = Variable(expiration.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthenticationTableCompanion(') + ..write('id: $id, ') + ..write('cookie: $cookie, ') + ..write('accessToken: $accessToken, ') + ..write('expiration: $expiration') + ..write(')')) + .toString(); + } +} + +class BlacklistTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BlacklistTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn elementType = GeneratedColumn( + 'element_type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn elementId = GeneratedColumn( + 'element_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, name, elementType, elementId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'blacklist_table'; + @override + Set get $primaryKey => {id}; + @override + BlacklistTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BlacklistTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + elementType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}element_type'])!, + elementId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}element_id'])!, + ); + } + + @override + BlacklistTable createAlias(String alias) { + return BlacklistTable(attachedDatabase, alias); + } +} + +class BlacklistTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String elementType; + final String elementId; + const BlacklistTableData( + {required this.id, + required this.name, + required this.elementType, + required this.elementId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['element_type'] = Variable(elementType); + map['element_id'] = Variable(elementId); + return map; + } + + BlacklistTableCompanion toCompanion(bool nullToAbsent) { + return BlacklistTableCompanion( + id: Value(id), + name: Value(name), + elementType: Value(elementType), + elementId: Value(elementId), + ); + } + + factory BlacklistTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BlacklistTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + elementType: serializer.fromJson(json['elementType']), + elementId: serializer.fromJson(json['elementId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'elementType': serializer.toJson(elementType), + 'elementId': serializer.toJson(elementId), + }; + } + + BlacklistTableData copyWith( + {int? id, String? name, String? elementType, String? elementId}) => + BlacklistTableData( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + BlacklistTableData copyWithCompanion(BlacklistTableCompanion data) { + return BlacklistTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + elementType: + data.elementType.present ? data.elementType.value : this.elementType, + elementId: data.elementId.present ? data.elementId.value : this.elementId, + ); + } + + @override + String toString() { + return (StringBuffer('BlacklistTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, elementType, elementId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BlacklistTableData && + other.id == this.id && + other.name == this.name && + other.elementType == this.elementType && + other.elementId == this.elementId); +} + +class BlacklistTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value elementType; + final Value elementId; + const BlacklistTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.elementType = const Value.absent(), + this.elementId = const Value.absent(), + }); + BlacklistTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String elementType, + required String elementId, + }) : name = Value(name), + elementType = Value(elementType), + elementId = Value(elementId); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? elementType, + Expression? elementId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (elementType != null) 'element_type': elementType, + if (elementId != null) 'element_id': elementId, + }); + } + + BlacklistTableCompanion copyWith( + {Value? id, + Value? name, + Value? elementType, + Value? elementId}) { + return BlacklistTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (elementType.present) { + map['element_type'] = Variable(elementType.value); + } + if (elementId.present) { + map['element_id'] = Variable(elementId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BlacklistTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } +} + +class PreferencesTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PreferencesTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn audioQuality = GeneratedColumn( + 'audio_quality', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceQualities.high.name)); + late final GeneratedColumn albumColorSync = GeneratedColumn( + 'album_color_sync', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("album_color_sync" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn amoledDarkTheme = GeneratedColumn( + 'amoled_dark_theme', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("amoled_dark_theme" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn checkUpdate = GeneratedColumn( + 'check_update', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("check_update" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn normalizeAudio = GeneratedColumn( + 'normalize_audio', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("normalize_audio" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn showSystemTrayIcon = GeneratedColumn( + 'show_system_tray_icon', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("show_system_tray_icon" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn systemTitleBar = GeneratedColumn( + 'system_title_bar', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("system_title_bar" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn skipNonMusic = GeneratedColumn( + 'skip_non_music', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("skip_non_music" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn closeBehavior = GeneratedColumn( + 'close_behavior', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(CloseBehavior.close.name)); + late final GeneratedColumn accentColorScheme = + GeneratedColumn('accent_color_scheme', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("Slate:0xff64748b")); + late final GeneratedColumn layoutMode = GeneratedColumn( + 'layout_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(LayoutMode.adaptive.name)); + late final GeneratedColumn locale = GeneratedColumn( + 'locale', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: + const Constant('{"languageCode":"system","countryCode":"system"}')); + late final GeneratedColumn market = GeneratedColumn( + 'market', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(Market.US.name)); + late final GeneratedColumn searchMode = GeneratedColumn( + 'search_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SearchMode.youtube.name)); + late final GeneratedColumn downloadLocation = GeneratedColumn( + 'download_location', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("")); + late final GeneratedColumn localLibraryLocation = + GeneratedColumn('local_library_location', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("")); + late final GeneratedColumn pipedInstance = GeneratedColumn( + 'piped_instance', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("https://pipedapi.kavin.rocks")); + late final GeneratedColumn invidiousInstance = + GeneratedColumn('invidious_instance', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("https://inv.nadeko.net")); + late final GeneratedColumn themeMode = GeneratedColumn( + 'theme_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(ThemeMode.system.name)); + late final GeneratedColumn audioSource = GeneratedColumn( + 'audio_source', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(AudioSource.youtube.name)); + late final GeneratedColumn youtubeClientEngine = + GeneratedColumn('youtube_client_engine', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name)); + late final GeneratedColumn streamMusicCodec = GeneratedColumn( + 'stream_music_codec', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceCodecs.weba.name)); + late final GeneratedColumn downloadMusicCodec = + GeneratedColumn('download_music_codec', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceCodecs.m4a.name)); + late final GeneratedColumn discordPresence = GeneratedColumn( + 'discord_presence', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("discord_presence" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn endlessPlayback = GeneratedColumn( + 'endless_playback', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("endless_playback" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn enableConnect = GeneratedColumn( + 'enable_connect', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enable_connect" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn connectPort = GeneratedColumn( + 'connect_port', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(-1)); + late final GeneratedColumn cacheMusic = GeneratedColumn( + 'cache_music', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("cache_music" IN (0, 1))'), + defaultValue: const Constant(true)); + @override + List get $columns => [ + id, + audioQuality, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + pipedInstance, + invidiousInstance, + themeMode, + audioSource, + youtubeClientEngine, + streamMusicCodec, + downloadMusicCodec, + discordPresence, + endlessPlayback, + enableConnect, + connectPort, + cacheMusic + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'preferences_table'; + @override + Set get $primaryKey => {id}; + @override + PreferencesTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PreferencesTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + audioQuality: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}audio_quality'])!, + albumColorSync: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}album_color_sync'])!, + amoledDarkTheme: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}amoled_dark_theme'])!, + checkUpdate: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}check_update'])!, + normalizeAudio: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}normalize_audio'])!, + showSystemTrayIcon: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}show_system_tray_icon'])!, + systemTitleBar: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}system_title_bar'])!, + skipNonMusic: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}skip_non_music'])!, + closeBehavior: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}close_behavior'])!, + accentColorScheme: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}accent_color_scheme'])!, + layoutMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}layout_mode'])!, + locale: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}locale'])!, + market: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}market'])!, + searchMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}search_mode'])!, + downloadLocation: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}download_location'])!, + localLibraryLocation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_library_location'])!, + pipedInstance: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}piped_instance'])!, + invidiousInstance: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}invidious_instance'])!, + themeMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}theme_mode'])!, + audioSource: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}audio_source'])!, + youtubeClientEngine: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}youtube_client_engine'])!, + streamMusicCodec: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}stream_music_codec'])!, + downloadMusicCodec: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}download_music_codec'])!, + discordPresence: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}discord_presence'])!, + endlessPlayback: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}endless_playback'])!, + enableConnect: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}enable_connect'])!, + connectPort: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}connect_port'])!, + cacheMusic: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}cache_music'])!, + ); + } + + @override + PreferencesTable createAlias(String alias) { + return PreferencesTable(attachedDatabase, alias); + } +} + +class PreferencesTableData extends DataClass + implements Insertable { + final int id; + final String audioQuality; + final bool albumColorSync; + final bool amoledDarkTheme; + final bool checkUpdate; + final bool normalizeAudio; + final bool showSystemTrayIcon; + final bool systemTitleBar; + final bool skipNonMusic; + final String closeBehavior; + final String accentColorScheme; + final String layoutMode; + final String locale; + final String market; + final String searchMode; + final String downloadLocation; + final String localLibraryLocation; + final String pipedInstance; + final String invidiousInstance; + final String themeMode; + final String audioSource; + final String youtubeClientEngine; + final String streamMusicCodec; + final String downloadMusicCodec; + final bool discordPresence; + final bool endlessPlayback; + final bool enableConnect; + final int connectPort; + final bool cacheMusic; + const PreferencesTableData( + {required this.id, + required this.audioQuality, + required this.albumColorSync, + required this.amoledDarkTheme, + required this.checkUpdate, + required this.normalizeAudio, + required this.showSystemTrayIcon, + required this.systemTitleBar, + required this.skipNonMusic, + required this.closeBehavior, + required this.accentColorScheme, + required this.layoutMode, + required this.locale, + required this.market, + required this.searchMode, + required this.downloadLocation, + required this.localLibraryLocation, + required this.pipedInstance, + required this.invidiousInstance, + required this.themeMode, + required this.audioSource, + required this.youtubeClientEngine, + required this.streamMusicCodec, + required this.downloadMusicCodec, + required this.discordPresence, + required this.endlessPlayback, + required this.enableConnect, + required this.connectPort, + required this.cacheMusic}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['audio_quality'] = Variable(audioQuality); + map['album_color_sync'] = Variable(albumColorSync); + map['amoled_dark_theme'] = Variable(amoledDarkTheme); + map['check_update'] = Variable(checkUpdate); + map['normalize_audio'] = Variable(normalizeAudio); + map['show_system_tray_icon'] = Variable(showSystemTrayIcon); + map['system_title_bar'] = Variable(systemTitleBar); + map['skip_non_music'] = Variable(skipNonMusic); + map['close_behavior'] = Variable(closeBehavior); + map['accent_color_scheme'] = Variable(accentColorScheme); + map['layout_mode'] = Variable(layoutMode); + map['locale'] = Variable(locale); + map['market'] = Variable(market); + map['search_mode'] = Variable(searchMode); + map['download_location'] = Variable(downloadLocation); + map['local_library_location'] = Variable(localLibraryLocation); + map['piped_instance'] = Variable(pipedInstance); + map['invidious_instance'] = Variable(invidiousInstance); + map['theme_mode'] = Variable(themeMode); + map['audio_source'] = Variable(audioSource); + map['youtube_client_engine'] = Variable(youtubeClientEngine); + map['stream_music_codec'] = Variable(streamMusicCodec); + map['download_music_codec'] = Variable(downloadMusicCodec); + map['discord_presence'] = Variable(discordPresence); + map['endless_playback'] = Variable(endlessPlayback); + map['enable_connect'] = Variable(enableConnect); + map['connect_port'] = Variable(connectPort); + map['cache_music'] = Variable(cacheMusic); + return map; + } + + PreferencesTableCompanion toCompanion(bool nullToAbsent) { + return PreferencesTableCompanion( + id: Value(id), + audioQuality: Value(audioQuality), + albumColorSync: Value(albumColorSync), + amoledDarkTheme: Value(amoledDarkTheme), + checkUpdate: Value(checkUpdate), + normalizeAudio: Value(normalizeAudio), + showSystemTrayIcon: Value(showSystemTrayIcon), + systemTitleBar: Value(systemTitleBar), + skipNonMusic: Value(skipNonMusic), + closeBehavior: Value(closeBehavior), + accentColorScheme: Value(accentColorScheme), + layoutMode: Value(layoutMode), + locale: Value(locale), + market: Value(market), + searchMode: Value(searchMode), + downloadLocation: Value(downloadLocation), + localLibraryLocation: Value(localLibraryLocation), + pipedInstance: Value(pipedInstance), + invidiousInstance: Value(invidiousInstance), + themeMode: Value(themeMode), + audioSource: Value(audioSource), + youtubeClientEngine: Value(youtubeClientEngine), + streamMusicCodec: Value(streamMusicCodec), + downloadMusicCodec: Value(downloadMusicCodec), + discordPresence: Value(discordPresence), + endlessPlayback: Value(endlessPlayback), + enableConnect: Value(enableConnect), + connectPort: Value(connectPort), + cacheMusic: Value(cacheMusic), + ); + } + + factory PreferencesTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PreferencesTableData( + id: serializer.fromJson(json['id']), + audioQuality: serializer.fromJson(json['audioQuality']), + albumColorSync: serializer.fromJson(json['albumColorSync']), + amoledDarkTheme: serializer.fromJson(json['amoledDarkTheme']), + checkUpdate: serializer.fromJson(json['checkUpdate']), + normalizeAudio: serializer.fromJson(json['normalizeAudio']), + showSystemTrayIcon: serializer.fromJson(json['showSystemTrayIcon']), + systemTitleBar: serializer.fromJson(json['systemTitleBar']), + skipNonMusic: serializer.fromJson(json['skipNonMusic']), + closeBehavior: serializer.fromJson(json['closeBehavior']), + accentColorScheme: serializer.fromJson(json['accentColorScheme']), + layoutMode: serializer.fromJson(json['layoutMode']), + locale: serializer.fromJson(json['locale']), + market: serializer.fromJson(json['market']), + searchMode: serializer.fromJson(json['searchMode']), + downloadLocation: serializer.fromJson(json['downloadLocation']), + localLibraryLocation: + serializer.fromJson(json['localLibraryLocation']), + pipedInstance: serializer.fromJson(json['pipedInstance']), + invidiousInstance: serializer.fromJson(json['invidiousInstance']), + themeMode: serializer.fromJson(json['themeMode']), + audioSource: serializer.fromJson(json['audioSource']), + youtubeClientEngine: + serializer.fromJson(json['youtubeClientEngine']), + streamMusicCodec: serializer.fromJson(json['streamMusicCodec']), + downloadMusicCodec: + serializer.fromJson(json['downloadMusicCodec']), + discordPresence: serializer.fromJson(json['discordPresence']), + endlessPlayback: serializer.fromJson(json['endlessPlayback']), + enableConnect: serializer.fromJson(json['enableConnect']), + connectPort: serializer.fromJson(json['connectPort']), + cacheMusic: serializer.fromJson(json['cacheMusic']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'audioQuality': serializer.toJson(audioQuality), + 'albumColorSync': serializer.toJson(albumColorSync), + 'amoledDarkTheme': serializer.toJson(amoledDarkTheme), + 'checkUpdate': serializer.toJson(checkUpdate), + 'normalizeAudio': serializer.toJson(normalizeAudio), + 'showSystemTrayIcon': serializer.toJson(showSystemTrayIcon), + 'systemTitleBar': serializer.toJson(systemTitleBar), + 'skipNonMusic': serializer.toJson(skipNonMusic), + 'closeBehavior': serializer.toJson(closeBehavior), + 'accentColorScheme': serializer.toJson(accentColorScheme), + 'layoutMode': serializer.toJson(layoutMode), + 'locale': serializer.toJson(locale), + 'market': serializer.toJson(market), + 'searchMode': serializer.toJson(searchMode), + 'downloadLocation': serializer.toJson(downloadLocation), + 'localLibraryLocation': serializer.toJson(localLibraryLocation), + 'pipedInstance': serializer.toJson(pipedInstance), + 'invidiousInstance': serializer.toJson(invidiousInstance), + 'themeMode': serializer.toJson(themeMode), + 'audioSource': serializer.toJson(audioSource), + 'youtubeClientEngine': serializer.toJson(youtubeClientEngine), + 'streamMusicCodec': serializer.toJson(streamMusicCodec), + 'downloadMusicCodec': serializer.toJson(downloadMusicCodec), + 'discordPresence': serializer.toJson(discordPresence), + 'endlessPlayback': serializer.toJson(endlessPlayback), + 'enableConnect': serializer.toJson(enableConnect), + 'connectPort': serializer.toJson(connectPort), + 'cacheMusic': serializer.toJson(cacheMusic), + }; + } + + PreferencesTableData copyWith( + {int? id, + String? audioQuality, + bool? albumColorSync, + bool? amoledDarkTheme, + bool? checkUpdate, + bool? normalizeAudio, + bool? showSystemTrayIcon, + bool? systemTitleBar, + bool? skipNonMusic, + String? closeBehavior, + String? accentColorScheme, + String? layoutMode, + String? locale, + String? market, + String? searchMode, + String? downloadLocation, + String? localLibraryLocation, + String? pipedInstance, + String? invidiousInstance, + String? themeMode, + String? audioSource, + String? youtubeClientEngine, + String? streamMusicCodec, + String? downloadMusicCodec, + bool? discordPresence, + bool? endlessPlayback, + bool? enableConnect, + int? connectPort, + bool? cacheMusic}) => + PreferencesTableData( + id: id ?? this.id, + audioQuality: audioQuality ?? this.audioQuality, + albumColorSync: albumColorSync ?? this.albumColorSync, + amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, + checkUpdate: checkUpdate ?? this.checkUpdate, + normalizeAudio: normalizeAudio ?? this.normalizeAudio, + showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, + systemTitleBar: systemTitleBar ?? this.systemTitleBar, + skipNonMusic: skipNonMusic ?? this.skipNonMusic, + closeBehavior: closeBehavior ?? this.closeBehavior, + accentColorScheme: accentColorScheme ?? this.accentColorScheme, + layoutMode: layoutMode ?? this.layoutMode, + locale: locale ?? this.locale, + market: market ?? this.market, + searchMode: searchMode ?? this.searchMode, + downloadLocation: downloadLocation ?? this.downloadLocation, + localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, + pipedInstance: pipedInstance ?? this.pipedInstance, + invidiousInstance: invidiousInstance ?? this.invidiousInstance, + themeMode: themeMode ?? this.themeMode, + audioSource: audioSource ?? this.audioSource, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, + downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, + discordPresence: discordPresence ?? this.discordPresence, + endlessPlayback: endlessPlayback ?? this.endlessPlayback, + enableConnect: enableConnect ?? this.enableConnect, + connectPort: connectPort ?? this.connectPort, + cacheMusic: cacheMusic ?? this.cacheMusic, + ); + PreferencesTableData copyWithCompanion(PreferencesTableCompanion data) { + return PreferencesTableData( + id: data.id.present ? data.id.value : this.id, + audioQuality: data.audioQuality.present + ? data.audioQuality.value + : this.audioQuality, + albumColorSync: data.albumColorSync.present + ? data.albumColorSync.value + : this.albumColorSync, + amoledDarkTheme: data.amoledDarkTheme.present + ? data.amoledDarkTheme.value + : this.amoledDarkTheme, + checkUpdate: + data.checkUpdate.present ? data.checkUpdate.value : this.checkUpdate, + normalizeAudio: data.normalizeAudio.present + ? data.normalizeAudio.value + : this.normalizeAudio, + showSystemTrayIcon: data.showSystemTrayIcon.present + ? data.showSystemTrayIcon.value + : this.showSystemTrayIcon, + systemTitleBar: data.systemTitleBar.present + ? data.systemTitleBar.value + : this.systemTitleBar, + skipNonMusic: data.skipNonMusic.present + ? data.skipNonMusic.value + : this.skipNonMusic, + closeBehavior: data.closeBehavior.present + ? data.closeBehavior.value + : this.closeBehavior, + accentColorScheme: data.accentColorScheme.present + ? data.accentColorScheme.value + : this.accentColorScheme, + layoutMode: + data.layoutMode.present ? data.layoutMode.value : this.layoutMode, + locale: data.locale.present ? data.locale.value : this.locale, + market: data.market.present ? data.market.value : this.market, + searchMode: + data.searchMode.present ? data.searchMode.value : this.searchMode, + downloadLocation: data.downloadLocation.present + ? data.downloadLocation.value + : this.downloadLocation, + localLibraryLocation: data.localLibraryLocation.present + ? data.localLibraryLocation.value + : this.localLibraryLocation, + pipedInstance: data.pipedInstance.present + ? data.pipedInstance.value + : this.pipedInstance, + invidiousInstance: data.invidiousInstance.present + ? data.invidiousInstance.value + : this.invidiousInstance, + themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode, + audioSource: + data.audioSource.present ? data.audioSource.value : this.audioSource, + youtubeClientEngine: data.youtubeClientEngine.present + ? data.youtubeClientEngine.value + : this.youtubeClientEngine, + streamMusicCodec: data.streamMusicCodec.present + ? data.streamMusicCodec.value + : this.streamMusicCodec, + downloadMusicCodec: data.downloadMusicCodec.present + ? data.downloadMusicCodec.value + : this.downloadMusicCodec, + discordPresence: data.discordPresence.present + ? data.discordPresence.value + : this.discordPresence, + endlessPlayback: data.endlessPlayback.present + ? data.endlessPlayback.value + : this.endlessPlayback, + enableConnect: data.enableConnect.present + ? data.enableConnect.value + : this.enableConnect, + connectPort: + data.connectPort.present ? data.connectPort.value : this.connectPort, + cacheMusic: + data.cacheMusic.present ? data.cacheMusic.value : this.cacheMusic, + ); + } + + @override + String toString() { + return (StringBuffer('PreferencesTableData(') + ..write('id: $id, ') + ..write('audioQuality: $audioQuality, ') + ..write('albumColorSync: $albumColorSync, ') + ..write('amoledDarkTheme: $amoledDarkTheme, ') + ..write('checkUpdate: $checkUpdate, ') + ..write('normalizeAudio: $normalizeAudio, ') + ..write('showSystemTrayIcon: $showSystemTrayIcon, ') + ..write('systemTitleBar: $systemTitleBar, ') + ..write('skipNonMusic: $skipNonMusic, ') + ..write('closeBehavior: $closeBehavior, ') + ..write('accentColorScheme: $accentColorScheme, ') + ..write('layoutMode: $layoutMode, ') + ..write('locale: $locale, ') + ..write('market: $market, ') + ..write('searchMode: $searchMode, ') + ..write('downloadLocation: $downloadLocation, ') + ..write('localLibraryLocation: $localLibraryLocation, ') + ..write('pipedInstance: $pipedInstance, ') + ..write('invidiousInstance: $invidiousInstance, ') + ..write('themeMode: $themeMode, ') + ..write('audioSource: $audioSource, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..write('streamMusicCodec: $streamMusicCodec, ') + ..write('downloadMusicCodec: $downloadMusicCodec, ') + ..write('discordPresence: $discordPresence, ') + ..write('endlessPlayback: $endlessPlayback, ') + ..write('enableConnect: $enableConnect, ') + ..write('connectPort: $connectPort, ') + ..write('cacheMusic: $cacheMusic') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + id, + audioQuality, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + pipedInstance, + invidiousInstance, + themeMode, + audioSource, + youtubeClientEngine, + streamMusicCodec, + downloadMusicCodec, + discordPresence, + endlessPlayback, + enableConnect, + connectPort, + cacheMusic + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PreferencesTableData && + other.id == this.id && + other.audioQuality == this.audioQuality && + other.albumColorSync == this.albumColorSync && + other.amoledDarkTheme == this.amoledDarkTheme && + other.checkUpdate == this.checkUpdate && + other.normalizeAudio == this.normalizeAudio && + other.showSystemTrayIcon == this.showSystemTrayIcon && + other.systemTitleBar == this.systemTitleBar && + other.skipNonMusic == this.skipNonMusic && + other.closeBehavior == this.closeBehavior && + other.accentColorScheme == this.accentColorScheme && + other.layoutMode == this.layoutMode && + other.locale == this.locale && + other.market == this.market && + other.searchMode == this.searchMode && + other.downloadLocation == this.downloadLocation && + other.localLibraryLocation == this.localLibraryLocation && + other.pipedInstance == this.pipedInstance && + other.invidiousInstance == this.invidiousInstance && + other.themeMode == this.themeMode && + other.audioSource == this.audioSource && + other.youtubeClientEngine == this.youtubeClientEngine && + other.streamMusicCodec == this.streamMusicCodec && + other.downloadMusicCodec == this.downloadMusicCodec && + other.discordPresence == this.discordPresence && + other.endlessPlayback == this.endlessPlayback && + other.enableConnect == this.enableConnect && + other.connectPort == this.connectPort && + other.cacheMusic == this.cacheMusic); +} + +class PreferencesTableCompanion extends UpdateCompanion { + final Value id; + final Value audioQuality; + final Value albumColorSync; + final Value amoledDarkTheme; + final Value checkUpdate; + final Value normalizeAudio; + final Value showSystemTrayIcon; + final Value systemTitleBar; + final Value skipNonMusic; + final Value closeBehavior; + final Value accentColorScheme; + final Value layoutMode; + final Value locale; + final Value market; + final Value searchMode; + final Value downloadLocation; + final Value localLibraryLocation; + final Value pipedInstance; + final Value invidiousInstance; + final Value themeMode; + final Value audioSource; + final Value youtubeClientEngine; + final Value streamMusicCodec; + final Value downloadMusicCodec; + final Value discordPresence; + final Value endlessPlayback; + final Value enableConnect; + final Value connectPort; + final Value cacheMusic; + const PreferencesTableCompanion({ + this.id = const Value.absent(), + this.audioQuality = const Value.absent(), + this.albumColorSync = const Value.absent(), + this.amoledDarkTheme = const Value.absent(), + this.checkUpdate = const Value.absent(), + this.normalizeAudio = const Value.absent(), + this.showSystemTrayIcon = const Value.absent(), + this.systemTitleBar = const Value.absent(), + this.skipNonMusic = const Value.absent(), + this.closeBehavior = const Value.absent(), + this.accentColorScheme = const Value.absent(), + this.layoutMode = const Value.absent(), + this.locale = const Value.absent(), + this.market = const Value.absent(), + this.searchMode = const Value.absent(), + this.downloadLocation = const Value.absent(), + this.localLibraryLocation = const Value.absent(), + this.pipedInstance = const Value.absent(), + this.invidiousInstance = const Value.absent(), + this.themeMode = const Value.absent(), + this.audioSource = const Value.absent(), + this.youtubeClientEngine = const Value.absent(), + this.streamMusicCodec = const Value.absent(), + this.downloadMusicCodec = const Value.absent(), + this.discordPresence = const Value.absent(), + this.endlessPlayback = const Value.absent(), + this.enableConnect = const Value.absent(), + this.connectPort = const Value.absent(), + this.cacheMusic = const Value.absent(), + }); + PreferencesTableCompanion.insert({ + this.id = const Value.absent(), + this.audioQuality = const Value.absent(), + this.albumColorSync = const Value.absent(), + this.amoledDarkTheme = const Value.absent(), + this.checkUpdate = const Value.absent(), + this.normalizeAudio = const Value.absent(), + this.showSystemTrayIcon = const Value.absent(), + this.systemTitleBar = const Value.absent(), + this.skipNonMusic = const Value.absent(), + this.closeBehavior = const Value.absent(), + this.accentColorScheme = const Value.absent(), + this.layoutMode = const Value.absent(), + this.locale = const Value.absent(), + this.market = const Value.absent(), + this.searchMode = const Value.absent(), + this.downloadLocation = const Value.absent(), + this.localLibraryLocation = const Value.absent(), + this.pipedInstance = const Value.absent(), + this.invidiousInstance = const Value.absent(), + this.themeMode = const Value.absent(), + this.audioSource = const Value.absent(), + this.youtubeClientEngine = const Value.absent(), + this.streamMusicCodec = const Value.absent(), + this.downloadMusicCodec = const Value.absent(), + this.discordPresence = const Value.absent(), + this.endlessPlayback = const Value.absent(), + this.enableConnect = const Value.absent(), + this.connectPort = const Value.absent(), + this.cacheMusic = const Value.absent(), + }); + static Insertable custom({ + Expression? id, + Expression? audioQuality, + Expression? albumColorSync, + Expression? amoledDarkTheme, + Expression? checkUpdate, + Expression? normalizeAudio, + Expression? showSystemTrayIcon, + Expression? systemTitleBar, + Expression? skipNonMusic, + Expression? closeBehavior, + Expression? accentColorScheme, + Expression? layoutMode, + Expression? locale, + Expression? market, + Expression? searchMode, + Expression? downloadLocation, + Expression? localLibraryLocation, + Expression? pipedInstance, + Expression? invidiousInstance, + Expression? themeMode, + Expression? audioSource, + Expression? youtubeClientEngine, + Expression? streamMusicCodec, + Expression? downloadMusicCodec, + Expression? discordPresence, + Expression? endlessPlayback, + Expression? enableConnect, + Expression? connectPort, + Expression? cacheMusic, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (audioQuality != null) 'audio_quality': audioQuality, + if (albumColorSync != null) 'album_color_sync': albumColorSync, + if (amoledDarkTheme != null) 'amoled_dark_theme': amoledDarkTheme, + if (checkUpdate != null) 'check_update': checkUpdate, + if (normalizeAudio != null) 'normalize_audio': normalizeAudio, + if (showSystemTrayIcon != null) + 'show_system_tray_icon': showSystemTrayIcon, + if (systemTitleBar != null) 'system_title_bar': systemTitleBar, + if (skipNonMusic != null) 'skip_non_music': skipNonMusic, + if (closeBehavior != null) 'close_behavior': closeBehavior, + if (accentColorScheme != null) 'accent_color_scheme': accentColorScheme, + if (layoutMode != null) 'layout_mode': layoutMode, + if (locale != null) 'locale': locale, + if (market != null) 'market': market, + if (searchMode != null) 'search_mode': searchMode, + if (downloadLocation != null) 'download_location': downloadLocation, + if (localLibraryLocation != null) + 'local_library_location': localLibraryLocation, + if (pipedInstance != null) 'piped_instance': pipedInstance, + if (invidiousInstance != null) 'invidious_instance': invidiousInstance, + if (themeMode != null) 'theme_mode': themeMode, + if (audioSource != null) 'audio_source': audioSource, + if (youtubeClientEngine != null) + 'youtube_client_engine': youtubeClientEngine, + if (streamMusicCodec != null) 'stream_music_codec': streamMusicCodec, + if (downloadMusicCodec != null) + 'download_music_codec': downloadMusicCodec, + if (discordPresence != null) 'discord_presence': discordPresence, + if (endlessPlayback != null) 'endless_playback': endlessPlayback, + if (enableConnect != null) 'enable_connect': enableConnect, + if (connectPort != null) 'connect_port': connectPort, + if (cacheMusic != null) 'cache_music': cacheMusic, + }); + } + + PreferencesTableCompanion copyWith( + {Value? id, + Value? audioQuality, + Value? albumColorSync, + Value? amoledDarkTheme, + Value? checkUpdate, + Value? normalizeAudio, + Value? showSystemTrayIcon, + Value? systemTitleBar, + Value? skipNonMusic, + Value? closeBehavior, + Value? accentColorScheme, + Value? layoutMode, + Value? locale, + Value? market, + Value? searchMode, + Value? downloadLocation, + Value? localLibraryLocation, + Value? pipedInstance, + Value? invidiousInstance, + Value? themeMode, + Value? audioSource, + Value? youtubeClientEngine, + Value? streamMusicCodec, + Value? downloadMusicCodec, + Value? discordPresence, + Value? endlessPlayback, + Value? enableConnect, + Value? connectPort, + Value? cacheMusic}) { + return PreferencesTableCompanion( + id: id ?? this.id, + audioQuality: audioQuality ?? this.audioQuality, + albumColorSync: albumColorSync ?? this.albumColorSync, + amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, + checkUpdate: checkUpdate ?? this.checkUpdate, + normalizeAudio: normalizeAudio ?? this.normalizeAudio, + showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, + systemTitleBar: systemTitleBar ?? this.systemTitleBar, + skipNonMusic: skipNonMusic ?? this.skipNonMusic, + closeBehavior: closeBehavior ?? this.closeBehavior, + accentColorScheme: accentColorScheme ?? this.accentColorScheme, + layoutMode: layoutMode ?? this.layoutMode, + locale: locale ?? this.locale, + market: market ?? this.market, + searchMode: searchMode ?? this.searchMode, + downloadLocation: downloadLocation ?? this.downloadLocation, + localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, + pipedInstance: pipedInstance ?? this.pipedInstance, + invidiousInstance: invidiousInstance ?? this.invidiousInstance, + themeMode: themeMode ?? this.themeMode, + audioSource: audioSource ?? this.audioSource, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, + downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, + discordPresence: discordPresence ?? this.discordPresence, + endlessPlayback: endlessPlayback ?? this.endlessPlayback, + enableConnect: enableConnect ?? this.enableConnect, + connectPort: connectPort ?? this.connectPort, + cacheMusic: cacheMusic ?? this.cacheMusic, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (audioQuality.present) { + map['audio_quality'] = Variable(audioQuality.value); + } + if (albumColorSync.present) { + map['album_color_sync'] = Variable(albumColorSync.value); + } + if (amoledDarkTheme.present) { + map['amoled_dark_theme'] = Variable(amoledDarkTheme.value); + } + if (checkUpdate.present) { + map['check_update'] = Variable(checkUpdate.value); + } + if (normalizeAudio.present) { + map['normalize_audio'] = Variable(normalizeAudio.value); + } + if (showSystemTrayIcon.present) { + map['show_system_tray_icon'] = Variable(showSystemTrayIcon.value); + } + if (systemTitleBar.present) { + map['system_title_bar'] = Variable(systemTitleBar.value); + } + if (skipNonMusic.present) { + map['skip_non_music'] = Variable(skipNonMusic.value); + } + if (closeBehavior.present) { + map['close_behavior'] = Variable(closeBehavior.value); + } + if (accentColorScheme.present) { + map['accent_color_scheme'] = Variable(accentColorScheme.value); + } + if (layoutMode.present) { + map['layout_mode'] = Variable(layoutMode.value); + } + if (locale.present) { + map['locale'] = Variable(locale.value); + } + if (market.present) { + map['market'] = Variable(market.value); + } + if (searchMode.present) { + map['search_mode'] = Variable(searchMode.value); + } + if (downloadLocation.present) { + map['download_location'] = Variable(downloadLocation.value); + } + if (localLibraryLocation.present) { + map['local_library_location'] = + Variable(localLibraryLocation.value); + } + if (pipedInstance.present) { + map['piped_instance'] = Variable(pipedInstance.value); + } + if (invidiousInstance.present) { + map['invidious_instance'] = Variable(invidiousInstance.value); + } + if (themeMode.present) { + map['theme_mode'] = Variable(themeMode.value); + } + if (audioSource.present) { + map['audio_source'] = Variable(audioSource.value); + } + if (youtubeClientEngine.present) { + map['youtube_client_engine'] = + Variable(youtubeClientEngine.value); + } + if (streamMusicCodec.present) { + map['stream_music_codec'] = Variable(streamMusicCodec.value); + } + if (downloadMusicCodec.present) { + map['download_music_codec'] = Variable(downloadMusicCodec.value); + } + if (discordPresence.present) { + map['discord_presence'] = Variable(discordPresence.value); + } + if (endlessPlayback.present) { + map['endless_playback'] = Variable(endlessPlayback.value); + } + if (enableConnect.present) { + map['enable_connect'] = Variable(enableConnect.value); + } + if (connectPort.present) { + map['connect_port'] = Variable(connectPort.value); + } + if (cacheMusic.present) { + map['cache_music'] = Variable(cacheMusic.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PreferencesTableCompanion(') + ..write('id: $id, ') + ..write('audioQuality: $audioQuality, ') + ..write('albumColorSync: $albumColorSync, ') + ..write('amoledDarkTheme: $amoledDarkTheme, ') + ..write('checkUpdate: $checkUpdate, ') + ..write('normalizeAudio: $normalizeAudio, ') + ..write('showSystemTrayIcon: $showSystemTrayIcon, ') + ..write('systemTitleBar: $systemTitleBar, ') + ..write('skipNonMusic: $skipNonMusic, ') + ..write('closeBehavior: $closeBehavior, ') + ..write('accentColorScheme: $accentColorScheme, ') + ..write('layoutMode: $layoutMode, ') + ..write('locale: $locale, ') + ..write('market: $market, ') + ..write('searchMode: $searchMode, ') + ..write('downloadLocation: $downloadLocation, ') + ..write('localLibraryLocation: $localLibraryLocation, ') + ..write('pipedInstance: $pipedInstance, ') + ..write('invidiousInstance: $invidiousInstance, ') + ..write('themeMode: $themeMode, ') + ..write('audioSource: $audioSource, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..write('streamMusicCodec: $streamMusicCodec, ') + ..write('downloadMusicCodec: $downloadMusicCodec, ') + ..write('discordPresence: $discordPresence, ') + ..write('endlessPlayback: $endlessPlayback, ') + ..write('enableConnect: $enableConnect, ') + ..write('connectPort: $connectPort, ') + ..write('cacheMusic: $cacheMusic') + ..write(')')) + .toString(); + } +} + +class ScrobblerTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + ScrobblerTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + late final GeneratedColumn username = GeneratedColumn( + 'username', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn passwordHash = GeneratedColumn( + 'password_hash', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, createdAt, username, passwordHash]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'scrobbler_table'; + @override + Set get $primaryKey => {id}; + @override + ScrobblerTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ScrobblerTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + username: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}username'])!, + passwordHash: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}password_hash'])!, + ); + } + + @override + ScrobblerTable createAlias(String alias) { + return ScrobblerTable(attachedDatabase, alias); + } +} + +class ScrobblerTableData extends DataClass + implements Insertable { + final int id; + final DateTime createdAt; + final String username; + final String passwordHash; + const ScrobblerTableData( + {required this.id, + required this.createdAt, + required this.username, + required this.passwordHash}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['username'] = Variable(username); + map['password_hash'] = Variable(passwordHash); + return map; + } + + ScrobblerTableCompanion toCompanion(bool nullToAbsent) { + return ScrobblerTableCompanion( + id: Value(id), + createdAt: Value(createdAt), + username: Value(username), + passwordHash: Value(passwordHash), + ); + } + + factory ScrobblerTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ScrobblerTableData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + username: serializer.fromJson(json['username']), + passwordHash: serializer.fromJson(json['passwordHash']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'username': serializer.toJson(username), + 'passwordHash': serializer.toJson(passwordHash), + }; + } + + ScrobblerTableData copyWith( + {int? id, + DateTime? createdAt, + String? username, + String? passwordHash}) => + ScrobblerTableData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + username: username ?? this.username, + passwordHash: passwordHash ?? this.passwordHash, + ); + ScrobblerTableData copyWithCompanion(ScrobblerTableCompanion data) { + return ScrobblerTableData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + username: data.username.present ? data.username.value : this.username, + passwordHash: data.passwordHash.present + ? data.passwordHash.value + : this.passwordHash, + ); + } + + @override + String toString() { + return (StringBuffer('ScrobblerTableData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('username: $username, ') + ..write('passwordHash: $passwordHash') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, createdAt, username, passwordHash); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ScrobblerTableData && + other.id == this.id && + other.createdAt == this.createdAt && + other.username == this.username && + other.passwordHash == this.passwordHash); +} + +class ScrobblerTableCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value username; + final Value passwordHash; + const ScrobblerTableCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.username = const Value.absent(), + this.passwordHash = const Value.absent(), + }); + ScrobblerTableCompanion.insert({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + required String username, + required String passwordHash, + }) : username = Value(username), + passwordHash = Value(passwordHash); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? username, + Expression? passwordHash, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (username != null) 'username': username, + if (passwordHash != null) 'password_hash': passwordHash, + }); + } + + ScrobblerTableCompanion copyWith( + {Value? id, + Value? createdAt, + Value? username, + Value? passwordHash}) { + return ScrobblerTableCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + username: username ?? this.username, + passwordHash: passwordHash ?? this.passwordHash, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (passwordHash.present) { + map['password_hash'] = Variable(passwordHash.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ScrobblerTableCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('username: $username, ') + ..write('passwordHash: $passwordHash') + ..write(')')) + .toString(); + } +} + +class SkipSegmentTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SkipSegmentTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn start = GeneratedColumn( + 'start', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn end = GeneratedColumn( + 'end', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [id, start, end, trackId, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'skip_segment_table'; + @override + Set get $primaryKey => {id}; + @override + SkipSegmentTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SkipSegmentTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + start: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}start'])!, + end: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}end'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SkipSegmentTable createAlias(String alias) { + return SkipSegmentTable(attachedDatabase, alias); + } +} + +class SkipSegmentTableData extends DataClass + implements Insertable { + final int id; + final int start; + final int end; + final String trackId; + final DateTime createdAt; + const SkipSegmentTableData( + {required this.id, + required this.start, + required this.end, + required this.trackId, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['start'] = Variable(start); + map['end'] = Variable(end); + map['track_id'] = Variable(trackId); + map['created_at'] = Variable(createdAt); + return map; + } + + SkipSegmentTableCompanion toCompanion(bool nullToAbsent) { + return SkipSegmentTableCompanion( + id: Value(id), + start: Value(start), + end: Value(end), + trackId: Value(trackId), + createdAt: Value(createdAt), + ); + } + + factory SkipSegmentTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SkipSegmentTableData( + id: serializer.fromJson(json['id']), + start: serializer.fromJson(json['start']), + end: serializer.fromJson(json['end']), + trackId: serializer.fromJson(json['trackId']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'start': serializer.toJson(start), + 'end': serializer.toJson(end), + 'trackId': serializer.toJson(trackId), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SkipSegmentTableData copyWith( + {int? id, + int? start, + int? end, + String? trackId, + DateTime? createdAt}) => + SkipSegmentTableData( + id: id ?? this.id, + start: start ?? this.start, + end: end ?? this.end, + trackId: trackId ?? this.trackId, + createdAt: createdAt ?? this.createdAt, + ); + SkipSegmentTableData copyWithCompanion(SkipSegmentTableCompanion data) { + return SkipSegmentTableData( + id: data.id.present ? data.id.value : this.id, + start: data.start.present ? data.start.value : this.start, + end: data.end.present ? data.end.value : this.end, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SkipSegmentTableData(') + ..write('id: $id, ') + ..write('start: $start, ') + ..write('end: $end, ') + ..write('trackId: $trackId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, start, end, trackId, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SkipSegmentTableData && + other.id == this.id && + other.start == this.start && + other.end == this.end && + other.trackId == this.trackId && + other.createdAt == this.createdAt); +} + +class SkipSegmentTableCompanion extends UpdateCompanion { + final Value id; + final Value start; + final Value end; + final Value trackId; + final Value createdAt; + const SkipSegmentTableCompanion({ + this.id = const Value.absent(), + this.start = const Value.absent(), + this.end = const Value.absent(), + this.trackId = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SkipSegmentTableCompanion.insert({ + this.id = const Value.absent(), + required int start, + required int end, + required String trackId, + this.createdAt = const Value.absent(), + }) : start = Value(start), + end = Value(end), + trackId = Value(trackId); + static Insertable custom({ + Expression? id, + Expression? start, + Expression? end, + Expression? trackId, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (start != null) 'start': start, + if (end != null) 'end': end, + if (trackId != null) 'track_id': trackId, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SkipSegmentTableCompanion copyWith( + {Value? id, + Value? start, + Value? end, + Value? trackId, + Value? createdAt}) { + return SkipSegmentTableCompanion( + id: id ?? this.id, + start: start ?? this.start, + end: end ?? this.end, + trackId: trackId ?? this.trackId, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (start.present) { + map['start'] = Variable(start.value); + } + if (end.present) { + map['end'] = Variable(end.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SkipSegmentTableCompanion(') + ..write('id: $id, ') + ..write('start: $start, ') + ..write('end: $end, ') + ..write('trackId: $trackId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class SourceMatchTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SourceMatchTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sourceId = GeneratedColumn( + 'source_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceType.youtube.name)); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, trackId, sourceId, sourceType, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'source_match_table'; + @override + Set get $primaryKey => {id}; + @override + SourceMatchTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SourceMatchTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + sourceId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_id'])!, + sourceType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_type'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SourceMatchTable createAlias(String alias) { + return SourceMatchTable(attachedDatabase, alias); + } +} + +class SourceMatchTableData extends DataClass + implements Insertable { + final int id; + final String trackId; + final String sourceId; + final String sourceType; + final DateTime createdAt; + const SourceMatchTableData( + {required this.id, + required this.trackId, + required this.sourceId, + required this.sourceType, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['track_id'] = Variable(trackId); + map['source_id'] = Variable(sourceId); + map['source_type'] = Variable(sourceType); + map['created_at'] = Variable(createdAt); + return map; + } + + SourceMatchTableCompanion toCompanion(bool nullToAbsent) { + return SourceMatchTableCompanion( + id: Value(id), + trackId: Value(trackId), + sourceId: Value(sourceId), + sourceType: Value(sourceType), + createdAt: Value(createdAt), + ); + } + + factory SourceMatchTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SourceMatchTableData( + id: serializer.fromJson(json['id']), + trackId: serializer.fromJson(json['trackId']), + sourceId: serializer.fromJson(json['sourceId']), + sourceType: serializer.fromJson(json['sourceType']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'trackId': serializer.toJson(trackId), + 'sourceId': serializer.toJson(sourceId), + 'sourceType': serializer.toJson(sourceType), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SourceMatchTableData copyWith( + {int? id, + String? trackId, + String? sourceId, + String? sourceType, + DateTime? createdAt}) => + SourceMatchTableData( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceId: sourceId ?? this.sourceId, + sourceType: sourceType ?? this.sourceType, + createdAt: createdAt ?? this.createdAt, + ); + SourceMatchTableData copyWithCompanion(SourceMatchTableCompanion data) { + return SourceMatchTableData( + id: data.id.present ? data.id.value : this.id, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + sourceId: data.sourceId.present ? data.sourceId.value : this.sourceId, + sourceType: + data.sourceType.present ? data.sourceType.value : this.sourceType, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SourceMatchTableData(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('sourceId: $sourceId, ') + ..write('sourceType: $sourceType, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, trackId, sourceId, sourceType, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SourceMatchTableData && + other.id == this.id && + other.trackId == this.trackId && + other.sourceId == this.sourceId && + other.sourceType == this.sourceType && + other.createdAt == this.createdAt); +} + +class SourceMatchTableCompanion extends UpdateCompanion { + final Value id; + final Value trackId; + final Value sourceId; + final Value sourceType; + final Value createdAt; + const SourceMatchTableCompanion({ + this.id = const Value.absent(), + this.trackId = const Value.absent(), + this.sourceId = const Value.absent(), + this.sourceType = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SourceMatchTableCompanion.insert({ + this.id = const Value.absent(), + required String trackId, + required String sourceId, + this.sourceType = const Value.absent(), + this.createdAt = const Value.absent(), + }) : trackId = Value(trackId), + sourceId = Value(sourceId); + static Insertable custom({ + Expression? id, + Expression? trackId, + Expression? sourceId, + Expression? sourceType, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (trackId != null) 'track_id': trackId, + if (sourceId != null) 'source_id': sourceId, + if (sourceType != null) 'source_type': sourceType, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SourceMatchTableCompanion copyWith( + {Value? id, + Value? trackId, + Value? sourceId, + Value? sourceType, + Value? createdAt}) { + return SourceMatchTableCompanion( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceId: sourceId ?? this.sourceId, + sourceType: sourceType ?? this.sourceType, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (sourceId.present) { + map['source_id'] = Variable(sourceId.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SourceMatchTableCompanion(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('sourceId: $sourceId, ') + ..write('sourceType: $sourceType, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class AudioPlayerStateTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AudioPlayerStateTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn playing = GeneratedColumn( + 'playing', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))')); + late final GeneratedColumn loopMode = GeneratedColumn( + 'loop_mode', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn shuffled = GeneratedColumn( + 'shuffled', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))')); + late final GeneratedColumn collections = GeneratedColumn( + 'collections', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn tracks = GeneratedColumn( + 'tracks', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("[]")); + late final GeneratedColumn currentIndex = GeneratedColumn( + 'current_index', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + @override + List get $columns => + [id, playing, loopMode, shuffled, collections, tracks, currentIndex]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'audio_player_state_table'; + @override + Set get $primaryKey => {id}; + @override + AudioPlayerStateTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AudioPlayerStateTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + playing: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}playing'])!, + loopMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}loop_mode'])!, + shuffled: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}shuffled'])!, + collections: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}collections'])!, + tracks: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tracks'])!, + currentIndex: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}current_index'])!, + ); + } + + @override + AudioPlayerStateTable createAlias(String alias) { + return AudioPlayerStateTable(attachedDatabase, alias); + } +} + +class AudioPlayerStateTableData extends DataClass + implements Insertable { + final int id; + final bool playing; + final String loopMode; + final bool shuffled; + final String collections; + final String tracks; + final int currentIndex; + const AudioPlayerStateTableData( + {required this.id, + required this.playing, + required this.loopMode, + required this.shuffled, + required this.collections, + required this.tracks, + required this.currentIndex}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['playing'] = Variable(playing); + map['loop_mode'] = Variable(loopMode); + map['shuffled'] = Variable(shuffled); + map['collections'] = Variable(collections); + map['tracks'] = Variable(tracks); + map['current_index'] = Variable(currentIndex); + return map; + } + + AudioPlayerStateTableCompanion toCompanion(bool nullToAbsent) { + return AudioPlayerStateTableCompanion( + id: Value(id), + playing: Value(playing), + loopMode: Value(loopMode), + shuffled: Value(shuffled), + collections: Value(collections), + tracks: Value(tracks), + currentIndex: Value(currentIndex), + ); + } + + factory AudioPlayerStateTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AudioPlayerStateTableData( + id: serializer.fromJson(json['id']), + playing: serializer.fromJson(json['playing']), + loopMode: serializer.fromJson(json['loopMode']), + shuffled: serializer.fromJson(json['shuffled']), + collections: serializer.fromJson(json['collections']), + tracks: serializer.fromJson(json['tracks']), + currentIndex: serializer.fromJson(json['currentIndex']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'playing': serializer.toJson(playing), + 'loopMode': serializer.toJson(loopMode), + 'shuffled': serializer.toJson(shuffled), + 'collections': serializer.toJson(collections), + 'tracks': serializer.toJson(tracks), + 'currentIndex': serializer.toJson(currentIndex), + }; + } + + AudioPlayerStateTableData copyWith( + {int? id, + bool? playing, + String? loopMode, + bool? shuffled, + String? collections, + String? tracks, + int? currentIndex}) => + AudioPlayerStateTableData( + id: id ?? this.id, + playing: playing ?? this.playing, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, + ); + AudioPlayerStateTableData copyWithCompanion( + AudioPlayerStateTableCompanion data) { + return AudioPlayerStateTableData( + id: data.id.present ? data.id.value : this.id, + playing: data.playing.present ? data.playing.value : this.playing, + loopMode: data.loopMode.present ? data.loopMode.value : this.loopMode, + shuffled: data.shuffled.present ? data.shuffled.value : this.shuffled, + collections: + data.collections.present ? data.collections.value : this.collections, + tracks: data.tracks.present ? data.tracks.value : this.tracks, + currentIndex: data.currentIndex.present + ? data.currentIndex.value + : this.currentIndex, + ); + } + + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableData(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, playing, loopMode, shuffled, collections, tracks, currentIndex); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AudioPlayerStateTableData && + other.id == this.id && + other.playing == this.playing && + other.loopMode == this.loopMode && + other.shuffled == this.shuffled && + other.collections == this.collections && + other.tracks == this.tracks && + other.currentIndex == this.currentIndex); +} + +class AudioPlayerStateTableCompanion + extends UpdateCompanion { + final Value id; + final Value playing; + final Value loopMode; + final Value shuffled; + final Value collections; + final Value tracks; + final Value currentIndex; + const AudioPlayerStateTableCompanion({ + this.id = const Value.absent(), + this.playing = const Value.absent(), + this.loopMode = const Value.absent(), + this.shuffled = const Value.absent(), + this.collections = const Value.absent(), + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), + }); + AudioPlayerStateTableCompanion.insert({ + this.id = const Value.absent(), + required bool playing, + required String loopMode, + required bool shuffled, + required String collections, + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), + }) : playing = Value(playing), + loopMode = Value(loopMode), + shuffled = Value(shuffled), + collections = Value(collections); + static Insertable custom({ + Expression? id, + Expression? playing, + Expression? loopMode, + Expression? shuffled, + Expression? collections, + Expression? tracks, + Expression? currentIndex, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (playing != null) 'playing': playing, + if (loopMode != null) 'loop_mode': loopMode, + if (shuffled != null) 'shuffled': shuffled, + if (collections != null) 'collections': collections, + if (tracks != null) 'tracks': tracks, + if (currentIndex != null) 'current_index': currentIndex, + }); + } + + AudioPlayerStateTableCompanion copyWith( + {Value? id, + Value? playing, + Value? loopMode, + Value? shuffled, + Value? collections, + Value? tracks, + Value? currentIndex}) { + return AudioPlayerStateTableCompanion( + id: id ?? this.id, + playing: playing ?? this.playing, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (playing.present) { + map['playing'] = Variable(playing.value); + } + if (loopMode.present) { + map['loop_mode'] = Variable(loopMode.value); + } + if (shuffled.present) { + map['shuffled'] = Variable(shuffled.value); + } + if (collections.present) { + map['collections'] = Variable(collections.value); + } + if (tracks.present) { + map['tracks'] = Variable(tracks.value); + } + if (currentIndex.present) { + map['current_index'] = Variable(currentIndex.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableCompanion(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') + ..write(')')) + .toString(); + } +} + +class HistoryTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + HistoryTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn itemId = GeneratedColumn( + 'item_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn data = GeneratedColumn( + 'data', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, createdAt, type, itemId, data]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'history_table'; + @override + Set get $primaryKey => {id}; + @override + HistoryTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return HistoryTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + itemId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}item_id'])!, + data: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}data'])!, + ); + } + + @override + HistoryTable createAlias(String alias) { + return HistoryTable(attachedDatabase, alias); + } +} + +class HistoryTableData extends DataClass + implements Insertable { + final int id; + final DateTime createdAt; + final String type; + final String itemId; + final String data; + const HistoryTableData( + {required this.id, + required this.createdAt, + required this.type, + required this.itemId, + required this.data}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['type'] = Variable(type); + map['item_id'] = Variable(itemId); + map['data'] = Variable(data); + return map; + } + + HistoryTableCompanion toCompanion(bool nullToAbsent) { + return HistoryTableCompanion( + id: Value(id), + createdAt: Value(createdAt), + type: Value(type), + itemId: Value(itemId), + data: Value(data), + ); + } + + factory HistoryTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return HistoryTableData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + type: serializer.fromJson(json['type']), + itemId: serializer.fromJson(json['itemId']), + data: serializer.fromJson(json['data']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'type': serializer.toJson(type), + 'itemId': serializer.toJson(itemId), + 'data': serializer.toJson(data), + }; + } + + HistoryTableData copyWith( + {int? id, + DateTime? createdAt, + String? type, + String? itemId, + String? data}) => + HistoryTableData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + itemId: itemId ?? this.itemId, + data: data ?? this.data, + ); + HistoryTableData copyWithCompanion(HistoryTableCompanion data) { + return HistoryTableData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + type: data.type.present ? data.type.value : this.type, + itemId: data.itemId.present ? data.itemId.value : this.itemId, + data: data.data.present ? data.data.value : this.data, + ); + } + + @override + String toString() { + return (StringBuffer('HistoryTableData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('type: $type, ') + ..write('itemId: $itemId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, createdAt, type, itemId, data); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is HistoryTableData && + other.id == this.id && + other.createdAt == this.createdAt && + other.type == this.type && + other.itemId == this.itemId && + other.data == this.data); +} + +class HistoryTableCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value type; + final Value itemId; + final Value data; + const HistoryTableCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.type = const Value.absent(), + this.itemId = const Value.absent(), + this.data = const Value.absent(), + }); + HistoryTableCompanion.insert({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + required String type, + required String itemId, + required String data, + }) : type = Value(type), + itemId = Value(itemId), + data = Value(data); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? type, + Expression? itemId, + Expression? data, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (type != null) 'type': type, + if (itemId != null) 'item_id': itemId, + if (data != null) 'data': data, + }); + } + + HistoryTableCompanion copyWith( + {Value? id, + Value? createdAt, + Value? type, + Value? itemId, + Value? data}) { + return HistoryTableCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + itemId: itemId ?? this.itemId, + data: data ?? this.data, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (itemId.present) { + map['item_id'] = Variable(itemId.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('HistoryTableCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('type: $type, ') + ..write('itemId: $itemId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } +} + +class LyricsTable extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LyricsTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn data = GeneratedColumn( + 'data', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, trackId, data]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'lyrics_table'; + @override + Set get $primaryKey => {id}; + @override + LyricsTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LyricsTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + data: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}data'])!, + ); + } + + @override + LyricsTable createAlias(String alias) { + return LyricsTable(attachedDatabase, alias); + } +} + +class LyricsTableData extends DataClass implements Insertable { + final int id; + final String trackId; + final String data; + const LyricsTableData( + {required this.id, required this.trackId, required this.data}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['track_id'] = Variable(trackId); + map['data'] = Variable(data); + return map; + } + + LyricsTableCompanion toCompanion(bool nullToAbsent) { + return LyricsTableCompanion( + id: Value(id), + trackId: Value(trackId), + data: Value(data), + ); + } + + factory LyricsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LyricsTableData( + id: serializer.fromJson(json['id']), + trackId: serializer.fromJson(json['trackId']), + data: serializer.fromJson(json['data']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'trackId': serializer.toJson(trackId), + 'data': serializer.toJson(data), + }; + } + + LyricsTableData copyWith({int? id, String? trackId, String? data}) => + LyricsTableData( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + data: data ?? this.data, + ); + LyricsTableData copyWithCompanion(LyricsTableCompanion data) { + return LyricsTableData( + id: data.id.present ? data.id.value : this.id, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + data: data.data.present ? data.data.value : this.data, + ); + } + + @override + String toString() { + return (StringBuffer('LyricsTableData(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, trackId, data); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LyricsTableData && + other.id == this.id && + other.trackId == this.trackId && + other.data == this.data); +} + +class LyricsTableCompanion extends UpdateCompanion { + final Value id; + final Value trackId; + final Value data; + const LyricsTableCompanion({ + this.id = const Value.absent(), + this.trackId = const Value.absent(), + this.data = const Value.absent(), + }); + LyricsTableCompanion.insert({ + this.id = const Value.absent(), + required String trackId, + required String data, + }) : trackId = Value(trackId), + data = Value(data); + static Insertable custom({ + Expression? id, + Expression? trackId, + Expression? data, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (trackId != null) 'track_id': trackId, + if (data != null) 'data': data, + }); + } + + LyricsTableCompanion copyWith( + {Value? id, Value? trackId, Value? data}) { + return LyricsTableCompanion( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + data: data ?? this.data, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LyricsTableCompanion(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } +} + +class MetadataPluginsTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MetadataPluginsTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 50), + type: DriftSqlType.string, + requiredDuringInsert: true); + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn version = GeneratedColumn( + 'version', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn author = GeneratedColumn( + 'author', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn entryPoint = GeneratedColumn( + 'entry_point', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn apis = GeneratedColumn( + 'apis', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn abilities = GeneratedColumn( + 'abilities', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn selected = GeneratedColumn( + 'selected', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("selected" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn repository = GeneratedColumn( + 'repository', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn pluginApiVersion = GeneratedColumn( + 'plugin_api_version', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('1.0.0')); + @override + List get $columns => [ + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selected, + repository, + pluginApiVersion + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'metadata_plugins_table'; + @override + Set get $primaryKey => {id}; + @override + MetadataPluginsTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MetadataPluginsTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description'])!, + version: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}version'])!, + author: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}author'])!, + entryPoint: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}entry_point'])!, + apis: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}apis'])!, + abilities: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}abilities'])!, + selected: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}selected'])!, + repository: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}repository']), + pluginApiVersion: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}plugin_api_version'])!, + ); + } + + @override + MetadataPluginsTable createAlias(String alias) { + return MetadataPluginsTable(attachedDatabase, alias); + } +} + +class MetadataPluginsTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String description; + final String version; + final String author; + final String entryPoint; + final String apis; + final String abilities; + final bool selected; + final String? repository; + final String pluginApiVersion; + const MetadataPluginsTableData( + {required this.id, + required this.name, + required this.description, + required this.version, + required this.author, + required this.entryPoint, + required this.apis, + required this.abilities, + required this.selected, + this.repository, + required this.pluginApiVersion}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['version'] = Variable(version); + map['author'] = Variable(author); + map['entry_point'] = Variable(entryPoint); + map['apis'] = Variable(apis); + map['abilities'] = Variable(abilities); + map['selected'] = Variable(selected); + if (!nullToAbsent || repository != null) { + map['repository'] = Variable(repository); + } + map['plugin_api_version'] = Variable(pluginApiVersion); + return map; + } + + MetadataPluginsTableCompanion toCompanion(bool nullToAbsent) { + return MetadataPluginsTableCompanion( + id: Value(id), + name: Value(name), + description: Value(description), + version: Value(version), + author: Value(author), + entryPoint: Value(entryPoint), + apis: Value(apis), + abilities: Value(abilities), + selected: Value(selected), + repository: repository == null && nullToAbsent + ? const Value.absent() + : Value(repository), + pluginApiVersion: Value(pluginApiVersion), + ); + } + + factory MetadataPluginsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MetadataPluginsTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + version: serializer.fromJson(json['version']), + author: serializer.fromJson(json['author']), + entryPoint: serializer.fromJson(json['entryPoint']), + apis: serializer.fromJson(json['apis']), + abilities: serializer.fromJson(json['abilities']), + selected: serializer.fromJson(json['selected']), + repository: serializer.fromJson(json['repository']), + pluginApiVersion: serializer.fromJson(json['pluginApiVersion']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'version': serializer.toJson(version), + 'author': serializer.toJson(author), + 'entryPoint': serializer.toJson(entryPoint), + 'apis': serializer.toJson(apis), + 'abilities': serializer.toJson(abilities), + 'selected': serializer.toJson(selected), + 'repository': serializer.toJson(repository), + 'pluginApiVersion': serializer.toJson(pluginApiVersion), + }; + } + + MetadataPluginsTableData copyWith( + {int? id, + String? name, + String? description, + String? version, + String? author, + String? entryPoint, + String? apis, + String? abilities, + bool? selected, + Value repository = const Value.absent(), + String? pluginApiVersion}) => + MetadataPluginsTableData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, + selected: selected ?? this.selected, + repository: repository.present ? repository.value : this.repository, + pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, + ); + MetadataPluginsTableData copyWithCompanion( + MetadataPluginsTableCompanion data) { + return MetadataPluginsTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: + data.description.present ? data.description.value : this.description, + version: data.version.present ? data.version.value : this.version, + author: data.author.present ? data.author.value : this.author, + entryPoint: + data.entryPoint.present ? data.entryPoint.value : this.entryPoint, + apis: data.apis.present ? data.apis.value : this.apis, + abilities: data.abilities.present ? data.abilities.value : this.abilities, + selected: data.selected.present ? data.selected.value : this.selected, + repository: + data.repository.present ? data.repository.value : this.repository, + pluginApiVersion: data.pluginApiVersion.present + ? data.pluginApiVersion.value + : this.pluginApiVersion, + ); + } + + @override + String toString() { + return (StringBuffer('MetadataPluginsTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') + ..write('selected: $selected, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, description, version, author, + entryPoint, apis, abilities, selected, repository, pluginApiVersion); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MetadataPluginsTableData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.version == this.version && + other.author == this.author && + other.entryPoint == this.entryPoint && + other.apis == this.apis && + other.abilities == this.abilities && + other.selected == this.selected && + other.repository == this.repository && + other.pluginApiVersion == this.pluginApiVersion); +} + +class MetadataPluginsTableCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value version; + final Value author; + final Value entryPoint; + final Value apis; + final Value abilities; + final Value selected; + final Value repository; + final Value pluginApiVersion; + const MetadataPluginsTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.version = const Value.absent(), + this.author = const Value.absent(), + this.entryPoint = const Value.absent(), + this.apis = const Value.absent(), + this.abilities = const Value.absent(), + this.selected = const Value.absent(), + this.repository = const Value.absent(), + this.pluginApiVersion = const Value.absent(), + }); + MetadataPluginsTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String description, + required String version, + required String author, + required String entryPoint, + required String apis, + required String abilities, + this.selected = const Value.absent(), + this.repository = const Value.absent(), + this.pluginApiVersion = const Value.absent(), + }) : name = Value(name), + description = Value(description), + version = Value(version), + author = Value(author), + entryPoint = Value(entryPoint), + apis = Value(apis), + abilities = Value(abilities); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? version, + Expression? author, + Expression? entryPoint, + Expression? apis, + Expression? abilities, + Expression? selected, + Expression? repository, + Expression? pluginApiVersion, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (version != null) 'version': version, + if (author != null) 'author': author, + if (entryPoint != null) 'entry_point': entryPoint, + if (apis != null) 'apis': apis, + if (abilities != null) 'abilities': abilities, + if (selected != null) 'selected': selected, + if (repository != null) 'repository': repository, + if (pluginApiVersion != null) 'plugin_api_version': pluginApiVersion, + }); + } + + MetadataPluginsTableCompanion copyWith( + {Value? id, + Value? name, + Value? description, + Value? version, + Value? author, + Value? entryPoint, + Value? apis, + Value? abilities, + Value? selected, + Value? repository, + Value? pluginApiVersion}) { + return MetadataPluginsTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, + selected: selected ?? this.selected, + repository: repository ?? this.repository, + pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (version.present) { + map['version'] = Variable(version.value); + } + if (author.present) { + map['author'] = Variable(author.value); + } + if (entryPoint.present) { + map['entry_point'] = Variable(entryPoint.value); + } + if (apis.present) { + map['apis'] = Variable(apis.value); + } + if (abilities.present) { + map['abilities'] = Variable(abilities.value); + } + if (selected.present) { + map['selected'] = Variable(selected.value); + } + if (repository.present) { + map['repository'] = Variable(repository.value); + } + if (pluginApiVersion.present) { + map['plugin_api_version'] = Variable(pluginApiVersion.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MetadataPluginsTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') + ..write('selected: $selected, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV8 extends GeneratedDatabase { + DatabaseAtV8(QueryExecutor e) : super(e); + late final AuthenticationTable authenticationTable = + AuthenticationTable(this); + late final BlacklistTable blacklistTable = BlacklistTable(this); + late final PreferencesTable preferencesTable = PreferencesTable(this); + late final ScrobblerTable scrobblerTable = ScrobblerTable(this); + late final SkipSegmentTable skipSegmentTable = SkipSegmentTable(this); + late final SourceMatchTable sourceMatchTable = SourceMatchTable(this); + late final AudioPlayerStateTable audioPlayerStateTable = + AudioPlayerStateTable(this); + late final HistoryTable historyTable = HistoryTable(this); + late final LyricsTable lyricsTable = LyricsTable(this); + late final MetadataPluginsTable metadataPluginsTable = + MetadataPluginsTable(this); + late final Index uniqueBlacklist = Index('unique_blacklist', + 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); + late final Index uniqTrackMatch = Index('uniq_track_match', + 'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)'); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + historyTable, + lyricsTable, + metadataPluginsTable, + uniqueBlacklist, + uniqTrackMatch + ]; + @override + int get schemaVersion => 8; +} diff --git a/untranslated_messages.json b/untranslated_messages.json index 5ae99b72..af89bb78 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1,114 +1,5 @@ { - "ar": [ - "hipotetical_calculation" - ], - - "bn": [ - "hipotetical_calculation" - ], - - "ca": [ - "hipotetical_calculation" - ], - - "cs": [ - "hipotetical_calculation" - ], - - "de": [ - "hipotetical_calculation" - ], - - "es": [ - "hipotetical_calculation" - ], - - "eu": [ - "hipotetical_calculation" - ], - - "fa": [ - "hipotetical_calculation" - ], - - "fi": [ - "hipotetical_calculation" - ], - - "fr": [ - "hipotetical_calculation", - "connection_request_denied" - ], - - "hi": [ - "hipotetical_calculation" - ], - - "id": [ - "hipotetical_calculation" - ], - - "it": [ - "hipotetical_calculation" - ], - - "ja": [ - "hipotetical_calculation" - ], - - "ka": [ - "hipotetical_calculation" - ], - - "ko": [ - "hipotetical_calculation" - ], - - "ne": [ - "hipotetical_calculation" - ], - "nl": [ - "hipotetical_calculation" - ], - - "pl": [ - "hipotetical_calculation" - ], - - "pt": [ - "hipotetical_calculation" - ], - - "ru": [ - "hipotetical_calculation" - ], - - "ta": [ - "hipotetical_calculation" - ], - - "th": [ - "hipotetical_calculation" - ], - - "tl": [ - "hipotetical_calculation" - ], - - "tr": [ - "hipotetical_calculation" - ], - - "uk": [ - "hipotetical_calculation" - ], - - "vi": [ - "hipotetical_calculation" - ], - - "zh": [ - "hipotetical_calculation" + "audio_source" ] } diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index f053614f..87b34e37 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -41,6 +43,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); FlutterTimezonePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi")); + IrondashEngineContextPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); LocalNotifierPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalNotifierPlugin")); MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar( @@ -51,6 +55,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); + SuperNativeExtensionsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); SystemThemePluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SystemThemePlugin")); TrayManagerPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index fae5ffe9..798e47c8 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -11,11 +11,13 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_inappwebview_windows flutter_secure_storage_windows flutter_timezone + irondash_engine_context local_notifier media_kit_libs_windows_audio permission_handler_windows screen_retriever_windows sqlite3_flutter_libs + super_native_extensions system_theme tray_manager url_launcher_windows @@ -24,7 +26,6 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flutter_discord_rpc - media_kit_native_event_loop metadata_god smtc_windows ) diff --git a/windows/packaging/exe/inno_setup.iss b/windows/packaging/exe/inno_setup.iss index 976da962..15ffec05 100644 --- a/windows/packaging/exe/inno_setup.iss +++ b/windows/packaging/exe/inno_setup.iss @@ -20,7 +20,7 @@ Compression=lzma SolidCompression=yes SetupIconFile={{SETUP_ICON_FILE}} WizardStyle=modern -WizardSmallImageFile="..\\..\\assets\\spotube-logo.bmp" +WizardSmallImageFile="..\\..\\assets\\branding\\spotube-logo.bmp" PrivilegesRequired={{PRIVILEGES_REQUIRED}} ArchitecturesAllowed=x64compatible ArchitecturesInstallIn64BitMode=x64compatible diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index de2d8916..394917c0 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -1,6 +1,11 @@ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" @@ -10,8 +15,26 @@ add_executable(${BINARY_NAME} WIN32 "Runner.rc" "runner.exe.manifest" ) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble)