diff --git a/.github/Dockerfile b/.github/Dockerfile deleted file mode 100644 index f6a9f538..00000000 --- a/.github/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -ARG FLUTTER_VERSION - -FROM --platform=linux/arm64 krtirtho/flutter_distributor:${FLUTTER_VERSION} - -ARG BUILD_VERSION - -WORKDIR /app - -COPY . . - -RUN chown -R $(whoami) /app - -RUN rustup target add aarch64-unknown-linux-gnu - -RUN flutter pub get - -RUN alias dpkg-deb="dpkg-deb --Zxz" &&\ - flutter_distributor package --platform=linux --targets=deb --skip-clean - -RUN make tar VERSION=${BUILD_VERSION} ARCH=arm64 PKG_ARCH=aarch64 - -RUN mv build/spotube-linux-*-aarch64.tar.xz dist/ &&\ - mv dist/**/spotube-*-linux.deb dist/Spotube-linux-aarch64.deb - -CMD [ "sleep", "5000000" ] \ No newline at end of file diff --git a/.github/workflows/spotube-publish-binary.yml b/.github/workflows/spotube-publish-binary.yml index f88e618c..e682dbdd 100644 --- a/.github/workflows/spotube-publish-binary.yml +++ b/.github/workflows/spotube-publish-binary.yml @@ -12,10 +12,10 @@ on: type: boolean default: true jobs: - description: Jobs to run (flathub,aur,winget,chocolatey,playstore) + description: Jobs to run (flathub,aur,winget,chocolatey) required: true type: string - default: "flathub,aur,winget,chocolatey,playstore" + default: "flathub,aur,winget,chocolatey" jobs: flathub: @@ -112,26 +112,26 @@ jobs: - name: Tagname (workflow dispatch) run: echo 'TAG_NAME=${{inputs.version}}' >> $GITHUB_ENV - - uses: robinraju/release-downloader@main - with: - repository: KRTirtho/spotube - tag: v${{ env.TAG_NAME }} - tarBall: false - zipBall: false - out-file-path: dist - fileName: "Spotube-playstore-all-arch.aab" + # - uses: robinraju/release-downloader@main + # with: + # repository: KRTirtho/spotube + # tag: v${{ env.TAG_NAME }} + # tarBall: false + # zipBall: false + # out-file-path: dist + # fileName: "Spotube-playstore-all-arch.aab" - - name: Create service-account.json - run: | - echo "${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_BASE64 }}" | base64 -d > service-account.json + # - name: Create service-account.json + # run: | + # echo "${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_BASE64 }}" | base64 -d > service-account.json - - name: Upload Android Release to Play Store - if: ${{!inputs.dry_run}} - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJson: ./service-account.json - releaseFiles: ./dist/Spotube-playstore-all-arch.aab - packageName: oss.krtirtho.spotube - track: production - status: draft - releaseName: ${{ env.TAG_NAME }} + # - name: Upload Android Release to Play Store + # if: ${{!inputs.dry_run}} + # uses: r0adkll/upload-google-play@v1 + # with: + # serviceAccountJson: ./service-account.json + # releaseFiles: ./dist/Spotube-playstore-all-arch.aab + # packageName: oss.krtirtho.spotube + # track: production + # status: draft + # releaseName: ${{ env.TAG_NAME }} diff --git a/.github/workflows/spotube-release-binary.yml b/.github/workflows/spotube-release-binary.yml index 4f2cff34..dfec7d44 100644 --- a/.github/workflows/spotube-release-binary.yml +++ b/.github/workflows/spotube-release-binary.yml @@ -37,19 +37,20 @@ jobs: files: | dist/Spotube-linux-x86_64.deb dist/Spotube-linux-x86_64.rpm + dist/Spotube-linux-x86_64.AppImage dist/spotube-linux-*-x86_64.tar.xz - os: ubuntu-22.04-arm platform: linux arch: arm64 files: | dist/Spotube-linux-aarch64.deb + dist/Spotube-linux-aarch64.AppImage dist/spotube-linux-*-aarch64.tar.xz - os: ubuntu-22.04 platform: android arch: all files: | build/Spotube-android-all-arch.apk - build/Spotube-playstore-all-arch.aab - os: windows-latest platform: windows arch: x86 @@ -77,6 +78,14 @@ jobs: cache: true git-source: https://github.com/flutter/flutter.git + - name: free disk space + if: ${{ matrix.platform == 'android' }} + run: | + sudo swapoff -a + sudo rm -f /swapfile + sudo apt clean + docker rmi $(docker image ls -aq) + df -h - name: Setup Java if: ${{matrix.platform == 'android'}} uses: actions/setup-java@v4 @@ -100,7 +109,7 @@ jobs: - name: Install ${{matrix.platform}} dependencies run: | flutter pub get - dart cli/cli.dart install-dependencies --platform=${{matrix.platform}} + dart cli/cli.dart install-dependencies --platform=${{matrix.platform}} --arch=${{matrix.arch}} - name: Sign Apk if: ${{matrix.platform == 'android'}} diff --git a/.gitignore b/.gitignore index 119e42e5..544dbba8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .history .svn/ + # IntelliJ related *.iml *.ipr diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ae3f15..b8a5b0e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [5.1.0](https://github.com/KRTirtho/spotube/compare/v5.0.0...v5.1.0) (2025-11-14) + +### Features + +- Show plugin source and set the only plugin as default if no plugin is there +- **playback**: Add dab music source +- **playback**: Add uncompressed flac playback support +- Add plugin audio source models and api service +- **plugins**: Filter plugins by abilities in plugins page and show abilities as badge +- Add setting default audio source support +- Move away from track source query and preferences audio quality and codec +- Add NewPipe support for desktop platforms +- Add default plugin loading capability +- **queue**: Add multi-select and bulk actions to queue ([#2839](https://github.com/KRTirtho/spotube/issues/2839)) +- **android**: Add 16KB page size support + +### Bug Fixes + +- Change plugin download directory to application support +- **playback**: Play next not working +- Downloaded tracks are not tagged with metadata +- Download not working in different devices and slow +- **playback**: Use stream instead of chunked serving of audio bytes + ## [5.0.0](https://github.com/KRTirtho/spotube/compare/v4.0.2...v5.0.0) (2025-09-08) ### Features diff --git a/README.md b/README.md index 4c5c1f1c..1043fabc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Spotube Logo 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! +Bring your own music metadata/playlist/audio-source 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 😉 @@ -202,7 +202,7 @@ If you are curious, you can [read the reason of choosing this license](https://d 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. [YouTubeExplodeDart](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. [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 @@ -216,7 +216,6 @@ If you are curious, you can [read the reason of choosing this license](https://d 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. @@ -243,15 +242,11 @@ If you are curious, you can [read the reason of choosing this license](https://d 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. @@ -259,15 +254,10 @@ If you are curious, you can [read the reason of choosing this license](https://d 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) @@ -276,7 +266,6 @@ If you are curious, you can [read the reason of choosing this license](https://d 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 @@ -291,9 +280,6 @@ If you are curious, you can [read the reason of choosing this license](https://d 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. @@ -305,12 +291,17 @@ If you are curious, you can [read the reason of choosing this license](https://d 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. [window_manager](https://leanflutter.dev) - 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. [archive](https://pub.dev/packages/archive) - Provides encoders and decoders for various archive and compression formats such as zip, tar, bzip2, gzip, and zlib. +1. [hetu_script](https://github.com/hetu-script/hetu-script) - Hetu is a lightweight scripting language for embedding in Flutter apps. +1. [get_it](https://github.com/flutter-it/get_it) - Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App" +1. [flutter_markdown_plus](https://pub.dev/packages/flutter_markdown_plus) - A Markdown renderer for Flutter. Create rich text output, including text styles, tables, links, and more, from plain text data formatted with simple Markdown tags. +1. [pub_semver](https://pub.dev/packages/pub_semver) - Versions and version constraints implementing pub's versioning policy. This is very similar to vanilla semver, with a few corner cases. +1. [change_case](https://github.com/mrgnhnt96/change_case) - An extension on String for the missing methods for camelCase, PascalCase, Capital Case, snake_case, param-case, CONSTANT_CASE and others. +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. [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. @@ -321,17 +312,23 @@ If you are curious, you can [read the reason of choosing this license](https://d 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. [test](https://pub.dev/packages/test) - A full featured library for writing and running Dart tests across platforms. 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. [yt_dlp_dart](https://github.com/KRTirtho/yt_dlp_dart.git) - A starting point for Dart libraries or applications. 1. [flutter_new_pipe_extractor](https://github.com/KRTirtho/flutter_new_pipe_extractor) - NewPipeExtractor binding for Flutter (Android only) +1. [hetu_std](https://github.com/hetu-community/hetu_std.git) - A sample command-line application. +1. [hetu_otp_util](https://github.com/hetu-community/hetu_otp_util.git) - A sample command-line application. +1. [hetu_spotube_plugin](https://github.com/KRTirtho/hetu_spotube_plugin) - A new Flutter package project. +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. +

© Copyright Spotube 2025

diff --git a/android/.gitignore b/android/.gitignore index 6f568019..2391a77e 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -11,3 +11,4 @@ GeneratedPluginRegistrant.java key.properties **/*.keystore **/*.jks +.kotlin \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index ee481eca..7319c6a8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -2,6 +2,7 @@ plugins { id "com.android.application" id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" + id "org.jetbrains.kotlin.plugin.compose" } def localProperties = new Properties() @@ -35,7 +36,7 @@ android { compileSdkVersion 36 - ndkVersion = "27.0.12077973" + ndkVersion = "29.0.14206865" compileOptions { coreLibraryDesugaringEnabled true diff --git a/android/settings.gradle b/android/settings.gradle index 1e8ffbe3..53d34a77 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,7 +19,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version '8.7.0' apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false + id "org.jetbrains.kotlin.plugin.compose" version "2.1.0" apply false } include ':app' \ No newline at end of file diff --git a/assets/images/logos/dab-music.png b/assets/images/logos/dab-music.png new file mode 100644 index 00000000..e09d3410 Binary files /dev/null and b/assets/images/logos/dab-music.png differ diff --git a/assets/images/logos/songlink-transparent.png b/assets/images/logos/songlink-transparent.png deleted file mode 100644 index fc4ae541..00000000 Binary files a/assets/images/logos/songlink-transparent.png and /dev/null differ diff --git a/assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug b/assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug new file mode 100644 index 00000000..41be05a4 Binary files /dev/null and b/assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug differ diff --git a/assets/plugins/spotube-plugin-youtube-audio/plugin.smplug b/assets/plugins/spotube-plugin-youtube-audio/plugin.smplug new file mode 100644 index 00000000..55aa2895 Binary files /dev/null and b/assets/plugins/spotube-plugin-youtube-audio/plugin.smplug differ diff --git a/cli/commands/build/android.dart b/cli/commands/build/android.dart index 4216553a..b9edeb84 100644 --- a/cli/commands/build/android.dart +++ b/cli/commands/build/android.dart @@ -2,9 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'package:args/command_runner.dart'; -import 'package:collection/collection.dart'; import 'package:path/path.dart'; -import 'package:xml/xml.dart'; import '../../core/env.dart'; import 'common.dart'; @@ -24,39 +22,6 @@ class AndroidBuildCommand extends Command with BuildCommandCommonSteps { "flutter build apk --flavor ${CliEnv.channel.name}", ); - await dotEnvFile.writeAsString( - "\nENABLE_UPDATE_CHECK=0" - "\nHIDE_DONATIONS=1", - mode: FileMode.append, - ); - - final androidManifestFile = File( - join(cwd.path, "android", "app", "src", "main", "AndroidManifest.xml")); - - final androidManifestXml = - XmlDocument.parse(await androidManifestFile.readAsString()); - - final deletingElement = - androidManifestXml.findAllElements("meta-data").firstWhereOrNull( - (el) => - el.getAttribute("android:name") == - "com.google.android.gms.car.application", - ); - - deletingElement?.parent?.children.remove(deletingElement); - - await androidManifestFile.writeAsString( - androidManifestXml.toXmlString(pretty: true), - ); - - await shell.run( - """ - dart run build_runner clean - dart run build_runner build --delete-conflicting-outputs - flutter build appbundle --flavor ${CliEnv.channel.name} - """, - ); - final ogApkFile = File( join( "build", @@ -71,22 +36,6 @@ class AndroidBuildCommand extends Command with BuildCommandCommonSteps { join(cwd.path, "build", "Spotube-android-all-arch.apk"), ); - final ogAppbundleFile = File( - join( - cwd.path, - "build", - "app", - "outputs", - "bundle", - "${CliEnv.channel.name}Release", - "app-${CliEnv.channel.name}-release.aab", - ), - ); - - await ogAppbundleFile.copy( - join(cwd.path, "build", "Spotube-playstore-all-arch.aab"), - ); - stdout.writeln("✅ Built Android Apk and Appbundle"); } } diff --git a/cli/commands/build/common.dart b/cli/commands/build/common.dart index 30906b3c..c30197f5 100644 --- a/cli/commands/build/common.dart +++ b/cli/commands/build/common.dart @@ -59,7 +59,7 @@ mixin BuildCommandCommonSteps on Command { """ flutter pub get dart run build_runner build --delete-conflicting-outputs - dart pub global activate flutter_distributor + dart pub global activate fastforge """, ); } diff --git a/cli/commands/build/linux.dart b/cli/commands/build/linux.dart index 378f5a72..3ca792ea 100644 --- a/cli/commands/build/linux.dart +++ b/cli/commands/build/linux.dart @@ -37,12 +37,11 @@ class LinuxBuildCommand extends Command with BuildCommandCommonSteps { await bootstrap(); await shell.run( - "flutter_distributor package --platform=linux --targets=deb", + "fastforge package --platform=linux --targets=deb,appimage", ); - if (architecture == "x86") { await shell.run( - "flutter_distributor package --platform=linux --targets=rpm", + "fastforge package --platform=linux --targets=rpm", ); } @@ -116,6 +115,23 @@ class LinuxBuildCommand extends Command with BuildCommandCommonSteps { await ogRpm.delete(); } + final ogAppImage = File( + join( + cwd.path, + "dist", + pubspec.version.toString(), + "spotube-${pubspec.version}-linux.AppImage", + ), + ); + await ogAppImage.copy( + join( + cwd.path, + "dist", + "Spotube-linux-$bundleArchName.AppImage", + ), + ); + await ogAppImage.delete(); + stdout.writeln("✅ Linux building done"); } } diff --git a/cli/commands/build/macos.dart b/cli/commands/build/macos.dart index e8f34b77..936f1fc8 100644 --- a/cli/commands/build/macos.dart +++ b/cli/commands/build/macos.dart @@ -21,7 +21,7 @@ class MacosBuildCommand extends Command with BuildCommandCommonSteps { """ flutter build macos appdmg appdmg.json ${join(cwd.path, "build", "Spotube-macos-universal.dmg")} - flutter_distributor package --platform=macos --targets pkg --skip-clean + fastforge package --platform=macos --targets pkg --skip-clean """, ); diff --git a/cli/commands/build/windows.dart b/cli/commands/build/windows.dart index c44ed52f..1045c11c 100644 --- a/cli/commands/build/windows.dart +++ b/cli/commands/build/windows.dart @@ -61,7 +61,7 @@ class WindowsBuildCommand extends Command with BuildCommandCommonSteps { ); await shell.run( - "flutter_distributor package --platform=windows --targets=exe --skip-clean", + "fastforge package --platform=windows --targets=exe --skip-clean", ); final ogExe = File( diff --git a/cli/commands/install-dependencies.dart b/cli/commands/install-dependencies.dart index e26b8078..56f679f1 100644 --- a/cli/commands/install-dependencies.dart +++ b/cli/commands/install-dependencies.dart @@ -37,14 +37,24 @@ class InstallDependenciesCommand extends Command { FutureOr? run() async { final shell = Shell(); + final arch = argResults?.option("arch") == "x86" ? "x86_64" : "aarch64"; + switch (argResults!.option("platform")) { case "windows": + await shell.run( + """ + choco install innosetup -y + """, + ); break; case "linux": await shell.run( """ sudo apt-get update -y - sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev libwebkit2gtk-4.1-0 libwebkit2gtk-4.1-dev libsoup-3.0-0 libsoup-3.0-dev + sudo apt-get install -y wget tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev libwebkit2gtk-4.1-0 libwebkit2gtk-4.1-dev libsoup-3.0-0 libsoup-3.0-dev + wget -O appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$arch.AppImage" + chmod +x appimagetool + sudo mv appimagetool /usr/local/bin/ """, ); break; diff --git a/drift_schemas/app_db/drift_schema_v10.json b/drift_schemas/app_db/drift_schema_v10.json new file mode 100644 index 00000000..5fb86d25 --- /dev/null +++ b/drift_schemas/app_db/drift_schema_v10.json @@ -0,0 +1 @@ +{"_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":"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":"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_id","getter_name":"audioSourceId","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"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":"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_info","getter_name":"sourceInfo","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"{}\")","default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","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":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":"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_for_metadata","getter_name":"selectedForMetadata","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected_for_metadata\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected_for_metadata\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"selected_for_audio_source","getter_name":"selectedForAudioSource","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected_for_audio_source\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected_for_audio_source\" 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('2.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_info","source_type"]}}]} \ No newline at end of file diff --git a/drift_schemas/app_db/drift_schema_v9.json b/drift_schemas/app_db/drift_schema_v9.json new file mode 100644 index 00000000..73af2588 --- /dev/null +++ b/drift_schemas/app_db/drift_schema_v9.json @@ -0,0 +1 @@ +{"_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":"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_for_metadata","getter_name":"selectedForMetadata","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected_for_metadata\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected_for_metadata\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"selected_for_audio_source","getter_name":"selectedForAudioSource","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected_for_audio_source\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected_for_audio_source\" 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('2.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"]}}]} \ No newline at end of file diff --git a/lib/collections/assets.gen.dart b/lib/collections/assets.gen.dart index 31fb54b8..7ab0ad03 100644 --- a/lib/collections/assets.gen.dart +++ b/lib/collections/assets.gen.dart @@ -1,3 +1,5 @@ +// dart format width=80 + /// GENERATED CODE - DO NOT MODIFY BY HAND /// ***************************************************** /// FlutterGen @@ -5,7 +7,7 @@ // coverage:ignore-file // ignore_for_file: type=lint -// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use +// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import import 'package:flutter/widgets.dart'; @@ -64,9 +66,26 @@ class $AssetsImagesGen { ]; } +class $AssetsPluginsGen { + const $AssetsPluginsGen(); + + /// Directory path: assets/plugins/spotube-plugin-musicbrainz-listenbrainz + $AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen + get spotubePluginMusicbrainzListenbrainz => + const $AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen(); + + /// Directory path: assets/plugins/spotube-plugin-youtube-audio + $AssetsPluginsSpotubePluginYoutubeAudioGen get spotubePluginYoutubeAudio => + const $AssetsPluginsSpotubePluginYoutubeAudioGen(); +} + class $AssetsImagesLogosGen { const $AssetsImagesLogosGen(); + /// File path: assets/images/logos/dab-music.png + AssetGenImage get dabMusic => + const AssetGenImage('assets/images/logos/dab-music.png'); + /// File path: assets/images/logos/invidious.jpg AssetGenImage get invidious => const AssetGenImage('assets/images/logos/invidious.jpg'); @@ -75,20 +94,39 @@ class $AssetsImagesLogosGen { AssetGenImage get jiosaavn => const AssetGenImage('assets/images/logos/jiosaavn.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 => [dabMusic, invidious, jiosaavn]; +} + +class $AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen { + const $AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen(); + + /// File path: assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug + String get plugin => + 'assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug'; /// List of all assets - List get values => [invidious, jiosaavn, songlinkTransparent]; + List get values => [plugin]; +} + +class $AssetsPluginsSpotubePluginYoutubeAudioGen { + const $AssetsPluginsSpotubePluginYoutubeAudioGen(); + + /// File path: assets/plugins/spotube-plugin-youtube-audio/plugin.smplug + String get plugin => + 'assets/plugins/spotube-plugin-youtube-audio/plugin.smplug'; + + /// List of all assets + List get values => [plugin]; } class Assets { - Assets._(); + const Assets._(); static const String license = 'LICENSE'; static const $AssetsBrandingGen branding = $AssetsBrandingGen(); static const $AssetsImagesGen images = $AssetsImagesGen(); + static const $AssetsPluginsGen plugins = $AssetsPluginsGen(); /// List of all assets static List get values => [license]; @@ -99,12 +137,14 @@ class AssetGenImage { this._assetName, { this.size, this.flavors = const {}, + this.animation, }); final String _assetName; final Size? size; final Set flavors; + final AssetGenImageAnimation? animation; Image image({ Key? key, @@ -127,7 +167,7 @@ class AssetGenImage { bool gaplessPlayback = true, bool isAntiAlias = false, String? package, - FilterQuality filterQuality = FilterQuality.low, + FilterQuality filterQuality = FilterQuality.medium, int? cacheWidth, int? cacheHeight, }) { @@ -174,3 +214,15 @@ class AssetGenImage { String get keyName => _assetName; } + +class AssetGenImageAnimation { + const AssetGenImageAnimation({ + required this.isAnimation, + required this.duration, + required this.frames, + }); + + final bool isAnimation; + final Duration duration; + final int frames; +} diff --git a/lib/collections/fonts.gen.dart b/lib/collections/fonts.gen.dart index 16cc6e82..d2c68231 100644 --- a/lib/collections/fonts.gen.dart +++ b/lib/collections/fonts.gen.dart @@ -1,3 +1,4 @@ +// dart format width=80 /// GENERATED CODE - DO NOT MODIFY BY HAND /// ***************************************************** /// FlutterGen @@ -5,7 +6,7 @@ // coverage:ignore-file // ignore_for_file: type=lint -// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use +// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import class FontFamily { FontFamily._(); diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index e039abb9..f5ff24bf 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -1,3 +1,4 @@ +// dart format width=80 // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** @@ -59,10 +60,7 @@ import 'package:spotube/pages/track/track.dart' as _i35; /// [_i1.AboutSpotubePage] class AboutSpotubeRoute extends _i41.PageRouteInfo { const AboutSpotubeRoute({List<_i41.PageRouteInfo>? children}) - : super( - AboutSpotubeRoute.name, - initialChildren: children, - ); + : super(AboutSpotubeRoute.name, initialChildren: children); static const String name = 'AboutSpotubeRoute'; @@ -83,15 +81,11 @@ class AlbumRoute extends _i41.PageRouteInfo { required _i43.SpotubeSimpleAlbumObject album, List<_i41.PageRouteInfo>? children, }) : super( - AlbumRoute.name, - args: AlbumRouteArgs( - key: key, - id: id, - album: album, - ), - rawPathParams: {'id': id}, - initialChildren: children, - ); + AlbumRoute.name, + args: AlbumRouteArgs(key: key, id: id, album: album), + rawPathParams: {'id': id}, + initialChildren: children, + ); static const String name = 'AlbumRoute'; @@ -99,21 +93,13 @@ class AlbumRoute extends _i41.PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return _i2.AlbumPage( - key: args.key, - id: args.id, - album: args.album, - ); + return _i2.AlbumPage(key: args.key, id: args.id, album: args.album); }, ); } class AlbumRouteArgs { - const AlbumRouteArgs({ - this.key, - required this.id, - required this.album, - }); + const AlbumRouteArgs({this.key, required this.id, required this.album}); final _i42.Key? key; @@ -135,14 +121,11 @@ class ArtistRoute extends _i41.PageRouteInfo { _i42.Key? key, List<_i41.PageRouteInfo>? children, }) : super( - ArtistRoute.name, - args: ArtistRouteArgs( - artistId: artistId, - key: key, - ), - rawPathParams: {'id': artistId}, - initialChildren: children, - ); + ArtistRoute.name, + args: ArtistRouteArgs(artistId: artistId, key: key), + rawPathParams: {'id': artistId}, + initialChildren: children, + ); static const String name = 'ArtistRoute'; @@ -151,20 +134,15 @@ class ArtistRoute extends _i41.PageRouteInfo { builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( - orElse: () => ArtistRouteArgs(artistId: pathParams.getString('id'))); - return _i3.ArtistPage( - args.artistId, - key: args.key, + orElse: () => ArtistRouteArgs(artistId: pathParams.getString('id')), ); + return _i3.ArtistPage(args.artistId, key: args.key); }, ); } class ArtistRouteArgs { - const ArtistRouteArgs({ - required this.artistId, - this.key, - }); + const ArtistRouteArgs({required this.artistId, this.key}); final String artistId; @@ -180,10 +158,7 @@ class ArtistRouteArgs { /// [_i4.BlackListPage] class BlackListRoute extends _i41.PageRouteInfo { const BlackListRoute({List<_i41.PageRouteInfo>? children}) - : super( - BlackListRoute.name, - initialChildren: children, - ); + : super(BlackListRoute.name, initialChildren: children); static const String name = 'BlackListRoute'; @@ -199,10 +174,7 @@ class BlackListRoute extends _i41.PageRouteInfo { /// [_i5.ConnectControlPage] class ConnectControlRoute extends _i41.PageRouteInfo { const ConnectControlRoute({List<_i41.PageRouteInfo>? children}) - : super( - ConnectControlRoute.name, - initialChildren: children, - ); + : super(ConnectControlRoute.name, initialChildren: children); static const String name = 'ConnectControlRoute'; @@ -218,10 +190,7 @@ class ConnectControlRoute extends _i41.PageRouteInfo { /// [_i6.ConnectPage] class ConnectRoute extends _i41.PageRouteInfo { const ConnectRoute({List<_i41.PageRouteInfo>? children}) - : super( - ConnectRoute.name, - initialChildren: children, - ); + : super(ConnectRoute.name, initialChildren: children); static const String name = 'ConnectRoute'; @@ -237,10 +206,7 @@ class ConnectRoute extends _i41.PageRouteInfo { /// [_i7.GettingStartedPage] class GettingStartedRoute extends _i41.PageRouteInfo { const GettingStartedRoute({List<_i41.PageRouteInfo>? children}) - : super( - GettingStartedRoute.name, - initialChildren: children, - ); + : super(GettingStartedRoute.name, initialChildren: children); static const String name = 'GettingStartedRoute'; @@ -262,15 +228,15 @@ class HomeBrowseSectionItemsRoute required _i43.SpotubeBrowseSectionObject section, List<_i41.PageRouteInfo>? children, }) : super( - HomeBrowseSectionItemsRoute.name, - args: HomeBrowseSectionItemsRouteArgs( - key: key, - sectionId: sectionId, - section: section, - ), - rawPathParams: {'sectionId': sectionId}, - initialChildren: children, - ); + HomeBrowseSectionItemsRoute.name, + args: HomeBrowseSectionItemsRouteArgs( + key: key, + sectionId: sectionId, + section: section, + ), + rawPathParams: {'sectionId': sectionId}, + initialChildren: children, + ); static const String name = 'HomeBrowseSectionItemsRoute'; @@ -310,10 +276,7 @@ class HomeBrowseSectionItemsRouteArgs { /// [_i9.HomePage] class HomeRoute extends _i41.PageRouteInfo { const HomeRoute({List<_i41.PageRouteInfo>? children}) - : super( - HomeRoute.name, - initialChildren: children, - ); + : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; @@ -329,10 +292,7 @@ class HomeRoute extends _i41.PageRouteInfo { /// [_i10.LastFMLoginPage] class LastFMLoginRoute extends _i41.PageRouteInfo { const LastFMLoginRoute({List<_i41.PageRouteInfo>? children}) - : super( - LastFMLoginRoute.name, - initialChildren: children, - ); + : super(LastFMLoginRoute.name, initialChildren: children); static const String name = 'LastFMLoginRoute'; @@ -348,10 +308,7 @@ class LastFMLoginRoute extends _i41.PageRouteInfo { /// [_i11.LibraryPage] class LibraryRoute extends _i41.PageRouteInfo { const LibraryRoute({List<_i41.PageRouteInfo>? children}) - : super( - LibraryRoute.name, - initialChildren: children, - ); + : super(LibraryRoute.name, initialChildren: children); static const String name = 'LibraryRoute'; @@ -371,13 +328,10 @@ class LikedPlaylistRoute extends _i41.PageRouteInfo { required _i43.SpotubeSimplePlaylistObject playlist, List<_i41.PageRouteInfo>? children, }) : super( - LikedPlaylistRoute.name, - args: LikedPlaylistRouteArgs( - key: key, - playlist: playlist, - ), - initialChildren: children, - ); + LikedPlaylistRoute.name, + args: LikedPlaylistRouteArgs(key: key, playlist: playlist), + initialChildren: children, + ); static const String name = 'LikedPlaylistRoute'; @@ -385,19 +339,13 @@ class LikedPlaylistRoute extends _i41.PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return _i12.LikedPlaylistPage( - key: args.key, - playlist: args.playlist, - ); + return _i12.LikedPlaylistPage(key: args.key, playlist: args.playlist); }, ); } class LikedPlaylistRouteArgs { - const LikedPlaylistRouteArgs({ - this.key, - required this.playlist, - }); + const LikedPlaylistRouteArgs({this.key, required this.playlist}); final _i42.Key? key; @@ -419,15 +367,15 @@ class LocalLibraryRoute extends _i41.PageRouteInfo { bool isCache = false, List<_i41.PageRouteInfo>? children, }) : super( - LocalLibraryRoute.name, - args: LocalLibraryRouteArgs( - location: location, - key: key, - isDownloads: isDownloads, - isCache: isCache, - ), - initialChildren: children, - ); + LocalLibraryRoute.name, + args: LocalLibraryRouteArgs( + location: location, + key: key, + isDownloads: isDownloads, + isCache: isCache, + ), + initialChildren: children, + ); static const String name = 'LocalLibraryRoute'; @@ -471,10 +419,7 @@ class LocalLibraryRouteArgs { /// [_i14.LogsPage] class LogsRoute extends _i41.PageRouteInfo { const LogsRoute({List<_i41.PageRouteInfo>? children}) - : super( - LogsRoute.name, - initialChildren: children, - ); + : super(LogsRoute.name, initialChildren: children); static const String name = 'LogsRoute'; @@ -490,10 +435,7 @@ class LogsRoute extends _i41.PageRouteInfo { /// [_i15.LyricsPage] class LyricsRoute extends _i41.PageRouteInfo { const LyricsRoute({List<_i41.PageRouteInfo>? children}) - : super( - LyricsRoute.name, - initialChildren: children, - ); + : super(LyricsRoute.name, initialChildren: children); static const String name = 'LyricsRoute'; @@ -513,13 +455,10 @@ class MiniLyricsRoute extends _i41.PageRouteInfo { required _i44.Size prevSize, List<_i41.PageRouteInfo>? children, }) : super( - MiniLyricsRoute.name, - args: MiniLyricsRouteArgs( - key: key, - prevSize: prevSize, - ), - initialChildren: children, - ); + MiniLyricsRoute.name, + args: MiniLyricsRouteArgs(key: key, prevSize: prevSize), + initialChildren: children, + ); static const String name = 'MiniLyricsRoute'; @@ -527,19 +466,13 @@ class MiniLyricsRoute extends _i41.PageRouteInfo { name, builder: (data) { final args = data.argsAs(); - return _i16.MiniLyricsPage( - key: args.key, - prevSize: args.prevSize, - ); + return _i16.MiniLyricsPage(key: args.key, prevSize: args.prevSize); }, ); } class MiniLyricsRouteArgs { - const MiniLyricsRouteArgs({ - this.key, - required this.prevSize, - }); + const MiniLyricsRouteArgs({this.key, required this.prevSize}); final _i44.Key? key; @@ -555,10 +488,7 @@ class MiniLyricsRouteArgs { /// [_i17.PlayerLyricsPage] class PlayerLyricsRoute extends _i41.PageRouteInfo { const PlayerLyricsRoute({List<_i41.PageRouteInfo>? children}) - : super( - PlayerLyricsRoute.name, - initialChildren: children, - ); + : super(PlayerLyricsRoute.name, initialChildren: children); static const String name = 'PlayerLyricsRoute'; @@ -574,10 +504,7 @@ class PlayerLyricsRoute extends _i41.PageRouteInfo { /// [_i18.PlayerQueuePage] class PlayerQueueRoute extends _i41.PageRouteInfo { const PlayerQueueRoute({List<_i41.PageRouteInfo>? children}) - : super( - PlayerQueueRoute.name, - initialChildren: children, - ); + : super(PlayerQueueRoute.name, initialChildren: children); static const String name = 'PlayerQueueRoute'; @@ -593,10 +520,7 @@ class PlayerQueueRoute extends _i41.PageRouteInfo { /// [_i19.PlayerTrackSourcesPage] class PlayerTrackSourcesRoute extends _i41.PageRouteInfo { const PlayerTrackSourcesRoute({List<_i41.PageRouteInfo>? children}) - : super( - PlayerTrackSourcesRoute.name, - initialChildren: children, - ); + : super(PlayerTrackSourcesRoute.name, initialChildren: children); static const String name = 'PlayerTrackSourcesRoute'; @@ -617,15 +541,11 @@ class PlaylistRoute extends _i41.PageRouteInfo { required _i43.SpotubeSimplePlaylistObject playlist, List<_i41.PageRouteInfo>? children, }) : super( - PlaylistRoute.name, - args: PlaylistRouteArgs( - key: key, - id: id, - playlist: playlist, - ), - rawPathParams: {'id': id}, - initialChildren: children, - ); + PlaylistRoute.name, + args: PlaylistRouteArgs(key: key, id: id, playlist: playlist), + rawPathParams: {'id': id}, + initialChildren: children, + ); static const String name = 'PlaylistRoute'; @@ -643,11 +563,7 @@ class PlaylistRoute extends _i41.PageRouteInfo { } class PlaylistRouteArgs { - const PlaylistRouteArgs({ - this.key, - required this.id, - required this.playlist, - }); + const PlaylistRouteArgs({this.key, required this.id, required this.playlist}); final _i42.Key? key; @@ -665,10 +581,7 @@ class PlaylistRouteArgs { /// [_i21.ProfilePage] class ProfileRoute extends _i41.PageRouteInfo { const ProfileRoute({List<_i41.PageRouteInfo>? children}) - : super( - ProfileRoute.name, - initialChildren: children, - ); + : super(ProfileRoute.name, initialChildren: children); static const String name = 'ProfileRoute'; @@ -684,10 +597,7 @@ class ProfileRoute extends _i41.PageRouteInfo { /// [_i22.RootAppPage] class RootAppRoute extends _i41.PageRouteInfo { const RootAppRoute({List<_i41.PageRouteInfo>? children}) - : super( - RootAppRoute.name, - initialChildren: children, - ); + : super(RootAppRoute.name, initialChildren: children); static const String name = 'RootAppRoute'; @@ -703,10 +613,7 @@ class RootAppRoute extends _i41.PageRouteInfo { /// [_i23.SearchPage] class SearchRoute extends _i41.PageRouteInfo { const SearchRoute({List<_i41.PageRouteInfo>? children}) - : super( - SearchRoute.name, - initialChildren: children, - ); + : super(SearchRoute.name, initialChildren: children); static const String name = 'SearchRoute'; @@ -728,14 +635,14 @@ class SettingsMetadataProviderFormRoute required List<_i43.MetadataFormFieldObject> fields, List<_i41.PageRouteInfo>? children, }) : super( - SettingsMetadataProviderFormRoute.name, - args: SettingsMetadataProviderFormRouteArgs( - key: key, - title: title, - fields: fields, - ), - initialChildren: children, - ); + SettingsMetadataProviderFormRoute.name, + args: SettingsMetadataProviderFormRouteArgs( + key: key, + title: title, + fields: fields, + ), + initialChildren: children, + ); static const String name = 'SettingsMetadataProviderFormRoute'; @@ -775,10 +682,7 @@ class SettingsMetadataProviderFormRouteArgs { /// [_i25.SettingsMetadataProviderPage] class SettingsMetadataProviderRoute extends _i41.PageRouteInfo { const SettingsMetadataProviderRoute({List<_i41.PageRouteInfo>? children}) - : super( - SettingsMetadataProviderRoute.name, - initialChildren: children, - ); + : super(SettingsMetadataProviderRoute.name, initialChildren: children); static const String name = 'SettingsMetadataProviderRoute'; @@ -794,10 +698,7 @@ class SettingsMetadataProviderRoute extends _i41.PageRouteInfo { /// [_i26.SettingsPage] class SettingsRoute extends _i41.PageRouteInfo { const SettingsRoute({List<_i41.PageRouteInfo>? children}) - : super( - SettingsRoute.name, - initialChildren: children, - ); + : super(SettingsRoute.name, initialChildren: children); static const String name = 'SettingsRoute'; @@ -813,10 +714,7 @@ class SettingsRoute extends _i41.PageRouteInfo { /// [_i27.SettingsScrobblingPage] class SettingsScrobblingRoute extends _i41.PageRouteInfo { const SettingsScrobblingRoute({List<_i41.PageRouteInfo>? children}) - : super( - SettingsScrobblingRoute.name, - initialChildren: children, - ); + : super(SettingsScrobblingRoute.name, initialChildren: children); static const String name = 'SettingsScrobblingRoute'; @@ -832,10 +730,7 @@ class SettingsScrobblingRoute extends _i41.PageRouteInfo { /// [_i28.StatsAlbumsPage] class StatsAlbumsRoute extends _i41.PageRouteInfo { const StatsAlbumsRoute({List<_i41.PageRouteInfo>? children}) - : super( - StatsAlbumsRoute.name, - initialChildren: children, - ); + : super(StatsAlbumsRoute.name, initialChildren: children); static const String name = 'StatsAlbumsRoute'; @@ -851,10 +746,7 @@ class StatsAlbumsRoute extends _i41.PageRouteInfo { /// [_i29.StatsArtistsPage] class StatsArtistsRoute extends _i41.PageRouteInfo { const StatsArtistsRoute({List<_i41.PageRouteInfo>? children}) - : super( - StatsArtistsRoute.name, - initialChildren: children, - ); + : super(StatsArtistsRoute.name, initialChildren: children); static const String name = 'StatsArtistsRoute'; @@ -870,10 +762,7 @@ class StatsArtistsRoute extends _i41.PageRouteInfo { /// [_i30.StatsMinutesPage] class StatsMinutesRoute extends _i41.PageRouteInfo { const StatsMinutesRoute({List<_i41.PageRouteInfo>? children}) - : super( - StatsMinutesRoute.name, - initialChildren: children, - ); + : super(StatsMinutesRoute.name, initialChildren: children); static const String name = 'StatsMinutesRoute'; @@ -889,10 +778,7 @@ class StatsMinutesRoute extends _i41.PageRouteInfo { /// [_i31.StatsPage] class StatsRoute extends _i41.PageRouteInfo { const StatsRoute({List<_i41.PageRouteInfo>? children}) - : super( - StatsRoute.name, - initialChildren: children, - ); + : super(StatsRoute.name, initialChildren: children); static const String name = 'StatsRoute'; @@ -908,10 +794,7 @@ class StatsRoute extends _i41.PageRouteInfo { /// [_i32.StatsPlaylistsPage] class StatsPlaylistsRoute extends _i41.PageRouteInfo { const StatsPlaylistsRoute({List<_i41.PageRouteInfo>? children}) - : super( - StatsPlaylistsRoute.name, - initialChildren: children, - ); + : super(StatsPlaylistsRoute.name, initialChildren: children); static const String name = 'StatsPlaylistsRoute'; @@ -927,10 +810,7 @@ class StatsPlaylistsRoute extends _i41.PageRouteInfo { /// [_i33.StatsStreamFeesPage] class StatsStreamFeesRoute extends _i41.PageRouteInfo { const StatsStreamFeesRoute({List<_i41.PageRouteInfo>? children}) - : super( - StatsStreamFeesRoute.name, - initialChildren: children, - ); + : super(StatsStreamFeesRoute.name, initialChildren: children); static const String name = 'StatsStreamFeesRoute'; @@ -946,10 +826,7 @@ class StatsStreamFeesRoute extends _i41.PageRouteInfo { /// [_i34.StatsStreamsPage] class StatsStreamsRoute extends _i41.PageRouteInfo { const StatsStreamsRoute({List<_i41.PageRouteInfo>? children}) - : super( - StatsStreamsRoute.name, - initialChildren: children, - ); + : super(StatsStreamsRoute.name, initialChildren: children); static const String name = 'StatsStreamsRoute'; @@ -969,14 +846,11 @@ class TrackRoute extends _i41.PageRouteInfo { required String trackId, List<_i41.PageRouteInfo>? children, }) : super( - TrackRoute.name, - args: TrackRouteArgs( - key: key, - trackId: trackId, - ), - rawPathParams: {'id': trackId}, - initialChildren: children, - ); + TrackRoute.name, + args: TrackRouteArgs(key: key, trackId: trackId), + rawPathParams: {'id': trackId}, + initialChildren: children, + ); static const String name = 'TrackRoute'; @@ -985,20 +859,15 @@ class TrackRoute extends _i41.PageRouteInfo { builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( - orElse: () => TrackRouteArgs(trackId: pathParams.getString('id'))); - return _i35.TrackPage( - key: args.key, - trackId: args.trackId, + orElse: () => TrackRouteArgs(trackId: pathParams.getString('id')), ); + return _i35.TrackPage(key: args.key, trackId: args.trackId); }, ); } class TrackRouteArgs { - const TrackRouteArgs({ - this.key, - required this.trackId, - }); + const TrackRouteArgs({this.key, required this.trackId}); final _i44.Key? key; @@ -1014,10 +883,7 @@ class TrackRouteArgs { /// [_i36.UserAlbumsPage] class UserAlbumsRoute extends _i41.PageRouteInfo { const UserAlbumsRoute({List<_i41.PageRouteInfo>? children}) - : super( - UserAlbumsRoute.name, - initialChildren: children, - ); + : super(UserAlbumsRoute.name, initialChildren: children); static const String name = 'UserAlbumsRoute'; @@ -1033,10 +899,7 @@ class UserAlbumsRoute extends _i41.PageRouteInfo { /// [_i37.UserArtistsPage] class UserArtistsRoute extends _i41.PageRouteInfo { const UserArtistsRoute({List<_i41.PageRouteInfo>? children}) - : super( - UserArtistsRoute.name, - initialChildren: children, - ); + : super(UserArtistsRoute.name, initialChildren: children); static const String name = 'UserArtistsRoute'; @@ -1052,10 +915,7 @@ class UserArtistsRoute extends _i41.PageRouteInfo { /// [_i38.UserDownloadsPage] class UserDownloadsRoute extends _i41.PageRouteInfo { const UserDownloadsRoute({List<_i41.PageRouteInfo>? children}) - : super( - UserDownloadsRoute.name, - initialChildren: children, - ); + : super(UserDownloadsRoute.name, initialChildren: children); static const String name = 'UserDownloadsRoute'; @@ -1071,10 +931,7 @@ class UserDownloadsRoute extends _i41.PageRouteInfo { /// [_i39.UserLocalLibraryPage] class UserLocalLibraryRoute extends _i41.PageRouteInfo { const UserLocalLibraryRoute({List<_i41.PageRouteInfo>? children}) - : super( - UserLocalLibraryRoute.name, - initialChildren: children, - ); + : super(UserLocalLibraryRoute.name, initialChildren: children); static const String name = 'UserLocalLibraryRoute'; @@ -1090,10 +947,7 @@ class UserLocalLibraryRoute extends _i41.PageRouteInfo { /// [_i40.UserPlaylistsPage] class UserPlaylistsRoute extends _i41.PageRouteInfo { const UserPlaylistsRoute({List<_i41.PageRouteInfo>? children}) - : super( - UserPlaylistsRoute.name, - initialChildren: children, - ); + : super(UserPlaylistsRoute.name, initialChildren: children); static const String name = 'UserPlaylistsRoute'; diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index b10ef7e3..99d9ff74 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -80,6 +80,7 @@ abstract class SpotubeIcons { static const hoverOff = Icons.back_hand_outlined; static const dragHandle = Icons.drag_indicator; static const lightning = Icons.flash_on_rounded; + static const lightningOutlined = FeatherIcons.zap; static const colorSync = FeatherIcons.activity; static const language = FeatherIcons.globe; static const error = FeatherIcons.alertTriangle; @@ -134,7 +135,7 @@ abstract class SpotubeIcons { static const list = FeatherIcons.list; static const device = FeatherIcons.smartphone; static const engine = FeatherIcons.server; - static const extensions = FeatherIcons.package; + static const extensions = Icons.extension_rounded; static const message = FeatherIcons.send; static const upload = FeatherIcons.uploadCloud; static const plugin = Icons.extension_outlined; diff --git a/lib/components/dialogs/link_open_permission_dialog.dart b/lib/components/dialogs/link_open_permission_dialog.dart new file mode 100644 index 00000000..a7212d0a --- /dev/null +++ b/lib/components/dialogs/link_open_permission_dialog.dart @@ -0,0 +1,69 @@ +import 'package:flutter/services.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 LinkOpenPermissionDialog extends StatelessWidget { + final String? href; + const LinkOpenPermissionDialog({super.key, this.href}); + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 450), + child: AlertDialog( + title: Row( + spacing: 8, + children: [ + const Icon(SpotubeIcons.warning), + Text(context.l10n.open_link_in_browser), + ], + ), + content: Text.rich( + TextSpan( + children: [ + 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), + ), + TextSpan(text: context.l10n.unsafe_url_warning), + ], + ), + ), + actions: [ + Button.ghost( + onPressed: () => Navigator.of(context).pop(false), + child: Text(context.l10n.cancel), + ), + Button.ghost( + onPressed: () { + if (href != null) { + Clipboard.setData(ClipboardData(text: href!)); + } + Navigator.of(context).pop(false); + }, + child: Text(context.l10n.copy_link), + ), + Button.destructive( + onPressed: () { + if (href != null) { + launchUrlString( + href!, + mode: LaunchMode.externalApplication, + ); + } + Navigator.of(context).pop(true); + }, + child: Text(context.l10n.open), + ), + ], + ), + ); + } +} diff --git a/lib/components/dialogs/replace_downloaded_dialog.dart b/lib/components/dialogs/replace_downloaded_dialog.dart index 6634a039..5b5b194e 100644 --- a/lib/components/dialogs/replace_downloaded_dialog.dart +++ b/lib/components/dialogs/replace_downloaded_dialog.dart @@ -12,13 +12,12 @@ class ReplaceDownloadedDialog extends ConsumerWidget { @override Widget build(BuildContext context, ref) { - final groupValue = ref.watch(replaceDownloadedFileState); final replaceAll = ref.watch(replaceDownloadedFileState); return AlertDialog( title: Text(context.l10n.track_exists(track.name)), content: RadioGroup( - value: groupValue, + value: replaceAll, onChanged: (value) { ref.read(replaceDownloadedFileState.notifier).state = value; }, diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index 3d3fd7e9..9d35a6fb 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -7,8 +7,7 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/server/track_sources.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; class TrackDetailsDialog extends HookConsumerWidget { final SpotubeFullTrackObject track; @@ -21,8 +20,7 @@ class TrackDetailsDialog extends HookConsumerWidget { Widget build(BuildContext context, ref) { final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); - final sourcedTrack = - ref.read(trackSourcesProvider(TrackSourceQuery.fromTrack(track))); + final sourcedTrack = ref.read(sourcedTrackProvider(track)); final detailsMap = { context.l10n.title: track.name, @@ -39,8 +37,7 @@ class TrackDetailsDialog extends HookConsumerWidget { // style: const TextStyle(color: Colors.blue), // ), context.l10n.duration: sourcedTrack.asData != null - ? Duration(milliseconds: sourcedTrack.asData!.value.info.durationMs) - .toHumanReadableString() + ? sourcedTrack.asData!.value.info.duration.toHumanReadableString() : Duration(milliseconds: track.durationMs).toHumanReadableString(), if (track.album.releaseDate != null) context.l10n.released: track.album.releaseDate, @@ -57,7 +54,7 @@ class TrackDetailsDialog extends HookConsumerWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - context.l10n.channel: Text(sourceInfo.artists), + context.l10n.channel: Text(sourceInfo.artists.join(", ")), if (sourcedTrack.asData?.value.url != null) context.l10n.streamUrl: Hyperlink( sourcedTrack.asData!.value.url ?? "", diff --git a/lib/components/markdown/markdown.dart b/lib/components/markdown/markdown.dart index 9ea2e77c..1fd4ac5b 100644 --- a/lib/components/markdown/markdown.dart +++ b/lib/components/markdown/markdown.dart @@ -1,9 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; -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:spotube/components/dialogs/link_open_permission_dialog.dart'; import 'package:url_launcher/url_launcher_string.dart'; class AppMarkdown extends StatelessWidget { @@ -28,61 +26,7 @@ class AppMarkdown extends StatelessWidget { final allowOpeningLink = await showDialog( context: context, builder: (context) { - return ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 450), - child: AlertDialog( - title: Row( - spacing: 8, - children: [ - const Icon(SpotubeIcons.warning), - Text(context.l10n.open_link_in_browser), - ], - ), - content: Text.rich( - TextSpan( - children: [ - 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), - ), - TextSpan(text: context.l10n.unsafe_url_warning), - ], - ), - ), - actions: [ - Button.ghost( - onPressed: () => Navigator.of(context).pop(false), - child: Text(context.l10n.cancel), - ), - Button.ghost( - onPressed: () { - if (href != null) { - Clipboard.setData(ClipboardData(text: href)); - } - Navigator.of(context).pop(false); - }, - child: Text(context.l10n.copy_link), - ), - Button.destructive( - onPressed: () { - if (href != null) { - launchUrlString( - href, - mode: LaunchMode.externalApplication, - ); - } - Navigator.of(context).pop(true); - }, - child: Text(context.l10n.open), - ), - ], - ), - ); + return LinkOpenPermissionDialog(href: href); }, ); diff --git a/lib/components/track_presentation/presentation_actions.dart b/lib/components/track_presentation/presentation_actions.dart index 735a4514..61202a48 100644 --- a/lib/components/track_presentation/presentation_actions.dart +++ b/lib/components/track_presentation/presentation_actions.dart @@ -8,12 +8,10 @@ import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/presentation_state.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; ToastOverlay showToastForAction( BuildContext context, @@ -70,8 +68,6 @@ class TrackPresentationActionsSection extends HookConsumerWidget { final downloader = ref.watch(downloadManagerProvider.notifier); final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final historyNotifier = ref.watch(playbackHistoryActionsProvider); - final audioSource = - ref.watch(userPreferencesProvider.select((s) => s.audioSource)); final state = ref.watch(presentationStateProvider(options.collection)); final notifier = @@ -85,16 +81,15 @@ class TrackPresentationActionsSection extends HookConsumerWidget { }) async { final fullTrackObjects = tracks.whereType().toList(); - final confirmed = audioSource == AudioSource.piped || - (await showDialog( - context: context, - builder: (context) { - return const ConfirmDownloadDialog(); - }, - ) ?? - false); + final confirmed = await showDialog( + context: context, + builder: (context) { + return const ConfirmDownloadDialog(); + }, + ) ?? + false; if (confirmed != true) return; - downloader.batchAddToQueue(fullTrackObjects); + downloader.addAllToQueue(fullTrackObjects); notifier.deselectAllTracks(); if (!context.mounted) return; showToastForAction(context, action, fullTrackObjects.length); diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 7943fe3d..7d14493e 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -1,10 +1,8 @@ -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.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/constrains.dart'; @@ -35,7 +33,6 @@ class TrackOptions extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final mediaQuery = MediaQuery.of(context); - final ThemeData(:colorScheme) = Theme.of(context); final trackOptionActions = ref.watch(trackOptionActionsProvider(track)); final ( @@ -45,7 +42,7 @@ class TrackOptions extends HookConsumerWidget { :isActiveTrack, :isAuthenticated, :isLiked, - :progressNotifier + :downloadTask ) = ref.watch(trackOptionsStateProvider(track)); final isLocalTrack = track is SpotubeLocalTrackObject; @@ -59,7 +56,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.delete, playlistId, ); @@ -73,7 +70,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.album, playlistId, ); @@ -97,7 +94,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.addToQueue, playlistId, ); @@ -110,7 +107,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.playNext, playlistId, ); @@ -124,7 +121,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.removeFromQueue, playlistId, ); @@ -139,7 +136,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.favorite, playlistId, ); @@ -162,7 +159,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.startRadio, playlistId, ); @@ -175,7 +172,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.addToPlaylist, playlistId, ); @@ -190,7 +187,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.removeFromPlaylist, playlistId, ); @@ -204,7 +201,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.download, playlistId, ); @@ -212,12 +209,19 @@ class TrackOptions extends HookConsumerWidget { }, enabled: !isInDownloadQueue, leading: isInDownloadQueue - ? HookBuilder(builder: (context) { - final progress = useListenable(progressNotifier); - return CircularProgressIndicator( - value: progress?.value, - ); - }) + ? StreamBuilder( + stream: downloadTask?.downloadedBytesStream, + builder: (context, snapshot) { + final progress = downloadTask?.totalSizeBytes == null || + downloadTask?.totalSizeBytes == 0 + ? 0 + : (snapshot.data ?? 0) / + downloadTask!.totalSizeBytes!; + return CircularProgressIndicator( + value: progress.toDouble(), + ); + }, + ) : const Icon(SpotubeIcons.download), title: Text(context.l10n.download_track), ), @@ -226,7 +230,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.blacklist, playlistId, ); @@ -250,7 +254,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.share, playlistId, ); @@ -264,25 +268,7 @@ class TrackOptions extends HookConsumerWidget { style: ButtonVariance.menu, onPressed: () async { await trackOptionActions.action( - context, - TrackOptionValue.songlink, - playlistId, - ); - onTapItem?.call(); - }, - leading: Assets.images.logos.songlinkTransparent.image( - width: 22, - height: 22, - color: colorScheme.foreground.withValues(alpha: 0.5), - ), - title: Text(context.l10n.song_link), - ), - if (!isLocalTrack) - ButtonTile( - style: ButtonVariance.menu, - onPressed: () async { - await trackOptionActions.action( - context, + rootNavigatorKey.currentContext!, TrackOptionValue.details, playlistId, ); diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index 955ac90d..ec3f50f3 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -39,6 +39,7 @@ class TrackTile extends HookConsumerWidget { final int? index; final SpotubeTrackObject track; final bool selected; + final bool selectionMode; final ValueChanged? onChanged; final Future Function()? onTap; final VoidCallback? onLongPress; @@ -53,6 +54,7 @@ class TrackTile extends HookConsumerWidget { this.index, required this.track, this.selected = false, + this.selectionMode = false, required this.playlist, this.onTap, this.onLongPress, @@ -81,6 +83,12 @@ class TrackTile extends HookConsumerWidget { [track.album.images], ); + // Treat either explicit selectionMode or presence of onChanged as selection + // context. Some lists enable selection by providing `onChanged` without + // toggling a dedicated `selectionMode` flag (e.g. playlists), so we must + // disable inner navigation in both cases. + final effectiveSelection = selectionMode || onChanged != null; + return LayoutBuilder(builder: (context, constrains) { return Listener( onPointerDown: (event) { @@ -222,7 +230,9 @@ class TrackTile extends HookConsumerWidget { children: [ Expanded( flex: 6, - child: switch (track) { + child: AbsorbPointer( + absorbing: selectionMode, + child: switch (track) { SpotubeLocalTrackObject() => Text( track.name, maxLines: 1, @@ -232,15 +242,17 @@ class TrackTile extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ Flexible( - child: Button( - style: ButtonVariance.link.copyWith( - padding: (context, states, value) => - EdgeInsets.zero, - ), - onPressed: () { - context - .navigateTo(TrackRoute(trackId: track.id)); - }, + child: Button( + style: ButtonVariance.link.copyWith( + padding: (context, states, value) => + EdgeInsets.zero, + ), + onPressed: effectiveSelection + ? null + : () { + context + .navigateTo(TrackRoute(trackId: track.id)); + }, child: Text( track.name, maxLines: 1, @@ -251,6 +263,7 @@ class TrackTile extends HookConsumerWidget { ], ), }, + ), ), if (constrains.mdAndUp) ...[ const SizedBox(width: 8), @@ -281,20 +294,25 @@ class TrackTile extends HookConsumerWidget { ), subtitle: Align( alignment: Alignment.centerLeft, - child: track is SpotubeLocalTrackObject + child: track is SpotubeLocalTrackObject ? Text( track.artists.asString(), ) : ClipRect( child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 40), - child: ArtistLink( - artists: track.artists, - onOverflowArtistClick: () { - context.navigateTo( - TrackRoute(trackId: track.id), - ); - }, + child: AbsorbPointer( + absorbing: effectiveSelection, + child: ArtistLink( + artists: track.artists, + onOverflowArtistClick: effectiveSelection + ? () {} + : () { + context.navigateTo( + TrackRoute(trackId: track.id), + ); + }, + ), ), ), ), diff --git a/lib/extensions/dio.dart b/lib/extensions/dio.dart new file mode 100644 index 00000000..81bb1e70 --- /dev/null +++ b/lib/extensions/dio.dart @@ -0,0 +1,168 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; + +extension ChunkDownloaderDioExtension on Dio { + Future chunkDownload( + String urlPath, + dynamic savePath, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + bool deleteOnError = true, + FileAccessMode fileAccessMode = FileAccessMode.write, + String lengthHeader = Headers.contentLengthHeader, + Object? data, + Options? options, + int connections = 4, + }) async { + final targetFile = File(savePath.toString()); + final tempRootDir = await getTemporaryDirectory(); + final tempSaveDir = Directory( + join( + tempRootDir.path, + 'Spotube', + '.chunk_dl_${targetFile.uri.pathSegments.last}', + ), + ); + if (await tempSaveDir.exists()) await tempSaveDir.delete(recursive: true); + await tempSaveDir.create(recursive: true); + + try { + int? totalLength; + bool supportsRange = false; + + Response? headResp; + try { + headResp = await head( + urlPath, + queryParameters: queryParameters, + options: Options( + headers: {'Range': 'bytes=0-0'}, + followRedirects: true, + ), + ); + } catch (_) { + // Some servers reject HEAD -> ignore + } + + final lengthStr = headResp?.headers[lengthHeader]?.first; + if (lengthStr != null) { + final parsed = int.tryParse(lengthStr); + if (parsed != null && parsed > 1) { + totalLength = parsed; + } + } + + supportsRange = headResp?.statusCode == 206 || + headResp?.headers.value(HttpHeaders.acceptRangesHeader) == 'bytes'; + + if (totalLength == null || totalLength <= 1) { + final resp = await get( + urlPath, + options: Options( + responseType: ResponseType.stream, + ), + queryParameters: queryParameters, + cancelToken: cancelToken, + ); + + final len = int.tryParse(resp.headers[lengthHeader]?.first ?? ''); + if (len == null || len <= 1) { + // can’t safely chunk — fallback + return download( + urlPath, + savePath, + onReceiveProgress: onReceiveProgress, + queryParameters: queryParameters, + cancelToken: cancelToken, + deleteOnError: deleteOnError, + options: options, + data: data, + ); + } + + totalLength = len; + supportsRange = + resp.headers.value(HttpHeaders.acceptRangesHeader)?.toLowerCase() == + 'bytes'; + } + + if (!supportsRange || connections <= 1) { + return download( + urlPath, + savePath, + onReceiveProgress: onReceiveProgress, + queryParameters: queryParameters, + cancelToken: cancelToken, + deleteOnError: deleteOnError, + options: options, + data: data, + ); + } + + final chunkSize = (totalLength / connections).ceil(); + int downloaded = 0; + + final partFiles = List.generate( + connections, + (i) => File(join(tempSaveDir.path, 'part_$i')), + ); + + final futures = List.generate(connections, (i) async { + final start = i * chunkSize; + final end = (i + 1) * chunkSize - 1; + if (start >= totalLength!) return; + + final resp = await get( + urlPath, + options: Options( + responseType: ResponseType.stream, + headers: {'Range': 'bytes=$start-$end'}, + ), + queryParameters: queryParameters, + cancelToken: cancelToken, + ); + + final file = partFiles[i]; + if (await file.exists()) await file.delete(); + await file.create(recursive: true); + final sink = file.openWrite(); + + await for (final chunk in resp.data!.stream) { + sink.add(chunk); + downloaded += chunk.length; + onReceiveProgress?.call(downloaded, totalLength); + } + + await sink.close(); + }); + + await Future.wait(futures); + + final targetSink = targetFile.openWrite(); + for (final f in partFiles) { + await targetSink.addStream(f.openRead()); + } + await targetSink.close(); + + await tempSaveDir.delete(recursive: true); + + return Response( + requestOptions: RequestOptions(path: urlPath), + data: targetFile, + statusCode: 200, + statusMessage: 'Chunked download completed ($connections connections)', + ); + } catch (e) { + if (deleteOnError) { + if (await targetFile.exists()) await targetFile.delete(); + if (await tempSaveDir.exists()) { + await tempSaveDir.delete(recursive: true); + } + } + rethrow; + } + } +} diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 99af2097..f1997517 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -477,5 +477,18 @@ "available_plugins": "الإضافات المتوفّرة", "configure_your_own_metadata_plugin": "تهيئة مزوّد بيانات للقائمة/الألبوم/الفنان/المصدر خاص بك", "audio_scrobblers": "أجهزة تتبع الصوت", - "scrobbling": "التتبع" + "scrobbling": "التتبع", + "download_music_format": "تنسيق تنزيل الموسيقى", + "streaming_music_format": "تنسيق بث الموسيقى", + "download_music_quality": "جودة تنزيل الموسيقى", + "streaming_music_quality": "جودة بث الموسيقى", + "default_metadata_source": "مصدر البيانات الوصفية الافتراضي", + "set_default_metadata_source": "تعيين مصدر البيانات الوصفية الافتراضي", + "default_audio_source": "مصدر الصوت الافتراضي", + "set_default_audio_source": "تعيين مصدر الصوت الافتراضي", + "plugins": "الإضافات", + "configure_plugins": "قم بتكوين مزود البيانات الوصفية ومكونات مصدر الصوت الخاصة بك", + "source": "المصدر: ", + "uncompressed": "غير مضغوط", + "dab_music_source_description": "لمحبي الصوتيات. يوفر تدفقات صوتية عالية الجودة/بدون فقدان. مطابقة دقيقة للمسارات بناءً على ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index 222e28b4..4d001da1 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -477,5 +477,18 @@ "available_plugins": "উপলব্ধ প্লাগইনগুলো", "configure_your_own_metadata_plugin": "নিজস্ব প্লেলিস্ট/অ্যালবাম/শিল্পী/ফিড মেটাডেটা প্রদানকারী কনফিগার করুন", "audio_scrobblers": "অডিও স্ক্রোব্বলার্স", - "scrobbling": "স্ক্রোব্বলিং" + "scrobbling": "স্ক্রোব্বলিং", + "download_music_format": "গান ডাউনলোডের বিন্যাস", + "streaming_music_format": "গান স্ট্রিমিং এর বিন্যাস", + "download_music_quality": "গান ডাউনলোডের মান", + "streaming_music_quality": "গান স্ট্রিমিং এর মান", + "default_metadata_source": "ডিফল্ট মেটাডেটা উৎস", + "set_default_metadata_source": "ডিফল্ট মেটাডেটা উৎস সেট করুন", + "default_audio_source": "ডিফল্ট অডিও উৎস", + "set_default_audio_source": "ডিফল্ট অডিও উৎস সেট করুন", + "plugins": "প্লাগইন", + "configure_plugins": "আপনার নিজের মেটাডেটা প্রদানকারী এবং অডিও উৎস প্লাগইন কনফিগার করুন", + "source": "উৎস: ", + "uncompressed": "অ-সংকুচিত", + "dab_music_source_description": "অডিওফাইলদের জন্য। উচ্চ-মানের/লসলেস অডিও স্ট্রিম প্রদান করে। সঠিক ISRC ভিত্তিক ট্র্যাক ম্যাচিং।" } \ No newline at end of file diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb index 0482468b..06ed7ec6 100644 --- a/lib/l10n/app_ca.arb +++ b/lib/l10n/app_ca.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Format de descàrrega de música", + "streaming_music_format": "Format de reproducció de música en temps real", + "download_music_quality": "Qualitat de descàrrega de música", + "streaming_music_quality": "Qualitat de reproducció de música en temps real", + "default_metadata_source": "Font de metadades per defecte", + "set_default_metadata_source": "Estableix la font de metadades per defecte", + "default_audio_source": "Font d'àudio per defecte", + "set_default_audio_source": "Estableix la font d'àudio per defecte", + "plugins": "Connectors", + "configure_plugins": "Configura els teus propis connectors de proveïdor de metadades i de font d'àudio", + "source": "Font: ", + "uncompressed": "Sense comprimir", + "dab_music_source_description": "Per als audiòfils. Ofereix fluxos d'àudio d'alta qualitat/sense pèrdua. Coincidència precisa de pistes basada en ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index d5d0f7af..59938004 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Formát stahování hudby", + "streaming_music_format": "Formát streamování hudby", + "download_music_quality": "Kvalita stahování hudby", + "streaming_music_quality": "Kvalita streamování hudby", + "default_metadata_source": "Výchozí zdroj metadat", + "set_default_metadata_source": "Nastavit výchozí zdroj metadat", + "default_audio_source": "Výchozí zdroj zvuku", + "set_default_audio_source": "Nastavit výchozí zdroj zvuku", + "plugins": "Pluginy", + "configure_plugins": "Konfigurujte své vlastní pluginy poskytovatele metadat a zdroje zvuku", + "source": "Zdroj: ", + "uncompressed": "Nekomprimováno", + "dab_music_source_description": "Pro audiofily. Poskytuje vysoce kvalitní/bezztrátové zvukové toky. Přesná shoda skladeb na základě ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 8ef22fac..458e7c07 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Musik-Downloadformat", + "streaming_music_format": "Musik-Streamingformat", + "download_music_quality": "Musik-Downloadqualität", + "streaming_music_quality": "Musik-Streamingqualität", + "default_metadata_source": "Standard-Metadatenquelle", + "set_default_metadata_source": "Standard-Metadatenquelle festlegen", + "default_audio_source": "Standard-Audioquelle", + "set_default_audio_source": "Standard-Audioquelle festlegen", + "plugins": "Plugins", + "configure_plugins": "Richte deine eigenen Metadatenanbieter- und Audioquellen-Plugins ein", + "source": "Quelle: ", + "uncompressed": "Unkomprimiert", + "dab_music_source_description": "Für Audiophile. Bietet hochwertige/verlustfreie Audiostreams. Präzises ISRC-basiertes Track-Matching." } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 34b56489..111d76a8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -264,8 +264,10 @@ "change_cover": "Change cover", "add_cover": "Add cover", "restore_defaults": "Restore defaults", - "download_music_codec": "Download music codec", - "streaming_music_codec": "Streaming music codec", + "download_music_format": "Download music format", + "streaming_music_format": "Streaming music format", + "download_music_quality": "Download music quality", + "streaming_music_quality": "Streaming music quality", "login_with_lastfm": "Login with Last.fm", "connect": "Connect", "disconnect_lastfm": "Disconnect Last.fm", @@ -434,7 +436,10 @@ "update_available": "Update available", "supports_scrobbling": "Supports scrobbling", "plugin_scrobbling_info": "This plugin scrobbles your music to generate your listening history.", - "default_plugin": "Default", + "default_metadata_source": "Default metadata source", + "set_default_metadata_source": "Set default metadata source", + "default_audio_source": "Default audio source", + "set_default_audio_source": "Set default audio source", "set_default": "Set default", "support": "Support", "support_plugin_development": "Support plugin development", @@ -452,14 +457,17 @@ "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", + "plugins": "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", + "configure_plugins": "Configure your own metadata provider and audio source plugins", "audio_scrobblers": "Audio Scrobblers", - "scrobbling": "Scrobbling" + "scrobbling": "Scrobbling", + "source": "Source: ", + "uncompressed": "Uncompressed", + "dab_music_source_description": "For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching." } diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 8321f2f5..32822763 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Formato de descarga de música", + "streaming_music_format": "Formato de transmisión de música", + "download_music_quality": "Calidad de descarga de música", + "streaming_music_quality": "Calidad de transmisión de música", + "default_metadata_source": "Fuente de metadatos predeterminada", + "set_default_metadata_source": "Establecer fuente de metadatos predeterminada", + "default_audio_source": "Fuente de audio predeterminada", + "set_default_audio_source": "Establecer fuente de audio predeterminada", + "plugins": "Plugins", + "configure_plugins": "Configura tus propios plugins de proveedor de metadatos y fuente de audio", + "source": "Fuente: ", + "uncompressed": "Sin comprimir", + "dab_music_source_description": "Para audiófilos. Proporciona transmisiones de audio de alta calidad/sin pérdida. Coincidencia precisa de pistas basada en ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_eu.arb b/lib/l10n/app_eu.arb index 0b49bea1..8c87fd2c 100644 --- a/lib/l10n/app_eu.arb +++ b/lib/l10n/app_eu.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Musika deskargatzeko formatua", + "streaming_music_format": "Musika streaming bidezko formatua", + "download_music_quality": "Musika deskargaren kalitatea", + "streaming_music_quality": "Streaming bidezko musika kalitatea", + "default_metadata_source": "Metadatu-iturburu lehenetsia", + "set_default_metadata_source": "Ezarri metadatu-iturburu lehenetsia", + "default_audio_source": "Audio-iturburu lehenetsia", + "set_default_audio_source": "Ezarri audio-iturburu lehenetsia", + "plugins": "Pluginak", + "configure_plugins": "Konfiguratu zure metadatu-hornitzaile eta audio-iturburu pluginak", + "source": "Iturburua: ", + "uncompressed": "Konprimitu gabea", + "dab_music_source_description": "Audiozaleentzat. Kalitate handiko/galerarik gabeko audio-streamak eskaintzen ditu. ISRC oinarritutako pistaren parekatze zehatza." } \ No newline at end of file diff --git a/lib/l10n/app_fa.arb b/lib/l10n/app_fa.arb index 73bb4d48..72f775fc 100644 --- a/lib/l10n/app_fa.arb +++ b/lib/l10n/app_fa.arb @@ -477,5 +477,18 @@ "available_plugins": "افزونه‌های موجود", "configure_your_own_metadata_plugin": "پیکربندی ارائه‌دهندهٔ متادیتا برای پلی‌لیست/آلبوم/هنرمند/فید به‌صورت سفارشی", "audio_scrobblers": "اسکراب‌بلرهای صوتی", - "scrobbling": "اسکراب‌بلینگ" + "scrobbling": "اسکراب‌بلینگ", + "download_music_format": "فرمت دانلود موسیقی", + "streaming_music_format": "فرمت پخش آنلاین موسیقی", + "download_music_quality": "کیفیت دانلود موسیقی", + "streaming_music_quality": "کیفیت پخش آنلاین موسیقی", + "default_metadata_source": "منبع پیش‌فرض فراداده", + "set_default_metadata_source": "تنظیم منبع پیش‌فرض فراداده", + "default_audio_source": "منبع پیش‌فرض صوت", + "set_default_audio_source": "تنظیم منبع پیش‌فرض صوت", + "plugins": "افزونه‌ها", + "configure_plugins": "افزونه‌های منبع صوت و ارائه‌دهنده فراداده خود را پیکربندی کنید", + "source": "منبع: ", + "uncompressed": "بدون فشرده‌سازی", + "dab_music_source_description": "مخصوص علاقه‌مندان صدا. ارائه‌دهنده استریم‌های باکیفیت/بدون افت. تطبیق دقیق آهنگ بر اساس ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_fi.arb b/lib/l10n/app_fi.arb index 93c6c577..d92e5acf 100644 --- a/lib/l10n/app_fi.arb +++ b/lib/l10n/app_fi.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Musiikin latausmuoto", + "streaming_music_format": "Musiikin suoratoistomuoto", + "download_music_quality": "Musiikin latauslaatu", + "streaming_music_quality": "Musiikin suoratoistolaadun", + "default_metadata_source": "Oletusarvoinen metatietolähde", + "set_default_metadata_source": "Aseta oletusmetatietolähde", + "default_audio_source": "Oletusarvoinen äänilähde", + "set_default_audio_source": "Aseta oletusäänilähde", + "plugins": "Laajennukset", + "configure_plugins": "Määritä omat metatietojen tarjoaja- ja äänilähdelaajennukset", + "source": "Lähde: ", + "uncompressed": "Pakkaamaton", + "dab_music_source_description": "Audiofiileille. Tarjoaa korkealaatuisia/häviöttömiä äänivirtoja. Tarkka ISRC-pohjainen kappaleiden tunnistus." } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 1851dbe1..e73c2eb2 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -478,5 +478,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Format de téléchargement de musique", + "streaming_music_format": "Format de streaming de musique", + "download_music_quality": "Qualité de téléchargement de musique", + "streaming_music_quality": "Qualité de streaming de musique", + "default_metadata_source": "Source de métadonnées par défaut", + "set_default_metadata_source": "Définir la source de métadonnées par défaut", + "default_audio_source": "Source audio par défaut", + "set_default_audio_source": "Définir la source audio par défaut", + "plugins": "Plugins", + "configure_plugins": "Configurez vos propres plugins de fournisseur de métadonnées et de source audio", + "source": "Source : ", + "uncompressed": "Non compressé", + "dab_music_source_description": "Pour les audiophiles. Fournit des flux audio de haute qualité/sans perte. Correspondance précise des pistes basée sur ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index d0c6ba36..8e8087bb 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -477,5 +477,18 @@ "available_plugins": "उपलब्ध प्लगइन", "configure_your_own_metadata_plugin": "अपनी खुद की प्लेलिस्ट/एल्बम/कलाकार/फ़ीड मेटाडेटा प्रदाता कॉन्फ़िगर करें", "audio_scrobblers": "ऑडियो स्क्रॉबलर्स", - "scrobbling": "स्क्रॉबलिंग" + "scrobbling": "स्क्रॉबलिंग", + "download_music_format": "संगीत डाउनलोड प्रारूप", + "streaming_music_format": "संगीत स्ट्रीमिंग प्रारूप", + "download_music_quality": "संगीत डाउनलोड गुणवत्ता", + "streaming_music_quality": "संगीत स्ट्रीमिंग गुणवत्ता", + "default_metadata_source": "डिफ़ॉल्ट मेटाडेटा स्रोत", + "set_default_metadata_source": "डिफ़ॉल्ट मेटाडेटा स्रोत सेट करें", + "default_audio_source": "डिफ़ॉल्ट ऑडियो स्रोत", + "set_default_audio_source": "डिफ़ॉल्ट ऑडियो स्रोत सेट करें", + "plugins": "प्लगइन्स", + "configure_plugins": "अपने स्वयं के मेटाडेटा प्रदाता और ऑडियो स्रोत प्लगइन्स कॉन्फ़िगर करें", + "source": "स्रोत: ", + "uncompressed": "असंपीड़ित", + "dab_music_source_description": "ऑडियोफाइलों के लिए। उच्च-गुणवत्ता/बिना हानि वाले ऑडियो स्ट्रीम प्रदान करता है। सटीक ISRC आधारित ट्रैक मिलान।" } \ No newline at end of file diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb index 8ab234a7..3405fd2f 100644 --- a/lib/l10n/app_id.arb +++ b/lib/l10n/app_id.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Format unduh musik", + "streaming_music_format": "Format streaming musik", + "download_music_quality": "Kualitas unduh musik", + "streaming_music_quality": "Kualitas streaming musik", + "default_metadata_source": "Sumber metadata default", + "set_default_metadata_source": "Atur sumber metadata default", + "default_audio_source": "Sumber audio default", + "set_default_audio_source": "Atur sumber audio default", + "plugins": "Plugin", + "configure_plugins": "Konfigurasi plugin penyedia metadata dan sumber audio Anda sendiri", + "source": "Sumber: ", + "uncompressed": "Tidak terkompresi", + "dab_music_source_description": "Untuk audiophile. Menyediakan aliran audio berkualitas tinggi/tanpa kehilangan. Pencocokkan trek yang akurat berdasarkan ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index f489db5d..c544dbf3 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -478,5 +478,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Formato download musica", + "streaming_music_format": "Formato streaming musica", + "download_music_quality": "Qualità download musica", + "streaming_music_quality": "Qualità streaming musica", + "default_metadata_source": "Fonte metadati predefinita", + "set_default_metadata_source": "Imposta fonte metadati predefinita", + "default_audio_source": "Fonte audio predefinita", + "set_default_audio_source": "Imposta fonte audio predefinita", + "plugins": "Plugin", + "configure_plugins": "Configura i tuoi plugin per fornitore metadati e fonte audio", + "source": "Fonte: ", + "uncompressed": "Non compresso", + "dab_music_source_description": "Per audiophile. Fornisce flussi audio di alta qualità/senza perdita. Abbinamento traccia accurato basato su ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 88fc51c3..991f56be 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -476,5 +476,18 @@ "available_plugins": "利用可能なプラグイン", "configure_your_own_metadata_plugin": "独自のプレイリスト/アルバム/アーティスト/フィードのメタデータプロバイダーを構成", "audio_scrobblers": "オーディオスクロッブラー", - "scrobbling": "Scrobbling" -} + "scrobbling": "Scrobbling", + "download_music_format": "音楽ダウンロード形式", + "streaming_music_format": "音楽ストリーミング形式", + "download_music_quality": "音楽ダウンロード品質", + "streaming_music_quality": "音楽ストリーミング品質", + "default_metadata_source": "デフォルトメタデータソース", + "set_default_metadata_source": "デフォルトメタデータソースを設定", + "default_audio_source": "デフォルトオーディオソース", + "set_default_audio_source": "デフォルトオーディオソースを設定", + "plugins": "プラグイン", + "configure_plugins": "独自のメタデータプロバイダーとオーディオソースプラグインを設定", + "source": "ソース: ", + "uncompressed": "非圧縮", + "dab_music_source_description": "オーディオファイル向け。高品質/ロスレスオーディオストリームを提供。正確なISRCベースのトラックマッチング。" +} \ No newline at end of file diff --git a/lib/l10n/app_ka.arb b/lib/l10n/app_ka.arb index 7aaa5cfb..6a0cb06c 100644 --- a/lib/l10n/app_ka.arb +++ b/lib/l10n/app_ka.arb @@ -477,5 +477,18 @@ "available_plugins": "ხელმისაწვდომი პლაგინები", "configure_your_own_metadata_plugin": "დააყენეთ თქვენი საკუთარი პლეილისტის/ალბომის/არტისტის/ფიდის მეტამონაცემების პროვაიდერი", "audio_scrobblers": "აუდიო სქრობლერები", - "scrobbling": "სქრობლინგი" + "scrobbling": "სქრობლინგი", + "download_music_format": "მუსიკის ჩამოტვირთვის ფორმატი", + "streaming_music_format": "სტრიმინგის მუსიკის ფორმატი", + "download_music_quality": "ჩამოტვირთვის ხარისხი", + "streaming_music_quality": "სტრიმინგის ხარისხი", + "default_metadata_source": "ნაგულისხმევი მეტამონაცემების წყარო", + "set_default_metadata_source": "ნაგულისხმევი მეტამონაცემების წყაროს დაყენება", + "default_audio_source": "ნაგულისხმევი აუდიო წყარო", + "set_default_audio_source": "ნაგულისხმევი აუდიო წყაროს დაყენება", + "plugins": "პლაგინები", + "configure_plugins": "თქვენი საკუთარი მეტამონაცემებისა და აუდიო წყაროს პლაგინების კონფიგურაცია", + "source": "წყარო: ", + "uncompressed": "შეუკუმშავი", + "dab_music_source_description": "აუდიოფილებისთვის. უზრუნველყოფს მაღალი ხარისხის/უკომპრესო აუდიო სტრიმებს. ზუსტი შესაბამისობა ISRC-ის მიხედვით." } \ No newline at end of file diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index b38e35c5..70a68f8b 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -478,5 +478,18 @@ "available_plugins": "사용 가능한 플러그인", "configure_your_own_metadata_plugin": "자신만의 플레이리스트/앨범/아티스트/피드 메타데이터 제공자 구성", "audio_scrobblers": "오디오 스크로블러", - "scrobbling": "스크로블링" + "scrobbling": "스크로블링", + "download_music_format": "다운로드 음악 포맷", + "streaming_music_format": "스트리밍 음악 포맷", + "download_music_quality": "다운로드 음질", + "streaming_music_quality": "스트리밍 음질", + "default_metadata_source": "기본 메타데이터 소스", + "set_default_metadata_source": "기본 메타데이터 소스 설정", + "default_audio_source": "기본 오디오 소스", + "set_default_audio_source": "기본 오디오 소스 설정", + "plugins": "플러그인", + "configure_plugins": "직접 메타데이터 제공자와 오디오 소스 플러그인을 구성하세요", + "source": "출처: ", + "uncompressed": "비압축", + "dab_music_source_description": "오디오파일을 위한 소스입니다. 고음질/무손실 오디오 스트림을 제공하며 ISRC 기반으로 정확한 트랙 매칭을 지원합니다." } \ No newline at end of file diff --git a/lib/l10n/app_ne.arb b/lib/l10n/app_ne.arb index 1da053c4..874c28a5 100644 --- a/lib/l10n/app_ne.arb +++ b/lib/l10n/app_ne.arb @@ -477,5 +477,18 @@ "available_plugins": "उपलब्ध प्लगइनहरू", "configure_your_own_metadata_plugin": "तपाईंको आफ्नै प्लेलिस्ट/एल्बम/कलाकार/फिड मेटाडेटा प्रदायक कन्फिगर गर्नुहोस्", "audio_scrobblers": "अडियो स्क्रब्बलरहरू", - "scrobbling": "स्क्रब्बलिंग" + "scrobbling": "स्क्रब्बलिंग", + "download_music_format": "सङ्गीत डाउनलोड ढाँचा", + "streaming_music_format": "स्ट्रिमिङ सङ्गीत ढाँचा", + "download_music_quality": "डाउनलोड गुणस्तर", + "streaming_music_quality": "स्ट्रिमिङ गुणस्तर", + "default_metadata_source": "पूर्वनिर्धारित मेटाडाटा स्रोत", + "set_default_metadata_source": "पूर्वनिर्धारित मेटाडाटा स्रोत सेट गर्नुहोस्", + "default_audio_source": "पूर्वनिर्धारित अडियो स्रोत", + "set_default_audio_source": "पूर्वनिर्धारित अडियो स्रोत सेट गर्नुहोस्", + "plugins": "प्लगइनहरू", + "configure_plugins": "आफ्नै मेटाडाटा प्रदायक र अडियो स्रोत प्लगइनहरू कन्फिगर गर्नुहोस्", + "source": "स्रोत: ", + "uncompressed": "असंक्षिप्त", + "dab_music_source_description": "अडियोप्रेमीहरूका लागि। उच्च गुणस्तर/लसलेस अडियो स्ट्रिमहरू उपलब्ध गराउँछ। ISRC-मा आधारित सटीक ट्र्याक मिलान।" } \ No newline at end of file diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 9395ec35..4d8deac1 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -477,5 +477,19 @@ "available_plugins": "Beschikbare plugins", "configure_your_own_metadata_plugin": "Configureer uw eigen metadata-aanbieder voor afspeellijst/album/artiest/feed", "audio_scrobblers": "Audioscrobblers", - "scrobbling": "Scrobbling" -} + "scrobbling": "Scrobbling", + "download_music_format": "Download muziekformaat", + "streaming_music_format": "Streaming muziekformaat", + "download_music_quality": "Downloadkwaliteit", + "streaming_music_quality": "Streamingkwaliteit", + "default_metadata_source": "Standaard metadata-bron", + "set_default_metadata_source": "Standaard metadata-bron instellen", + "default_audio_source": "Standaard audiobron", + "set_default_audio_source": "Standaard audiobron instellen", + "plugins": "Plug-ins", + "configure_plugins": "Configureer je eigen metadata- en audiobron-plug-ins", + "source": "Bron: ", + "uncompressed": "Ongecomprimeerd", + "dab_music_source_description": "Voor audiofielen. Biedt hoge kwaliteit/lossless audiostreams. Nauwkeurige trackmatching op basis van ISRC.", + "audio_source": "Audiobron" +} \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index fac9070a..80da1e89 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Format pobierania muzyki", + "streaming_music_format": "Format strumieniowania muzyki", + "download_music_quality": "Jakość pobierania", + "streaming_music_quality": "Jakość strumieniowania", + "default_metadata_source": "Domyślne źródło metadanych", + "set_default_metadata_source": "Ustaw domyślne źródło metadanych", + "default_audio_source": "Domyślne źródło audio", + "set_default_audio_source": "Ustaw domyślne źródło audio", + "plugins": "Wtyczki", + "configure_plugins": "Skonfiguruj własne wtyczki dostawców metadanych i źródeł audio", + "source": "Źródło: ", + "uncompressed": "Nieskompresowany", + "dab_music_source_description": "Dla audiofilów. Oferuje strumienie audio wysokiej jakości/lossless. Precyzyjne dopasowanie utworów na podstawie ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 71e5ab55..fa7845c3 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Formato de download de música", + "streaming_music_format": "Formato de streaming de música", + "download_music_quality": "Qualidade de download", + "streaming_music_quality": "Qualidade de streaming", + "default_metadata_source": "Fonte padrão de metadados", + "set_default_metadata_source": "Definir fonte padrão de metadados", + "default_audio_source": "Fonte de áudio padrão", + "set_default_audio_source": "Definir fonte de áudio padrão", + "plugins": "Plugins", + "configure_plugins": "Configure seus próprios plugins de provedores de metadados e fontes de áudio", + "source": "Fonte: ", + "uncompressed": "Não comprimido", + "dab_music_source_description": "Para audiófilos. Fornece streams de áudio de alta qualidade/sem perdas. Correspondência precisa de faixas baseada em ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index bc7586ed..2e864268 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -477,5 +477,18 @@ "available_plugins": "Доступные плагины", "configure_your_own_metadata_plugin": "Настройте свой собственный поставщик метаданных для плейлиста/альбома/артиста/ленты", "audio_scrobblers": "Аудио скробблеры", - "scrobbling": "Скробблинг" + "scrobbling": "Скробблинг", + "download_music_format": "Формат загрузки музыки", + "streaming_music_format": "Формат потоковой музыки", + "download_music_quality": "Качество загрузки", + "streaming_music_quality": "Качество стриминга", + "default_metadata_source": "Источник метаданных по умолчанию", + "set_default_metadata_source": "Задать источник метаданных по умолчанию", + "default_audio_source": "Источник аудио по умолчанию", + "set_default_audio_source": "Задать источник аудио по умолчанию", + "plugins": "Плагины", + "configure_plugins": "Настройте собственные плагины провайдеров метаданных и источников аудио", + "source": "Источник: ", + "uncompressed": "Несжатый", + "dab_music_source_description": "Для аудиофилов. Предоставляет высококачественные/lossless аудиопотоки. Точное совпадение треков по ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_ta.arb b/lib/l10n/app_ta.arb index 01991a39..6cea7b1a 100644 --- a/lib/l10n/app_ta.arb +++ b/lib/l10n/app_ta.arb @@ -475,5 +475,18 @@ "available_plugins": "கிடைக்கக்கூடிய பிளகின்கள்", "configure_your_own_metadata_plugin": "உங்கள் சொந்த பிளேலிஸ்ட்/ஆல்பம்/கலைஞர்/ஊட்ட மெட்டாடேட்டா வழங்குநரை உள்ளமைக்கவும்", "audio_scrobblers": "ஆடியோ ஸ்க்ரோப்ளர்கள்", - "scrobbling": "ஸ்க்ரோப்ளிங்" + "scrobbling": "ஸ்க்ரோப்ளிங்", + "download_music_format": "இசை பதிவிறக்க வடிவம்", + "streaming_music_format": "இசை ஸ்ட்ரீமிங் வடிவம்", + "download_music_quality": "பதிவிறக்க தரம்", + "streaming_music_quality": "ஸ்ட்ரீமிங் தரம்", + "default_metadata_source": "இயல்புநிலை மெட்டாடேட்டா மூலம்", + "set_default_metadata_source": "இயல்புநிலை மெட்டாடேட்டா மூலத்தை அமை", + "default_audio_source": "இயல்புநிலை ஆடியோ மூலம்", + "set_default_audio_source": "இயல்புநிலை ஆடியோ மூலத்தை அமை", + "plugins": "செருகுநிரல்கள்", + "configure_plugins": "உங்கள் சொந்த மெட்டாடேட்டா வழங்குநர் மற்றும் ஆடியோ மூல செருகுநிரல்களை அமைக்கவும்", + "source": "மூலம்: ", + "uncompressed": "அழுத்தப்படாத", + "dab_music_source_description": "ஆடியோஃபைல்களுக்காக. உயர்தர/லாஸ்லெஸ் ஆடியோ ஸ்ட்ரீம்களை வழங்குகிறது. ISRC அடிப்படையில் துல்லியமான பாடல் பொருத்தம்." } \ No newline at end of file diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb index 4b999fe0..4f2efc0e 100644 --- a/lib/l10n/app_th.arb +++ b/lib/l10n/app_th.arb @@ -478,5 +478,18 @@ "available_plugins": "ปลั๊กอินที่มีอยู่", "configure_your_own_metadata_plugin": "กำหนดค่าผู้ให้บริการเมตาดาต้าเพลย์ลิสต์/อัลบั้ม/ศิลปิน/ฟีดของคุณเอง", "audio_scrobblers": "เครื่อง scrobbler เสียง", - "scrobbling": "Scrobbling" + "scrobbling": "Scrobbling", + "download_music_format": "รูปแบบการดาวน์โหลดเพลง", + "streaming_music_format": "รูปแบบการสตรีมเพลง", + "download_music_quality": "คุณภาพการดาวน์โหลด", + "streaming_music_quality": "คุณภาพการสตรีม", + "default_metadata_source": "แหล่งเมตาดาต้าพื้นฐาน", + "set_default_metadata_source": "ตั้งค่าแหล่งเมตาดาต้าพื้นฐาน", + "default_audio_source": "แหล่งเสียงพื้นฐาน", + "set_default_audio_source": "ตั้งค่าแหล่งเสียงพื้นฐาน", + "plugins": "ปลั๊กอิน", + "configure_plugins": "กำหนดค่าปลั๊กอินผู้ให้บริการเมตาดาต้าและแหล่งเสียงของคุณเอง", + "source": "แหล่งที่มา: ", + "uncompressed": "ไม่บีบอัด", + "dab_music_source_description": "สำหรับคนรักเสียงเพลง ให้สตรีมเสียงคุณภาพสูง/ไร้การสูญเสียการบีบอัด การจับคู่แทร็กแม่นยำตาม ISRC" } \ No newline at end of file diff --git a/lib/l10n/app_tl.arb b/lib/l10n/app_tl.arb index 45e0b070..bf1f174c 100644 --- a/lib/l10n/app_tl.arb +++ b/lib/l10n/app_tl.arb @@ -475,5 +475,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "I-download na format ng musika", + "streaming_music_format": "Format ng streaming ng musika", + "download_music_quality": "Kalidad ng i-download na musika", + "streaming_music_quality": "Kalidad ng streaming ng musika", + "default_metadata_source": "Default na pinagmulan ng metadata", + "set_default_metadata_source": "Itakda ang default na pinagmulan ng metadata", + "default_audio_source": "Default na pinagmulan ng audio", + "set_default_audio_source": "Itakda ang default na pinagmulan ng audio", + "plugins": "Mga plugin", + "configure_plugins": "I-configure ang sarili mong metadata provider at mga audio source plugin", + "source": "Pinagmulan: ", + "uncompressed": "Hindi naka-compress", + "dab_music_source_description": "Para sa mga audiophile. Nagbibigay ng de-kalidad/walang loss na audio streams. Tumpak na pagtutugma ng track batay sa ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index ff5785dd..72734d3b 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Müzik indirme formatı", + "streaming_music_format": "Müzik akış formatı", + "download_music_quality": "İndirilen müzik kalitesi", + "streaming_music_quality": "Yayınlanan müzik kalitesi", + "default_metadata_source": "Varsayılan meta veri kaynağı", + "set_default_metadata_source": "Varsayılan meta veri kaynağını ayarla", + "default_audio_source": "Varsayılan ses kaynağı", + "set_default_audio_source": "Varsayılan ses kaynağını ayarla", + "plugins": "Eklentiler", + "configure_plugins": "Kendi meta veri sağlayıcı ve ses kaynağı eklentilerinizi yapılandırın", + "source": "Kaynak: ", + "uncompressed": "Sıkıştırılmamış", + "dab_music_source_description": "Audiophile'ler için. Yüksek kaliteli/kayıpsız ses akışları sağlar. Doğru ISRC tabanlı parça eşleştirme." } \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 8eb5a8e9..bdb723ad 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -477,5 +477,18 @@ "available_plugins": "Доступні плагіни", "configure_your_own_metadata_plugin": "Налаштуйте свій власний провайдер метаданих для плейлиста/альбому/виконавця/стрічки", "audio_scrobblers": "Аудіо скробблери", - "scrobbling": "Скроблінг" + "scrobbling": "Скроблінг", + "download_music_format": "Формат завантаження музики", + "streaming_music_format": "Формат потокової музики", + "download_music_quality": "Якість завантаженої музики", + "streaming_music_quality": "Якість потокової музики", + "default_metadata_source": "Джерело метаданих за замовчуванням", + "set_default_metadata_source": "Встановити джерело метаданих за замовчуванням", + "default_audio_source": "Джерело аудіо за замовчуванням", + "set_default_audio_source": "Встановити джерело аудіо за замовчуванням", + "plugins": "Плагіни", + "configure_plugins": "Налаштуйте власні плагіни метаданих і аудіоджерела", + "source": "Джерело: ", + "uncompressed": "Без стиснення", + "dab_music_source_description": "Для аудіофілів. Забезпечує високоякісні/без втрат аудіопотоки. Точна відповідність треків на основі ISRC." } \ No newline at end of file diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index 453e12e9..5733963e 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -477,5 +477,18 @@ "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" + "scrobbling": "Scrobbling", + "download_music_format": "Định dạng nhạc tải về", + "streaming_music_format": "Định dạng nhạc phát trực tuyến", + "download_music_quality": "Chất lượng nhạc tải về", + "streaming_music_quality": "Chất lượng nhạc phát trực tuyến", + "default_metadata_source": "Nguồn siêu dữ liệu mặc định", + "set_default_metadata_source": "Đặt nguồn siêu dữ liệu mặc định", + "default_audio_source": "Nguồn âm thanh mặc định", + "set_default_audio_source": "Đặt nguồn âm thanh mặc định", + "plugins": "Tiện ích bổ sung", + "configure_plugins": "Cấu hình nhà cung cấp siêu dữ liệu và tiện ích nguồn âm thanh riêng", + "source": "Nguồn: ", + "uncompressed": "Không nén", + "dab_music_source_description": "Dành cho người yêu âm nhạc chất lượng cao. Cung cấp luồng âm thanh chất lượng cao/không nén. Phù hợp bài hát dựa trên ISRC chính xác." } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index cccb3214..44f7d38c 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -477,5 +477,18 @@ "available_plugins": "可用插件", "configure_your_own_metadata_plugin": "配置您自己的播放列表/专辑/艺人/订阅元数据提供者", "audio_scrobblers": "音频 Scrobblers", - "scrobbling": "Scrobbling" + "scrobbling": "Scrobbling", + "download_music_format": "下载音乐格式", + "streaming_music_format": "流媒体音乐格式", + "download_music_quality": "下载音乐质量", + "streaming_music_quality": "流媒体音乐质量", + "default_metadata_source": "默认元数据源", + "set_default_metadata_source": "设置默认元数据源", + "default_audio_source": "默认音频源", + "set_default_audio_source": "设置默认音频源", + "plugins": "插件", + "configure_plugins": "配置您自己的元数据提供者和音频源插件", + "source": "来源:", + "uncompressed": "无损", + "dab_music_source_description": "适合发烧友。提供高质量/无损音频流。基于 ISRC 的精确曲目匹配。" } \ No newline at end of file diff --git a/lib/l10n/app_zh_TW.arb b/lib/l10n/app_zh_TW.arb index fa4c3e67..934006d5 100644 --- a/lib/l10n/app_zh_TW.arb +++ b/lib/l10n/app_zh_TW.arb @@ -477,5 +477,18 @@ "available_plugins": "可用的外掛程式", "configure_your_own_metadata_plugin": "設定您自己的播放清單/專輯/藝人/動態中繼資料供應商", "audio_scrobblers": "音訊 Scrobblers", - "scrobbling": "Scrobbling" + "scrobbling": "Scrobbling", + "download_music_format": "下載音樂格式", + "streaming_music_format": "串流音樂格式", + "download_music_quality": "下載音樂品質", + "streaming_music_quality": "串流音樂品質", + "default_metadata_source": "預設中繼資料來源", + "set_default_metadata_source": "設定預設中繼資料來源", + "default_audio_source": "預設音訊來源", + "set_default_audio_source": "設定預設音訊來源", + "plugins": "外掛程式", + "configure_plugins": "配置您自己的中繼資料提供者和音訊來源外掛程式", + "source": "來源:", + "uncompressed": "未壓縮", + "dab_music_source_description": "適合音響發燒友。提供高品質/無損音訊串流。精確的 ISRC 曲目比對。" } \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index bf6f5211..e9d7913d 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -1743,17 +1743,29 @@ abstract class AppLocalizations { /// **'Restore defaults'** String get restore_defaults; - /// No description provided for @download_music_codec. + /// No description provided for @download_music_format. /// /// In en, this message translates to: - /// **'Download music codec'** - String get download_music_codec; + /// **'Download music format'** + String get download_music_format; - /// No description provided for @streaming_music_codec. + /// No description provided for @streaming_music_format. /// /// In en, this message translates to: - /// **'Streaming music codec'** - String get streaming_music_codec; + /// **'Streaming music format'** + String get streaming_music_format; + + /// No description provided for @download_music_quality. + /// + /// In en, this message translates to: + /// **'Download music quality'** + String get download_music_quality; + + /// No description provided for @streaming_music_quality. + /// + /// In en, this message translates to: + /// **'Streaming music quality'** + String get streaming_music_quality; /// No description provided for @login_with_lastfm. /// @@ -2763,11 +2775,29 @@ abstract class AppLocalizations { /// **'This plugin scrobbles your music to generate your listening history.'** String get plugin_scrobbling_info; - /// No description provided for @default_plugin. + /// No description provided for @default_metadata_source. /// /// In en, this message translates to: - /// **'Default'** - String get default_plugin; + /// **'Default metadata source'** + String get default_metadata_source; + + /// No description provided for @set_default_metadata_source. + /// + /// In en, this message translates to: + /// **'Set default metadata source'** + String get set_default_metadata_source; + + /// No description provided for @default_audio_source. + /// + /// In en, this message translates to: + /// **'Default audio source'** + String get default_audio_source; + + /// No description provided for @set_default_audio_source. + /// + /// In en, this message translates to: + /// **'Set default audio source'** + String get set_default_audio_source; /// No description provided for @set_default. /// @@ -2871,11 +2901,11 @@ abstract class AppLocalizations { /// **'Input doesn\'t match the required format'** String get input_does_not_match_format; - /// No description provided for @metadata_provider_plugins. + /// No description provided for @plugins. /// /// In en, this message translates to: - /// **'Metadata Provider Plugins'** - String get metadata_provider_plugins; + /// **'Plugins'** + String get plugins; /// No description provided for @paste_plugin_download_url. /// @@ -2913,11 +2943,11 @@ abstract class AppLocalizations { /// **'Available plugins'** String get available_plugins; - /// No description provided for @configure_your_own_metadata_plugin. + /// No description provided for @configure_plugins. /// /// In en, this message translates to: - /// **'Configure your own playlist/album/artist/feed metadata provider'** - String get configure_your_own_metadata_plugin; + /// **'Configure your own metadata provider and audio source plugins'** + String get configure_plugins; /// No description provided for @audio_scrobblers. /// @@ -2930,6 +2960,24 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Scrobbling'** String get scrobbling; + + /// No description provided for @source. + /// + /// In en, this message translates to: + /// **'Source: '** + String get source; + + /// No description provided for @uncompressed. + /// + /// In en, this message translates to: + /// **'Uncompressed'** + String get uncompressed; + + /// No description provided for @dab_music_source_description. + /// + /// In en, this message translates to: + /// **'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.'** + String get dab_music_source_description; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index b5734db6..8fd50ffa 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -874,10 +874,16 @@ class AppLocalizationsAr extends AppLocalizations { String get restore_defaults => 'استعادة الإعدادات الافتراضية'; @override - String get download_music_codec => 'تنزيل ترميز الموسيقى'; + String get download_music_format => 'تنسيق تنزيل الموسيقى'; @override - String get streaming_music_codec => 'ترميز الموسيقى بالتدفق'; + String get streaming_music_format => 'تنسيق بث الموسيقى'; + + @override + String get download_music_quality => 'جودة تنزيل الموسيقى'; + + @override + String get streaming_music_quality => 'جودة بث الموسيقى'; @override String get login_with_lastfm => 'تسجيل الدخول باستخدام Last.fm'; @@ -1443,7 +1449,17 @@ class AppLocalizationsAr extends AppLocalizations { 'تقوم هذه الإضافة بتتبع مقاطعك الموسيقية لإنشاء سجل الاستماع الخاص بك.'; @override - String get default_plugin => 'الافتراضي'; + String get default_metadata_source => 'مصدر البيانات الوصفية الافتراضي'; + + @override + String get set_default_metadata_source => + 'تعيين مصدر البيانات الوصفية الافتراضي'; + + @override + String get default_audio_source => 'مصدر الصوت الافتراضي'; + + @override + String get set_default_audio_source => 'تعيين مصدر الصوت الافتراضي'; @override String get set_default => 'تعيين كافتراضي'; @@ -1504,7 +1520,7 @@ class AppLocalizationsAr extends AppLocalizations { 'المدخل لا يتوافق مع التنسيق المطلوب'; @override - String get metadata_provider_plugins => 'إضافات مزود البيانات'; + String get plugins => 'الإضافات'; @override String get paste_plugin_download_url => @@ -1529,12 +1545,22 @@ class AppLocalizationsAr extends AppLocalizations { String get available_plugins => 'الإضافات المتوفّرة'; @override - String get configure_your_own_metadata_plugin => - 'تهيئة مزوّد بيانات للقائمة/الألبوم/الفنان/المصدر خاص بك'; + String get configure_plugins => + 'قم بتكوين مزود البيانات الوصفية ومكونات مصدر الصوت الخاصة بك'; @override String get audio_scrobblers => 'أجهزة تتبع الصوت'; @override String get scrobbling => 'التتبع'; + + @override + String get source => 'المصدر: '; + + @override + String get uncompressed => 'غير مضغوط'; + + @override + String get dab_music_source_description => + 'لمحبي الصوتيات. يوفر تدفقات صوتية عالية الجودة/بدون فقدان. مطابقة دقيقة للمسارات بناءً على ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_bn.dart b/lib/l10n/generated/app_localizations_bn.dart index 4503564e..7dc1e07f 100644 --- a/lib/l10n/generated/app_localizations_bn.dart +++ b/lib/l10n/generated/app_localizations_bn.dart @@ -873,10 +873,16 @@ class AppLocalizationsBn extends AppLocalizations { String get restore_defaults => 'ডিফল্ট সেটিংস পুনরুদ্ধার করুন'; @override - String get download_music_codec => 'সঙ্গীত কোডেক ডাউনলোড করুন'; + String get download_music_format => 'গান ডাউনলোডের বিন্যাস'; @override - String get streaming_music_codec => 'স্ট্রিমিং সঙ্গীত কোডেক'; + String get streaming_music_format => 'গান স্ট্রিমিং এর বিন্যাস'; + + @override + String get download_music_quality => 'গান ডাউনলোডের মান'; + + @override + String get streaming_music_quality => 'গান স্ট্রিমিং এর মান'; @override String get login_with_lastfm => 'Last.fm দিয়ে লগইন করুন'; @@ -1443,7 +1449,16 @@ class AppLocalizationsBn extends AppLocalizations { 'এই প্লাগইনটি আপনার সঙ্গীত স্ক্রোব্বল করে আপনার শোনা ইতিহাস তৈরি করে।'; @override - String get default_plugin => 'ডিফল্ট'; + String get default_metadata_source => 'ডিফল্ট মেটাডেটা উৎস'; + + @override + String get set_default_metadata_source => 'ডিফল্ট মেটাডেটা উৎস সেট করুন'; + + @override + String get default_audio_source => 'ডিফল্ট অডিও উৎস'; + + @override + String get set_default_audio_source => 'ডিফল্ট অডিও উৎস সেট করুন'; @override String get set_default => 'ডিফল্ট হিসাবে নির্ধারণ করুন'; @@ -1505,7 +1520,7 @@ class AppLocalizationsBn extends AppLocalizations { 'ইনপুট প্রয়োজনীয় ফরম্যাটের সাথে মেলে না'; @override - String get metadata_provider_plugins => 'মেটাডেটা প্রদানকারী প্লাগইনসমূহ'; + String get plugins => 'প্লাগইন'; @override String get paste_plugin_download_url => @@ -1530,12 +1545,22 @@ class AppLocalizationsBn extends AppLocalizations { String get available_plugins => 'উপলব্ধ প্লাগইনগুলো'; @override - String get configure_your_own_metadata_plugin => - 'নিজস্ব প্লেলিস্ট/অ্যালবাম/শিল্পী/ফিড মেটাডেটা প্রদানকারী কনফিগার করুন'; + String get configure_plugins => + 'আপনার নিজের মেটাডেটা প্রদানকারী এবং অডিও উৎস প্লাগইন কনফিগার করুন'; @override String get audio_scrobblers => 'অডিও স্ক্রোব্বলার্স'; @override String get scrobbling => 'স্ক্রোব্বলিং'; + + @override + String get source => 'উৎস: '; + + @override + String get uncompressed => 'অ-সংকুচিত'; + + @override + String get dab_music_source_description => + 'অডিওফাইলদের জন্য। উচ্চ-মানের/লসলেস অডিও স্ট্রিম প্রদান করে। সঠিক ISRC ভিত্তিক ট্র্যাক ম্যাচিং।'; } diff --git a/lib/l10n/generated/app_localizations_ca.dart b/lib/l10n/generated/app_localizations_ca.dart index 70899f04..a4f587c9 100644 --- a/lib/l10n/generated/app_localizations_ca.dart +++ b/lib/l10n/generated/app_localizations_ca.dart @@ -876,10 +876,18 @@ class AppLocalizationsCa extends AppLocalizations { String get restore_defaults => 'Restaura els valors per defecte'; @override - String get download_music_codec => 'Descarrega el codec de música'; + String get download_music_format => 'Format de descàrrega de música'; @override - String get streaming_music_codec => 'Codec de música en streaming'; + String get streaming_music_format => + 'Format de reproducció de música en temps real'; + + @override + String get download_music_quality => 'Qualitat de descàrrega de música'; + + @override + String get streaming_music_quality => + 'Qualitat de reproducció de música en temps real'; @override String get login_with_lastfm => 'Inicia la sessió amb Last.fm'; @@ -1450,7 +1458,18 @@ class AppLocalizationsCa extends AppLocalizations { 'Aquest complement fa scrobbling de la teva música per generar l’historial d’escoltes.'; @override - String get default_plugin => 'Predeterminat'; + String get default_metadata_source => 'Font de metadades per defecte'; + + @override + String get set_default_metadata_source => + 'Estableix la font de metadades per defecte'; + + @override + String get default_audio_source => 'Font d\'àudio per defecte'; + + @override + String get set_default_audio_source => + 'Estableix la font d\'àudio per defecte'; @override String get set_default => 'Establir com a predeterminat'; @@ -1514,8 +1533,7 @@ class AppLocalizationsCa extends AppLocalizations { 'L’entrada no coincideix amb el format requerit'; @override - String get metadata_provider_plugins => - 'Complements de proveïdor de metadades'; + String get plugins => 'Connectors'; @override String get paste_plugin_download_url => @@ -1540,12 +1558,22 @@ class AppLocalizationsCa extends AppLocalizations { 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'; + String get configure_plugins => + 'Configura els teus propis connectors de proveïdor de metadades i de font d\'àudio'; @override String get audio_scrobblers => 'Scrobblers d’àudio'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Font: '; + + @override + String get uncompressed => 'Sense comprimir'; + + @override + String get dab_music_source_description => + 'Per als audiòfils. Ofereix fluxos d\'àudio d\'alta qualitat/sense pèrdua. Coincidència precisa de pistes basada en ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_cs.dart b/lib/l10n/generated/app_localizations_cs.dart index 3cb620e0..24d5b34b 100644 --- a/lib/l10n/generated/app_localizations_cs.dart +++ b/lib/l10n/generated/app_localizations_cs.dart @@ -869,10 +869,16 @@ class AppLocalizationsCs extends AppLocalizations { String get restore_defaults => 'Obnovit výchozí'; @override - String get download_music_codec => 'Kodek pro stahování'; + String get download_music_format => 'Formát stahování hudby'; @override - String get streaming_music_codec => 'Kodek pro streamování'; + String get streaming_music_format => 'Formát streamování hudby'; + + @override + String get download_music_quality => 'Kvalita stahování hudby'; + + @override + String get streaming_music_quality => 'Kvalita streamování hudby'; @override String get login_with_lastfm => 'Přihlásit se pomocí Last.fm'; @@ -1442,7 +1448,16 @@ class AppLocalizationsCs extends AppLocalizations { 'Tento plugin scrobbles vaši hudbu pro vytvoření historie poslechů.'; @override - String get default_plugin => 'Výchozí'; + String get default_metadata_source => 'Výchozí zdroj metadat'; + + @override + String get set_default_metadata_source => 'Nastavit výchozí zdroj metadat'; + + @override + String get default_audio_source => 'Výchozí zdroj zvuku'; + + @override + String get set_default_audio_source => 'Nastavit výchozí zdroj zvuku'; @override String get set_default => 'Nastavit jako výchozí'; @@ -1505,7 +1520,7 @@ class AppLocalizationsCs extends AppLocalizations { 'Vstup neodpovídá požadovanému formátu'; @override - String get metadata_provider_plugins => 'Pluginy poskytovatelů metadat'; + String get plugins => 'Pluginy'; @override String get paste_plugin_download_url => @@ -1530,12 +1545,22 @@ class AppLocalizationsCs extends AppLocalizations { 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'; + String get configure_plugins => + 'Konfigurujte své vlastní pluginy poskytovatele metadat a zdroje zvuku'; @override String get audio_scrobblers => 'Audio scrobblers'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Zdroj: '; + + @override + String get uncompressed => 'Nekomprimováno'; + + @override + String get dab_music_source_description => + 'Pro audiofily. Poskytuje vysoce kvalitní/bezztrátové zvukové toky. Přesná shoda skladeb na základě ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 75c858f5..4ab10266 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -879,10 +879,16 @@ class AppLocalizationsDe extends AppLocalizations { String get restore_defaults => 'Standardeinstellungen wiederherstellen'; @override - String get download_music_codec => 'Musik-Codec herunterladen'; + String get download_music_format => 'Musik-Downloadformat'; @override - String get streaming_music_codec => 'Streaming-Musik-Codec'; + String get streaming_music_format => 'Musik-Streamingformat'; + + @override + String get download_music_quality => 'Musik-Downloadqualität'; + + @override + String get streaming_music_quality => 'Musik-Streamingqualität'; @override String get login_with_lastfm => 'Mit Last.fm anmelden'; @@ -1455,7 +1461,17 @@ class AppLocalizationsDe extends AppLocalizations { 'Dieses Plugin scrobbelt Ihre Musik, um Ihre Hörhistorie zu erstellen.'; @override - String get default_plugin => 'Standard'; + String get default_metadata_source => 'Standard-Metadatenquelle'; + + @override + String get set_default_metadata_source => + 'Standard-Metadatenquelle festlegen'; + + @override + String get default_audio_source => 'Standard-Audioquelle'; + + @override + String get set_default_audio_source => 'Standard-Audioquelle festlegen'; @override String get set_default => 'Als Standard festlegen'; @@ -1517,7 +1533,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Eingabe entspricht nicht dem geforderten Format'; @override - String get metadata_provider_plugins => 'Plugins für Metadatenanbieter'; + String get plugins => 'Plugins'; @override String get paste_plugin_download_url => @@ -1542,12 +1558,22 @@ class AppLocalizationsDe extends AppLocalizations { 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'; + String get configure_plugins => + 'Richte deine eigenen Metadatenanbieter- und Audioquellen-Plugins ein'; @override String get audio_scrobblers => 'Audio-Scrobbler'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Quelle: '; + + @override + String get uncompressed => 'Unkomprimiert'; + + @override + String get dab_music_source_description => + 'Für Audiophile. Bietet hochwertige/verlustfreie Audiostreams. Präzises ISRC-basiertes Track-Matching.'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index e6d4db1e..83a2c24c 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -871,10 +871,16 @@ class AppLocalizationsEn extends AppLocalizations { String get restore_defaults => 'Restore defaults'; @override - String get download_music_codec => 'Download music codec'; + String get download_music_format => 'Download music format'; @override - String get streaming_music_codec => 'Streaming music codec'; + String get streaming_music_format => 'Streaming music format'; + + @override + String get download_music_quality => 'Download music quality'; + + @override + String get streaming_music_quality => 'Streaming music quality'; @override String get login_with_lastfm => 'Login with Last.fm'; @@ -1442,7 +1448,16 @@ class AppLocalizationsEn extends AppLocalizations { 'This plugin scrobbles your music to generate your listening history.'; @override - String get default_plugin => 'Default'; + String get default_metadata_source => 'Default metadata source'; + + @override + String get set_default_metadata_source => 'Set default metadata source'; + + @override + String get default_audio_source => 'Default audio source'; + + @override + String get set_default_audio_source => 'Set default audio source'; @override String get set_default => 'Set default'; @@ -1503,7 +1518,7 @@ class AppLocalizationsEn extends AppLocalizations { 'Input doesn\'t match the required format'; @override - String get metadata_provider_plugins => 'Metadata Provider Plugins'; + String get plugins => 'Plugins'; @override String get paste_plugin_download_url => @@ -1528,12 +1543,22 @@ class AppLocalizationsEn extends AppLocalizations { String get available_plugins => 'Available plugins'; @override - String get configure_your_own_metadata_plugin => - 'Configure your own playlist/album/artist/feed metadata provider'; + String get configure_plugins => + 'Configure your own metadata provider and audio source plugins'; @override String get audio_scrobblers => 'Audio Scrobblers'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Source: '; + + @override + String get uncompressed => 'Uncompressed'; + + @override + String get dab_music_source_description => + 'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index f51f829c..0fcd6739 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -876,10 +876,16 @@ class AppLocalizationsEs extends AppLocalizations { String get restore_defaults => 'Restaurar valores predeterminados'; @override - String get download_music_codec => 'Descargar códec de música'; + String get download_music_format => 'Formato de descarga de música'; @override - String get streaming_music_codec => 'Códec de música en streaming'; + String get streaming_music_format => 'Formato de transmisión de música'; + + @override + String get download_music_quality => 'Calidad de descarga de música'; + + @override + String get streaming_music_quality => 'Calidad de transmisión de música'; @override String get login_with_lastfm => 'Iniciar sesión con Last.fm'; @@ -1452,7 +1458,18 @@ class AppLocalizationsEs extends AppLocalizations { 'Este complemento scrobblea tu música para generar tu historial de reproducción.'; @override - String get default_plugin => 'Predeterminado'; + String get default_metadata_source => 'Fuente de metadatos predeterminada'; + + @override + String get set_default_metadata_source => + 'Establecer fuente de metadatos predeterminada'; + + @override + String get default_audio_source => 'Fuente de audio predeterminada'; + + @override + String get set_default_audio_source => + 'Establecer fuente de audio predeterminada'; @override String get set_default => 'Establecer como predeterminado'; @@ -1517,8 +1534,7 @@ class AppLocalizationsEs extends AppLocalizations { 'La entrada no coincide con el formato requerido'; @override - String get metadata_provider_plugins => - 'Complementos de proveedor de metadatos'; + String get plugins => 'Plugins'; @override String get paste_plugin_download_url => @@ -1543,12 +1559,22 @@ class AppLocalizationsEs extends AppLocalizations { 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'; + String get configure_plugins => + 'Configura tus propios plugins de proveedor de metadatos y fuente de audio'; @override String get audio_scrobblers => 'Scrobblers de audio'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Fuente: '; + + @override + String get uncompressed => 'Sin comprimir'; + + @override + String get dab_music_source_description => + 'Para audiófilos. Proporciona transmisiones de audio de alta calidad/sin pérdida. Coincidencia precisa de pistas basada en ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_eu.dart b/lib/l10n/generated/app_localizations_eu.dart index 523f110f..5f80397e 100644 --- a/lib/l10n/generated/app_localizations_eu.dart +++ b/lib/l10n/generated/app_localizations_eu.dart @@ -876,10 +876,16 @@ class AppLocalizationsEu extends AppLocalizations { String get restore_defaults => 'Berrezarri berezko balioak'; @override - String get download_music_codec => 'Deskargatutako musikaren codec-a'; + String get download_music_format => 'Musika deskargatzeko formatua'; @override - String get streaming_music_codec => 'Streaming musikaren codec-a'; + String get streaming_music_format => 'Musika streaming bidezko formatua'; + + @override + String get download_music_quality => 'Musika deskargaren kalitatea'; + + @override + String get streaming_music_quality => 'Streaming bidezko musika kalitatea'; @override String get login_with_lastfm => 'Hasi saioa Last.fm-n'; @@ -1451,7 +1457,17 @@ class AppLocalizationsEu extends AppLocalizations { 'Plugin honek zure musika scrobbled egiten du zure entzuteen historia sortzeko.'; @override - String get default_plugin => 'Lehenetsia'; + String get default_metadata_source => 'Metadatu-iturburu lehenetsia'; + + @override + String get set_default_metadata_source => + 'Ezarri metadatu-iturburu lehenetsia'; + + @override + String get default_audio_source => 'Audio-iturburu lehenetsia'; + + @override + String get set_default_audio_source => 'Ezarri audio-iturburu lehenetsia'; @override String get set_default => 'Lehenetsi gisa ezarri'; @@ -1515,7 +1531,7 @@ class AppLocalizationsEu extends AppLocalizations { 'Sarrera ezin da beharrezko formatutik desberdina izan'; @override - String get metadata_provider_plugins => 'Metadaten hornitzailearen pluginak'; + String get plugins => 'Pluginak'; @override String get paste_plugin_download_url => @@ -1540,12 +1556,22 @@ class AppLocalizationsEu extends AppLocalizations { String get available_plugins => 'Eskaintzen diren pluginak'; @override - String get configure_your_own_metadata_plugin => - 'Konfiguratu zureko playlists-/album-/artista-/feed-metadaten hornitzailea'; + String get configure_plugins => + 'Konfiguratu zure metadatu-hornitzaile eta audio-iturburu pluginak'; @override String get audio_scrobblers => 'Audio scrobbler-ak'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Iturburua: '; + + @override + String get uncompressed => 'Konprimitu gabea'; + + @override + String get dab_music_source_description => + 'Audiozaleentzat. Kalitate handiko/galerarik gabeko audio-streamak eskaintzen ditu. ISRC oinarritutako pistaren parekatze zehatza.'; } diff --git a/lib/l10n/generated/app_localizations_fa.dart b/lib/l10n/generated/app_localizations_fa.dart index c63e723a..5c0b7c2b 100644 --- a/lib/l10n/generated/app_localizations_fa.dart +++ b/lib/l10n/generated/app_localizations_fa.dart @@ -870,10 +870,16 @@ class AppLocalizationsFa extends AppLocalizations { String get restore_defaults => 'بازیابی پیش فرض ها'; @override - String get download_music_codec => 'دانلود کدک موسیقی'; + String get download_music_format => 'فرمت دانلود موسیقی'; @override - String get streaming_music_codec => 'کدک موسیقی استریمینگ'; + String get streaming_music_format => 'فرمت پخش آنلاین موسیقی'; + + @override + String get download_music_quality => 'کیفیت دانلود موسیقی'; + + @override + String get streaming_music_quality => 'کیفیت پخش آنلاین موسیقی'; @override String get login_with_lastfm => 'ورود با Last.fm'; @@ -1441,7 +1447,16 @@ class AppLocalizationsFa extends AppLocalizations { 'این افزونه موسیقی شما را اسکراب می‌کند تا تاریخچهٔ شنیداری‌تان را تولید کند.'; @override - String get default_plugin => 'پیش‌فرض'; + String get default_metadata_source => 'منبع پیش‌فرض فراداده'; + + @override + String get set_default_metadata_source => 'تنظیم منبع پیش‌فرض فراداده'; + + @override + String get default_audio_source => 'منبع پیش‌فرض صوت'; + + @override + String get set_default_audio_source => 'تنظیم منبع پیش‌فرض صوت'; @override String get set_default => 'تنظیم به عنوان پیش‌فرض'; @@ -1503,7 +1518,7 @@ class AppLocalizationsFa extends AppLocalizations { 'ورودی با قالب مورد نیاز تطابق ندارد'; @override - String get metadata_provider_plugins => 'افزونه‌های ارائه‌دهندهٔ متادیتا'; + String get plugins => 'افزونه‌ها'; @override String get paste_plugin_download_url => @@ -1528,12 +1543,22 @@ class AppLocalizationsFa extends AppLocalizations { String get available_plugins => 'افزونه‌های موجود'; @override - String get configure_your_own_metadata_plugin => - 'پیکربندی ارائه‌دهندهٔ متادیتا برای پلی‌لیست/آلبوم/هنرمند/فید به‌صورت سفارشی'; + String get configure_plugins => + 'افزونه‌های منبع صوت و ارائه‌دهنده فراداده خود را پیکربندی کنید'; @override String get audio_scrobblers => 'اسکراب‌بلرهای صوتی'; @override String get scrobbling => 'اسکراب‌بلینگ'; + + @override + String get source => 'منبع: '; + + @override + String get uncompressed => 'بدون فشرده‌سازی'; + + @override + String get dab_music_source_description => + 'مخصوص علاقه‌مندان صدا. ارائه‌دهنده استریم‌های باکیفیت/بدون افت. تطبیق دقیق آهنگ بر اساس ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_fi.dart b/lib/l10n/generated/app_localizations_fi.dart index e1ba7f5a..3f616849 100644 --- a/lib/l10n/generated/app_localizations_fi.dart +++ b/lib/l10n/generated/app_localizations_fi.dart @@ -872,10 +872,16 @@ class AppLocalizationsFi extends AppLocalizations { String get restore_defaults => 'Palauta oletukset'; @override - String get download_music_codec => 'Ladatun musiikin codefc'; + String get download_music_format => 'Musiikin latausmuoto'; @override - String get streaming_music_codec => 'Suoratoistetun musiikin codec'; + String get streaming_music_format => 'Musiikin suoratoistomuoto'; + + @override + String get download_music_quality => 'Musiikin latauslaatu'; + + @override + String get streaming_music_quality => 'Musiikin suoratoistolaadun'; @override String get login_with_lastfm => 'Kirjaudu sisään Last.fm:llä'; @@ -1443,7 +1449,16 @@ class AppLocalizationsFi extends AppLocalizations { 'Tämä lisäosa scrobblaa musiikkisi luodakseen kuunteluhistoriasi.'; @override - String get default_plugin => 'Oletus'; + String get default_metadata_source => 'Oletusarvoinen metatietolähde'; + + @override + String get set_default_metadata_source => 'Aseta oletusmetatietolähde'; + + @override + String get default_audio_source => 'Oletusarvoinen äänilähde'; + + @override + String get set_default_audio_source => 'Aseta oletusäänilähde'; @override String get set_default => 'Aseta oletukseksi'; @@ -1503,7 +1518,7 @@ class AppLocalizationsFi extends AppLocalizations { String get input_does_not_match_format => 'Syöte ei vastaa vaadittua muotoa'; @override - String get metadata_provider_plugins => 'Metatietojen tarjoajan lisäosat'; + String get plugins => 'Laajennukset'; @override String get paste_plugin_download_url => @@ -1528,12 +1543,22 @@ class AppLocalizationsFi extends AppLocalizations { 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'; + String get configure_plugins => + 'Määritä omat metatietojen tarjoaja- ja äänilähdelaajennukset'; @override String get audio_scrobblers => 'Äänen scrobblerit'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Lähde: '; + + @override + String get uncompressed => 'Pakkaamaton'; + + @override + String get dab_music_source_description => + 'Audiofiileille. Tarjoaa korkealaatuisia/häviöttömiä äänivirtoja. Tarkka ISRC-pohjainen kappaleiden tunnistus.'; } diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index 88350997..3637391b 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -880,10 +880,16 @@ class AppLocalizationsFr extends AppLocalizations { String get restore_defaults => 'Restaurer les valeurs par défaut'; @override - String get download_music_codec => 'Télécharger le codec musical'; + String get download_music_format => 'Format de téléchargement de musique'; @override - String get streaming_music_codec => 'Codec de musique en streaming'; + String get streaming_music_format => 'Format de streaming de musique'; + + @override + String get download_music_quality => 'Qualité de téléchargement de musique'; + + @override + String get streaming_music_quality => 'Qualité de streaming de musique'; @override String get login_with_lastfm => 'Se connecter avec Last.fm'; @@ -1457,7 +1463,17 @@ class AppLocalizationsFr extends AppLocalizations { 'Ce plugin scrobble votre musique pour générer votre historique d\'écoute.'; @override - String get default_plugin => 'Par défaut'; + String get default_metadata_source => 'Source de métadonnées par défaut'; + + @override + String get set_default_metadata_source => + 'Définir la source de métadonnées par défaut'; + + @override + String get default_audio_source => 'Source audio par défaut'; + + @override + String get set_default_audio_source => 'Définir la source audio par défaut'; @override String get set_default => 'Définir par défaut'; @@ -1521,8 +1537,7 @@ class AppLocalizationsFr extends AppLocalizations { 'L\'entrée ne correspond pas au format requis'; @override - String get metadata_provider_plugins => - 'Plugins de fournisseur de métadonnées'; + String get plugins => 'Plugins'; @override String get paste_plugin_download_url => @@ -1548,12 +1563,22 @@ class AppLocalizationsFr extends AppLocalizations { 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'; + String get configure_plugins => + 'Configurez vos propres plugins de fournisseur de métadonnées et de source audio'; @override String get audio_scrobblers => 'Scrobblers audio'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Source : '; + + @override + String get uncompressed => 'Non compressé'; + + @override + String get dab_music_source_description => + 'Pour les audiophiles. Fournit des flux audio de haute qualité/sans perte. Correspondance précise des pistes basée sur ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index f3ba4802..0434e8db 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -872,10 +872,16 @@ class AppLocalizationsHi extends AppLocalizations { String get restore_defaults => 'डिफ़ॉल्ट सेटिंग्स को बहाल करें'; @override - String get download_music_codec => 'संगीत कोडेक डाउनलोड करें'; + String get download_music_format => 'संगीत डाउनलोड प्रारूप'; @override - String get streaming_music_codec => 'स्ट्रीमिंग संगीत कोडेक'; + String get streaming_music_format => 'संगीत स्ट्रीमिंग प्रारूप'; + + @override + String get download_music_quality => 'संगीत डाउनलोड गुणवत्ता'; + + @override + String get streaming_music_quality => 'संगीत स्ट्रीमिंग गुणवत्ता'; @override String get login_with_lastfm => 'Last.fm से लॉगिन करें'; @@ -1448,7 +1454,16 @@ class AppLocalizationsHi extends AppLocalizations { 'यह प्लगइन आपके सुनने के इतिहास को उत्पन्न करने के लिए आपके संगीत को स्क्रॉबल करता है।'; @override - String get default_plugin => 'डिफ़ॉल्ट'; + String get default_metadata_source => 'डिफ़ॉल्ट मेटाडेटा स्रोत'; + + @override + String get set_default_metadata_source => 'डिफ़ॉल्ट मेटाडेटा स्रोत सेट करें'; + + @override + String get default_audio_source => 'डिफ़ॉल्ट ऑडियो स्रोत'; + + @override + String get set_default_audio_source => 'डिफ़ॉल्ट ऑडियो स्रोत सेट करें'; @override String get set_default => 'डिफ़ॉल्ट सेट करें'; @@ -1509,7 +1524,7 @@ class AppLocalizationsHi extends AppLocalizations { 'इनपुट आवश्यक प्रारूप से मेल नहीं खाता है'; @override - String get metadata_provider_plugins => 'मेटाडेटा प्रदाता प्लगइन'; + String get plugins => 'प्लगइन्स'; @override String get paste_plugin_download_url => @@ -1534,12 +1549,22 @@ class AppLocalizationsHi extends AppLocalizations { String get available_plugins => 'उपलब्ध प्लगइन'; @override - String get configure_your_own_metadata_plugin => - 'अपनी खुद की प्लेलिस्ट/एल्बम/कलाकार/फ़ीड मेटाडेटा प्रदाता कॉन्फ़िगर करें'; + String get configure_plugins => + 'अपने स्वयं के मेटाडेटा प्रदाता और ऑडियो स्रोत प्लगइन्स कॉन्फ़िगर करें'; @override String get audio_scrobblers => 'ऑडियो स्क्रॉबलर्स'; @override String get scrobbling => 'स्क्रॉबलिंग'; + + @override + String get source => 'स्रोत: '; + + @override + String get uncompressed => 'असंपीड़ित'; + + @override + String get dab_music_source_description => + 'ऑडियोफाइलों के लिए। उच्च-गुणवत्ता/बिना हानि वाले ऑडियो स्ट्रीम प्रदान करता है। सटीक ISRC आधारित ट्रैक मिलान।'; } diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index c56f1ece..ce250425 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -874,10 +874,16 @@ class AppLocalizationsId extends AppLocalizations { String get restore_defaults => 'Kembalikan semula'; @override - String get download_music_codec => 'Unduh codec musik'; + String get download_music_format => 'Format unduh musik'; @override - String get streaming_music_codec => 'Streaming codec musik'; + String get streaming_music_format => 'Format streaming musik'; + + @override + String get download_music_quality => 'Kualitas unduh musik'; + + @override + String get streaming_music_quality => 'Kualitas streaming musik'; @override String get login_with_lastfm => 'Masuk dengan Last.fm'; @@ -1449,7 +1455,16 @@ class AppLocalizationsId extends AppLocalizations { 'Plugin ini scrobble musik Anda untuk menghasilkan riwayat mendengarkan Anda.'; @override - String get default_plugin => 'Bawaan'; + String get default_metadata_source => 'Sumber metadata default'; + + @override + String get set_default_metadata_source => 'Atur sumber metadata default'; + + @override + String get default_audio_source => 'Sumber audio default'; + + @override + String get set_default_audio_source => 'Atur sumber audio default'; @override String get set_default => 'Atur sebagai bawaan'; @@ -1511,7 +1526,7 @@ class AppLocalizationsId extends AppLocalizations { 'Masukan tidak cocok dengan format yang diperlukan'; @override - String get metadata_provider_plugins => 'Plugin Penyedia Metadata'; + String get plugins => 'Plugin'; @override String get paste_plugin_download_url => @@ -1536,12 +1551,22 @@ class AppLocalizationsId extends AppLocalizations { String get available_plugins => 'Plugin yang tersedia'; @override - String get configure_your_own_metadata_plugin => - 'Konfigurasi penyedia metadata playlist/album/artis/feed Anda sendiri'; + String get configure_plugins => + 'Konfigurasi plugin penyedia metadata dan sumber audio Anda sendiri'; @override String get audio_scrobblers => 'Scrobblers Audio'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Sumber: '; + + @override + String get uncompressed => 'Tidak terkompresi'; + + @override + String get dab_music_source_description => + 'Untuk audiophile. Menyediakan aliran audio berkualitas tinggi/tanpa kehilangan. Pencocokkan trek yang akurat berdasarkan ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index dc8ed9cd..f2dfa5ed 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -874,10 +874,16 @@ class AppLocalizationsIt extends AppLocalizations { String get restore_defaults => 'Ripristina default'; @override - String get download_music_codec => 'Codec musicale scaricamento'; + String get download_music_format => 'Formato download musica'; @override - String get streaming_music_codec => 'Codec musicale streaming'; + String get streaming_music_format => 'Formato streaming musica'; + + @override + String get download_music_quality => 'Qualità download musica'; + + @override + String get streaming_music_quality => 'Qualità streaming musica'; @override String get login_with_lastfm => 'Accesso a Last.fm'; @@ -1448,7 +1454,17 @@ class AppLocalizationsIt extends AppLocalizations { 'Questo plugin scrobbla la tua musica per generare la tua cronologia di ascolti.'; @override - String get default_plugin => 'Predefinito'; + String get default_metadata_source => 'Fonte metadati predefinita'; + + @override + String get set_default_metadata_source => + 'Imposta fonte metadati predefinita'; + + @override + String get default_audio_source => 'Fonte audio predefinita'; + + @override + String get set_default_audio_source => 'Imposta fonte audio predefinita'; @override String get set_default => 'Imposta come predefinito'; @@ -1510,7 +1526,7 @@ class AppLocalizationsIt extends AppLocalizations { 'L\'input non corrisponde al formato richiesto'; @override - String get metadata_provider_plugins => 'Plugin del provider di metadati'; + String get plugins => 'Plugin'; @override String get paste_plugin_download_url => @@ -1535,12 +1551,22 @@ class AppLocalizationsIt extends AppLocalizations { 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'; + String get configure_plugins => + 'Configura i tuoi plugin per fornitore metadati e fonte audio'; @override String get audio_scrobblers => 'Scrobbler audio'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Fonte: '; + + @override + String get uncompressed => 'Non compresso'; + + @override + String get dab_music_source_description => + 'Per audiophile. Fornisce flussi audio di alta qualità/senza perdita. Abbinamento traccia accurato basato su ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index 7ce62161..2505f68a 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -861,10 +861,16 @@ class AppLocalizationsJa extends AppLocalizations { String get restore_defaults => '設定を初期化'; @override - String get download_music_codec => 'ダウンロード用の音声コーデック'; + String get download_music_format => '音楽ダウンロード形式'; @override - String get streaming_music_codec => 'ストリーミング用の音声コーデック'; + String get streaming_music_format => '音楽ストリーミング形式'; + + @override + String get download_music_quality => '音楽ダウンロード品質'; + + @override + String get streaming_music_quality => '音楽ストリーミング品質'; @override String get login_with_lastfm => 'Last.fmでログイン'; @@ -1416,7 +1422,16 @@ class AppLocalizationsJa extends AppLocalizations { String get plugin_scrobbling_info => 'このプラグインは、あなたの音楽をscrobbleして視聴履歴を生成します。'; @override - String get default_plugin => 'デフォルト'; + String get default_metadata_source => 'デフォルトメタデータソース'; + + @override + String get set_default_metadata_source => 'デフォルトメタデータソースを設定'; + + @override + String get default_audio_source => 'デフォルトオーディオソース'; + + @override + String get set_default_audio_source => 'デフォルトオーディオソースを設定'; @override String get set_default => 'デフォルトに設定'; @@ -1474,7 +1489,7 @@ class AppLocalizationsJa extends AppLocalizations { String get input_does_not_match_format => '入力が必須フォーマットと一致しません'; @override - String get metadata_provider_plugins => 'メタデータプロバイダープラグイン'; + String get plugins => 'プラグイン'; @override String get paste_plugin_download_url => @@ -1499,12 +1514,21 @@ class AppLocalizationsJa extends AppLocalizations { String get available_plugins => '利用可能なプラグイン'; @override - String get configure_your_own_metadata_plugin => - '独自のプレイリスト/アルバム/アーティスト/フィードのメタデータプロバイダーを構成'; + String get configure_plugins => '独自のメタデータプロバイダーとオーディオソースプラグインを設定'; @override String get audio_scrobblers => 'オーディオスクロッブラー'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'ソース: '; + + @override + String get uncompressed => '非圧縮'; + + @override + String get dab_music_source_description => + 'オーディオファイル向け。高品質/ロスレスオーディオストリームを提供。正確なISRCベースのトラックマッチング。'; } diff --git a/lib/l10n/generated/app_localizations_ka.dart b/lib/l10n/generated/app_localizations_ka.dart index a28bd02d..c8557037 100644 --- a/lib/l10n/generated/app_localizations_ka.dart +++ b/lib/l10n/generated/app_localizations_ka.dart @@ -872,10 +872,16 @@ class AppLocalizationsKa extends AppLocalizations { String get restore_defaults => 'ნაგულისხმევი პარამეტრების აღდგენა'; @override - String get download_music_codec => 'მუსიკის კოდეკის გადმოწერა'; + String get download_music_format => 'მუსიკის ჩამოტვირთვის ფორმატი'; @override - String get streaming_music_codec => 'სტრიმინგ მუსიკის კოდეკი'; + String get streaming_music_format => 'სტრიმინგის მუსიკის ფორმატი'; + + @override + String get download_music_quality => 'ჩამოტვირთვის ხარისხი'; + + @override + String get streaming_music_quality => 'სტრიმინგის ხარისხი'; @override String get login_with_lastfm => 'Last.fm-ით შესვლა'; @@ -1448,7 +1454,17 @@ class AppLocalizationsKa extends AppLocalizations { 'ეს პლაგინი აწარმოებს თქვენი მუსიკის სქრობლინგს, რათა შექმნას თქვენი მოსმენის ისტორია.'; @override - String get default_plugin => 'ნაგულისხმევი'; + String get default_metadata_source => 'ნაგულისხმევი მეტამონაცემების წყარო'; + + @override + String get set_default_metadata_source => + 'ნაგულისხმევი მეტამონაცემების წყაროს დაყენება'; + + @override + String get default_audio_source => 'ნაგულისხმევი აუდიო წყარო'; + + @override + String get set_default_audio_source => 'ნაგულისხმევი აუდიო წყაროს დაყენება'; @override String get set_default => 'ნაგულისხმევად დაყენება'; @@ -1511,8 +1527,7 @@ class AppLocalizationsKa extends AppLocalizations { 'შეყვანა არ ემთხვევა საჭირო ფორმატს'; @override - String get metadata_provider_plugins => - 'მეტამონაცემების პროვაიდერების პლაგინები'; + String get plugins => 'პლაგინები'; @override String get paste_plugin_download_url => @@ -1537,12 +1552,22 @@ class AppLocalizationsKa extends AppLocalizations { String get available_plugins => 'ხელმისაწვდომი პლაგინები'; @override - String get configure_your_own_metadata_plugin => - 'დააყენეთ თქვენი საკუთარი პლეილისტის/ალბომის/არტისტის/ფიდის მეტამონაცემების პროვაიდერი'; + String get configure_plugins => + 'თქვენი საკუთარი მეტამონაცემებისა და აუდიო წყაროს პლაგინების კონფიგურაცია'; @override String get audio_scrobblers => 'აუდიო სქრობლერები'; @override String get scrobbling => 'სქრობლინგი'; + + @override + String get source => 'წყარო: '; + + @override + String get uncompressed => 'შეუკუმშავი'; + + @override + String get dab_music_source_description => + 'აუდიოფილებისთვის. უზრუნველყოფს მაღალი ხარისხის/უკომპრესო აუდიო სტრიმებს. ზუსტი შესაბამისობა ISRC-ის მიხედვით.'; } diff --git a/lib/l10n/generated/app_localizations_ko.dart b/lib/l10n/generated/app_localizations_ko.dart index 40104b52..42ea337a 100644 --- a/lib/l10n/generated/app_localizations_ko.dart +++ b/lib/l10n/generated/app_localizations_ko.dart @@ -862,10 +862,16 @@ class AppLocalizationsKo extends AppLocalizations { String get restore_defaults => '기본값으로 복원'; @override - String get download_music_codec => '다운로드 음악 코덱'; + String get download_music_format => '다운로드 음악 포맷'; @override - String get streaming_music_codec => '스트리밍 음악 코덱'; + String get streaming_music_format => '스트리밍 음악 포맷'; + + @override + String get download_music_quality => '다운로드 음질'; + + @override + String get streaming_music_quality => '스트리밍 음질'; @override String get login_with_lastfm => 'Last.fm에 로그인'; @@ -1421,7 +1427,16 @@ class AppLocalizationsKo extends AppLocalizations { String get plugin_scrobbling_info => '이 플러그인은 음악을 스크로블하여 청취 기록을 생성합니다.'; @override - String get default_plugin => '기본'; + String get default_metadata_source => '기본 메타데이터 소스'; + + @override + String get set_default_metadata_source => '기본 메타데이터 소스 설정'; + + @override + String get default_audio_source => '기본 오디오 소스'; + + @override + String get set_default_audio_source => '기본 오디오 소스 설정'; @override String get set_default => '기본값으로 설정'; @@ -1479,7 +1494,7 @@ class AppLocalizationsKo extends AppLocalizations { String get input_does_not_match_format => '입력이 필요한 형식과 일치하지 않습니다'; @override - String get metadata_provider_plugins => '메타데이터 제공자 플러그인'; + String get plugins => '플러그인'; @override String get paste_plugin_download_url => @@ -1503,12 +1518,21 @@ class AppLocalizationsKo extends AppLocalizations { String get available_plugins => '사용 가능한 플러그인'; @override - String get configure_your_own_metadata_plugin => - '자신만의 플레이리스트/앨범/아티스트/피드 메타데이터 제공자 구성'; + String get configure_plugins => '직접 메타데이터 제공자와 오디오 소스 플러그인을 구성하세요'; @override String get audio_scrobblers => '오디오 스크로블러'; @override String get scrobbling => '스크로블링'; + + @override + String get source => '출처: '; + + @override + String get uncompressed => '비압축'; + + @override + String get dab_music_source_description => + '오디오파일을 위한 소스입니다. 고음질/무손실 오디오 스트림을 제공하며 ISRC 기반으로 정확한 트랙 매칭을 지원합니다.'; } diff --git a/lib/l10n/generated/app_localizations_ne.dart b/lib/l10n/generated/app_localizations_ne.dart index 18d155fe..8f881b51 100644 --- a/lib/l10n/generated/app_localizations_ne.dart +++ b/lib/l10n/generated/app_localizations_ne.dart @@ -880,10 +880,16 @@ class AppLocalizationsNe extends AppLocalizations { String get restore_defaults => 'पूर्वनिर्धारितहरू पुनः स्थापित गर्नुहोस्'; @override - String get download_music_codec => 'साङ्गीत कोडेक डाउनलोड गर्नुहोस्'; + String get download_music_format => 'सङ्गीत डाउनलोड ढाँचा'; @override - String get streaming_music_codec => 'स्ट्रिमिङ साङ्गीत कोडेक'; + String get streaming_music_format => 'स्ट्रिमिङ सङ्गीत ढाँचा'; + + @override + String get download_music_quality => 'डाउनलोड गुणस्तर'; + + @override + String get streaming_music_quality => 'स्ट्रिमिङ गुणस्तर'; @override String get login_with_lastfm => 'लास्ट.एफ.एम सँग लगइन गर्नुहोस्'; @@ -1454,7 +1460,18 @@ class AppLocalizationsNe extends AppLocalizations { 'यो प्लगइनले तपाईंको सुन्ने इतिहास उत्पन्न गर्न तपाईंको संगीतलाई स्क्रब्बल गर्दछ।'; @override - String get default_plugin => 'पूर्वनिर्धारित'; + String get default_metadata_source => 'पूर्वनिर्धारित मेटाडाटा स्रोत'; + + @override + String get set_default_metadata_source => + 'पूर्वनिर्धारित मेटाडाटा स्रोत सेट गर्नुहोस्'; + + @override + String get default_audio_source => 'पूर्वनिर्धारित अडियो स्रोत'; + + @override + String get set_default_audio_source => + 'पूर्वनिर्धारित अडियो स्रोत सेट गर्नुहोस्'; @override String get set_default => 'पूर्वनिर्धारित सेट गर्नुहोस्'; @@ -1515,7 +1532,7 @@ class AppLocalizationsNe extends AppLocalizations { String get input_does_not_match_format => 'इनपुट आवश्यक ढाँचासँग मेल खाँदैन'; @override - String get metadata_provider_plugins => 'मेटाडेटा प्रदायक प्लगइनहरू'; + String get plugins => 'प्लगइनहरू'; @override String get paste_plugin_download_url => @@ -1540,12 +1557,22 @@ class AppLocalizationsNe extends AppLocalizations { String get available_plugins => 'उपलब्ध प्लगइनहरू'; @override - String get configure_your_own_metadata_plugin => - 'तपाईंको आफ्नै प्लेलिस्ट/एल्बम/कलाकार/फिड मेटाडेटा प्रदायक कन्फिगर गर्नुहोस्'; + String get configure_plugins => + 'आफ्नै मेटाडाटा प्रदायक र अडियो स्रोत प्लगइनहरू कन्फिगर गर्नुहोस्'; @override String get audio_scrobblers => 'अडियो स्क्रब्बलरहरू'; @override String get scrobbling => 'स्क्रब्बलिंग'; + + @override + String get source => 'स्रोत: '; + + @override + String get uncompressed => 'असंक्षिप्त'; + + @override + String get dab_music_source_description => + 'अडियोप्रेमीहरूका लागि। उच्च गुणस्तर/लसलेस अडियो स्ट्रिमहरू उपलब्ध गराउँछ। ISRC-मा आधारित सटीक ट्र्याक मिलान।'; } diff --git a/lib/l10n/generated/app_localizations_nl.dart b/lib/l10n/generated/app_localizations_nl.dart index 3074e958..0a73c640 100644 --- a/lib/l10n/generated/app_localizations_nl.dart +++ b/lib/l10n/generated/app_localizations_nl.dart @@ -815,7 +815,7 @@ class AppLocalizationsNl extends AppLocalizations { String get search_mode => 'Zoekmodus'; @override - String get audio_source => 'Audio Source'; + String get audio_source => 'Audiobron'; @override String get ok => 'Oké'; @@ -872,10 +872,16 @@ class AppLocalizationsNl extends AppLocalizations { String get restore_defaults => 'Standaardwaarden herstellen'; @override - String get download_music_codec => 'Download-codec'; + String get download_music_format => 'Download muziekformaat'; @override - String get streaming_music_codec => 'Streaming-codec'; + String get streaming_music_format => 'Streaming muziekformaat'; + + @override + String get download_music_quality => 'Downloadkwaliteit'; + + @override + String get streaming_music_quality => 'Streamingkwaliteit'; @override String get login_with_lastfm => 'Inloggen met Last.fm'; @@ -1446,7 +1452,16 @@ class AppLocalizationsNl extends AppLocalizations { 'Deze plugin scrobblet uw muziek om uw luistergeschiedenis te genereren.'; @override - String get default_plugin => 'Standaard'; + String get default_metadata_source => 'Standaard metadata-bron'; + + @override + String get set_default_metadata_source => 'Standaard metadata-bron instellen'; + + @override + String get default_audio_source => 'Standaard audiobron'; + + @override + String get set_default_audio_source => 'Standaard audiobron instellen'; @override String get set_default => 'Instellen als standaard'; @@ -1509,7 +1524,7 @@ class AppLocalizationsNl extends AppLocalizations { 'Invoer komt niet overeen met het vereiste formaat'; @override - String get metadata_provider_plugins => 'Metadata-aanbieder Plugins'; + String get plugins => 'Plug-ins'; @override String get paste_plugin_download_url => @@ -1534,12 +1549,22 @@ class AppLocalizationsNl extends AppLocalizations { String get available_plugins => 'Beschikbare plugins'; @override - String get configure_your_own_metadata_plugin => - 'Configureer uw eigen metadata-aanbieder voor afspeellijst/album/artiest/feed'; + String get configure_plugins => + 'Configureer je eigen metadata- en audiobron-plug-ins'; @override String get audio_scrobblers => 'Audioscrobblers'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Bron: '; + + @override + String get uncompressed => 'Ongecomprimeerd'; + + @override + String get dab_music_source_description => + 'Voor audiofielen. Biedt hoge kwaliteit/lossless audiostreams. Nauwkeurige trackmatching op basis van ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_pl.dart b/lib/l10n/generated/app_localizations_pl.dart index 969204da..5e185035 100644 --- a/lib/l10n/generated/app_localizations_pl.dart +++ b/lib/l10n/generated/app_localizations_pl.dart @@ -873,10 +873,16 @@ class AppLocalizationsPl extends AppLocalizations { String get restore_defaults => 'Przywróć domyślne'; @override - String get download_music_codec => 'Pobierz kodek muzyczny'; + String get download_music_format => 'Format pobierania muzyki'; @override - String get streaming_music_codec => 'Kodek strumieniowy muzyki'; + String get streaming_music_format => 'Format strumieniowania muzyki'; + + @override + String get download_music_quality => 'Jakość pobierania'; + + @override + String get streaming_music_quality => 'Jakość strumieniowania'; @override String get login_with_lastfm => 'Zaloguj się z Last.fm'; @@ -1449,7 +1455,16 @@ class AppLocalizationsPl extends AppLocalizations { 'Ta wtyczka scrobbluje Twoją muzykę, aby wygenerować historię odsłuchań.'; @override - String get default_plugin => 'Domyślna'; + String get default_metadata_source => 'Domyślne źródło metadanych'; + + @override + String get set_default_metadata_source => 'Ustaw domyślne źródło metadanych'; + + @override + String get default_audio_source => 'Domyślne źródło audio'; + + @override + String get set_default_audio_source => 'Ustaw domyślne źródło audio'; @override String get set_default => 'Ustaw jako domyślną'; @@ -1511,7 +1526,7 @@ class AppLocalizationsPl extends AppLocalizations { 'Wprowadzony tekst nie pasuje do wymaganego formatu'; @override - String get metadata_provider_plugins => 'Wtyczki dostawców metadanych'; + String get plugins => 'Wtyczki'; @override String get paste_plugin_download_url => @@ -1536,12 +1551,22 @@ class AppLocalizationsPl extends AppLocalizations { 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'; + String get configure_plugins => + 'Skonfiguruj własne wtyczki dostawców metadanych i źródeł audio'; @override String get audio_scrobblers => 'Scrobblery audio'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Źródło: '; + + @override + String get uncompressed => 'Nieskompresowany'; + + @override + String get dab_music_source_description => + 'Dla audiofilów. Oferuje strumienie audio wysokiej jakości/lossless. Precyzyjne dopasowanie utworów na podstawie ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 35d9881d..8d2eabe7 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -873,10 +873,16 @@ class AppLocalizationsPt extends AppLocalizations { String get restore_defaults => 'Restaurar padrões'; @override - String get download_music_codec => 'Descarregar codec de música'; + String get download_music_format => 'Formato de download de música'; @override - String get streaming_music_codec => 'Codec de streaming de música'; + String get streaming_music_format => 'Formato de streaming de música'; + + @override + String get download_music_quality => 'Qualidade de download'; + + @override + String get streaming_music_quality => 'Qualidade de streaming'; @override String get login_with_lastfm => 'Iniciar sessão com o Last.fm'; @@ -1446,7 +1452,16 @@ class AppLocalizationsPt extends AppLocalizations { 'Este plugin faz o scrobbling de sua música para gerar seu histórico de audição.'; @override - String get default_plugin => 'Padrão'; + String get default_metadata_source => 'Fonte padrão de metadados'; + + @override + String get set_default_metadata_source => 'Definir fonte padrão de metadados'; + + @override + String get default_audio_source => 'Fonte de áudio padrão'; + + @override + String get set_default_audio_source => 'Definir fonte de áudio padrão'; @override String get set_default => 'Definir como padrão'; @@ -1508,7 +1523,7 @@ class AppLocalizationsPt extends AppLocalizations { 'A entrada não corresponde ao formato exigido'; @override - String get metadata_provider_plugins => 'Plugins do provedor de metadados'; + String get plugins => 'Plugins'; @override String get paste_plugin_download_url => @@ -1533,12 +1548,22 @@ class AppLocalizationsPt extends AppLocalizations { 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'; + String get configure_plugins => + 'Configure seus próprios plugins de provedores de metadados e fontes de áudio'; @override String get audio_scrobblers => 'Scrobblers de áudio'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Fonte: '; + + @override + String get uncompressed => 'Não comprimido'; + + @override + String get dab_music_source_description => + 'Para audiófilos. Fornece streams de áudio de alta qualidade/sem perdas. Correspondência precisa de faixas baseada em ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index e4cd090b..31be6a7b 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -874,10 +874,16 @@ class AppLocalizationsRu extends AppLocalizations { String get restore_defaults => 'Восстановить настройки по умолчанию'; @override - String get download_music_codec => 'Загрузить кодек для музыки'; + String get download_music_format => 'Формат загрузки музыки'; @override - String get streaming_music_codec => 'Кодек потоковой передачи музыки'; + String get streaming_music_format => 'Формат потоковой музыки'; + + @override + String get download_music_quality => 'Качество загрузки'; + + @override + String get streaming_music_quality => 'Качество стриминга'; @override String get login_with_lastfm => 'Войти с помощью Last.fm'; @@ -1448,7 +1454,17 @@ class AppLocalizationsRu extends AppLocalizations { 'Этот плагин скробблит вашу музыку для создания вашей истории прослушиваний.'; @override - String get default_plugin => 'По умолчанию'; + String get default_metadata_source => 'Источник метаданных по умолчанию'; + + @override + String get set_default_metadata_source => + 'Задать источник метаданных по умолчанию'; + + @override + String get default_audio_source => 'Источник аудио по умолчанию'; + + @override + String get set_default_audio_source => 'Задать источник аудио по умолчанию'; @override String get set_default => 'Установить по умолчанию'; @@ -1511,7 +1527,7 @@ class AppLocalizationsRu extends AppLocalizations { 'Введенные данные не соответствуют требуемому формату'; @override - String get metadata_provider_plugins => 'Плагины поставщика метаданных'; + String get plugins => 'Плагины'; @override String get paste_plugin_download_url => @@ -1536,12 +1552,22 @@ class AppLocalizationsRu extends AppLocalizations { String get available_plugins => 'Доступные плагины'; @override - String get configure_your_own_metadata_plugin => - 'Настройте свой собственный поставщик метаданных для плейлиста/альбома/артиста/ленты'; + String get configure_plugins => + 'Настройте собственные плагины провайдеров метаданных и источников аудио'; @override String get audio_scrobblers => 'Аудио скробблеры'; @override String get scrobbling => 'Скробблинг'; + + @override + String get source => 'Источник: '; + + @override + String get uncompressed => 'Несжатый'; + + @override + String get dab_music_source_description => + 'Для аудиофилов. Предоставляет высококачественные/lossless аудиопотоки. Точное совпадение треков по ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_ta.dart b/lib/l10n/generated/app_localizations_ta.dart index 0a131edd..062a99dc 100644 --- a/lib/l10n/generated/app_localizations_ta.dart +++ b/lib/l10n/generated/app_localizations_ta.dart @@ -879,10 +879,16 @@ class AppLocalizationsTa extends AppLocalizations { String get restore_defaults => 'இயல்புநிலைகளை மீட்டமை'; @override - String get download_music_codec => 'இசை கோடெக்கை பதிவிறக்கு'; + String get download_music_format => 'இசை பதிவிறக்க வடிவம்'; @override - String get streaming_music_codec => 'இசை கோடெக்கை ஸ்ட்ரீம் செய்'; + String get streaming_music_format => 'இசை ஸ்ட்ரீமிங் வடிவம்'; + + @override + String get download_music_quality => 'பதிவிறக்க தரம்'; + + @override + String get streaming_music_quality => 'ஸ்ட்ரீமிங் தரம்'; @override String get login_with_lastfm => 'Last.fm உடன் உள்நுழைக'; @@ -1455,7 +1461,17 @@ class AppLocalizationsTa extends AppLocalizations { 'இந்த பிளகின் உங்கள் கேட்பதின் வரலாற்றை உருவாக்க உங்கள் இசையை ஸ்க்ரோப்ள் செய்கிறது.'; @override - String get default_plugin => 'இயல்புநிலை'; + String get default_metadata_source => 'இயல்புநிலை மெட்டாடேட்டா மூலம்'; + + @override + String get set_default_metadata_source => + 'இயல்புநிலை மெட்டாடேட்டா மூலத்தை அமை'; + + @override + String get default_audio_source => 'இயல்புநிலை ஆடியோ மூலம்'; + + @override + String get set_default_audio_source => 'இயல்புநிலை ஆடியோ மூலத்தை அமை'; @override String get set_default => 'இயல்புநிலையாக அமைக்கவும்'; @@ -1517,7 +1533,7 @@ class AppLocalizationsTa extends AppLocalizations { 'உள்ளீடு தேவையான வடிவத்துடன் பொருந்தவில்லை'; @override - String get metadata_provider_plugins => 'மெட்டாடேட்டா வழங்குநர் பிளகின்கள்'; + String get plugins => 'செருகுநிரல்கள்'; @override String get paste_plugin_download_url => @@ -1542,12 +1558,22 @@ class AppLocalizationsTa extends AppLocalizations { String get available_plugins => 'கிடைக்கக்கூடிய பிளகின்கள்'; @override - String get configure_your_own_metadata_plugin => - 'உங்கள் சொந்த பிளேலிஸ்ட்/ஆல்பம்/கலைஞர்/ஊட்ட மெட்டாடேட்டா வழங்குநரை உள்ளமைக்கவும்'; + String get configure_plugins => + 'உங்கள் சொந்த மெட்டாடேட்டா வழங்குநர் மற்றும் ஆடியோ மூல செருகுநிரல்களை அமைக்கவும்'; @override String get audio_scrobblers => 'ஆடியோ ஸ்க்ரோப்ளர்கள்'; @override String get scrobbling => 'ஸ்க்ரோப்ளிங்'; + + @override + String get source => 'மூலம்: '; + + @override + String get uncompressed => 'அழுத்தப்படாத'; + + @override + String get dab_music_source_description => + 'ஆடியோஃபைல்களுக்காக. உயர்தர/லாஸ்லெஸ் ஆடியோ ஸ்ட்ரீம்களை வழங்குகிறது. ISRC அடிப்படையில் துல்லியமான பாடல் பொருத்தம்.'; } diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index 85230bfd..16584ab8 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -872,10 +872,16 @@ class AppLocalizationsTh extends AppLocalizations { String get restore_defaults => 'คืนค่าเริ่มต้น'; @override - String get download_music_codec => 'ดาวน์โหลดโคเดคเพลง'; + String get download_music_format => 'รูปแบบการดาวน์โหลดเพลง'; @override - String get streaming_music_codec => 'สตรีมมิ่งโคเดคเพลง'; + String get streaming_music_format => 'รูปแบบการสตรีมเพลง'; + + @override + String get download_music_quality => 'คุณภาพการดาวน์โหลด'; + + @override + String get streaming_music_quality => 'คุณภาพการสตรีม'; @override String get login_with_lastfm => 'เข้าสู่ระบบด้วย Last.fm'; @@ -1440,7 +1446,16 @@ class AppLocalizationsTh extends AppLocalizations { 'ปลั๊กอินนี้จะ scrobble เพลงของคุณเพื่อสร้างประวัติการฟังของคุณ'; @override - String get default_plugin => 'ค่าเริ่มต้น'; + String get default_metadata_source => 'แหล่งเมตาดาต้าพื้นฐาน'; + + @override + String get set_default_metadata_source => 'ตั้งค่าแหล่งเมตาดาต้าพื้นฐาน'; + + @override + String get default_audio_source => 'แหล่งเสียงพื้นฐาน'; + + @override + String get set_default_audio_source => 'ตั้งค่าแหล่งเสียงพื้นฐาน'; @override String get set_default => 'ตั้งค่าเริ่มต้น'; @@ -1500,7 +1515,7 @@ class AppLocalizationsTh extends AppLocalizations { String get input_does_not_match_format => 'อินพุตไม่ตรงกับรูปแบบที่ต้องการ'; @override - String get metadata_provider_plugins => 'ปลั๊กอินผู้ให้บริการเมตาดาต้า'; + String get plugins => 'ปลั๊กอิน'; @override String get paste_plugin_download_url => @@ -1525,12 +1540,22 @@ class AppLocalizationsTh extends AppLocalizations { String get available_plugins => 'ปลั๊กอินที่มีอยู่'; @override - String get configure_your_own_metadata_plugin => - 'กำหนดค่าผู้ให้บริการเมตาดาต้าเพลย์ลิสต์/อัลบั้ม/ศิลปิน/ฟีดของคุณเอง'; + String get configure_plugins => + 'กำหนดค่าปลั๊กอินผู้ให้บริการเมตาดาต้าและแหล่งเสียงของคุณเอง'; @override String get audio_scrobblers => 'เครื่อง scrobbler เสียง'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'แหล่งที่มา: '; + + @override + String get uncompressed => 'ไม่บีบอัด'; + + @override + String get dab_music_source_description => + 'สำหรับคนรักเสียงเพลง ให้สตรีมเสียงคุณภาพสูง/ไร้การสูญเสียการบีบอัด การจับคู่แทร็กแม่นยำตาม ISRC'; } diff --git a/lib/l10n/generated/app_localizations_tl.dart b/lib/l10n/generated/app_localizations_tl.dart index 361a7bf0..5febc92d 100644 --- a/lib/l10n/generated/app_localizations_tl.dart +++ b/lib/l10n/generated/app_localizations_tl.dart @@ -878,10 +878,16 @@ class AppLocalizationsTl extends AppLocalizations { String get restore_defaults => 'Ibalik ang mga default'; @override - String get download_music_codec => 'Codec para sa pag-download ng musika'; + String get download_music_format => 'I-download na format ng musika'; @override - String get streaming_music_codec => 'Codec para sa pag-stream ng musika'; + String get streaming_music_format => 'Format ng streaming ng musika'; + + @override + String get download_music_quality => 'Kalidad ng i-download na musika'; + + @override + String get streaming_music_quality => 'Kalidad ng streaming ng musika'; @override String get login_with_lastfm => 'Mag-login gamit ang Last.fm'; @@ -1456,7 +1462,18 @@ class AppLocalizationsTl extends AppLocalizations { 'Sinis-scrobble ng plugin na ito ang iyong musika upang mabuo ang iyong kasaysayan ng pakikinig.'; @override - String get default_plugin => 'Default'; + String get default_metadata_source => 'Default na pinagmulan ng metadata'; + + @override + String get set_default_metadata_source => + 'Itakda ang default na pinagmulan ng metadata'; + + @override + String get default_audio_source => 'Default na pinagmulan ng audio'; + + @override + String get set_default_audio_source => + 'Itakda ang default na pinagmulan ng audio'; @override String get set_default => 'Itakda bilang default'; @@ -1518,7 +1535,7 @@ class AppLocalizationsTl extends AppLocalizations { 'Ang input ay hindi tumutugma sa kinakailangang format'; @override - String get metadata_provider_plugins => 'Mga Plugin ng Metadata Provider'; + String get plugins => 'Mga plugin'; @override String get paste_plugin_download_url => @@ -1543,12 +1560,22 @@ class AppLocalizationsTl extends AppLocalizations { 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'; + String get configure_plugins => + 'I-configure ang sarili mong metadata provider at mga audio source plugin'; @override String get audio_scrobblers => 'Mga Audio Scrobbler'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Pinagmulan: '; + + @override + String get uncompressed => 'Hindi naka-compress'; + + @override + String get dab_music_source_description => + 'Para sa mga audiophile. Nagbibigay ng de-kalidad/walang loss na audio streams. Tumpak na pagtutugma ng track batay sa ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index 4dc65bbc..c2280f47 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -875,10 +875,16 @@ class AppLocalizationsTr extends AppLocalizations { String get restore_defaults => 'Varsayılanları geri yükle'; @override - String get download_music_codec => 'Müzik codec bileşenini indir'; + String get download_music_format => 'Müzik indirme formatı'; @override - String get streaming_music_codec => 'Müzik codec\'i akışı'; + String get streaming_music_format => 'Müzik akış formatı'; + + @override + String get download_music_quality => 'İndirilen müzik kalitesi'; + + @override + String get streaming_music_quality => 'Yayınlanan müzik kalitesi'; @override String get login_with_lastfm => 'Last.fm ile giriş yap'; @@ -1450,7 +1456,17 @@ class AppLocalizationsTr extends AppLocalizations { 'Bu eklenti, dinleme geçmişinizi oluşturmak için müziğinizi scrobble eder.'; @override - String get default_plugin => 'Varsayılan'; + String get default_metadata_source => 'Varsayılan meta veri kaynağı'; + + @override + String get set_default_metadata_source => + 'Varsayılan meta veri kaynağını ayarla'; + + @override + String get default_audio_source => 'Varsayılan ses kaynağı'; + + @override + String get set_default_audio_source => 'Varsayılan ses kaynağını ayarla'; @override String get set_default => 'Varsayılan olarak ayarla'; @@ -1511,7 +1527,7 @@ class AppLocalizationsTr extends AppLocalizations { 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'; + String get plugins => 'Eklentiler'; @override String get paste_plugin_download_url => @@ -1536,12 +1552,22 @@ class AppLocalizationsTr extends AppLocalizations { 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'; + String get configure_plugins => + 'Kendi meta veri sağlayıcı ve ses kaynağı eklentilerinizi yapılandırın'; @override String get audio_scrobblers => 'Ses Scrobbler\'lar'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Kaynak: '; + + @override + String get uncompressed => 'Sıkıştırılmamış'; + + @override + String get dab_music_source_description => + 'Audiophile\'ler için. Yüksek kaliteli/kayıpsız ses akışları sağlar. Doğru ISRC tabanlı parça eşleştirme.'; } diff --git a/lib/l10n/generated/app_localizations_uk.dart b/lib/l10n/generated/app_localizations_uk.dart index 35a18d55..c2bed426 100644 --- a/lib/l10n/generated/app_localizations_uk.dart +++ b/lib/l10n/generated/app_localizations_uk.dart @@ -875,10 +875,16 @@ class AppLocalizationsUk extends AppLocalizations { String get restore_defaults => 'Відновити налаштування за замовчуванням'; @override - String get download_music_codec => 'Завантажити кодек для музики'; + String get download_music_format => 'Формат завантаження музики'; @override - String get streaming_music_codec => 'Кодек потокової передачі музики'; + String get streaming_music_format => 'Формат потокової музики'; + + @override + String get download_music_quality => 'Якість завантаженої музики'; + + @override + String get streaming_music_quality => 'Якість потокової музики'; @override String get login_with_lastfm => 'Увійти з Last.fm'; @@ -1446,7 +1452,18 @@ class AppLocalizationsUk extends AppLocalizations { 'Цей плагін скроббить вашу музику, щоб створити вашу історію прослуховувань.'; @override - String get default_plugin => 'За замовчуванням'; + String get default_metadata_source => 'Джерело метаданих за замовчуванням'; + + @override + String get set_default_metadata_source => + 'Встановити джерело метаданих за замовчуванням'; + + @override + String get default_audio_source => 'Джерело аудіо за замовчуванням'; + + @override + String get set_default_audio_source => + 'Встановити джерело аудіо за замовчуванням'; @override String get set_default => 'Встановити за замовчуванням'; @@ -1507,7 +1524,7 @@ class AppLocalizationsUk extends AppLocalizations { 'Введені дані не відповідають необхідному формату'; @override - String get metadata_provider_plugins => 'Плагіни провайдера метаданих'; + String get plugins => 'Плагіни'; @override String get paste_plugin_download_url => @@ -1532,12 +1549,22 @@ class AppLocalizationsUk extends AppLocalizations { String get available_plugins => 'Доступні плагіни'; @override - String get configure_your_own_metadata_plugin => - 'Налаштуйте свій власний провайдер метаданих для плейлиста/альбому/виконавця/стрічки'; + String get configure_plugins => + 'Налаштуйте власні плагіни метаданих і аудіоджерела'; @override String get audio_scrobblers => 'Аудіо скробблери'; @override String get scrobbling => 'Скроблінг'; + + @override + String get source => 'Джерело: '; + + @override + String get uncompressed => 'Без стиснення'; + + @override + String get dab_music_source_description => + 'Для аудіофілів. Забезпечує високоякісні/без втрат аудіопотоки. Точна відповідність треків на основі ISRC.'; } diff --git a/lib/l10n/generated/app_localizations_vi.dart b/lib/l10n/generated/app_localizations_vi.dart index 6015931e..4d7a8945 100644 --- a/lib/l10n/generated/app_localizations_vi.dart +++ b/lib/l10n/generated/app_localizations_vi.dart @@ -875,10 +875,16 @@ class AppLocalizationsVi extends AppLocalizations { String get restore_defaults => 'Khôi phục mặc định'; @override - String get download_music_codec => 'Định dạng tải xuống'; + String get download_music_format => 'Định dạng nhạc tải về'; @override - String get streaming_music_codec => 'Định dạng nghe'; + String get streaming_music_format => 'Định dạng nhạc phát trực tuyến'; + + @override + String get download_music_quality => 'Chất lượng nhạc tải về'; + + @override + String get streaming_music_quality => 'Chất lượng nhạc phát trực tuyến'; @override String get login_with_lastfm => 'Đăng nhập bằng tài khoản Last.fm'; @@ -1450,7 +1456,16 @@ class AppLocalizationsVi extends AppLocalizations { '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'; + String get default_metadata_source => 'Nguồn siêu dữ liệu mặc định'; + + @override + String get set_default_metadata_source => 'Đặt nguồn siêu dữ liệu mặc định'; + + @override + String get default_audio_source => 'Nguồn âm thanh mặc định'; + + @override + String get set_default_audio_source => 'Đặt nguồn âm thanh mặc định'; @override String get set_default => 'Đặt làm mặc định'; @@ -1513,7 +1528,7 @@ class AppLocalizationsVi extends AppLocalizations { 'Đầ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'; + String get plugins => 'Tiện ích bổ sung'; @override String get paste_plugin_download_url => @@ -1538,12 +1553,22 @@ class AppLocalizationsVi extends AppLocalizations { 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'; + String get configure_plugins => + 'Cấu hình nhà cung cấp siêu dữ liệu và tiện ích nguồn âm thanh riêng'; @override String get audio_scrobblers => 'Bộ scrobbler âm thanh'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => 'Nguồn: '; + + @override + String get uncompressed => 'Không nén'; + + @override + String get dab_music_source_description => + 'Dành cho người yêu âm nhạc chất lượng cao. Cung cấp luồng âm thanh chất lượng cao/không nén. Phù hợp bài hát dựa trên ISRC chính xác.'; } diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index e42b6994..ac7d4890 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -859,10 +859,16 @@ class AppLocalizationsZh extends AppLocalizations { String get restore_defaults => '恢复默认值'; @override - String get download_music_codec => '下载音乐编解码器'; + String get download_music_format => '下载音乐格式'; @override - String get streaming_music_codec => '流媒体音乐编解码器'; + String get streaming_music_format => '流媒体音乐格式'; + + @override + String get download_music_quality => '下载音乐质量'; + + @override + String get streaming_music_quality => '流媒体音乐质量'; @override String get login_with_lastfm => '使用 Last.fm 登录'; @@ -1412,7 +1418,16 @@ class AppLocalizationsZh extends AppLocalizations { String get plugin_scrobbling_info => '此插件会 scrobble 您的音乐以生成您的收听历史记录。'; @override - String get default_plugin => '默认'; + String get default_metadata_source => '默认元数据源'; + + @override + String get set_default_metadata_source => '设置默认元数据源'; + + @override + String get default_audio_source => '默认音频源'; + + @override + String get set_default_audio_source => '设置默认音频源'; @override String get set_default => '设为默认'; @@ -1469,7 +1484,7 @@ class AppLocalizationsZh extends AppLocalizations { String get input_does_not_match_format => '输入与所需格式不匹配'; @override - String get metadata_provider_plugins => '元数据提供者插件'; + String get plugins => '插件'; @override String get paste_plugin_download_url => @@ -1493,13 +1508,23 @@ class AppLocalizationsZh extends AppLocalizations { String get available_plugins => '可用插件'; @override - String get configure_your_own_metadata_plugin => '配置您自己的播放列表/专辑/艺人/订阅元数据提供者'; + String get configure_plugins => '配置您自己的元数据提供者和音频源插件'; @override String get audio_scrobblers => '音频 Scrobblers'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => '来源:'; + + @override + String get uncompressed => '无损'; + + @override + String get dab_music_source_description => + '适合发烧友。提供高质量/无损音频流。基于 ISRC 的精确曲目匹配。'; } /// The translations for Chinese, as used in Taiwan (`zh_TW`). @@ -2357,10 +2382,16 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get restore_defaults => '恢復預設值'; @override - String get download_music_codec => '下載音樂編解碼器'; + String get download_music_format => '下載音樂格式'; @override - String get streaming_music_codec => '串流音樂編解碼器'; + String get streaming_music_format => '串流音樂格式'; + + @override + String get download_music_quality => '下載音樂品質'; + + @override + String get streaming_music_quality => '串流音樂品質'; @override String get login_with_lastfm => '使用 Last.fm 登入'; @@ -2910,7 +2941,16 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get plugin_scrobbling_info => '此外掛程式會 Scrobble 您的音樂以產生您的收聽記錄。'; @override - String get default_plugin => '預設'; + String get default_metadata_source => '預設中繼資料來源'; + + @override + String get set_default_metadata_source => '設定預設中繼資料來源'; + + @override + String get default_audio_source => '預設音訊來源'; + + @override + String get set_default_audio_source => '設定預設音訊來源'; @override String get set_default => '設為預設'; @@ -2967,7 +3007,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get input_does_not_match_format => '輸入不符合所需格式'; @override - String get metadata_provider_plugins => '中繼資料供應商外掛程式'; + String get plugins => '外掛程式'; @override String get paste_plugin_download_url => @@ -2991,11 +3031,21 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { String get available_plugins => '可用的外掛程式'; @override - String get configure_your_own_metadata_plugin => '設定您自己的播放清單/專輯/藝人/動態中繼資料供應商'; + String get configure_plugins => '配置您自己的中繼資料提供者和音訊來源外掛程式'; @override String get audio_scrobblers => '音訊 Scrobblers'; @override String get scrobbling => 'Scrobbling'; + + @override + String get source => '來源:'; + + @override + String get uncompressed => '未壓縮'; + + @override + String get dab_music_source_description => + '適合音響發燒友。提供高品質/無損音訊串流。精確的 ISRC 曲目比對。'; } diff --git a/lib/main.dart b/lib/main.dart index b5789d6f..ecf7148d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -83,6 +83,8 @@ Future main(List rawArgs) async { // force High Refresh Rate on some Android devices (like One Plus) if (kIsAndroid) { await FlutterDisplayMode.setHighRefreshRate(); + } + if (kIsAndroid || kIsDesktop) { await NewPipeExtractor.init(); } @@ -150,11 +152,13 @@ class Spotube extends HookConsumerWidget { ref.listen(audioPlayerStreamListenersProvider, (_, __) {}); ref.listen(bonsoirProvider, (_, __) {}); ref.listen(connectClientsProvider, (_, __) {}); - ref.listen(metadataPluginsProvider, (_, __) {}); - ref.listen(metadataPluginProvider, (_, __) {}); ref.listen(serverProvider, (_, __) {}); ref.listen(trayManagerProvider, (_, __) {}); + ref.listen(metadataPluginsProvider, (_, __) {}); + ref.listen(metadataPluginProvider, (_, __) {}); + ref.listen(audioSourcePluginProvider, (_, __) {}); ref.listen(metadataPluginUpdateCheckerProvider, (_, __) {}); + ref.listen(audioSourcePluginUpdateCheckerProvider, (_, __) {}); useFixWindowStretching(); useDisableBatteryOptimizations(); diff --git a/lib/models/connect/connect.freezed.dart b/lib/models/connect/connect.freezed.dart index 9f9b558b..157d0911 100644 --- a/lib/models/connect/connect.freezed.dart +++ b/lib/models/connect/connect.freezed.dart @@ -112,8 +112,13 @@ mixin _$WebSocketLoadEventData { required TResult orElse(), }) => throw _privateConstructorUsedError; + + /// Serializes this WebSocketLoadEventData to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of WebSocketLoadEventData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $WebSocketLoadEventDataCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -142,6 +147,8 @@ 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({ @@ -190,6 +197,8 @@ 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({ @@ -213,6 +222,8 @@ 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 { @@ -281,12 +292,14 @@ class _$WebSocketLoadEventDataPlaylistImpl other.initialIndex == initialIndex)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_tracks), collection, initialIndex); - @JsonKey(ignore: true) + /// Create a copy of WebSocketLoadEventData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$WebSocketLoadEventDataPlaylistImplCopyWith< @@ -420,8 +433,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$WebSocketLoadEventDataPlaylistImplCopyWith< _$WebSocketLoadEventDataPlaylistImpl> get copyWith => throw _privateConstructorUsedError; @@ -456,6 +472,8 @@ 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({ @@ -479,6 +497,8 @@ 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 { @@ -545,12 +565,14 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { other.initialIndex == initialIndex)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_tracks), collection, initialIndex); - @JsonKey(ignore: true) + /// Create a copy of WebSocketLoadEventData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl> @@ -683,8 +705,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index bc30627d..f1c66c1a 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -16,13 +16,14 @@ import 'package:spotube/models/metadata/market.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; import 'package:flutter/widgets.dart' hide Table, Key, View; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:drift/native.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/youtube_engine/newpipe_engine.dart'; import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart'; import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart'; +import 'package:spotube/utils/platform.dart'; import 'package:sqlite3/sqlite3.dart'; import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; @@ -58,14 +59,14 @@ part 'typeconverters/subtitle.dart'; AudioPlayerStateTable, HistoryTable, LyricsTable, - MetadataPluginsTable, + PluginsTable, ], ) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 8; + int get schemaVersion => 10; @override MigrationStrategy get migration { @@ -199,6 +200,43 @@ class AppDatabase extends _$AppDatabase { } }); }, + from8To9: (m, schema) async { + await m + .renameTable(schema.pluginsTable, "metadata_plugins_table") + .catchError((e, stack) => AppLogger.reportError(e, stack)); + await m + .renameColumn( + schema.pluginsTable, + "selected", + pluginsTable.selectedForMetadata, + ) + .catchError((e, stack) => AppLogger.reportError(e, stack)); + await m + .addColumn( + schema.pluginsTable, + pluginsTable.selectedForAudioSource, + ) + .catchError((e, stack) => AppLogger.reportError(e, stack)); + }, + from9To10: (m, schema) async { + await m + .dropColumn(schema.preferencesTable, "piped_instance") + .catchError((e, stack) => AppLogger.reportError(e, stack)); + await m + .dropColumn(schema.preferencesTable, "invidious_instance") + .catchError((e, stack) => AppLogger.reportError(e, stack)); + await m + .addColumn( + schema.sourceMatchTable, + sourceMatchTable.sourceInfo, + ) + .catchError((e, stack) => AppLogger.reportError(e, stack)); + await customStatement("DROP INDEX IF EXISTS uniq_track_match;") + .catchError((e, stack) => AppLogger.reportError(e, stack)); + await m + .dropColumn(schema.sourceMatchTable, "source_id") + .catchError((e, stack) => AppLogger.reportError(e, stack)); + }, ), ); } diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index ba24c037..8aa14899 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -18,15 +18,12 @@ class $AuthenticationTableTable extends AuthenticationTable requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _cookieMeta = const VerificationMeta('cookie'); @override late final GeneratedColumnWithTypeConverter cookie = GeneratedColumn('cookie', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true) .withConverter( $AuthenticationTableTable.$convertercookie); - static const VerificationMeta _accessTokenMeta = - const VerificationMeta('accessToken'); @override late final GeneratedColumnWithTypeConverter accessToken = GeneratedColumn('access_token', aliasedName, false, @@ -55,8 +52,6 @@ class $AuthenticationTableTable extends AuthenticationTable if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } - context.handle(_cookieMeta, const VerificationResult.success()); - context.handle(_accessTokenMeta, const VerificationResult.success()); if (data.containsKey('expiration')) { context.handle( _expirationMeta, @@ -301,8 +296,6 @@ class $BlacklistTableTable extends BlacklistTable late final GeneratedColumn name = GeneratedColumn( 'name', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _elementTypeMeta = - const VerificationMeta('elementType'); @override late final GeneratedColumnWithTypeConverter elementType = GeneratedColumn('element_type', aliasedName, false, @@ -336,7 +329,6 @@ class $BlacklistTableTable extends BlacklistTable } else if (isInserting) { context.missing(_nameMeta); } - context.handle(_elementTypeMeta, const VerificationResult.success()); if (data.containsKey('element_id')) { context.handle(_elementIdMeta, elementId.isAcceptableOrUnknown(data['element_id']!, _elementIdMeta)); @@ -566,17 +558,6 @@ class $PreferencesTableTable extends PreferencesTable requiredDuringInsert: false, defaultConstraints: GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _audioQualityMeta = - const VerificationMeta('audioQuality'); - @override - late final GeneratedColumnWithTypeConverter - audioQuality = GeneratedColumn( - 'audio_quality', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: Constant(SourceQualities.high.name)) - .withConverter( - $PreferencesTableTable.$converteraudioQuality); static const VerificationMeta _albumColorSyncMeta = const VerificationMeta('albumColorSync'); @override @@ -647,8 +628,6 @@ class $PreferencesTableTable extends PreferencesTable defaultConstraints: GeneratedColumn.constraintIsAlways( 'CHECK ("skip_non_music" IN (0, 1))'), defaultValue: const Constant(false)); - static const VerificationMeta _closeBehaviorMeta = - const VerificationMeta('closeBehavior'); @override late final GeneratedColumnWithTypeConverter closeBehavior = GeneratedColumn( @@ -658,8 +637,6 @@ class $PreferencesTableTable extends PreferencesTable defaultValue: Constant(CloseBehavior.close.name)) .withConverter( $PreferencesTableTable.$convertercloseBehavior); - static const VerificationMeta _accentColorSchemeMeta = - const VerificationMeta('accentColorScheme'); @override late final GeneratedColumnWithTypeConverter accentColorScheme = GeneratedColumn( @@ -669,8 +646,6 @@ class $PreferencesTableTable extends PreferencesTable defaultValue: const Constant("Slate:0xff64748b")) .withConverter( $PreferencesTableTable.$converteraccentColorScheme); - static const VerificationMeta _layoutModeMeta = - const VerificationMeta('layoutMode'); @override late final GeneratedColumnWithTypeConverter layoutMode = GeneratedColumn('layout_mode', aliasedName, false, @@ -679,7 +654,6 @@ class $PreferencesTableTable extends PreferencesTable defaultValue: Constant(LayoutMode.adaptive.name)) .withConverter( $PreferencesTableTable.$converterlayoutMode); - static const VerificationMeta _localeMeta = const VerificationMeta('locale'); @override late final GeneratedColumnWithTypeConverter locale = GeneratedColumn('locale', aliasedName, false, @@ -688,7 +662,6 @@ class $PreferencesTableTable extends PreferencesTable defaultValue: const Constant( '{"languageCode":"system","countryCode":"system"}')) .withConverter($PreferencesTableTable.$converterlocale); - static const VerificationMeta _marketMeta = const VerificationMeta('market'); @override late final GeneratedColumnWithTypeConverter market = GeneratedColumn('market', aliasedName, false, @@ -696,8 +669,6 @@ class $PreferencesTableTable extends PreferencesTable requiredDuringInsert: false, defaultValue: Constant(Market.US.name)) .withConverter($PreferencesTableTable.$convertermarket); - static const VerificationMeta _searchModeMeta = - const VerificationMeta('searchMode'); @override late final GeneratedColumnWithTypeConverter searchMode = GeneratedColumn('search_mode', aliasedName, false, @@ -714,8 +685,6 @@ class $PreferencesTableTable extends PreferencesTable type: DriftSqlType.string, requiredDuringInsert: false, defaultValue: const Constant("")); - static const VerificationMeta _localLibraryLocationMeta = - const VerificationMeta('localLibraryLocation'); @override late final GeneratedColumnWithTypeConverter, String> localLibraryLocation = GeneratedColumn( @@ -725,24 +694,6 @@ class $PreferencesTableTable extends PreferencesTable defaultValue: const Constant("")) .withConverter>( $PreferencesTableTable.$converterlocalLibraryLocation); - static const VerificationMeta _pipedInstanceMeta = - const VerificationMeta('pipedInstance'); - @override - late final GeneratedColumn pipedInstance = GeneratedColumn( - 'piped_instance', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant("https://pipedapi.kavin.rocks")); - static const VerificationMeta _invidiousInstanceMeta = - const VerificationMeta('invidiousInstance'); - @override - late final GeneratedColumn invidiousInstance = - GeneratedColumn('invidious_instance', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: const Constant("https://inv.nadeko.net")); - static const VerificationMeta _themeModeMeta = - const VerificationMeta('themeMode'); @override late final GeneratedColumnWithTypeConverter themeMode = GeneratedColumn('theme_mode', aliasedName, false, @@ -750,18 +701,12 @@ class $PreferencesTableTable extends PreferencesTable requiredDuringInsert: false, defaultValue: Constant(ThemeMode.system.name)) .withConverter($PreferencesTableTable.$converterthemeMode); - static const VerificationMeta _audioSourceMeta = - const VerificationMeta('audioSource'); + static const VerificationMeta _audioSourceIdMeta = + const VerificationMeta('audioSourceId'); @override - late final GeneratedColumnWithTypeConverter audioSource = - GeneratedColumn('audio_source', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: Constant(AudioSource.youtube.name)) - .withConverter( - $PreferencesTableTable.$converteraudioSource); - static const VerificationMeta _youtubeClientEngineMeta = - const VerificationMeta('youtubeClientEngine'); + late final GeneratedColumn audioSourceId = GeneratedColumn( + 'audio_source_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); @override late final GeneratedColumnWithTypeConverter youtubeClientEngine = GeneratedColumn( @@ -771,28 +716,6 @@ class $PreferencesTableTable extends PreferencesTable defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name)) .withConverter( $PreferencesTableTable.$converteryoutubeClientEngine); - static const VerificationMeta _streamMusicCodecMeta = - const VerificationMeta('streamMusicCodec'); - @override - late final GeneratedColumnWithTypeConverter - streamMusicCodec = GeneratedColumn( - 'stream_music_codec', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: Constant(SourceCodecs.weba.name)) - .withConverter( - $PreferencesTableTable.$converterstreamMusicCodec); - static const VerificationMeta _downloadMusicCodecMeta = - const VerificationMeta('downloadMusicCodec'); - @override - late final GeneratedColumnWithTypeConverter - downloadMusicCodec = GeneratedColumn( - 'download_music_codec', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: Constant(SourceCodecs.m4a.name)) - .withConverter( - $PreferencesTableTable.$converterdownloadMusicCodec); static const VerificationMeta _discordPresenceMeta = const VerificationMeta('discordPresence'); @override @@ -844,7 +767,6 @@ class $PreferencesTableTable extends PreferencesTable @override List get $columns => [ id, - audioQuality, albumColorSync, amoledDarkTheme, checkUpdate, @@ -860,13 +782,9 @@ class $PreferencesTableTable extends PreferencesTable searchMode, downloadLocation, localLibraryLocation, - pipedInstance, - invidiousInstance, themeMode, - audioSource, + audioSourceId, youtubeClientEngine, - streamMusicCodec, - downloadMusicCodec, discordPresence, endlessPlayback, enableConnect, @@ -887,7 +805,6 @@ class $PreferencesTableTable extends PreferencesTable if (data.containsKey('id')) { context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); } - context.handle(_audioQualityMeta, const VerificationResult.success()); if (data.containsKey('album_color_sync')) { context.handle( _albumColorSyncMeta, @@ -930,38 +847,18 @@ class $PreferencesTableTable extends PreferencesTable skipNonMusic.isAcceptableOrUnknown( data['skip_non_music']!, _skipNonMusicMeta)); } - context.handle(_closeBehaviorMeta, const VerificationResult.success()); - context.handle(_accentColorSchemeMeta, const VerificationResult.success()); - context.handle(_layoutModeMeta, const VerificationResult.success()); - context.handle(_localeMeta, const VerificationResult.success()); - context.handle(_marketMeta, const VerificationResult.success()); - context.handle(_searchModeMeta, const VerificationResult.success()); if (data.containsKey('download_location')) { context.handle( _downloadLocationMeta, downloadLocation.isAcceptableOrUnknown( data['download_location']!, _downloadLocationMeta)); } - context.handle( - _localLibraryLocationMeta, const VerificationResult.success()); - if (data.containsKey('piped_instance')) { + if (data.containsKey('audio_source_id')) { context.handle( - _pipedInstanceMeta, - pipedInstance.isAcceptableOrUnknown( - data['piped_instance']!, _pipedInstanceMeta)); + _audioSourceIdMeta, + audioSourceId.isAcceptableOrUnknown( + data['audio_source_id']!, _audioSourceIdMeta)); } - if (data.containsKey('invidious_instance')) { - context.handle( - _invidiousInstanceMeta, - invidiousInstance.isAcceptableOrUnknown( - data['invidious_instance']!, _invidiousInstanceMeta)); - } - context.handle(_themeModeMeta, const VerificationResult.success()); - context.handle(_audioSourceMeta, const VerificationResult.success()); - context.handle( - _youtubeClientEngineMeta, const VerificationResult.success()); - context.handle(_streamMusicCodecMeta, const VerificationResult.success()); - context.handle(_downloadMusicCodecMeta, const VerificationResult.success()); if (data.containsKey('discord_presence')) { context.handle( _discordPresenceMeta, @@ -1003,9 +900,6 @@ class $PreferencesTableTable extends PreferencesTable return PreferencesTableData( id: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - audioQuality: $PreferencesTableTable.$converteraudioQuality.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}audio_quality'])!), albumColorSync: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}album_color_sync'])!, amoledDarkTheme: attachedDatabase.typeMapping.read( @@ -1044,25 +938,14 @@ class $PreferencesTableTable extends PreferencesTable .$converterlocalLibraryLocation .fromSql(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: $PreferencesTableTable.$converterthemeMode.fromSql( attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}theme_mode'])!), - audioSource: $PreferencesTableTable.$converteraudioSource.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}audio_source'])!), + audioSourceId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}audio_source_id']), youtubeClientEngine: $PreferencesTableTable.$converteryoutubeClientEngine .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}youtube_client_engine'])!), - streamMusicCodec: $PreferencesTableTable.$converterstreamMusicCodec - .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}stream_music_codec'])!), - downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec - .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, - data['${effectivePrefix}download_music_codec'])!), discordPresence: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}discord_presence'])!, endlessPlayback: attachedDatabase.typeMapping @@ -1081,9 +964,6 @@ class $PreferencesTableTable extends PreferencesTable return $PreferencesTableTable(attachedDatabase, alias); } - static JsonTypeConverter2 - $converteraudioQuality = - const EnumNameConverter(SourceQualities.values); static JsonTypeConverter2 $convertercloseBehavior = const EnumNameConverter(CloseBehavior.values); @@ -1101,23 +981,14 @@ class $PreferencesTableTable extends PreferencesTable const StringListConverter(); static JsonTypeConverter2 $converterthemeMode = const EnumNameConverter(ThemeMode.values); - static JsonTypeConverter2 $converteraudioSource = - const EnumNameConverter(AudioSource.values); static JsonTypeConverter2 $converteryoutubeClientEngine = const EnumNameConverter(YoutubeClientEngine.values); - static JsonTypeConverter2 - $converterstreamMusicCodec = - const EnumNameConverter(SourceCodecs.values); - static JsonTypeConverter2 - $converterdownloadMusicCodec = - const EnumNameConverter(SourceCodecs.values); } class PreferencesTableData extends DataClass implements Insertable { final int id; - final SourceQualities audioQuality; final bool albumColorSync; final bool amoledDarkTheme; final bool checkUpdate; @@ -1133,13 +1004,9 @@ class PreferencesTableData extends DataClass final SearchMode searchMode; final String downloadLocation; final List localLibraryLocation; - final String pipedInstance; - final String invidiousInstance; final ThemeMode themeMode; - final AudioSource audioSource; + final String? audioSourceId; final YoutubeClientEngine youtubeClientEngine; - final SourceCodecs streamMusicCodec; - final SourceCodecs downloadMusicCodec; final bool discordPresence; final bool endlessPlayback; final bool enableConnect; @@ -1147,7 +1014,6 @@ class PreferencesTableData extends DataClass final bool cacheMusic; const PreferencesTableData( {required this.id, - required this.audioQuality, required this.albumColorSync, required this.amoledDarkTheme, required this.checkUpdate, @@ -1163,13 +1029,9 @@ class PreferencesTableData extends DataClass required this.searchMode, required this.downloadLocation, required this.localLibraryLocation, - required this.pipedInstance, - required this.invidiousInstance, required this.themeMode, - required this.audioSource, + this.audioSourceId, required this.youtubeClientEngine, - required this.streamMusicCodec, - required this.downloadMusicCodec, required this.discordPresence, required this.endlessPlayback, required this.enableConnect, @@ -1179,10 +1041,6 @@ class PreferencesTableData extends DataClass Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); - { - map['audio_quality'] = Variable( - $PreferencesTableTable.$converteraudioQuality.toSql(audioQuality)); - } map['album_color_sync'] = Variable(albumColorSync); map['amoled_dark_theme'] = Variable(amoledDarkTheme); map['check_update'] = Variable(checkUpdate); @@ -1221,31 +1079,18 @@ class PreferencesTableData extends DataClass .$converterlocalLibraryLocation .toSql(localLibraryLocation)); } - map['piped_instance'] = Variable(pipedInstance); - map['invidious_instance'] = Variable(invidiousInstance); { map['theme_mode'] = Variable( $PreferencesTableTable.$converterthemeMode.toSql(themeMode)); } - { - map['audio_source'] = Variable( - $PreferencesTableTable.$converteraudioSource.toSql(audioSource)); + if (!nullToAbsent || audioSourceId != null) { + map['audio_source_id'] = Variable(audioSourceId); } { map['youtube_client_engine'] = Variable($PreferencesTableTable .$converteryoutubeClientEngine .toSql(youtubeClientEngine)); } - { - map['stream_music_codec'] = Variable($PreferencesTableTable - .$converterstreamMusicCodec - .toSql(streamMusicCodec)); - } - { - map['download_music_codec'] = Variable($PreferencesTableTable - .$converterdownloadMusicCodec - .toSql(downloadMusicCodec)); - } map['discord_presence'] = Variable(discordPresence); map['endless_playback'] = Variable(endlessPlayback); map['enable_connect'] = Variable(enableConnect); @@ -1257,7 +1102,6 @@ class PreferencesTableData extends DataClass PreferencesTableCompanion toCompanion(bool nullToAbsent) { return PreferencesTableCompanion( id: Value(id), - audioQuality: Value(audioQuality), albumColorSync: Value(albumColorSync), amoledDarkTheme: Value(amoledDarkTheme), checkUpdate: Value(checkUpdate), @@ -1273,13 +1117,11 @@ class PreferencesTableData extends DataClass searchMode: Value(searchMode), downloadLocation: Value(downloadLocation), localLibraryLocation: Value(localLibraryLocation), - pipedInstance: Value(pipedInstance), - invidiousInstance: Value(invidiousInstance), themeMode: Value(themeMode), - audioSource: Value(audioSource), + audioSourceId: audioSourceId == null && nullToAbsent + ? const Value.absent() + : Value(audioSourceId), youtubeClientEngine: Value(youtubeClientEngine), - streamMusicCodec: Value(streamMusicCodec), - downloadMusicCodec: Value(downloadMusicCodec), discordPresence: Value(discordPresence), endlessPlayback: Value(endlessPlayback), enableConnect: Value(enableConnect), @@ -1293,8 +1135,6 @@ class PreferencesTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return PreferencesTableData( id: serializer.fromJson(json['id']), - audioQuality: $PreferencesTableTable.$converteraudioQuality - .fromJson(serializer.fromJson(json['audioQuality'])), albumColorSync: serializer.fromJson(json['albumColorSync']), amoledDarkTheme: serializer.fromJson(json['amoledDarkTheme']), checkUpdate: serializer.fromJson(json['checkUpdate']), @@ -1316,18 +1156,11 @@ class PreferencesTableData extends DataClass downloadLocation: serializer.fromJson(json['downloadLocation']), localLibraryLocation: serializer.fromJson>(json['localLibraryLocation']), - pipedInstance: serializer.fromJson(json['pipedInstance']), - invidiousInstance: serializer.fromJson(json['invidiousInstance']), themeMode: $PreferencesTableTable.$converterthemeMode .fromJson(serializer.fromJson(json['themeMode'])), - audioSource: $PreferencesTableTable.$converteraudioSource - .fromJson(serializer.fromJson(json['audioSource'])), + audioSourceId: serializer.fromJson(json['audioSourceId']), youtubeClientEngine: $PreferencesTableTable.$converteryoutubeClientEngine .fromJson(serializer.fromJson(json['youtubeClientEngine'])), - streamMusicCodec: $PreferencesTableTable.$converterstreamMusicCodec - .fromJson(serializer.fromJson(json['streamMusicCodec'])), - downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec - .fromJson(serializer.fromJson(json['downloadMusicCodec'])), discordPresence: serializer.fromJson(json['discordPresence']), endlessPlayback: serializer.fromJson(json['endlessPlayback']), enableConnect: serializer.fromJson(json['enableConnect']), @@ -1340,8 +1173,6 @@ class PreferencesTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'audioQuality': serializer.toJson( - $PreferencesTableTable.$converteraudioQuality.toJson(audioQuality)), 'albumColorSync': serializer.toJson(albumColorSync), 'amoledDarkTheme': serializer.toJson(amoledDarkTheme), 'checkUpdate': serializer.toJson(checkUpdate), @@ -1362,21 +1193,12 @@ class PreferencesTableData extends DataClass 'downloadLocation': serializer.toJson(downloadLocation), 'localLibraryLocation': serializer.toJson>(localLibraryLocation), - 'pipedInstance': serializer.toJson(pipedInstance), - 'invidiousInstance': serializer.toJson(invidiousInstance), 'themeMode': serializer.toJson( $PreferencesTableTable.$converterthemeMode.toJson(themeMode)), - 'audioSource': serializer.toJson( - $PreferencesTableTable.$converteraudioSource.toJson(audioSource)), + 'audioSourceId': serializer.toJson(audioSourceId), 'youtubeClientEngine': serializer.toJson($PreferencesTableTable .$converteryoutubeClientEngine .toJson(youtubeClientEngine)), - 'streamMusicCodec': serializer.toJson($PreferencesTableTable - .$converterstreamMusicCodec - .toJson(streamMusicCodec)), - 'downloadMusicCodec': serializer.toJson($PreferencesTableTable - .$converterdownloadMusicCodec - .toJson(downloadMusicCodec)), 'discordPresence': serializer.toJson(discordPresence), 'endlessPlayback': serializer.toJson(endlessPlayback), 'enableConnect': serializer.toJson(enableConnect), @@ -1387,7 +1209,6 @@ class PreferencesTableData extends DataClass PreferencesTableData copyWith( {int? id, - SourceQualities? audioQuality, bool? albumColorSync, bool? amoledDarkTheme, bool? checkUpdate, @@ -1403,13 +1224,9 @@ class PreferencesTableData extends DataClass SearchMode? searchMode, String? downloadLocation, List? localLibraryLocation, - String? pipedInstance, - String? invidiousInstance, ThemeMode? themeMode, - AudioSource? audioSource, + Value audioSourceId = const Value.absent(), YoutubeClientEngine? youtubeClientEngine, - SourceCodecs? streamMusicCodec, - SourceCodecs? downloadMusicCodec, bool? discordPresence, bool? endlessPlayback, bool? enableConnect, @@ -1417,7 +1234,6 @@ class PreferencesTableData extends DataClass bool? cacheMusic}) => PreferencesTableData( id: id ?? this.id, - audioQuality: audioQuality ?? this.audioQuality, albumColorSync: albumColorSync ?? this.albumColorSync, amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, checkUpdate: checkUpdate ?? this.checkUpdate, @@ -1433,13 +1249,10 @@ class PreferencesTableData extends DataClass 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, + audioSourceId: + audioSourceId.present ? audioSourceId.value : this.audioSourceId, youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, - streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, - downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, discordPresence: discordPresence ?? this.discordPresence, endlessPlayback: endlessPlayback ?? this.endlessPlayback, enableConnect: enableConnect ?? this.enableConnect, @@ -1449,9 +1262,6 @@ class PreferencesTableData extends DataClass 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, @@ -1490,24 +1300,13 @@ class PreferencesTableData extends DataClass 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, + audioSourceId: data.audioSourceId.present + ? data.audioSourceId.value + : this.audioSourceId, 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, @@ -1528,7 +1327,6 @@ class PreferencesTableData extends DataClass String toString() { return (StringBuffer('PreferencesTableData(') ..write('id: $id, ') - ..write('audioQuality: $audioQuality, ') ..write('albumColorSync: $albumColorSync, ') ..write('amoledDarkTheme: $amoledDarkTheme, ') ..write('checkUpdate: $checkUpdate, ') @@ -1544,13 +1342,9 @@ class PreferencesTableData extends DataClass ..write('searchMode: $searchMode, ') ..write('downloadLocation: $downloadLocation, ') ..write('localLibraryLocation: $localLibraryLocation, ') - ..write('pipedInstance: $pipedInstance, ') - ..write('invidiousInstance: $invidiousInstance, ') ..write('themeMode: $themeMode, ') - ..write('audioSource: $audioSource, ') + ..write('audioSourceId: $audioSourceId, ') ..write('youtubeClientEngine: $youtubeClientEngine, ') - ..write('streamMusicCodec: $streamMusicCodec, ') - ..write('downloadMusicCodec: $downloadMusicCodec, ') ..write('discordPresence: $discordPresence, ') ..write('endlessPlayback: $endlessPlayback, ') ..write('enableConnect: $enableConnect, ') @@ -1563,7 +1357,6 @@ class PreferencesTableData extends DataClass @override int get hashCode => Object.hashAll([ id, - audioQuality, albumColorSync, amoledDarkTheme, checkUpdate, @@ -1579,13 +1372,9 @@ class PreferencesTableData extends DataClass searchMode, downloadLocation, localLibraryLocation, - pipedInstance, - invidiousInstance, themeMode, - audioSource, + audioSourceId, youtubeClientEngine, - streamMusicCodec, - downloadMusicCodec, discordPresence, endlessPlayback, enableConnect, @@ -1597,7 +1386,6 @@ class PreferencesTableData extends DataClass 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 && @@ -1613,13 +1401,9 @@ class PreferencesTableData extends DataClass 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.audioSourceId == this.audioSourceId && other.youtubeClientEngine == this.youtubeClientEngine && - other.streamMusicCodec == this.streamMusicCodec && - other.downloadMusicCodec == this.downloadMusicCodec && other.discordPresence == this.discordPresence && other.endlessPlayback == this.endlessPlayback && other.enableConnect == this.enableConnect && @@ -1629,7 +1413,6 @@ class PreferencesTableData extends DataClass class PreferencesTableCompanion extends UpdateCompanion { final Value id; - final Value audioQuality; final Value albumColorSync; final Value amoledDarkTheme; final Value checkUpdate; @@ -1645,13 +1428,9 @@ class PreferencesTableCompanion extends UpdateCompanion { final Value searchMode; final Value downloadLocation; final Value> localLibraryLocation; - final Value pipedInstance; - final Value invidiousInstance; final Value themeMode; - final Value audioSource; + final Value audioSourceId; final Value youtubeClientEngine; - final Value streamMusicCodec; - final Value downloadMusicCodec; final Value discordPresence; final Value endlessPlayback; final Value enableConnect; @@ -1659,7 +1438,6 @@ class PreferencesTableCompanion extends UpdateCompanion { 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(), @@ -1675,13 +1453,9 @@ class PreferencesTableCompanion extends UpdateCompanion { 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.audioSourceId = 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(), @@ -1690,7 +1464,6 @@ class PreferencesTableCompanion extends UpdateCompanion { }); 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(), @@ -1706,13 +1479,9 @@ class PreferencesTableCompanion extends UpdateCompanion { 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.audioSourceId = 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(), @@ -1721,7 +1490,6 @@ class PreferencesTableCompanion extends UpdateCompanion { }); static Insertable custom({ Expression? id, - Expression? audioQuality, Expression? albumColorSync, Expression? amoledDarkTheme, Expression? checkUpdate, @@ -1737,13 +1505,9 @@ class PreferencesTableCompanion extends UpdateCompanion { Expression? searchMode, Expression? downloadLocation, Expression? localLibraryLocation, - Expression? pipedInstance, - Expression? invidiousInstance, Expression? themeMode, - Expression? audioSource, + Expression? audioSourceId, Expression? youtubeClientEngine, - Expression? streamMusicCodec, - Expression? downloadMusicCodec, Expression? discordPresence, Expression? endlessPlayback, Expression? enableConnect, @@ -1752,7 +1516,6 @@ class PreferencesTableCompanion extends UpdateCompanion { }) { 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, @@ -1770,15 +1533,10 @@ class PreferencesTableCompanion extends UpdateCompanion { 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 (audioSourceId != null) 'audio_source_id': audioSourceId, 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, @@ -1789,7 +1547,6 @@ class PreferencesTableCompanion extends UpdateCompanion { PreferencesTableCompanion copyWith( {Value? id, - Value? audioQuality, Value? albumColorSync, Value? amoledDarkTheme, Value? checkUpdate, @@ -1805,13 +1562,9 @@ class PreferencesTableCompanion extends UpdateCompanion { Value? searchMode, Value? downloadLocation, Value>? localLibraryLocation, - Value? pipedInstance, - Value? invidiousInstance, Value? themeMode, - Value? audioSource, + Value? audioSourceId, Value? youtubeClientEngine, - Value? streamMusicCodec, - Value? downloadMusicCodec, Value? discordPresence, Value? endlessPlayback, Value? enableConnect, @@ -1819,7 +1572,6 @@ class PreferencesTableCompanion extends UpdateCompanion { Value? cacheMusic}) { return PreferencesTableCompanion( id: id ?? this.id, - audioQuality: audioQuality ?? this.audioQuality, albumColorSync: albumColorSync ?? this.albumColorSync, amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, checkUpdate: checkUpdate ?? this.checkUpdate, @@ -1835,13 +1587,9 @@ class PreferencesTableCompanion extends UpdateCompanion { 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, + audioSourceId: audioSourceId ?? this.audioSourceId, youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, - streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, - downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, discordPresence: discordPresence ?? this.discordPresence, endlessPlayback: endlessPlayback ?? this.endlessPlayback, enableConnect: enableConnect ?? this.enableConnect, @@ -1856,11 +1604,6 @@ class PreferencesTableCompanion extends UpdateCompanion { if (id.present) { map['id'] = Variable(id.value); } - if (audioQuality.present) { - map['audio_quality'] = Variable($PreferencesTableTable - .$converteraudioQuality - .toSql(audioQuality.value)); - } if (albumColorSync.present) { map['album_color_sync'] = Variable(albumColorSync.value); } @@ -1916,36 +1659,18 @@ class PreferencesTableCompanion extends UpdateCompanion { .$converterlocalLibraryLocation .toSql(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( $PreferencesTableTable.$converterthemeMode.toSql(themeMode.value)); } - if (audioSource.present) { - map['audio_source'] = Variable($PreferencesTableTable - .$converteraudioSource - .toSql(audioSource.value)); + if (audioSourceId.present) { + map['audio_source_id'] = Variable(audioSourceId.value); } if (youtubeClientEngine.present) { map['youtube_client_engine'] = Variable($PreferencesTableTable .$converteryoutubeClientEngine .toSql(youtubeClientEngine.value)); } - if (streamMusicCodec.present) { - map['stream_music_codec'] = Variable($PreferencesTableTable - .$converterstreamMusicCodec - .toSql(streamMusicCodec.value)); - } - if (downloadMusicCodec.present) { - map['download_music_codec'] = Variable($PreferencesTableTable - .$converterdownloadMusicCodec - .toSql(downloadMusicCodec.value)); - } if (discordPresence.present) { map['discord_presence'] = Variable(discordPresence.value); } @@ -1968,7 +1693,6 @@ class PreferencesTableCompanion extends UpdateCompanion { String toString() { return (StringBuffer('PreferencesTableCompanion(') ..write('id: $id, ') - ..write('audioQuality: $audioQuality, ') ..write('albumColorSync: $albumColorSync, ') ..write('amoledDarkTheme: $amoledDarkTheme, ') ..write('checkUpdate: $checkUpdate, ') @@ -1984,13 +1708,9 @@ class PreferencesTableCompanion extends UpdateCompanion { ..write('searchMode: $searchMode, ') ..write('downloadLocation: $downloadLocation, ') ..write('localLibraryLocation: $localLibraryLocation, ') - ..write('pipedInstance: $pipedInstance, ') - ..write('invidiousInstance: $invidiousInstance, ') ..write('themeMode: $themeMode, ') - ..write('audioSource: $audioSource, ') + ..write('audioSourceId: $audioSourceId, ') ..write('youtubeClientEngine: $youtubeClientEngine, ') - ..write('streamMusicCodec: $streamMusicCodec, ') - ..write('downloadMusicCodec: $downloadMusicCodec, ') ..write('discordPresence: $discordPresence, ') ..write('endlessPlayback: $endlessPlayback, ') ..write('enableConnect: $enableConnect, ') @@ -2030,8 +1750,6 @@ class $ScrobblerTableTable extends ScrobblerTable late final GeneratedColumn username = GeneratedColumn( 'username', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _passwordHashMeta = - const VerificationMeta('passwordHash'); @override late final GeneratedColumnWithTypeConverter passwordHash = GeneratedColumn( @@ -2064,7 +1782,6 @@ class $ScrobblerTableTable extends ScrobblerTable } else if (isInserting) { context.missing(_usernameMeta); } - context.handle(_passwordHashMeta, const VerificationResult.success()); return context; } @@ -2589,22 +2306,20 @@ class $SourceMatchTableTable extends SourceMatchTable late final GeneratedColumn trackId = GeneratedColumn( 'track_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _sourceIdMeta = - const VerificationMeta('sourceId'); + static const VerificationMeta _sourceInfoMeta = + const VerificationMeta('sourceInfo'); @override - late final GeneratedColumn sourceId = GeneratedColumn( - 'source_id', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sourceInfo = GeneratedColumn( + 'source_info', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("{}")); static const VerificationMeta _sourceTypeMeta = const VerificationMeta('sourceType'); @override - late final GeneratedColumnWithTypeConverter sourceType = - GeneratedColumn('source_type', aliasedName, false, - type: DriftSqlType.string, - requiredDuringInsert: false, - defaultValue: Constant(SourceType.youtube.name)) - .withConverter( - $SourceMatchTableTable.$convertersourceType); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); static const VerificationMeta _createdAtMeta = const VerificationMeta('createdAt'); @override @@ -2615,7 +2330,7 @@ class $SourceMatchTableTable extends SourceMatchTable defaultValue: currentDateAndTime); @override List get $columns => - [id, trackId, sourceId, sourceType, createdAt]; + [id, trackId, sourceInfo, sourceType, createdAt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -2636,13 +2351,20 @@ class $SourceMatchTableTable extends SourceMatchTable } else if (isInserting) { context.missing(_trackIdMeta); } - if (data.containsKey('source_id')) { - context.handle(_sourceIdMeta, - sourceId.isAcceptableOrUnknown(data['source_id']!, _sourceIdMeta)); - } else if (isInserting) { - context.missing(_sourceIdMeta); + if (data.containsKey('source_info')) { + context.handle( + _sourceInfoMeta, + sourceInfo.isAcceptableOrUnknown( + data['source_info']!, _sourceInfoMeta)); + } + if (data.containsKey('source_type')) { + context.handle( + _sourceTypeMeta, + sourceType.isAcceptableOrUnknown( + data['source_type']!, _sourceTypeMeta)); + } else if (isInserting) { + context.missing(_sourceTypeMeta); } - context.handle(_sourceTypeMeta, const VerificationResult.success()); if (data.containsKey('created_at')) { context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); @@ -2660,11 +2382,10 @@ class $SourceMatchTableTable extends SourceMatchTable .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: $SourceMatchTableTable.$convertersourceType.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}source_type'])!), + sourceInfo: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_info'])!, + sourceType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_type'])!, createdAt: attachedDatabase.typeMapping .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, ); @@ -2674,22 +2395,19 @@ class $SourceMatchTableTable extends SourceMatchTable $SourceMatchTableTable createAlias(String alias) { return $SourceMatchTableTable(attachedDatabase, alias); } - - static JsonTypeConverter2 $convertersourceType = - const EnumNameConverter(SourceType.values); } class SourceMatchTableData extends DataClass implements Insertable { final int id; final String trackId; - final String sourceId; - final SourceType sourceType; + final String sourceInfo; + final String sourceType; final DateTime createdAt; const SourceMatchTableData( {required this.id, required this.trackId, - required this.sourceId, + required this.sourceInfo, required this.sourceType, required this.createdAt}); @override @@ -2697,11 +2415,8 @@ class SourceMatchTableData extends DataClass final map = {}; map['id'] = Variable(id); map['track_id'] = Variable(trackId); - map['source_id'] = Variable(sourceId); - { - map['source_type'] = Variable( - $SourceMatchTableTable.$convertersourceType.toSql(sourceType)); - } + map['source_info'] = Variable(sourceInfo); + map['source_type'] = Variable(sourceType); map['created_at'] = Variable(createdAt); return map; } @@ -2710,7 +2425,7 @@ class SourceMatchTableData extends DataClass return SourceMatchTableCompanion( id: Value(id), trackId: Value(trackId), - sourceId: Value(sourceId), + sourceInfo: Value(sourceInfo), sourceType: Value(sourceType), createdAt: Value(createdAt), ); @@ -2722,9 +2437,8 @@ class SourceMatchTableData extends DataClass return SourceMatchTableData( id: serializer.fromJson(json['id']), trackId: serializer.fromJson(json['trackId']), - sourceId: serializer.fromJson(json['sourceId']), - sourceType: $SourceMatchTableTable.$convertersourceType - .fromJson(serializer.fromJson(json['sourceType'])), + sourceInfo: serializer.fromJson(json['sourceInfo']), + sourceType: serializer.fromJson(json['sourceType']), createdAt: serializer.fromJson(json['createdAt']), ); } @@ -2734,9 +2448,8 @@ class SourceMatchTableData extends DataClass return { 'id': serializer.toJson(id), 'trackId': serializer.toJson(trackId), - 'sourceId': serializer.toJson(sourceId), - 'sourceType': serializer.toJson( - $SourceMatchTableTable.$convertersourceType.toJson(sourceType)), + 'sourceInfo': serializer.toJson(sourceInfo), + 'sourceType': serializer.toJson(sourceType), 'createdAt': serializer.toJson(createdAt), }; } @@ -2744,13 +2457,13 @@ class SourceMatchTableData extends DataClass SourceMatchTableData copyWith( {int? id, String? trackId, - String? sourceId, - SourceType? sourceType, + String? sourceInfo, + String? sourceType, DateTime? createdAt}) => SourceMatchTableData( id: id ?? this.id, trackId: trackId ?? this.trackId, - sourceId: sourceId ?? this.sourceId, + sourceInfo: sourceInfo ?? this.sourceInfo, sourceType: sourceType ?? this.sourceType, createdAt: createdAt ?? this.createdAt, ); @@ -2758,7 +2471,8 @@ class SourceMatchTableData extends DataClass 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, + sourceInfo: + data.sourceInfo.present ? data.sourceInfo.value : this.sourceInfo, sourceType: data.sourceType.present ? data.sourceType.value : this.sourceType, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, @@ -2770,7 +2484,7 @@ class SourceMatchTableData extends DataClass return (StringBuffer('SourceMatchTableData(') ..write('id: $id, ') ..write('trackId: $trackId, ') - ..write('sourceId: $sourceId, ') + ..write('sourceInfo: $sourceInfo, ') ..write('sourceType: $sourceType, ') ..write('createdAt: $createdAt') ..write(')')) @@ -2778,14 +2492,15 @@ class SourceMatchTableData extends DataClass } @override - int get hashCode => Object.hash(id, trackId, sourceId, sourceType, createdAt); + int get hashCode => + Object.hash(id, trackId, sourceInfo, 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.sourceInfo == this.sourceInfo && other.sourceType == this.sourceType && other.createdAt == this.createdAt); } @@ -2793,35 +2508,35 @@ class SourceMatchTableData extends DataClass class SourceMatchTableCompanion extends UpdateCompanion { final Value id; final Value trackId; - final Value sourceId; - final Value sourceType; + final Value sourceInfo; + final Value sourceType; final Value createdAt; const SourceMatchTableCompanion({ this.id = const Value.absent(), this.trackId = const Value.absent(), - this.sourceId = const Value.absent(), + this.sourceInfo = 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.sourceInfo = const Value.absent(), + required String sourceType, this.createdAt = const Value.absent(), }) : trackId = Value(trackId), - sourceId = Value(sourceId); + sourceType = Value(sourceType); static Insertable custom({ Expression? id, Expression? trackId, - Expression? sourceId, + Expression? sourceInfo, Expression? sourceType, Expression? createdAt, }) { return RawValuesInsertable({ if (id != null) 'id': id, if (trackId != null) 'track_id': trackId, - if (sourceId != null) 'source_id': sourceId, + if (sourceInfo != null) 'source_info': sourceInfo, if (sourceType != null) 'source_type': sourceType, if (createdAt != null) 'created_at': createdAt, }); @@ -2830,13 +2545,13 @@ class SourceMatchTableCompanion extends UpdateCompanion { SourceMatchTableCompanion copyWith( {Value? id, Value? trackId, - Value? sourceId, - Value? sourceType, + Value? sourceInfo, + Value? sourceType, Value? createdAt}) { return SourceMatchTableCompanion( id: id ?? this.id, trackId: trackId ?? this.trackId, - sourceId: sourceId ?? this.sourceId, + sourceInfo: sourceInfo ?? this.sourceInfo, sourceType: sourceType ?? this.sourceType, createdAt: createdAt ?? this.createdAt, ); @@ -2851,12 +2566,11 @@ class SourceMatchTableCompanion extends UpdateCompanion { if (trackId.present) { map['track_id'] = Variable(trackId.value); } - if (sourceId.present) { - map['source_id'] = Variable(sourceId.value); + if (sourceInfo.present) { + map['source_info'] = Variable(sourceInfo.value); } if (sourceType.present) { - map['source_type'] = Variable( - $SourceMatchTableTable.$convertersourceType.toSql(sourceType.value)); + map['source_type'] = Variable(sourceType.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); @@ -2869,7 +2583,7 @@ class SourceMatchTableCompanion extends UpdateCompanion { return (StringBuffer('SourceMatchTableCompanion(') ..write('id: $id, ') ..write('trackId: $trackId, ') - ..write('sourceId: $sourceId, ') + ..write('sourceInfo: $sourceInfo, ') ..write('sourceType: $sourceType, ') ..write('createdAt: $createdAt') ..write(')')) @@ -2901,8 +2615,6 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))')); - static const VerificationMeta _loopModeMeta = - const VerificationMeta('loopMode'); @override late final GeneratedColumnWithTypeConverter loopMode = GeneratedColumn('loop_mode', aliasedName, false, @@ -2918,15 +2630,12 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))')); - static const VerificationMeta _collectionsMeta = - const VerificationMeta('collections'); @override late final GeneratedColumnWithTypeConverter, String> collections = GeneratedColumn('collections', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true) .withConverter>( $AudioPlayerStateTableTable.$convertercollections); - static const VerificationMeta _tracksMeta = const VerificationMeta('tracks'); @override late final GeneratedColumnWithTypeConverter, String> tracks = GeneratedColumn('tracks', aliasedName, false, @@ -2966,15 +2675,12 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable } else if (isInserting) { context.missing(_playingMeta); } - context.handle(_loopModeMeta, const VerificationResult.success()); if (data.containsKey('shuffled')) { context.handle(_shuffledMeta, shuffled.isAcceptableOrUnknown(data['shuffled']!, _shuffledMeta)); } else if (isInserting) { context.missing(_shuffledMeta); } - context.handle(_collectionsMeta, const VerificationResult.success()); - context.handle(_tracksMeta, const VerificationResult.success()); if (data.containsKey('current_index')) { context.handle( _currentIndexMeta, @@ -3305,7 +3011,6 @@ class $HistoryTableTable extends HistoryTable type: DriftSqlType.dateTime, requiredDuringInsert: false, defaultValue: currentDateAndTime); - static const VerificationMeta _typeMeta = const VerificationMeta('type'); @override late final GeneratedColumnWithTypeConverter type = GeneratedColumn('type', aliasedName, false, @@ -3316,7 +3021,6 @@ class $HistoryTableTable extends HistoryTable late final GeneratedColumn itemId = GeneratedColumn( 'item_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _dataMeta = const VerificationMeta('data'); @override late final GeneratedColumnWithTypeConverter, String> data = GeneratedColumn('data', aliasedName, false, @@ -3342,14 +3046,12 @@ class $HistoryTableTable extends HistoryTable context.handle(_createdAtMeta, createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); } - context.handle(_typeMeta, const VerificationResult.success()); if (data.containsKey('item_id')) { context.handle(_itemIdMeta, itemId.isAcceptableOrUnknown(data['item_id']!, _itemIdMeta)); } else if (isInserting) { context.missing(_itemIdMeta); } - context.handle(_dataMeta, const VerificationResult.success()); return context; } @@ -3608,7 +3310,6 @@ class $LyricsTableTable extends LyricsTable late final GeneratedColumn trackId = GeneratedColumn( 'track_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _dataMeta = const VerificationMeta('data'); @override late final GeneratedColumnWithTypeConverter data = GeneratedColumn('data', aliasedName, false, @@ -3635,7 +3336,6 @@ class $LyricsTableTable extends LyricsTable } else if (isInserting) { context.missing(_trackIdMeta); } - context.handle(_dataMeta, const VerificationResult.success()); return context; } @@ -3807,12 +3507,12 @@ class LyricsTableCompanion extends UpdateCompanion { } } -class $MetadataPluginsTableTable extends MetadataPluginsTable - with TableInfo<$MetadataPluginsTableTable, MetadataPluginsTableData> { +class $PluginsTableTable extends PluginsTable + with TableInfo<$PluginsTableTable, PluginsTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $MetadataPluginsTableTable(this.attachedDatabase, [this._alias]); + $PluginsTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( @@ -3853,31 +3553,36 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable late final GeneratedColumn entryPoint = GeneratedColumn( 'entry_point', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _apisMeta = const VerificationMeta('apis'); @override late final GeneratedColumnWithTypeConverter, String> apis = GeneratedColumn('apis', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $MetadataPluginsTableTable.$converterapis); - static const VerificationMeta _abilitiesMeta = - const VerificationMeta('abilities'); + .withConverter>($PluginsTableTable.$converterapis); @override late final GeneratedColumnWithTypeConverter, String> abilities = GeneratedColumn('abilities', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true) - .withConverter>( - $MetadataPluginsTableTable.$converterabilities); - static const VerificationMeta _selectedMeta = - const VerificationMeta('selected'); + .withConverter>($PluginsTableTable.$converterabilities); + static const VerificationMeta _selectedForMetadataMeta = + const VerificationMeta('selectedForMetadata'); @override - late final GeneratedColumn selected = GeneratedColumn( - 'selected', aliasedName, false, + late final GeneratedColumn selectedForMetadata = GeneratedColumn( + 'selected_for_metadata', aliasedName, false, type: DriftSqlType.bool, requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('CHECK ("selected" IN (0, 1))'), + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_metadata" IN (0, 1))'), defaultValue: const Constant(false)); + static const VerificationMeta _selectedForAudioSourceMeta = + const VerificationMeta('selectedForAudioSource'); + @override + late final GeneratedColumn selectedForAudioSource = + GeneratedColumn('selected_for_audio_source', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_audio_source" IN (0, 1))'), + defaultValue: const Constant(false)); static const VerificationMeta _repositoryMeta = const VerificationMeta('repository'); @override @@ -3891,7 +3596,7 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable 'plugin_api_version', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: false, - defaultValue: const Constant('1.0.0')); + defaultValue: const Constant('2.0.0')); @override List get $columns => [ id, @@ -3902,7 +3607,8 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable entryPoint, apis, abilities, - selected, + selectedForMetadata, + selectedForAudioSource, repository, pluginApiVersion ]; @@ -3910,10 +3616,9 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'metadata_plugins_table'; + static const String $name = 'plugins_table'; @override - VerificationContext validateIntegrity( - Insertable instance, + VerificationContext validateIntegrity(Insertable instance, {bool isInserting = false}) { final context = VerificationContext(); final data = instance.toColumns(true); @@ -3954,11 +3659,17 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable } else if (isInserting) { context.missing(_entryPointMeta); } - context.handle(_apisMeta, const VerificationResult.success()); - context.handle(_abilitiesMeta, const VerificationResult.success()); - if (data.containsKey('selected')) { - context.handle(_selectedMeta, - selected.isAcceptableOrUnknown(data['selected']!, _selectedMeta)); + if (data.containsKey('selected_for_metadata')) { + context.handle( + _selectedForMetadataMeta, + selectedForMetadata.isAcceptableOrUnknown( + data['selected_for_metadata']!, _selectedForMetadataMeta)); + } + if (data.containsKey('selected_for_audio_source')) { + context.handle( + _selectedForAudioSourceMeta, + selectedForAudioSource.isAcceptableOrUnknown( + data['selected_for_audio_source']!, _selectedForAudioSourceMeta)); } if (data.containsKey('repository')) { context.handle( @@ -3978,10 +3689,9 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable @override Set get $primaryKey => {id}; @override - MetadataPluginsTableData map(Map data, - {String? tablePrefix}) { + PluginsTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return MetadataPluginsTableData( + return PluginsTableData( id: attachedDatabase.typeMapping .read(DriftSqlType.int, data['${effectivePrefix}id'])!, name: attachedDatabase.typeMapping @@ -3994,14 +3704,17 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable .read(DriftSqlType.string, data['${effectivePrefix}author'])!, entryPoint: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}entry_point'])!, - apis: $MetadataPluginsTableTable.$converterapis.fromSql(attachedDatabase + apis: $PluginsTableTable.$converterapis.fromSql(attachedDatabase .typeMapping .read(DriftSqlType.string, data['${effectivePrefix}apis'])!), - abilities: $MetadataPluginsTableTable.$converterabilities.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}abilities'])!), - selected: attachedDatabase.typeMapping - .read(DriftSqlType.bool, data['${effectivePrefix}selected'])!, + abilities: $PluginsTableTable.$converterabilities.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}abilities'])!), + selectedForMetadata: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}selected_for_metadata'])!, + selectedForAudioSource: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}selected_for_audio_source'])!, repository: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}repository']), pluginApiVersion: attachedDatabase.typeMapping.read( @@ -4010,8 +3723,8 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable } @override - $MetadataPluginsTableTable createAlias(String alias) { - return $MetadataPluginsTableTable(attachedDatabase, alias); + $PluginsTableTable createAlias(String alias) { + return $PluginsTableTable(attachedDatabase, alias); } static TypeConverter, String> $converterapis = @@ -4020,8 +3733,8 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable const StringListConverter(); } -class MetadataPluginsTableData extends DataClass - implements Insertable { +class PluginsTableData extends DataClass + implements Insertable { final int id; final String name; final String description; @@ -4030,10 +3743,11 @@ class MetadataPluginsTableData extends DataClass final String entryPoint; final List apis; final List abilities; - final bool selected; + final bool selectedForMetadata; + final bool selectedForAudioSource; final String? repository; final String pluginApiVersion; - const MetadataPluginsTableData( + const PluginsTableData( {required this.id, required this.name, required this.description, @@ -4042,7 +3756,8 @@ class MetadataPluginsTableData extends DataClass required this.entryPoint, required this.apis, required this.abilities, - required this.selected, + required this.selectedForMetadata, + required this.selectedForAudioSource, this.repository, required this.pluginApiVersion}); @override @@ -4055,14 +3770,15 @@ class MetadataPluginsTableData extends DataClass map['author'] = Variable(author); map['entry_point'] = Variable(entryPoint); { - map['apis'] = Variable( - $MetadataPluginsTableTable.$converterapis.toSql(apis)); + map['apis'] = + Variable($PluginsTableTable.$converterapis.toSql(apis)); } { map['abilities'] = Variable( - $MetadataPluginsTableTable.$converterabilities.toSql(abilities)); + $PluginsTableTable.$converterabilities.toSql(abilities)); } - map['selected'] = Variable(selected); + map['selected_for_metadata'] = Variable(selectedForMetadata); + map['selected_for_audio_source'] = Variable(selectedForAudioSource); if (!nullToAbsent || repository != null) { map['repository'] = Variable(repository); } @@ -4070,8 +3786,8 @@ class MetadataPluginsTableData extends DataClass return map; } - MetadataPluginsTableCompanion toCompanion(bool nullToAbsent) { - return MetadataPluginsTableCompanion( + PluginsTableCompanion toCompanion(bool nullToAbsent) { + return PluginsTableCompanion( id: Value(id), name: Value(name), description: Value(description), @@ -4080,7 +3796,8 @@ class MetadataPluginsTableData extends DataClass entryPoint: Value(entryPoint), apis: Value(apis), abilities: Value(abilities), - selected: Value(selected), + selectedForMetadata: Value(selectedForMetadata), + selectedForAudioSource: Value(selectedForAudioSource), repository: repository == null && nullToAbsent ? const Value.absent() : Value(repository), @@ -4088,10 +3805,10 @@ class MetadataPluginsTableData extends DataClass ); } - factory MetadataPluginsTableData.fromJson(Map json, + factory PluginsTableData.fromJson(Map json, {ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; - return MetadataPluginsTableData( + return PluginsTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), description: serializer.fromJson(json['description']), @@ -4100,7 +3817,10 @@ class MetadataPluginsTableData extends DataClass entryPoint: serializer.fromJson(json['entryPoint']), apis: serializer.fromJson>(json['apis']), abilities: serializer.fromJson>(json['abilities']), - selected: serializer.fromJson(json['selected']), + selectedForMetadata: + serializer.fromJson(json['selectedForMetadata']), + selectedForAudioSource: + serializer.fromJson(json['selectedForAudioSource']), repository: serializer.fromJson(json['repository']), pluginApiVersion: serializer.fromJson(json['pluginApiVersion']), ); @@ -4117,13 +3837,14 @@ class MetadataPluginsTableData extends DataClass 'entryPoint': serializer.toJson(entryPoint), 'apis': serializer.toJson>(apis), 'abilities': serializer.toJson>(abilities), - 'selected': serializer.toJson(selected), + 'selectedForMetadata': serializer.toJson(selectedForMetadata), + 'selectedForAudioSource': serializer.toJson(selectedForAudioSource), 'repository': serializer.toJson(repository), 'pluginApiVersion': serializer.toJson(pluginApiVersion), }; } - MetadataPluginsTableData copyWith( + PluginsTableData copyWith( {int? id, String? name, String? description, @@ -4132,10 +3853,11 @@ class MetadataPluginsTableData extends DataClass String? entryPoint, List? apis, List? abilities, - bool? selected, + bool? selectedForMetadata, + bool? selectedForAudioSource, Value repository = const Value.absent(), String? pluginApiVersion}) => - MetadataPluginsTableData( + PluginsTableData( id: id ?? this.id, name: name ?? this.name, description: description ?? this.description, @@ -4144,13 +3866,14 @@ class MetadataPluginsTableData extends DataClass entryPoint: entryPoint ?? this.entryPoint, apis: apis ?? this.apis, abilities: abilities ?? this.abilities, - selected: selected ?? this.selected, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, repository: repository.present ? repository.value : this.repository, pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, ); - MetadataPluginsTableData copyWithCompanion( - MetadataPluginsTableCompanion data) { - return MetadataPluginsTableData( + PluginsTableData copyWithCompanion(PluginsTableCompanion data) { + return PluginsTableData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, description: @@ -4161,7 +3884,12 @@ class MetadataPluginsTableData extends DataClass 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, + selectedForMetadata: data.selectedForMetadata.present + ? data.selectedForMetadata.value + : this.selectedForMetadata, + selectedForAudioSource: data.selectedForAudioSource.present + ? data.selectedForAudioSource.value + : this.selectedForAudioSource, repository: data.repository.present ? data.repository.value : this.repository, pluginApiVersion: data.pluginApiVersion.present @@ -4172,7 +3900,7 @@ class MetadataPluginsTableData extends DataClass @override String toString() { - return (StringBuffer('MetadataPluginsTableData(') + return (StringBuffer('PluginsTableData(') ..write('id: $id, ') ..write('name: $name, ') ..write('description: $description, ') @@ -4181,7 +3909,8 @@ class MetadataPluginsTableData extends DataClass ..write('entryPoint: $entryPoint, ') ..write('apis: $apis, ') ..write('abilities: $abilities, ') - ..write('selected: $selected, ') + ..write('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') ..write('repository: $repository, ') ..write('pluginApiVersion: $pluginApiVersion') ..write(')')) @@ -4189,12 +3918,23 @@ class MetadataPluginsTableData extends DataClass } @override - int get hashCode => Object.hash(id, name, description, version, author, - entryPoint, apis, abilities, selected, repository, pluginApiVersion); + int get hashCode => Object.hash( + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selectedForMetadata, + selectedForAudioSource, + repository, + pluginApiVersion); @override bool operator ==(Object other) => identical(this, other) || - (other is MetadataPluginsTableData && + (other is PluginsTableData && other.id == this.id && other.name == this.name && other.description == this.description && @@ -4203,13 +3943,13 @@ class MetadataPluginsTableData extends DataClass other.entryPoint == this.entryPoint && other.apis == this.apis && other.abilities == this.abilities && - other.selected == this.selected && + other.selectedForMetadata == this.selectedForMetadata && + other.selectedForAudioSource == this.selectedForAudioSource && other.repository == this.repository && other.pluginApiVersion == this.pluginApiVersion); } -class MetadataPluginsTableCompanion - extends UpdateCompanion { +class PluginsTableCompanion extends UpdateCompanion { final Value id; final Value name; final Value description; @@ -4218,10 +3958,11 @@ class MetadataPluginsTableCompanion final Value entryPoint; final Value> apis; final Value> abilities; - final Value selected; + final Value selectedForMetadata; + final Value selectedForAudioSource; final Value repository; final Value pluginApiVersion; - const MetadataPluginsTableCompanion({ + const PluginsTableCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), this.description = const Value.absent(), @@ -4230,11 +3971,12 @@ class MetadataPluginsTableCompanion this.entryPoint = const Value.absent(), this.apis = const Value.absent(), this.abilities = const Value.absent(), - this.selected = const Value.absent(), + this.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = const Value.absent(), this.repository = const Value.absent(), this.pluginApiVersion = const Value.absent(), }); - MetadataPluginsTableCompanion.insert({ + PluginsTableCompanion.insert({ this.id = const Value.absent(), required String name, required String description, @@ -4243,7 +3985,8 @@ class MetadataPluginsTableCompanion required String entryPoint, required List apis, required List abilities, - this.selected = const Value.absent(), + this.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = const Value.absent(), this.repository = const Value.absent(), this.pluginApiVersion = const Value.absent(), }) : name = Value(name), @@ -4253,7 +3996,7 @@ class MetadataPluginsTableCompanion entryPoint = Value(entryPoint), apis = Value(apis), abilities = Value(abilities); - static Insertable custom({ + static Insertable custom({ Expression? id, Expression? name, Expression? description, @@ -4262,7 +4005,8 @@ class MetadataPluginsTableCompanion Expression? entryPoint, Expression? apis, Expression? abilities, - Expression? selected, + Expression? selectedForMetadata, + Expression? selectedForAudioSource, Expression? repository, Expression? pluginApiVersion, }) { @@ -4275,13 +4019,16 @@ class MetadataPluginsTableCompanion if (entryPoint != null) 'entry_point': entryPoint, if (apis != null) 'apis': apis, if (abilities != null) 'abilities': abilities, - if (selected != null) 'selected': selected, + if (selectedForMetadata != null) + 'selected_for_metadata': selectedForMetadata, + if (selectedForAudioSource != null) + 'selected_for_audio_source': selectedForAudioSource, if (repository != null) 'repository': repository, if (pluginApiVersion != null) 'plugin_api_version': pluginApiVersion, }); } - MetadataPluginsTableCompanion copyWith( + PluginsTableCompanion copyWith( {Value? id, Value? name, Value? description, @@ -4290,10 +4037,11 @@ class MetadataPluginsTableCompanion Value? entryPoint, Value>? apis, Value>? abilities, - Value? selected, + Value? selectedForMetadata, + Value? selectedForAudioSource, Value? repository, Value? pluginApiVersion}) { - return MetadataPluginsTableCompanion( + return PluginsTableCompanion( id: id ?? this.id, name: name ?? this.name, description: description ?? this.description, @@ -4302,7 +4050,9 @@ class MetadataPluginsTableCompanion entryPoint: entryPoint ?? this.entryPoint, apis: apis ?? this.apis, abilities: abilities ?? this.abilities, - selected: selected ?? this.selected, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, repository: repository ?? this.repository, pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, ); @@ -4330,16 +4080,19 @@ class MetadataPluginsTableCompanion map['entry_point'] = Variable(entryPoint.value); } if (apis.present) { - map['apis'] = Variable( - $MetadataPluginsTableTable.$converterapis.toSql(apis.value)); + map['apis'] = + Variable($PluginsTableTable.$converterapis.toSql(apis.value)); } if (abilities.present) { - map['abilities'] = Variable($MetadataPluginsTableTable - .$converterabilities - .toSql(abilities.value)); + map['abilities'] = Variable( + $PluginsTableTable.$converterabilities.toSql(abilities.value)); } - if (selected.present) { - map['selected'] = Variable(selected.value); + if (selectedForMetadata.present) { + map['selected_for_metadata'] = Variable(selectedForMetadata.value); + } + if (selectedForAudioSource.present) { + map['selected_for_audio_source'] = + Variable(selectedForAudioSource.value); } if (repository.present) { map['repository'] = Variable(repository.value); @@ -4352,7 +4105,7 @@ class MetadataPluginsTableCompanion @override String toString() { - return (StringBuffer('MetadataPluginsTableCompanion(') + return (StringBuffer('PluginsTableCompanion(') ..write('id: $id, ') ..write('name: $name, ') ..write('description: $description, ') @@ -4361,7 +4114,8 @@ class MetadataPluginsTableCompanion ..write('entryPoint: $entryPoint, ') ..write('apis: $apis, ') ..write('abilities: $abilities, ') - ..write('selected: $selected, ') + ..write('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') ..write('repository: $repository, ') ..write('pluginApiVersion: $pluginApiVersion') ..write(')')) @@ -4386,12 +4140,9 @@ abstract class _$AppDatabase extends GeneratedDatabase { $AudioPlayerStateTableTable(this); late final $HistoryTableTable historyTable = $HistoryTableTable(this); late final $LyricsTableTable lyricsTable = $LyricsTableTable(this); - late final $MetadataPluginsTableTable metadataPluginsTable = - $MetadataPluginsTableTable(this); + late final $PluginsTableTable pluginsTable = $PluginsTableTable(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>(); @@ -4406,9 +4157,8 @@ abstract class _$AppDatabase extends GeneratedDatabase { audioPlayerStateTable, historyTable, lyricsTable, - metadataPluginsTable, - uniqueBlacklist, - uniqTrackMatch + pluginsTable, + uniqueBlacklist ]; } @@ -4733,7 +4483,6 @@ typedef $$BlacklistTableTableProcessedTableManager = ProcessedTableManager< typedef $$PreferencesTableTableCreateCompanionBuilder = PreferencesTableCompanion Function({ Value id, - Value audioQuality, Value albumColorSync, Value amoledDarkTheme, Value checkUpdate, @@ -4749,13 +4498,9 @@ typedef $$PreferencesTableTableCreateCompanionBuilder Value searchMode, Value downloadLocation, Value> localLibraryLocation, - Value pipedInstance, - Value invidiousInstance, Value themeMode, - Value audioSource, + Value audioSourceId, Value youtubeClientEngine, - Value streamMusicCodec, - Value downloadMusicCodec, Value discordPresence, Value endlessPlayback, Value enableConnect, @@ -4765,7 +4510,6 @@ typedef $$PreferencesTableTableCreateCompanionBuilder typedef $$PreferencesTableTableUpdateCompanionBuilder = PreferencesTableCompanion Function({ Value id, - Value audioQuality, Value albumColorSync, Value amoledDarkTheme, Value checkUpdate, @@ -4781,13 +4525,9 @@ typedef $$PreferencesTableTableUpdateCompanionBuilder Value searchMode, Value downloadLocation, Value> localLibraryLocation, - Value pipedInstance, - Value invidiousInstance, Value themeMode, - Value audioSource, + Value audioSourceId, Value youtubeClientEngine, - Value streamMusicCodec, - Value downloadMusicCodec, Value discordPresence, Value endlessPlayback, Value enableConnect, @@ -4807,11 +4547,6 @@ class $$PreferencesTableTableFilterComposer ColumnFilters get id => $composableBuilder( column: $table.id, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters - get audioQuality => $composableBuilder( - column: $table.audioQuality, - builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get albumColorSync => $composableBuilder( column: $table.albumColorSync, builder: (column) => ColumnFilters(column)); @@ -4877,22 +4612,13 @@ class $$PreferencesTableTableFilterComposer column: $table.localLibraryLocation, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get pipedInstance => $composableBuilder( - column: $table.pipedInstance, builder: (column) => ColumnFilters(column)); - - ColumnFilters get invidiousInstance => $composableBuilder( - column: $table.invidiousInstance, - builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters get themeMode => $composableBuilder( column: $table.themeMode, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters - get audioSource => $composableBuilder( - column: $table.audioSource, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnFilters get audioSourceId => $composableBuilder( + column: $table.audioSourceId, builder: (column) => ColumnFilters(column)); ColumnWithTypeConverterFilters @@ -4900,16 +4626,6 @@ class $$PreferencesTableTableFilterComposer column: $table.youtubeClientEngine, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnWithTypeConverterFilters - get streamMusicCodec => $composableBuilder( - column: $table.streamMusicCodec, - builder: (column) => ColumnWithTypeConverterFilters(column)); - - ColumnWithTypeConverterFilters - get downloadMusicCodec => $composableBuilder( - column: $table.downloadMusicCodec, - builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get discordPresence => $composableBuilder( column: $table.discordPresence, builder: (column) => ColumnFilters(column)); @@ -4940,10 +4656,6 @@ class $$PreferencesTableTableOrderingComposer ColumnOrderings get id => $composableBuilder( column: $table.id, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get audioQuality => $composableBuilder( - column: $table.audioQuality, - builder: (column) => ColumnOrderings(column)); - ColumnOrderings get albumColorSync => $composableBuilder( column: $table.albumColorSync, builder: (column) => ColumnOrderings(column)); @@ -4999,32 +4711,17 @@ class $$PreferencesTableTableOrderingComposer column: $table.localLibraryLocation, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get pipedInstance => $composableBuilder( - column: $table.pipedInstance, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get invidiousInstance => $composableBuilder( - column: $table.invidiousInstance, - builder: (column) => ColumnOrderings(column)); - ColumnOrderings get themeMode => $composableBuilder( column: $table.themeMode, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get audioSource => $composableBuilder( - column: $table.audioSource, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get audioSourceId => $composableBuilder( + column: $table.audioSourceId, + builder: (column) => ColumnOrderings(column)); ColumnOrderings get youtubeClientEngine => $composableBuilder( column: $table.youtubeClientEngine, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get streamMusicCodec => $composableBuilder( - column: $table.streamMusicCodec, - builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get downloadMusicCodec => $composableBuilder( - column: $table.downloadMusicCodec, - builder: (column) => ColumnOrderings(column)); - ColumnOrderings get discordPresence => $composableBuilder( column: $table.discordPresence, builder: (column) => ColumnOrderings(column)); @@ -5056,10 +4753,6 @@ class $$PreferencesTableTableAnnotationComposer GeneratedColumn get id => $composableBuilder(column: $table.id, builder: (column) => column); - GeneratedColumnWithTypeConverter get audioQuality => - $composableBuilder( - column: $table.audioQuality, builder: (column) => column); - GeneratedColumn get albumColorSync => $composableBuilder( column: $table.albumColorSync, builder: (column) => column); @@ -5110,31 +4803,16 @@ class $$PreferencesTableTableAnnotationComposer get localLibraryLocation => $composableBuilder( column: $table.localLibraryLocation, builder: (column) => column); - GeneratedColumn get pipedInstance => $composableBuilder( - column: $table.pipedInstance, builder: (column) => column); - - GeneratedColumn get invidiousInstance => $composableBuilder( - column: $table.invidiousInstance, builder: (column) => column); - GeneratedColumnWithTypeConverter get themeMode => $composableBuilder(column: $table.themeMode, builder: (column) => column); - GeneratedColumnWithTypeConverter get audioSource => - $composableBuilder( - column: $table.audioSource, builder: (column) => column); + GeneratedColumn get audioSourceId => $composableBuilder( + column: $table.audioSourceId, builder: (column) => column); GeneratedColumnWithTypeConverter get youtubeClientEngine => $composableBuilder( column: $table.youtubeClientEngine, builder: (column) => column); - GeneratedColumnWithTypeConverter get streamMusicCodec => - $composableBuilder( - column: $table.streamMusicCodec, builder: (column) => column); - - GeneratedColumnWithTypeConverter - get downloadMusicCodec => $composableBuilder( - column: $table.downloadMusicCodec, builder: (column) => column); - GeneratedColumn get discordPresence => $composableBuilder( column: $table.discordPresence, builder: (column) => column); @@ -5180,7 +4858,6 @@ class $$PreferencesTableTableTableManager extends RootTableManager< $$PreferencesTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), - Value audioQuality = const Value.absent(), Value albumColorSync = const Value.absent(), Value amoledDarkTheme = const Value.absent(), Value checkUpdate = const Value.absent(), @@ -5196,14 +4873,10 @@ class $$PreferencesTableTableTableManager extends RootTableManager< Value searchMode = const Value.absent(), Value downloadLocation = const Value.absent(), Value> localLibraryLocation = const Value.absent(), - Value pipedInstance = const Value.absent(), - Value invidiousInstance = const Value.absent(), Value themeMode = const Value.absent(), - Value audioSource = const Value.absent(), + Value audioSourceId = const Value.absent(), Value youtubeClientEngine = const Value.absent(), - Value streamMusicCodec = const Value.absent(), - Value downloadMusicCodec = const Value.absent(), Value discordPresence = const Value.absent(), Value endlessPlayback = const Value.absent(), Value enableConnect = const Value.absent(), @@ -5212,7 +4885,6 @@ class $$PreferencesTableTableTableManager extends RootTableManager< }) => PreferencesTableCompanion( id: id, - audioQuality: audioQuality, albumColorSync: albumColorSync, amoledDarkTheme: amoledDarkTheme, checkUpdate: checkUpdate, @@ -5228,13 +4900,9 @@ class $$PreferencesTableTableTableManager extends RootTableManager< searchMode: searchMode, downloadLocation: downloadLocation, localLibraryLocation: localLibraryLocation, - pipedInstance: pipedInstance, - invidiousInstance: invidiousInstance, themeMode: themeMode, - audioSource: audioSource, + audioSourceId: audioSourceId, youtubeClientEngine: youtubeClientEngine, - streamMusicCodec: streamMusicCodec, - downloadMusicCodec: downloadMusicCodec, discordPresence: discordPresence, endlessPlayback: endlessPlayback, enableConnect: enableConnect, @@ -5243,7 +4911,6 @@ class $$PreferencesTableTableTableManager extends RootTableManager< ), createCompanionCallback: ({ Value id = const Value.absent(), - Value audioQuality = const Value.absent(), Value albumColorSync = const Value.absent(), Value amoledDarkTheme = const Value.absent(), Value checkUpdate = const Value.absent(), @@ -5259,14 +4926,10 @@ class $$PreferencesTableTableTableManager extends RootTableManager< Value searchMode = const Value.absent(), Value downloadLocation = const Value.absent(), Value> localLibraryLocation = const Value.absent(), - Value pipedInstance = const Value.absent(), - Value invidiousInstance = const Value.absent(), Value themeMode = const Value.absent(), - Value audioSource = const Value.absent(), + Value audioSourceId = const Value.absent(), Value youtubeClientEngine = const Value.absent(), - Value streamMusicCodec = const Value.absent(), - Value downloadMusicCodec = const Value.absent(), Value discordPresence = const Value.absent(), Value endlessPlayback = const Value.absent(), Value enableConnect = const Value.absent(), @@ -5275,7 +4938,6 @@ class $$PreferencesTableTableTableManager extends RootTableManager< }) => PreferencesTableCompanion.insert( id: id, - audioQuality: audioQuality, albumColorSync: albumColorSync, amoledDarkTheme: amoledDarkTheme, checkUpdate: checkUpdate, @@ -5291,13 +4953,9 @@ class $$PreferencesTableTableTableManager extends RootTableManager< searchMode: searchMode, downloadLocation: downloadLocation, localLibraryLocation: localLibraryLocation, - pipedInstance: pipedInstance, - invidiousInstance: invidiousInstance, themeMode: themeMode, - audioSource: audioSource, + audioSourceId: audioSourceId, youtubeClientEngine: youtubeClientEngine, - streamMusicCodec: streamMusicCodec, - downloadMusicCodec: downloadMusicCodec, discordPresence: discordPresence, endlessPlayback: endlessPlayback, enableConnect: enableConnect, @@ -5658,16 +5316,16 @@ typedef $$SourceMatchTableTableCreateCompanionBuilder = SourceMatchTableCompanion Function({ Value id, required String trackId, - required String sourceId, - Value sourceType, + Value sourceInfo, + required String sourceType, Value createdAt, }); typedef $$SourceMatchTableTableUpdateCompanionBuilder = SourceMatchTableCompanion Function({ Value id, Value trackId, - Value sourceId, - Value sourceType, + Value sourceInfo, + Value sourceType, Value createdAt, }); @@ -5686,13 +5344,11 @@ class $$SourceMatchTableTableFilterComposer ColumnFilters get trackId => $composableBuilder( column: $table.trackId, builder: (column) => ColumnFilters(column)); - ColumnFilters get sourceId => $composableBuilder( - column: $table.sourceId, builder: (column) => ColumnFilters(column)); + ColumnFilters get sourceInfo => $composableBuilder( + column: $table.sourceInfo, builder: (column) => ColumnFilters(column)); - ColumnWithTypeConverterFilters - get sourceType => $composableBuilder( - column: $table.sourceType, - builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnFilters get sourceType => $composableBuilder( + column: $table.sourceType, builder: (column) => ColumnFilters(column)); ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column)); @@ -5713,8 +5369,8 @@ class $$SourceMatchTableTableOrderingComposer ColumnOrderings get trackId => $composableBuilder( column: $table.trackId, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get sourceId => $composableBuilder( - column: $table.sourceId, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get sourceInfo => $composableBuilder( + column: $table.sourceInfo, builder: (column) => ColumnOrderings(column)); ColumnOrderings get sourceType => $composableBuilder( column: $table.sourceType, builder: (column) => ColumnOrderings(column)); @@ -5738,12 +5394,11 @@ class $$SourceMatchTableTableAnnotationComposer GeneratedColumn get trackId => $composableBuilder(column: $table.trackId, builder: (column) => column); - GeneratedColumn get sourceId => - $composableBuilder(column: $table.sourceId, builder: (column) => column); + GeneratedColumn get sourceInfo => $composableBuilder( + column: $table.sourceInfo, builder: (column) => column); - GeneratedColumnWithTypeConverter get sourceType => - $composableBuilder( - column: $table.sourceType, builder: (column) => column); + GeneratedColumn get sourceType => $composableBuilder( + column: $table.sourceType, builder: (column) => column); GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); @@ -5779,28 +5434,28 @@ class $$SourceMatchTableTableTableManager extends RootTableManager< updateCompanionCallback: ({ Value id = const Value.absent(), Value trackId = const Value.absent(), - Value sourceId = const Value.absent(), - Value sourceType = const Value.absent(), + Value sourceInfo = const Value.absent(), + Value sourceType = const Value.absent(), Value createdAt = const Value.absent(), }) => SourceMatchTableCompanion( id: id, trackId: trackId, - sourceId: sourceId, + sourceInfo: sourceInfo, sourceType: sourceType, createdAt: createdAt, ), createCompanionCallback: ({ Value id = const Value.absent(), required String trackId, - required String sourceId, - Value sourceType = const Value.absent(), + Value sourceInfo = const Value.absent(), + required String sourceType, Value createdAt = const Value.absent(), }) => SourceMatchTableCompanion.insert( id: id, trackId: trackId, - sourceId: sourceId, + sourceInfo: sourceInfo, sourceType: sourceType, createdAt: createdAt, ), @@ -6352,8 +6007,8 @@ typedef $$LyricsTableTableProcessedTableManager = ProcessedTableManager< ), LyricsTableData, PrefetchHooks Function()>; -typedef $$MetadataPluginsTableTableCreateCompanionBuilder - = MetadataPluginsTableCompanion Function({ +typedef $$PluginsTableTableCreateCompanionBuilder = PluginsTableCompanion + Function({ Value id, required String name, required String description, @@ -6362,12 +6017,13 @@ typedef $$MetadataPluginsTableTableCreateCompanionBuilder required String entryPoint, required List apis, required List abilities, - Value selected, + Value selectedForMetadata, + Value selectedForAudioSource, Value repository, Value pluginApiVersion, }); -typedef $$MetadataPluginsTableTableUpdateCompanionBuilder - = MetadataPluginsTableCompanion Function({ +typedef $$PluginsTableTableUpdateCompanionBuilder = PluginsTableCompanion + Function({ Value id, Value name, Value description, @@ -6376,14 +6032,15 @@ typedef $$MetadataPluginsTableTableUpdateCompanionBuilder Value entryPoint, Value> apis, Value> abilities, - Value selected, + Value selectedForMetadata, + Value selectedForAudioSource, Value repository, Value pluginApiVersion, }); -class $$MetadataPluginsTableTableFilterComposer - extends Composer<_$AppDatabase, $MetadataPluginsTableTable> { - $$MetadataPluginsTableTableFilterComposer({ +class $$PluginsTableTableFilterComposer + extends Composer<_$AppDatabase, $PluginsTableTable> { + $$PluginsTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -6418,8 +6075,13 @@ class $$MetadataPluginsTableTableFilterComposer column: $table.abilities, builder: (column) => ColumnWithTypeConverterFilters(column)); - ColumnFilters get selected => $composableBuilder( - column: $table.selected, builder: (column) => ColumnFilters(column)); + ColumnFilters get selectedForMetadata => $composableBuilder( + column: $table.selectedForMetadata, + builder: (column) => ColumnFilters(column)); + + ColumnFilters get selectedForAudioSource => $composableBuilder( + column: $table.selectedForAudioSource, + builder: (column) => ColumnFilters(column)); ColumnFilters get repository => $composableBuilder( column: $table.repository, builder: (column) => ColumnFilters(column)); @@ -6429,9 +6091,9 @@ class $$MetadataPluginsTableTableFilterComposer builder: (column) => ColumnFilters(column)); } -class $$MetadataPluginsTableTableOrderingComposer - extends Composer<_$AppDatabase, $MetadataPluginsTableTable> { - $$MetadataPluginsTableTableOrderingComposer({ +class $$PluginsTableTableOrderingComposer + extends Composer<_$AppDatabase, $PluginsTableTable> { + $$PluginsTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -6462,8 +6124,13 @@ class $$MetadataPluginsTableTableOrderingComposer ColumnOrderings get abilities => $composableBuilder( column: $table.abilities, builder: (column) => ColumnOrderings(column)); - ColumnOrderings get selected => $composableBuilder( - column: $table.selected, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get selectedForMetadata => $composableBuilder( + column: $table.selectedForMetadata, + builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get selectedForAudioSource => $composableBuilder( + column: $table.selectedForAudioSource, + builder: (column) => ColumnOrderings(column)); ColumnOrderings get repository => $composableBuilder( column: $table.repository, builder: (column) => ColumnOrderings(column)); @@ -6473,9 +6140,9 @@ class $$MetadataPluginsTableTableOrderingComposer builder: (column) => ColumnOrderings(column)); } -class $$MetadataPluginsTableTableAnnotationComposer - extends Composer<_$AppDatabase, $MetadataPluginsTableTable> { - $$MetadataPluginsTableTableAnnotationComposer({ +class $$PluginsTableTableAnnotationComposer + extends Composer<_$AppDatabase, $PluginsTableTable> { + $$PluginsTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -6506,8 +6173,11 @@ class $$MetadataPluginsTableTableAnnotationComposer GeneratedColumnWithTypeConverter, String> get abilities => $composableBuilder(column: $table.abilities, builder: (column) => column); - GeneratedColumn get selected => - $composableBuilder(column: $table.selected, builder: (column) => column); + GeneratedColumn get selectedForMetadata => $composableBuilder( + column: $table.selectedForMetadata, builder: (column) => column); + + GeneratedColumn get selectedForAudioSource => $composableBuilder( + column: $table.selectedForAudioSource, builder: (column) => column); GeneratedColumn get repository => $composableBuilder( column: $table.repository, builder: (column) => column); @@ -6516,35 +6186,31 @@ class $$MetadataPluginsTableTableAnnotationComposer column: $table.pluginApiVersion, builder: (column) => column); } -class $$MetadataPluginsTableTableTableManager extends RootTableManager< +class $$PluginsTableTableTableManager extends RootTableManager< _$AppDatabase, - $MetadataPluginsTableTable, - MetadataPluginsTableData, - $$MetadataPluginsTableTableFilterComposer, - $$MetadataPluginsTableTableOrderingComposer, - $$MetadataPluginsTableTableAnnotationComposer, - $$MetadataPluginsTableTableCreateCompanionBuilder, - $$MetadataPluginsTableTableUpdateCompanionBuilder, + $PluginsTableTable, + PluginsTableData, + $$PluginsTableTableFilterComposer, + $$PluginsTableTableOrderingComposer, + $$PluginsTableTableAnnotationComposer, + $$PluginsTableTableCreateCompanionBuilder, + $$PluginsTableTableUpdateCompanionBuilder, ( - MetadataPluginsTableData, - BaseReferences<_$AppDatabase, $MetadataPluginsTableTable, - MetadataPluginsTableData> + PluginsTableData, + BaseReferences<_$AppDatabase, $PluginsTableTable, PluginsTableData> ), - MetadataPluginsTableData, + PluginsTableData, PrefetchHooks Function()> { - $$MetadataPluginsTableTableTableManager( - _$AppDatabase db, $MetadataPluginsTableTable table) + $$PluginsTableTableTableManager(_$AppDatabase db, $PluginsTableTable table) : super(TableManagerState( db: db, table: table, createFilteringComposer: () => - $$MetadataPluginsTableTableFilterComposer($db: db, $table: table), + $$PluginsTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$MetadataPluginsTableTableOrderingComposer( - $db: db, $table: table), + $$PluginsTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$MetadataPluginsTableTableAnnotationComposer( - $db: db, $table: table), + $$PluginsTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), Value name = const Value.absent(), @@ -6554,11 +6220,12 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< Value entryPoint = const Value.absent(), Value> apis = const Value.absent(), Value> abilities = const Value.absent(), - Value selected = const Value.absent(), + Value selectedForMetadata = const Value.absent(), + Value selectedForAudioSource = const Value.absent(), Value repository = const Value.absent(), Value pluginApiVersion = const Value.absent(), }) => - MetadataPluginsTableCompanion( + PluginsTableCompanion( id: id, name: name, description: description, @@ -6567,7 +6234,8 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< entryPoint: entryPoint, apis: apis, abilities: abilities, - selected: selected, + selectedForMetadata: selectedForMetadata, + selectedForAudioSource: selectedForAudioSource, repository: repository, pluginApiVersion: pluginApiVersion, ), @@ -6580,11 +6248,12 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< required String entryPoint, required List apis, required List abilities, - Value selected = const Value.absent(), + Value selectedForMetadata = const Value.absent(), + Value selectedForAudioSource = const Value.absent(), Value repository = const Value.absent(), Value pluginApiVersion = const Value.absent(), }) => - MetadataPluginsTableCompanion.insert( + PluginsTableCompanion.insert( id: id, name: name, description: description, @@ -6593,7 +6262,8 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< entryPoint: entryPoint, apis: apis, abilities: abilities, - selected: selected, + selectedForMetadata: selectedForMetadata, + selectedForAudioSource: selectedForAudioSource, repository: repository, pluginApiVersion: pluginApiVersion, ), @@ -6604,23 +6274,21 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< )); } -typedef $$MetadataPluginsTableTableProcessedTableManager - = ProcessedTableManager< - _$AppDatabase, - $MetadataPluginsTableTable, - MetadataPluginsTableData, - $$MetadataPluginsTableTableFilterComposer, - $$MetadataPluginsTableTableOrderingComposer, - $$MetadataPluginsTableTableAnnotationComposer, - $$MetadataPluginsTableTableCreateCompanionBuilder, - $$MetadataPluginsTableTableUpdateCompanionBuilder, - ( - MetadataPluginsTableData, - BaseReferences<_$AppDatabase, $MetadataPluginsTableTable, - MetadataPluginsTableData> - ), - MetadataPluginsTableData, - PrefetchHooks Function()>; +typedef $$PluginsTableTableProcessedTableManager = ProcessedTableManager< + _$AppDatabase, + $PluginsTableTable, + PluginsTableData, + $$PluginsTableTableFilterComposer, + $$PluginsTableTableOrderingComposer, + $$PluginsTableTableAnnotationComposer, + $$PluginsTableTableCreateCompanionBuilder, + $$PluginsTableTableUpdateCompanionBuilder, + ( + PluginsTableData, + BaseReferences<_$AppDatabase, $PluginsTableTable, PluginsTableData> + ), + PluginsTableData, + PrefetchHooks Function()>; class $AppDatabaseManager { final _$AppDatabase _db; @@ -6643,6 +6311,6 @@ class $AppDatabaseManager { $$HistoryTableTableTableManager(_db, _db.historyTable); $$LyricsTableTableTableManager get lyricsTable => $$LyricsTableTableTableManager(_db, _db.lyricsTable); - $$MetadataPluginsTableTableTableManager get metadataPluginsTable => - $$MetadataPluginsTableTableTableManager(_db, _db.metadataPluginsTable); + $$PluginsTableTableTableManager get pluginsTable => + $$PluginsTableTableTableManager(_db, _db.pluginsTable); } diff --git a/lib/models/database/database.steps.dart b/lib/models/database/database.steps.dart index a228f5a7..42cbdf6d 100644 --- a/lib/models/database/database.steps.dart +++ b/lib/models/database/database.steps.dart @@ -1,10 +1,10 @@ +// 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 import 'package:flutter/material.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/market.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; // GENERATED BY drift_dev, DO NOT MODIFY. final class Schema2 extends i0.VersionedSchema { @@ -329,8 +329,7 @@ class Shape2 extends i0.VersionedTable { i1.GeneratedColumn _column_7(String aliasedName) => i1.GeneratedColumn('audio_quality', aliasedName, false, - type: i1.DriftSqlType.string, - defaultValue: Constant(SourceQualities.high.name)); + type: i1.DriftSqlType.string, defaultValue: Constant("high")); i1.GeneratedColumn _column_8(String aliasedName) => i1.GeneratedColumn('album_color_sync', aliasedName, false, type: i1.DriftSqlType.bool, @@ -417,16 +416,13 @@ i1.GeneratedColumn _column_25(String aliasedName) => defaultValue: Constant(ThemeMode.system.name)); i1.GeneratedColumn _column_26(String aliasedName) => i1.GeneratedColumn('audio_source', aliasedName, false, - type: i1.DriftSqlType.string, - defaultValue: Constant(AudioSource.youtube.name)); + type: i1.DriftSqlType.string, defaultValue: Constant("youtube")); i1.GeneratedColumn _column_27(String aliasedName) => i1.GeneratedColumn('stream_music_codec', aliasedName, false, - type: i1.DriftSqlType.string, - defaultValue: Constant(SourceCodecs.weba.name)); + type: i1.DriftSqlType.string, defaultValue: Constant("weba")); i1.GeneratedColumn _column_28(String aliasedName) => i1.GeneratedColumn('download_music_codec', aliasedName, false, - type: i1.DriftSqlType.string, - defaultValue: Constant(SourceCodecs.m4a.name)); + type: i1.DriftSqlType.string, defaultValue: Constant("m4a")); i1.GeneratedColumn _column_29(String aliasedName) => i1.GeneratedColumn('discord_presence', aliasedName, false, type: i1.DriftSqlType.bool, @@ -511,8 +507,7 @@ i1.GeneratedColumn _column_38(String aliasedName) => type: i1.DriftSqlType.string); i1.GeneratedColumn _column_39(String aliasedName) => i1.GeneratedColumn('source_type', aliasedName, false, - type: i1.DriftSqlType.string, - defaultValue: Constant(SourceType.youtube.name)); + type: i1.DriftSqlType.string, defaultValue: Constant("youtube")); class Shape6 extends i0.VersionedTable { Shape6({required super.source, required super.alias}) : super.aliased(); @@ -1407,7 +1402,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("Slate:0xff64748b")); + defaultValue: const Constant("Orange:0xFFf97315")); final class Schema6 extends i0.VersionedSchema { Schema6({required super.database}) : super(version: 6); @@ -2053,7 +2048,7 @@ final class Schema8 extends i0.VersionedSchema { _column_13, _column_14, _column_15, - _column_55, + _column_69, _column_17, _column_18, _column_19, @@ -2188,7 +2183,7 @@ final class Schema8 extends i0.VersionedSchema { _column_65, _column_66, _column_67, - _column_69, + _column_70, ], attachedDatabase: database, ), @@ -2200,8 +2195,550 @@ final class Schema8 extends i0.VersionedSchema { } i1.GeneratedColumn _column_69(String aliasedName) => + i1.GeneratedColumn('accent_color_scheme', aliasedName, false, + type: i1.DriftSqlType.string, + defaultValue: const Constant("Slate:0xff64748b")); +i1.GeneratedColumn _column_70(String aliasedName) => i1.GeneratedColumn('plugin_api_version', aliasedName, false, type: i1.DriftSqlType.string, defaultValue: const Constant('1.0.0')); + +final class Schema9 extends i0.VersionedSchema { + Schema9({required super.database}) : super(version: 9); + @override + late final List entities = [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + historyTable, + lyricsTable, + pluginsTable, + 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_69, + _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 Shape16 pluginsTable = Shape16( + source: i0.VersionedTable( + entityName: '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_71, + _column_72, + _column_67, + _column_73, + ], + 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)'); +} + +class Shape16 extends i0.VersionedTable { + Shape16({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get description => + columnsByName['description']! as i1.GeneratedColumn; + i1.GeneratedColumn get version => + columnsByName['version']! as i1.GeneratedColumn; + i1.GeneratedColumn get author => + columnsByName['author']! as i1.GeneratedColumn; + i1.GeneratedColumn get entryPoint => + columnsByName['entry_point']! as i1.GeneratedColumn; + i1.GeneratedColumn get apis => + columnsByName['apis']! as i1.GeneratedColumn; + i1.GeneratedColumn get abilities => + columnsByName['abilities']! as i1.GeneratedColumn; + i1.GeneratedColumn get selectedForMetadata => + columnsByName['selected_for_metadata']! as i1.GeneratedColumn; + i1.GeneratedColumn get selectedForAudioSource => + columnsByName['selected_for_audio_source']! as i1.GeneratedColumn; + i1.GeneratedColumn get repository => + columnsByName['repository']! as i1.GeneratedColumn; + i1.GeneratedColumn get pluginApiVersion => + columnsByName['plugin_api_version']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_71(String aliasedName) => + i1.GeneratedColumn('selected_for_metadata', aliasedName, false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_metadata" IN (0, 1))'), + defaultValue: const Constant(false)); +i1.GeneratedColumn _column_72(String aliasedName) => + i1.GeneratedColumn('selected_for_audio_source', aliasedName, false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_audio_source" IN (0, 1))'), + defaultValue: const Constant(false)); +i1.GeneratedColumn _column_73(String aliasedName) => + i1.GeneratedColumn('plugin_api_version', aliasedName, false, + type: i1.DriftSqlType.string, defaultValue: const Constant('2.0.0')); + +final class Schema10 extends i0.VersionedSchema { + Schema10({required super.database}) : super(version: 10); + @override + late final List entities = [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + historyTable, + lyricsTable, + pluginsTable, + 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 Shape17 preferencesTable = Shape17( + source: i0.VersionedTable( + entityName: 'preferences_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_13, + _column_14, + _column_15, + _column_69, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_25, + _column_74, + _column_54, + _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 Shape18 sourceMatchTable = Shape18( + source: i0.VersionedTable( + entityName: 'source_match_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_37, + _column_75, + _column_76, + _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 Shape16 pluginsTable = Shape16( + source: i0.VersionedTable( + entityName: '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_71, + _column_72, + _column_67, + _column_73, + ], + 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_info, source_type)'); +} + +class Shape17 extends i0.VersionedTable { + Shape17({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get albumColorSync => + columnsByName['album_color_sync']! as i1.GeneratedColumn; + i1.GeneratedColumn get amoledDarkTheme => + columnsByName['amoled_dark_theme']! as i1.GeneratedColumn; + i1.GeneratedColumn get checkUpdate => + columnsByName['check_update']! as i1.GeneratedColumn; + i1.GeneratedColumn get normalizeAudio => + columnsByName['normalize_audio']! as i1.GeneratedColumn; + i1.GeneratedColumn get showSystemTrayIcon => + columnsByName['show_system_tray_icon']! as i1.GeneratedColumn; + i1.GeneratedColumn get systemTitleBar => + columnsByName['system_title_bar']! as i1.GeneratedColumn; + i1.GeneratedColumn get skipNonMusic => + columnsByName['skip_non_music']! as i1.GeneratedColumn; + i1.GeneratedColumn get closeBehavior => + columnsByName['close_behavior']! as i1.GeneratedColumn; + i1.GeneratedColumn get accentColorScheme => + columnsByName['accent_color_scheme']! as i1.GeneratedColumn; + i1.GeneratedColumn get layoutMode => + columnsByName['layout_mode']! as i1.GeneratedColumn; + i1.GeneratedColumn get locale => + columnsByName['locale']! as i1.GeneratedColumn; + i1.GeneratedColumn get market => + columnsByName['market']! as i1.GeneratedColumn; + i1.GeneratedColumn get searchMode => + columnsByName['search_mode']! as i1.GeneratedColumn; + i1.GeneratedColumn get downloadLocation => + columnsByName['download_location']! as i1.GeneratedColumn; + i1.GeneratedColumn get localLibraryLocation => + columnsByName['local_library_location']! as i1.GeneratedColumn; + i1.GeneratedColumn get themeMode => + columnsByName['theme_mode']! as i1.GeneratedColumn; + i1.GeneratedColumn get audioSourceId => + columnsByName['audio_source_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get youtubeClientEngine => + columnsByName['youtube_client_engine']! as i1.GeneratedColumn; + i1.GeneratedColumn get discordPresence => + columnsByName['discord_presence']! as i1.GeneratedColumn; + i1.GeneratedColumn get endlessPlayback => + columnsByName['endless_playback']! as i1.GeneratedColumn; + i1.GeneratedColumn get enableConnect => + columnsByName['enable_connect']! as i1.GeneratedColumn; + i1.GeneratedColumn get connectPort => + columnsByName['connect_port']! as i1.GeneratedColumn; + i1.GeneratedColumn get cacheMusic => + columnsByName['cache_music']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_74(String aliasedName) => + i1.GeneratedColumn('audio_source_id', aliasedName, true, + type: i1.DriftSqlType.string); + +class Shape18 extends i0.VersionedTable { + Shape18({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get trackId => + columnsByName['track_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get sourceInfo => + columnsByName['source_info']! as i1.GeneratedColumn; + i1.GeneratedColumn get sourceType => + columnsByName['source_type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_75(String aliasedName) => + i1.GeneratedColumn('source_info', aliasedName, false, + type: i1.DriftSqlType.string, defaultValue: const Constant("{}")); +i1.GeneratedColumn _column_76(String aliasedName) => + i1.GeneratedColumn('source_type', aliasedName, false, + type: i1.DriftSqlType.string); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, @@ -2210,6 +2747,8 @@ i0.MigrationStepWithVersion migrationSteps({ 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, + required Future Function(i1.Migrator m, Schema9 schema) from8To9, + required Future Function(i1.Migrator m, Schema10 schema) from9To10, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -2248,6 +2787,16 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from7To8(migrator, schema); return 8; + case 8: + final schema = Schema9(database: database); + final migrator = i1.Migrator(database, schema); + await from8To9(migrator, schema); + return 9; + case 9: + final schema = Schema10(database: database); + final migrator = i1.Migrator(database, schema); + await from9To10(migrator, schema); + return 10; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -2262,6 +2811,8 @@ i1.OnUpgrade stepByStep({ 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, + required Future Function(i1.Migrator m, Schema9 schema) from8To9, + required Future Function(i1.Migrator m, Schema10 schema) from9To10, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( @@ -2272,4 +2823,6 @@ i1.OnUpgrade stepByStep({ from5To6: from5To6, from6To7: from6To7, from7To8: from7To8, + from8To9: from8To9, + from9To10: from9To10, )); diff --git a/lib/models/database/tables/metadata_plugins.dart b/lib/models/database/tables/metadata_plugins.dart index 8fa3b064..3447497d 100644 --- a/lib/models/database/tables/metadata_plugins.dart +++ b/lib/models/database/tables/metadata_plugins.dart @@ -1,6 +1,6 @@ part of '../database.dart'; -class MetadataPluginsTable extends Table { +class PluginsTable extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get name => text().withLength(min: 1, max: 50)(); TextColumn get description => text()(); @@ -9,8 +9,11 @@ class MetadataPluginsTable extends Table { TextColumn get entryPoint => text()(); TextColumn get apis => text().map(const StringListConverter())(); TextColumn get abilities => text().map(const StringListConverter())(); - BoolColumn get selected => boolean().withDefault(const Constant(false))(); + BoolColumn get selectedForMetadata => + boolean().withDefault(const Constant(false))(); + BoolColumn get selectedForAudioSource => + boolean().withDefault(const Constant(false))(); TextColumn get repository => text().nullable()(); TextColumn get pluginApiVersion => - text().withDefault(const Constant('1.0.0'))(); + text().withDefault(const Constant('2.0.0'))(); } diff --git a/lib/models/database/tables/preferences.dart b/lib/models/database/tables/preferences.dart index 85014920..3029e2a8 100644 --- a/lib/models/database/tables/preferences.dart +++ b/lib/models/database/tables/preferences.dart @@ -11,15 +11,6 @@ enum CloseBehavior { close, } -enum AudioSource { - youtube, - piped, - jiosaavn, - invidious; - - String get label => name[0].toUpperCase() + name.substring(1); -} - enum YoutubeClientEngine { ytDlp("yt-dlp"), youtubeExplode("YouTubeExplode"), @@ -39,14 +30,6 @@ enum YoutubeClientEngine { } } -enum MusicCodec { - m4a._("M4a (Best for downloaded music)"), - weba._("WebA (Best for streamed music)\nDoesn't support audio metadata"); - - final String label; - const MusicCodec._(this.label); -} - enum SearchMode { youtube._("YouTube"), youtubeMusic._("YouTube Music"); @@ -62,8 +45,6 @@ enum SearchMode { class PreferencesTable extends Table { IntColumn get id => integer().autoIncrement()(); - TextColumn get audioQuality => textEnum() - .withDefault(Constant(SourceQualities.high.name))(); BoolColumn get albumColorSync => boolean().withDefault(const Constant(true))(); BoolColumn get amoledDarkTheme => @@ -95,20 +76,11 @@ class PreferencesTable extends Table { TextColumn get downloadLocation => text().withDefault(const Constant(""))(); TextColumn get localLibraryLocation => text().withDefault(const Constant("")).map(const StringListConverter())(); - TextColumn get pipedInstance => - text().withDefault(const Constant("https://pipedapi.kavin.rocks"))(); - TextColumn get invidiousInstance => - text().withDefault(const Constant("https://inv.nadeko.net"))(); TextColumn get themeMode => textEnum().withDefault(Constant(ThemeMode.system.name))(); - TextColumn get audioSource => - textEnum().withDefault(Constant(AudioSource.youtube.name))(); + TextColumn get audioSourceId => text().nullable()(); TextColumn get youtubeClientEngine => textEnum() .withDefault(Constant(YoutubeClientEngine.youtubeExplode.name))(); - TextColumn get streamMusicCodec => - textEnum().withDefault(Constant(SourceCodecs.weba.name))(); - TextColumn get downloadMusicCodec => - textEnum().withDefault(Constant(SourceCodecs.m4a.name))(); BoolColumn get discordPresence => boolean().withDefault(const Constant(true))(); BoolColumn get endlessPlayback => @@ -122,7 +94,6 @@ class PreferencesTable extends Table { static PreferencesTableData defaults() { return PreferencesTableData( id: 0, - audioQuality: SourceQualities.high, albumColorSync: true, amoledDarkTheme: false, checkUpdate: true, @@ -138,13 +109,11 @@ class PreferencesTable extends Table { searchMode: SearchMode.youtube, downloadLocation: "", localLibraryLocation: [], - pipedInstance: "https://pipedapi.kavin.rocks", - invidiousInstance: "https://inv.nadeko.net", themeMode: ThemeMode.system, - audioSource: AudioSource.youtube, - youtubeClientEngine: YoutubeClientEngine.youtubeExplode, - streamMusicCodec: SourceCodecs.m4a, - downloadMusicCodec: SourceCodecs.m4a, + audioSourceId: null, + youtubeClientEngine: kIsIOS + ? YoutubeClientEngine.youtubeExplode + : YoutubeClientEngine.newPipe, discordPresence: true, endlessPlayback: true, enableConnect: false, diff --git a/lib/models/database/tables/source_match.dart b/lib/models/database/tables/source_match.dart index 78d0eb05..66a4959c 100644 --- a/lib/models/database/tables/source_match.dart +++ b/lib/models/database/tables/source_match.dart @@ -1,25 +1,9 @@ part of '../database.dart'; -enum SourceType { - youtube._("YouTube"), - youtubeMusic._("YouTube Music"), - jiosaavn._("JioSaavn"); - - final String label; - - const SourceType._(this.label); -} - -@TableIndex( - name: "uniq_track_match", - columns: {#trackId, #sourceId, #sourceType}, - unique: true, -) class SourceMatchTable extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get trackId => text()(); - TextColumn get sourceId => text()(); - TextColumn get sourceType => - textEnum().withDefault(Constant(SourceType.youtube.name))(); + TextColumn get sourceInfo => text().withDefault(const Constant("{}"))(); + TextColumn get sourceType => text()(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); } diff --git a/lib/models/metadata/audio_source.dart b/lib/models/metadata/audio_source.dart new file mode 100644 index 00000000..4fb790ea --- /dev/null +++ b/lib/models/metadata/audio_source.dart @@ -0,0 +1,110 @@ +part of 'metadata.dart'; + +final oneOptionalDecimalFormatter = NumberFormat('0.#', 'en_US'); + +enum SpotubeMediaCompressionType { + lossy, + lossless, +} + +@Freezed(unionKey: 'type') +class SpotubeAudioSourceContainerPreset + with _$SpotubeAudioSourceContainerPreset { + const SpotubeAudioSourceContainerPreset._(); + + @FreezedUnionValue("lossy") + factory SpotubeAudioSourceContainerPreset.lossy({ + required SpotubeMediaCompressionType type, + required String name, + required List qualities, + }) = SpotubeAudioSourceContainerPresetLossy; + + @FreezedUnionValue("lossless") + factory SpotubeAudioSourceContainerPreset.lossless({ + required SpotubeMediaCompressionType type, + required String name, + required List qualities, + }) = SpotubeAudioSourceContainerPresetLossless; + + factory SpotubeAudioSourceContainerPreset.fromJson( + Map json) => + _$SpotubeAudioSourceContainerPresetFromJson(json); + + String getFileExtension() { + return switch (name) { + "mp4" => "m4a", + "webm" => "weba", + _ => name, + }; + } +} + +@freezed +class SpotubeAudioLossyContainerQuality + with _$SpotubeAudioLossyContainerQuality { + const SpotubeAudioLossyContainerQuality._(); + + factory SpotubeAudioLossyContainerQuality({ + required int bitrate, // bits per second + }) = _SpotubeAudioLossyContainerQuality; + + factory SpotubeAudioLossyContainerQuality.fromJson( + Map json) => + _$SpotubeAudioLossyContainerQualityFromJson(json); + + @override + toString() { + return "${oneOptionalDecimalFormatter.format(bitrate / 1000)}kbps"; + } +} + +@freezed +class SpotubeAudioLosslessContainerQuality + with _$SpotubeAudioLosslessContainerQuality { + const SpotubeAudioLosslessContainerQuality._(); + + factory SpotubeAudioLosslessContainerQuality({ + required int bitDepth, // bit + required int sampleRate, // hz + }) = _SpotubeAudioLosslessContainerQuality; + + factory SpotubeAudioLosslessContainerQuality.fromJson( + Map json) => + _$SpotubeAudioLosslessContainerQualityFromJson(json); + + @override + toString() { + return "${bitDepth}bit • ${oneOptionalDecimalFormatter.format(sampleRate / 1000)}kHz"; + } +} + +@freezed +class SpotubeAudioSourceMatchObject with _$SpotubeAudioSourceMatchObject { + factory SpotubeAudioSourceMatchObject({ + required String id, + required String title, + required List artists, + required Duration duration, + String? thumbnail, + required String externalUri, + }) = _SpotubeAudioSourceMatchObject; + + factory SpotubeAudioSourceMatchObject.fromJson(Map json) => + _$SpotubeAudioSourceMatchObjectFromJson(json); +} + +@freezed +class SpotubeAudioSourceStreamObject with _$SpotubeAudioSourceStreamObject { + factory SpotubeAudioSourceStreamObject({ + required String url, + required String container, + required SpotubeMediaCompressionType type, + String? codec, + double? bitrate, + int? bitDepth, + double? sampleRate, + }) = _SpotubeAudioSourceStreamObject; + + factory SpotubeAudioSourceStreamObject.fromJson(Map json) => + _$SpotubeAudioSourceStreamObjectFromJson(json); +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index 97da704c..e68bcd14 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:intl/intl.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart'; @@ -15,6 +16,7 @@ import 'package:spotube/utils/primitive_utils.dart'; part 'metadata.g.dart'; part 'metadata.freezed.dart'; +part 'audio_source.dart'; part 'album.dart'; part 'artist.dart'; part 'browse.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index 54fd452a..fee1cbc2 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -14,6 +14,1500 @@ T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); +SpotubeAudioSourceContainerPreset _$SpotubeAudioSourceContainerPresetFromJson( + Map json) { + switch (json['type']) { + case 'lossy': + return SpotubeAudioSourceContainerPresetLossy.fromJson(json); + case 'lossless': + return SpotubeAudioSourceContainerPresetLossless.fromJson(json); + + default: + throw CheckedFromJsonException( + json, + 'type', + 'SpotubeAudioSourceContainerPreset', + 'Invalid union type "${json['type']}"!'); + } +} + +/// @nodoc +mixin _$SpotubeAudioSourceContainerPreset { + SpotubeMediaCompressionType get type => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + List get qualities => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(SpotubeMediaCompressionType type, String name, + List qualities) + lossy, + required TResult Function(SpotubeMediaCompressionType type, String name, + List qualities) + lossless, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossy, + TResult? Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossless, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossy, + TResult Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossless, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeAudioSourceContainerPresetLossy value) + lossy, + required TResult Function(SpotubeAudioSourceContainerPresetLossless value) + lossless, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeAudioSourceContainerPresetLossy value)? lossy, + TResult? Function(SpotubeAudioSourceContainerPresetLossless value)? + lossless, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeAudioSourceContainerPresetLossy value)? lossy, + TResult Function(SpotubeAudioSourceContainerPresetLossless value)? lossless, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Serializes this SpotubeAudioSourceContainerPreset to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeAudioSourceContainerPresetCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeAudioSourceContainerPresetCopyWith<$Res> { + factory $SpotubeAudioSourceContainerPresetCopyWith( + SpotubeAudioSourceContainerPreset value, + $Res Function(SpotubeAudioSourceContainerPreset) then) = + _$SpotubeAudioSourceContainerPresetCopyWithImpl<$Res, + SpotubeAudioSourceContainerPreset>; + @useResult + $Res call({SpotubeMediaCompressionType type, String name}); +} + +/// @nodoc +class _$SpotubeAudioSourceContainerPresetCopyWithImpl<$Res, + $Val extends SpotubeAudioSourceContainerPreset> + implements $SpotubeAudioSourceContainerPresetCopyWith<$Res> { + _$SpotubeAudioSourceContainerPresetCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + Object? name = null, + }) { + return _then(_value.copyWith( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as SpotubeMediaCompressionType, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeAudioSourceContainerPresetLossyImplCopyWith<$Res> + implements $SpotubeAudioSourceContainerPresetCopyWith<$Res> { + factory _$$SpotubeAudioSourceContainerPresetLossyImplCopyWith( + _$SpotubeAudioSourceContainerPresetLossyImpl value, + $Res Function(_$SpotubeAudioSourceContainerPresetLossyImpl) then) = + __$$SpotubeAudioSourceContainerPresetLossyImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {SpotubeMediaCompressionType type, + String name, + List qualities}); +} + +/// @nodoc +class __$$SpotubeAudioSourceContainerPresetLossyImplCopyWithImpl<$Res> + extends _$SpotubeAudioSourceContainerPresetCopyWithImpl<$Res, + _$SpotubeAudioSourceContainerPresetLossyImpl> + implements _$$SpotubeAudioSourceContainerPresetLossyImplCopyWith<$Res> { + __$$SpotubeAudioSourceContainerPresetLossyImplCopyWithImpl( + _$SpotubeAudioSourceContainerPresetLossyImpl _value, + $Res Function(_$SpotubeAudioSourceContainerPresetLossyImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + Object? name = null, + Object? qualities = null, + }) { + return _then(_$SpotubeAudioSourceContainerPresetLossyImpl( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as SpotubeMediaCompressionType, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + qualities: null == qualities + ? _value._qualities + : qualities // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeAudioSourceContainerPresetLossyImpl + extends SpotubeAudioSourceContainerPresetLossy { + _$SpotubeAudioSourceContainerPresetLossyImpl( + {required this.type, + required this.name, + required final List qualities}) + : _qualities = qualities, + super._(); + + factory _$SpotubeAudioSourceContainerPresetLossyImpl.fromJson( + Map json) => + _$$SpotubeAudioSourceContainerPresetLossyImplFromJson(json); + + @override + final SpotubeMediaCompressionType type; + @override + final String name; + final List _qualities; + @override + List get qualities { + if (_qualities is EqualUnmodifiableListView) return _qualities; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_qualities); + } + + @override + String toString() { + return 'SpotubeAudioSourceContainerPreset.lossy(type: $type, name: $name, qualities: $qualities)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeAudioSourceContainerPresetLossyImpl && + (identical(other.type, type) || other.type == type) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality() + .equals(other._qualities, _qualities)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, type, name, const DeepCollectionEquality().hash(_qualities)); + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeAudioSourceContainerPresetLossyImplCopyWith< + _$SpotubeAudioSourceContainerPresetLossyImpl> + get copyWith => + __$$SpotubeAudioSourceContainerPresetLossyImplCopyWithImpl< + _$SpotubeAudioSourceContainerPresetLossyImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(SpotubeMediaCompressionType type, String name, + List qualities) + lossy, + required TResult Function(SpotubeMediaCompressionType type, String name, + List qualities) + lossless, + }) { + return lossy(type, name, qualities); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossy, + TResult? Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossless, + }) { + return lossy?.call(type, name, qualities); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossy, + TResult Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossless, + required TResult orElse(), + }) { + if (lossy != null) { + return lossy(type, name, qualities); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeAudioSourceContainerPresetLossy value) + lossy, + required TResult Function(SpotubeAudioSourceContainerPresetLossless value) + lossless, + }) { + return lossy(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeAudioSourceContainerPresetLossy value)? lossy, + TResult? Function(SpotubeAudioSourceContainerPresetLossless value)? + lossless, + }) { + return lossy?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeAudioSourceContainerPresetLossy value)? lossy, + TResult Function(SpotubeAudioSourceContainerPresetLossless value)? lossless, + required TResult orElse(), + }) { + if (lossy != null) { + return lossy(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SpotubeAudioSourceContainerPresetLossyImplToJson( + this, + ); + } +} + +abstract class SpotubeAudioSourceContainerPresetLossy + extends SpotubeAudioSourceContainerPreset { + factory SpotubeAudioSourceContainerPresetLossy( + {required final SpotubeMediaCompressionType type, + required final String name, + required final List qualities}) = + _$SpotubeAudioSourceContainerPresetLossyImpl; + SpotubeAudioSourceContainerPresetLossy._() : super._(); + + factory SpotubeAudioSourceContainerPresetLossy.fromJson( + Map json) = + _$SpotubeAudioSourceContainerPresetLossyImpl.fromJson; + + @override + SpotubeMediaCompressionType get type; + @override + String get name; + @override + List get qualities; + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeAudioSourceContainerPresetLossyImplCopyWith< + _$SpotubeAudioSourceContainerPresetLossyImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SpotubeAudioSourceContainerPresetLosslessImplCopyWith<$Res> + implements $SpotubeAudioSourceContainerPresetCopyWith<$Res> { + factory _$$SpotubeAudioSourceContainerPresetLosslessImplCopyWith( + _$SpotubeAudioSourceContainerPresetLosslessImpl value, + $Res Function(_$SpotubeAudioSourceContainerPresetLosslessImpl) then) = + __$$SpotubeAudioSourceContainerPresetLosslessImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {SpotubeMediaCompressionType type, + String name, + List qualities}); +} + +/// @nodoc +class __$$SpotubeAudioSourceContainerPresetLosslessImplCopyWithImpl<$Res> + extends _$SpotubeAudioSourceContainerPresetCopyWithImpl<$Res, + _$SpotubeAudioSourceContainerPresetLosslessImpl> + implements _$$SpotubeAudioSourceContainerPresetLosslessImplCopyWith<$Res> { + __$$SpotubeAudioSourceContainerPresetLosslessImplCopyWithImpl( + _$SpotubeAudioSourceContainerPresetLosslessImpl _value, + $Res Function(_$SpotubeAudioSourceContainerPresetLosslessImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + Object? name = null, + Object? qualities = null, + }) { + return _then(_$SpotubeAudioSourceContainerPresetLosslessImpl( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as SpotubeMediaCompressionType, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + qualities: null == qualities + ? _value._qualities + : qualities // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeAudioSourceContainerPresetLosslessImpl + extends SpotubeAudioSourceContainerPresetLossless { + _$SpotubeAudioSourceContainerPresetLosslessImpl( + {required this.type, + required this.name, + required final List qualities}) + : _qualities = qualities, + super._(); + + factory _$SpotubeAudioSourceContainerPresetLosslessImpl.fromJson( + Map json) => + _$$SpotubeAudioSourceContainerPresetLosslessImplFromJson(json); + + @override + final SpotubeMediaCompressionType type; + @override + final String name; + final List _qualities; + @override + List get qualities { + if (_qualities is EqualUnmodifiableListView) return _qualities; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_qualities); + } + + @override + String toString() { + return 'SpotubeAudioSourceContainerPreset.lossless(type: $type, name: $name, qualities: $qualities)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeAudioSourceContainerPresetLosslessImpl && + (identical(other.type, type) || other.type == type) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality() + .equals(other._qualities, _qualities)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, type, name, const DeepCollectionEquality().hash(_qualities)); + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeAudioSourceContainerPresetLosslessImplCopyWith< + _$SpotubeAudioSourceContainerPresetLosslessImpl> + get copyWith => + __$$SpotubeAudioSourceContainerPresetLosslessImplCopyWithImpl< + _$SpotubeAudioSourceContainerPresetLosslessImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(SpotubeMediaCompressionType type, String name, + List qualities) + lossy, + required TResult Function(SpotubeMediaCompressionType type, String name, + List qualities) + lossless, + }) { + return lossless(type, name, qualities); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossy, + TResult? Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossless, + }) { + return lossless?.call(type, name, qualities); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossy, + TResult Function(SpotubeMediaCompressionType type, String name, + List qualities)? + lossless, + required TResult orElse(), + }) { + if (lossless != null) { + return lossless(type, name, qualities); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeAudioSourceContainerPresetLossy value) + lossy, + required TResult Function(SpotubeAudioSourceContainerPresetLossless value) + lossless, + }) { + return lossless(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeAudioSourceContainerPresetLossy value)? lossy, + TResult? Function(SpotubeAudioSourceContainerPresetLossless value)? + lossless, + }) { + return lossless?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeAudioSourceContainerPresetLossy value)? lossy, + TResult Function(SpotubeAudioSourceContainerPresetLossless value)? lossless, + required TResult orElse(), + }) { + if (lossless != null) { + return lossless(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SpotubeAudioSourceContainerPresetLosslessImplToJson( + this, + ); + } +} + +abstract class SpotubeAudioSourceContainerPresetLossless + extends SpotubeAudioSourceContainerPreset { + factory SpotubeAudioSourceContainerPresetLossless( + {required final SpotubeMediaCompressionType type, + required final String name, + required final List + qualities}) = _$SpotubeAudioSourceContainerPresetLosslessImpl; + SpotubeAudioSourceContainerPresetLossless._() : super._(); + + factory SpotubeAudioSourceContainerPresetLossless.fromJson( + Map json) = + _$SpotubeAudioSourceContainerPresetLosslessImpl.fromJson; + + @override + SpotubeMediaCompressionType get type; + @override + String get name; + @override + List get qualities; + + /// Create a copy of SpotubeAudioSourceContainerPreset + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeAudioSourceContainerPresetLosslessImplCopyWith< + _$SpotubeAudioSourceContainerPresetLosslessImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeAudioLossyContainerQuality _$SpotubeAudioLossyContainerQualityFromJson( + Map json) { + return _SpotubeAudioLossyContainerQuality.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeAudioLossyContainerQuality { + int get bitrate => throw _privateConstructorUsedError; + + /// Serializes this SpotubeAudioLossyContainerQuality to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeAudioLossyContainerQuality + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeAudioLossyContainerQualityCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeAudioLossyContainerQualityCopyWith<$Res> { + factory $SpotubeAudioLossyContainerQualityCopyWith( + SpotubeAudioLossyContainerQuality value, + $Res Function(SpotubeAudioLossyContainerQuality) then) = + _$SpotubeAudioLossyContainerQualityCopyWithImpl<$Res, + SpotubeAudioLossyContainerQuality>; + @useResult + $Res call({int bitrate}); +} + +/// @nodoc +class _$SpotubeAudioLossyContainerQualityCopyWithImpl<$Res, + $Val extends SpotubeAudioLossyContainerQuality> + implements $SpotubeAudioLossyContainerQualityCopyWith<$Res> { + _$SpotubeAudioLossyContainerQualityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeAudioLossyContainerQuality + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? bitrate = null, + }) { + return _then(_value.copyWith( + bitrate: null == bitrate + ? _value.bitrate + : bitrate // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeAudioLossyContainerQualityImplCopyWith<$Res> + implements $SpotubeAudioLossyContainerQualityCopyWith<$Res> { + factory _$$SpotubeAudioLossyContainerQualityImplCopyWith( + _$SpotubeAudioLossyContainerQualityImpl value, + $Res Function(_$SpotubeAudioLossyContainerQualityImpl) then) = + __$$SpotubeAudioLossyContainerQualityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int bitrate}); +} + +/// @nodoc +class __$$SpotubeAudioLossyContainerQualityImplCopyWithImpl<$Res> + extends _$SpotubeAudioLossyContainerQualityCopyWithImpl<$Res, + _$SpotubeAudioLossyContainerQualityImpl> + implements _$$SpotubeAudioLossyContainerQualityImplCopyWith<$Res> { + __$$SpotubeAudioLossyContainerQualityImplCopyWithImpl( + _$SpotubeAudioLossyContainerQualityImpl _value, + $Res Function(_$SpotubeAudioLossyContainerQualityImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeAudioLossyContainerQuality + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? bitrate = null, + }) { + return _then(_$SpotubeAudioLossyContainerQualityImpl( + bitrate: null == bitrate + ? _value.bitrate + : bitrate // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeAudioLossyContainerQualityImpl + extends _SpotubeAudioLossyContainerQuality { + _$SpotubeAudioLossyContainerQualityImpl({required this.bitrate}) : super._(); + + factory _$SpotubeAudioLossyContainerQualityImpl.fromJson( + Map json) => + _$$SpotubeAudioLossyContainerQualityImplFromJson(json); + + @override + final int bitrate; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeAudioLossyContainerQualityImpl && + (identical(other.bitrate, bitrate) || other.bitrate == bitrate)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, bitrate); + + /// Create a copy of SpotubeAudioLossyContainerQuality + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeAudioLossyContainerQualityImplCopyWith< + _$SpotubeAudioLossyContainerQualityImpl> + get copyWith => __$$SpotubeAudioLossyContainerQualityImplCopyWithImpl< + _$SpotubeAudioLossyContainerQualityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeAudioLossyContainerQualityImplToJson( + this, + ); + } +} + +abstract class _SpotubeAudioLossyContainerQuality + extends SpotubeAudioLossyContainerQuality { + factory _SpotubeAudioLossyContainerQuality({required final int bitrate}) = + _$SpotubeAudioLossyContainerQualityImpl; + _SpotubeAudioLossyContainerQuality._() : super._(); + + factory _SpotubeAudioLossyContainerQuality.fromJson( + Map json) = + _$SpotubeAudioLossyContainerQualityImpl.fromJson; + + @override + int get bitrate; + + /// Create a copy of SpotubeAudioLossyContainerQuality + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeAudioLossyContainerQualityImplCopyWith< + _$SpotubeAudioLossyContainerQualityImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeAudioLosslessContainerQuality + _$SpotubeAudioLosslessContainerQualityFromJson(Map json) { + return _SpotubeAudioLosslessContainerQuality.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeAudioLosslessContainerQuality { + int get bitDepth => throw _privateConstructorUsedError; // bit + int get sampleRate => throw _privateConstructorUsedError; + + /// Serializes this SpotubeAudioLosslessContainerQuality to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeAudioLosslessContainerQuality + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeAudioLosslessContainerQualityCopyWith< + SpotubeAudioLosslessContainerQuality> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeAudioLosslessContainerQualityCopyWith<$Res> { + factory $SpotubeAudioLosslessContainerQualityCopyWith( + SpotubeAudioLosslessContainerQuality value, + $Res Function(SpotubeAudioLosslessContainerQuality) then) = + _$SpotubeAudioLosslessContainerQualityCopyWithImpl<$Res, + SpotubeAudioLosslessContainerQuality>; + @useResult + $Res call({int bitDepth, int sampleRate}); +} + +/// @nodoc +class _$SpotubeAudioLosslessContainerQualityCopyWithImpl<$Res, + $Val extends SpotubeAudioLosslessContainerQuality> + implements $SpotubeAudioLosslessContainerQualityCopyWith<$Res> { + _$SpotubeAudioLosslessContainerQualityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeAudioLosslessContainerQuality + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? bitDepth = null, + Object? sampleRate = null, + }) { + return _then(_value.copyWith( + bitDepth: null == bitDepth + ? _value.bitDepth + : bitDepth // ignore: cast_nullable_to_non_nullable + as int, + sampleRate: null == sampleRate + ? _value.sampleRate + : sampleRate // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeAudioLosslessContainerQualityImplCopyWith<$Res> + implements $SpotubeAudioLosslessContainerQualityCopyWith<$Res> { + factory _$$SpotubeAudioLosslessContainerQualityImplCopyWith( + _$SpotubeAudioLosslessContainerQualityImpl value, + $Res Function(_$SpotubeAudioLosslessContainerQualityImpl) then) = + __$$SpotubeAudioLosslessContainerQualityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int bitDepth, int sampleRate}); +} + +/// @nodoc +class __$$SpotubeAudioLosslessContainerQualityImplCopyWithImpl<$Res> + extends _$SpotubeAudioLosslessContainerQualityCopyWithImpl<$Res, + _$SpotubeAudioLosslessContainerQualityImpl> + implements _$$SpotubeAudioLosslessContainerQualityImplCopyWith<$Res> { + __$$SpotubeAudioLosslessContainerQualityImplCopyWithImpl( + _$SpotubeAudioLosslessContainerQualityImpl _value, + $Res Function(_$SpotubeAudioLosslessContainerQualityImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeAudioLosslessContainerQuality + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? bitDepth = null, + Object? sampleRate = null, + }) { + return _then(_$SpotubeAudioLosslessContainerQualityImpl( + bitDepth: null == bitDepth + ? _value.bitDepth + : bitDepth // ignore: cast_nullable_to_non_nullable + as int, + sampleRate: null == sampleRate + ? _value.sampleRate + : sampleRate // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeAudioLosslessContainerQualityImpl + extends _SpotubeAudioLosslessContainerQuality { + _$SpotubeAudioLosslessContainerQualityImpl( + {required this.bitDepth, required this.sampleRate}) + : super._(); + + factory _$SpotubeAudioLosslessContainerQualityImpl.fromJson( + Map json) => + _$$SpotubeAudioLosslessContainerQualityImplFromJson(json); + + @override + final int bitDepth; +// bit + @override + final int sampleRate; + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeAudioLosslessContainerQualityImpl && + (identical(other.bitDepth, bitDepth) || + other.bitDepth == bitDepth) && + (identical(other.sampleRate, sampleRate) || + other.sampleRate == sampleRate)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, bitDepth, sampleRate); + + /// Create a copy of SpotubeAudioLosslessContainerQuality + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeAudioLosslessContainerQualityImplCopyWith< + _$SpotubeAudioLosslessContainerQualityImpl> + get copyWith => __$$SpotubeAudioLosslessContainerQualityImplCopyWithImpl< + _$SpotubeAudioLosslessContainerQualityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeAudioLosslessContainerQualityImplToJson( + this, + ); + } +} + +abstract class _SpotubeAudioLosslessContainerQuality + extends SpotubeAudioLosslessContainerQuality { + factory _SpotubeAudioLosslessContainerQuality( + {required final int bitDepth, required final int sampleRate}) = + _$SpotubeAudioLosslessContainerQualityImpl; + _SpotubeAudioLosslessContainerQuality._() : super._(); + + factory _SpotubeAudioLosslessContainerQuality.fromJson( + Map json) = + _$SpotubeAudioLosslessContainerQualityImpl.fromJson; + + @override + int get bitDepth; // bit + @override + int get sampleRate; + + /// Create a copy of SpotubeAudioLosslessContainerQuality + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeAudioLosslessContainerQualityImplCopyWith< + _$SpotubeAudioLosslessContainerQualityImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeAudioSourceMatchObject _$SpotubeAudioSourceMatchObjectFromJson( + Map json) { + return _SpotubeAudioSourceMatchObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeAudioSourceMatchObject { + String get id => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + List get artists => throw _privateConstructorUsedError; + Duration get duration => throw _privateConstructorUsedError; + String? get thumbnail => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; + + /// Serializes this SpotubeAudioSourceMatchObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeAudioSourceMatchObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeAudioSourceMatchObjectCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeAudioSourceMatchObjectCopyWith<$Res> { + factory $SpotubeAudioSourceMatchObjectCopyWith( + SpotubeAudioSourceMatchObject value, + $Res Function(SpotubeAudioSourceMatchObject) then) = + _$SpotubeAudioSourceMatchObjectCopyWithImpl<$Res, + SpotubeAudioSourceMatchObject>; + @useResult + $Res call( + {String id, + String title, + List artists, + Duration duration, + String? thumbnail, + String externalUri}); +} + +/// @nodoc +class _$SpotubeAudioSourceMatchObjectCopyWithImpl<$Res, + $Val extends SpotubeAudioSourceMatchObject> + implements $SpotubeAudioSourceMatchObjectCopyWith<$Res> { + _$SpotubeAudioSourceMatchObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeAudioSourceMatchObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? artists = null, + Object? duration = null, + Object? thumbnail = freezed, + Object? externalUri = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as Duration, + thumbnail: freezed == thumbnail + ? _value.thumbnail + : thumbnail // ignore: cast_nullable_to_non_nullable + as String?, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeAudioSourceMatchObjectImplCopyWith<$Res> + implements $SpotubeAudioSourceMatchObjectCopyWith<$Res> { + factory _$$SpotubeAudioSourceMatchObjectImplCopyWith( + _$SpotubeAudioSourceMatchObjectImpl value, + $Res Function(_$SpotubeAudioSourceMatchObjectImpl) then) = + __$$SpotubeAudioSourceMatchObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String title, + List artists, + Duration duration, + String? thumbnail, + String externalUri}); +} + +/// @nodoc +class __$$SpotubeAudioSourceMatchObjectImplCopyWithImpl<$Res> + extends _$SpotubeAudioSourceMatchObjectCopyWithImpl<$Res, + _$SpotubeAudioSourceMatchObjectImpl> + implements _$$SpotubeAudioSourceMatchObjectImplCopyWith<$Res> { + __$$SpotubeAudioSourceMatchObjectImplCopyWithImpl( + _$SpotubeAudioSourceMatchObjectImpl _value, + $Res Function(_$SpotubeAudioSourceMatchObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeAudioSourceMatchObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? artists = null, + Object? duration = null, + Object? thumbnail = freezed, + Object? externalUri = null, + }) { + return _then(_$SpotubeAudioSourceMatchObjectImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value._artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as Duration, + thumbnail: freezed == thumbnail + ? _value.thumbnail + : thumbnail // ignore: cast_nullable_to_non_nullable + as String?, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeAudioSourceMatchObjectImpl + implements _SpotubeAudioSourceMatchObject { + _$SpotubeAudioSourceMatchObjectImpl( + {required this.id, + required this.title, + required final List artists, + required this.duration, + this.thumbnail, + required this.externalUri}) + : _artists = artists; + + factory _$SpotubeAudioSourceMatchObjectImpl.fromJson( + Map json) => + _$$SpotubeAudioSourceMatchObjectImplFromJson(json); + + @override + final String id; + @override + final String title; + final List _artists; + @override + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + + @override + final Duration duration; + @override + final String? thumbnail; + @override + final String externalUri; + + @override + String toString() { + return 'SpotubeAudioSourceMatchObject(id: $id, title: $title, artists: $artists, duration: $duration, thumbnail: $thumbnail, externalUri: $externalUri)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeAudioSourceMatchObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + const DeepCollectionEquality().equals(other._artists, _artists) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.thumbnail, thumbnail) || + other.thumbnail == thumbnail) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + title, + const DeepCollectionEquality().hash(_artists), + duration, + thumbnail, + externalUri); + + /// Create a copy of SpotubeAudioSourceMatchObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeAudioSourceMatchObjectImplCopyWith< + _$SpotubeAudioSourceMatchObjectImpl> + get copyWith => __$$SpotubeAudioSourceMatchObjectImplCopyWithImpl< + _$SpotubeAudioSourceMatchObjectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeAudioSourceMatchObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeAudioSourceMatchObject + implements SpotubeAudioSourceMatchObject { + factory _SpotubeAudioSourceMatchObject( + {required final String id, + required final String title, + required final List artists, + required final Duration duration, + final String? thumbnail, + required final String externalUri}) = _$SpotubeAudioSourceMatchObjectImpl; + + factory _SpotubeAudioSourceMatchObject.fromJson(Map json) = + _$SpotubeAudioSourceMatchObjectImpl.fromJson; + + @override + String get id; + @override + String get title; + @override + List get artists; + @override + Duration get duration; + @override + String? get thumbnail; + @override + String get externalUri; + + /// Create a copy of SpotubeAudioSourceMatchObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeAudioSourceMatchObjectImplCopyWith< + _$SpotubeAudioSourceMatchObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeAudioSourceStreamObject _$SpotubeAudioSourceStreamObjectFromJson( + Map json) { + return _SpotubeAudioSourceStreamObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeAudioSourceStreamObject { + String get url => throw _privateConstructorUsedError; + String get container => throw _privateConstructorUsedError; + SpotubeMediaCompressionType get type => throw _privateConstructorUsedError; + String? get codec => throw _privateConstructorUsedError; + double? get bitrate => throw _privateConstructorUsedError; + int? get bitDepth => throw _privateConstructorUsedError; + double? get sampleRate => throw _privateConstructorUsedError; + + /// Serializes this SpotubeAudioSourceStreamObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeAudioSourceStreamObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeAudioSourceStreamObjectCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeAudioSourceStreamObjectCopyWith<$Res> { + factory $SpotubeAudioSourceStreamObjectCopyWith( + SpotubeAudioSourceStreamObject value, + $Res Function(SpotubeAudioSourceStreamObject) then) = + _$SpotubeAudioSourceStreamObjectCopyWithImpl<$Res, + SpotubeAudioSourceStreamObject>; + @useResult + $Res call( + {String url, + String container, + SpotubeMediaCompressionType type, + String? codec, + double? bitrate, + int? bitDepth, + double? sampleRate}); +} + +/// @nodoc +class _$SpotubeAudioSourceStreamObjectCopyWithImpl<$Res, + $Val extends SpotubeAudioSourceStreamObject> + implements $SpotubeAudioSourceStreamObjectCopyWith<$Res> { + _$SpotubeAudioSourceStreamObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeAudioSourceStreamObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? container = null, + Object? type = null, + Object? codec = freezed, + Object? bitrate = freezed, + Object? bitDepth = freezed, + Object? sampleRate = freezed, + }) { + return _then(_value.copyWith( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + container: null == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as SpotubeMediaCompressionType, + codec: freezed == codec + ? _value.codec + : codec // ignore: cast_nullable_to_non_nullable + as String?, + bitrate: freezed == bitrate + ? _value.bitrate + : bitrate // ignore: cast_nullable_to_non_nullable + as double?, + bitDepth: freezed == bitDepth + ? _value.bitDepth + : bitDepth // ignore: cast_nullable_to_non_nullable + as int?, + sampleRate: freezed == sampleRate + ? _value.sampleRate + : sampleRate // ignore: cast_nullable_to_non_nullable + as double?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeAudioSourceStreamObjectImplCopyWith<$Res> + implements $SpotubeAudioSourceStreamObjectCopyWith<$Res> { + factory _$$SpotubeAudioSourceStreamObjectImplCopyWith( + _$SpotubeAudioSourceStreamObjectImpl value, + $Res Function(_$SpotubeAudioSourceStreamObjectImpl) then) = + __$$SpotubeAudioSourceStreamObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String url, + String container, + SpotubeMediaCompressionType type, + String? codec, + double? bitrate, + int? bitDepth, + double? sampleRate}); +} + +/// @nodoc +class __$$SpotubeAudioSourceStreamObjectImplCopyWithImpl<$Res> + extends _$SpotubeAudioSourceStreamObjectCopyWithImpl<$Res, + _$SpotubeAudioSourceStreamObjectImpl> + implements _$$SpotubeAudioSourceStreamObjectImplCopyWith<$Res> { + __$$SpotubeAudioSourceStreamObjectImplCopyWithImpl( + _$SpotubeAudioSourceStreamObjectImpl _value, + $Res Function(_$SpotubeAudioSourceStreamObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeAudioSourceStreamObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? container = null, + Object? type = null, + Object? codec = freezed, + Object? bitrate = freezed, + Object? bitDepth = freezed, + Object? sampleRate = freezed, + }) { + return _then(_$SpotubeAudioSourceStreamObjectImpl( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + container: null == container + ? _value.container + : container // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as SpotubeMediaCompressionType, + codec: freezed == codec + ? _value.codec + : codec // ignore: cast_nullable_to_non_nullable + as String?, + bitrate: freezed == bitrate + ? _value.bitrate + : bitrate // ignore: cast_nullable_to_non_nullable + as double?, + bitDepth: freezed == bitDepth + ? _value.bitDepth + : bitDepth // ignore: cast_nullable_to_non_nullable + as int?, + sampleRate: freezed == sampleRate + ? _value.sampleRate + : sampleRate // ignore: cast_nullable_to_non_nullable + as double?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeAudioSourceStreamObjectImpl + implements _SpotubeAudioSourceStreamObject { + _$SpotubeAudioSourceStreamObjectImpl( + {required this.url, + required this.container, + required this.type, + this.codec, + this.bitrate, + this.bitDepth, + this.sampleRate}); + + factory _$SpotubeAudioSourceStreamObjectImpl.fromJson( + Map json) => + _$$SpotubeAudioSourceStreamObjectImplFromJson(json); + + @override + final String url; + @override + final String container; + @override + final SpotubeMediaCompressionType type; + @override + final String? codec; + @override + final double? bitrate; + @override + final int? bitDepth; + @override + final double? sampleRate; + + @override + String toString() { + return 'SpotubeAudioSourceStreamObject(url: $url, container: $container, type: $type, codec: $codec, bitrate: $bitrate, bitDepth: $bitDepth, sampleRate: $sampleRate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeAudioSourceStreamObjectImpl && + (identical(other.url, url) || other.url == url) && + (identical(other.container, container) || + other.container == container) && + (identical(other.type, type) || other.type == type) && + (identical(other.codec, codec) || other.codec == codec) && + (identical(other.bitrate, bitrate) || other.bitrate == bitrate) && + (identical(other.bitDepth, bitDepth) || + other.bitDepth == bitDepth) && + (identical(other.sampleRate, sampleRate) || + other.sampleRate == sampleRate)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, url, container, type, codec, bitrate, bitDepth, sampleRate); + + /// Create a copy of SpotubeAudioSourceStreamObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeAudioSourceStreamObjectImplCopyWith< + _$SpotubeAudioSourceStreamObjectImpl> + get copyWith => __$$SpotubeAudioSourceStreamObjectImplCopyWithImpl< + _$SpotubeAudioSourceStreamObjectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeAudioSourceStreamObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeAudioSourceStreamObject + implements SpotubeAudioSourceStreamObject { + factory _SpotubeAudioSourceStreamObject( + {required final String url, + required final String container, + required final SpotubeMediaCompressionType type, + final String? codec, + final double? bitrate, + final int? bitDepth, + final double? sampleRate}) = _$SpotubeAudioSourceStreamObjectImpl; + + factory _SpotubeAudioSourceStreamObject.fromJson(Map json) = + _$SpotubeAudioSourceStreamObjectImpl.fromJson; + + @override + String get url; + @override + String get container; + @override + SpotubeMediaCompressionType get type; + @override + String? get codec; + @override + double? get bitrate; + @override + int? get bitDepth; + @override + double? get sampleRate; + + /// Create a copy of SpotubeAudioSourceStreamObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeAudioSourceStreamObjectImplCopyWith< + _$SpotubeAudioSourceStreamObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + SpotubeFullAlbumObject _$SpotubeFullAlbumObjectFromJson( Map json) { return _SpotubeFullAlbumObject.fromJson(json); @@ -33,8 +1527,12 @@ mixin _$SpotubeFullAlbumObject { String? get recordLabel => throw _privateConstructorUsedError; List? get genres => throw _privateConstructorUsedError; + /// Serializes this SpotubeFullAlbumObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeFullAlbumObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeFullAlbumObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -69,6 +1567,8 @@ 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({ @@ -160,6 +1660,8 @@ 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({ @@ -308,7 +1810,7 @@ class _$SpotubeFullAlbumObjectImpl implements _SpotubeFullAlbumObject { const DeepCollectionEquality().equals(other._genres, _genres)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -323,7 +1825,9 @@ class _$SpotubeFullAlbumObjectImpl implements _SpotubeFullAlbumObject { recordLabel, const DeepCollectionEquality().hash(_genres)); - @JsonKey(ignore: true) + /// Create a copy of SpotubeFullAlbumObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeFullAlbumObjectImplCopyWith<_$SpotubeFullAlbumObjectImpl> @@ -374,8 +1878,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeFullAlbumObjectImplCopyWith<_$SpotubeFullAlbumObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -396,8 +1903,12 @@ mixin _$SpotubeSimpleAlbumObject { SpotubeAlbumType get albumType => throw _privateConstructorUsedError; String? get releaseDate => throw _privateConstructorUsedError; + /// Serializes this SpotubeSimpleAlbumObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeSimpleAlbumObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeSimpleAlbumObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -429,6 +1940,8 @@ 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({ @@ -502,6 +2015,8 @@ 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({ @@ -613,7 +2128,7 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { other.releaseDate == releaseDate)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -625,7 +2140,9 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { albumType, releaseDate); - @JsonKey(ignore: true) + /// Create a copy of SpotubeSimpleAlbumObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeSimpleAlbumObjectImplCopyWith<_$SpotubeSimpleAlbumObjectImpl> @@ -667,8 +2184,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeSimpleAlbumObjectImplCopyWith<_$SpotubeSimpleAlbumObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -687,8 +2207,12 @@ mixin _$SpotubeFullArtistObject { List? get genres => throw _privateConstructorUsedError; int? get followers => throw _privateConstructorUsedError; + /// Serializes this SpotubeFullArtistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeFullArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeFullArtistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -719,6 +2243,8 @@ 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({ @@ -786,6 +2312,8 @@ 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({ @@ -889,7 +2417,7 @@ class _$SpotubeFullArtistObjectImpl implements _SpotubeFullArtistObject { other.followers == followers)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -900,7 +2428,9 @@ class _$SpotubeFullArtistObjectImpl implements _SpotubeFullArtistObject { const DeepCollectionEquality().hash(_genres), followers); - @JsonKey(ignore: true) + /// Create a copy of SpotubeFullArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeFullArtistObjectImplCopyWith<_$SpotubeFullArtistObjectImpl> @@ -939,8 +2469,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeFullArtistObjectImplCopyWith<_$SpotubeFullArtistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -957,8 +2490,12 @@ mixin _$SpotubeSimpleArtistObject { String get externalUri => throw _privateConstructorUsedError; List? get images => throw _privateConstructorUsedError; + /// Serializes this SpotubeSimpleArtistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeSimpleArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeSimpleArtistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -987,6 +2524,8 @@ 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({ @@ -1042,6 +2581,8 @@ 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({ @@ -1117,12 +2658,14 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject { const DeepCollectionEquality().equals(other._images, _images)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, name, externalUri, const DeepCollectionEquality().hash(_images)); - @JsonKey(ignore: true) + /// Create a copy of SpotubeSimpleArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeSimpleArtistObjectImplCopyWith<_$SpotubeSimpleArtistObjectImpl> @@ -1156,8 +2699,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeSimpleArtistObjectImplCopyWith<_$SpotubeSimpleArtistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1175,9 +2721,13 @@ 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; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeBrowseSectionObjectCopyWith> get copyWith => throw _privateConstructorUsedError; } @@ -1209,6 +2759,8 @@ 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({ @@ -1355,12 +2909,14 @@ class _$SpotubeBrowseSectionObjectImpl const DeepCollectionEquality().equals(other._items, _items)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, title, externalUri, browseMore, const DeepCollectionEquality().hash(_items)); - @JsonKey(ignore: true) + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeBrowseSectionObjectImplCopyWith> get copyWith => throw _privateConstructorUsedError; @@ -1486,8 +3045,13 @@ mixin _$MetadataFormFieldObject { required TResult orElse(), }) => throw _privateConstructorUsedError; + + /// Serializes this MetadataFormFieldObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $MetadataFormFieldObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -1512,6 +3076,8 @@ 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({ @@ -1555,6 +3121,8 @@ 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({ @@ -1655,12 +3223,14 @@ class _$MetadataFormFieldInputObjectImpl (identical(other.regex, regex) || other.regex == regex)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, objectType, id, variant, placeholder, defaultValue, required, regex); - @JsonKey(ignore: true) + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$MetadataFormFieldInputObjectImplCopyWith< @@ -1786,8 +3356,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$MetadataFormFieldInputObjectImplCopyWith< _$MetadataFormFieldInputObjectImpl> get copyWith => throw _privateConstructorUsedError; @@ -1815,6 +3388,8 @@ 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({ @@ -1864,11 +3439,13 @@ class _$MetadataFormFieldTextObjectImpl implements MetadataFormFieldTextObject { (identical(other.text, text) || other.text == text)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, objectType, text); - @JsonKey(ignore: true) + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$MetadataFormFieldTextObjectImplCopyWith<_$MetadataFormFieldTextObjectImpl> @@ -1980,8 +3557,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$MetadataFormFieldTextObjectImplCopyWith<_$MetadataFormFieldTextObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1996,8 +3576,12 @@ mixin _$SpotubeImageObject { int? get width => throw _privateConstructorUsedError; int? get height => throw _privateConstructorUsedError; + /// Serializes this SpotubeImageObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeImageObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeImageObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -2021,6 +3605,8 @@ 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({ @@ -2064,6 +3650,8 @@ 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({ @@ -2118,11 +3706,13 @@ class _$SpotubeImageObjectImpl implements _SpotubeImageObject { (identical(other.height, height) || other.height == height)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, url, width, height); - @JsonKey(ignore: true) + /// Create a copy of SpotubeImageObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeImageObjectImplCopyWith<_$SpotubeImageObjectImpl> get copyWith => @@ -2152,8 +3742,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeImageObjectImplCopyWith<_$SpotubeImageObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -2171,9 +3764,13 @@ 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; - @JsonKey(ignore: true) + + /// Create a copy of SpotubePaginationResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubePaginationResponseObjectCopyWith> get copyWith => throw _privateConstructorUsedError; @@ -2202,6 +3799,8 @@ 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({ @@ -2343,12 +3944,14 @@ class _$SpotubePaginationResponseObjectImpl const DeepCollectionEquality().equals(other._items, _items)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, limit, nextOffset, total, hasMore, const DeepCollectionEquality().hash(_items)); - @JsonKey(ignore: true) + /// Create a copy of SpotubePaginationResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubePaginationResponseObjectImplCopyWith> get copyWith => throw _privateConstructorUsedError; @@ -2410,8 +4016,12 @@ mixin _$SpotubeFullPlaylistObject { bool get collaborative => throw _privateConstructorUsedError; bool get public => throw _privateConstructorUsedError; + /// Serializes this SpotubeFullPlaylistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeFullPlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeFullPlaylistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -2447,6 +4057,8 @@ 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({ @@ -2500,6 +4112,8 @@ 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 { @@ -2543,6 +4157,8 @@ 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({ @@ -2676,7 +4292,7 @@ class _$SpotubeFullPlaylistObjectImpl implements _SpotubeFullPlaylistObject { (identical(other.public, public) || other.public == public)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -2690,7 +4306,9 @@ class _$SpotubeFullPlaylistObjectImpl implements _SpotubeFullPlaylistObject { collaborative, public); - @JsonKey(ignore: true) + /// Create a copy of SpotubeFullPlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeFullPlaylistObjectImplCopyWith<_$SpotubeFullPlaylistObjectImpl> @@ -2738,8 +4356,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeFullPlaylistObjectImplCopyWith<_$SpotubeFullPlaylistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -2758,8 +4379,12 @@ mixin _$SpotubeSimplePlaylistObject { SpotubeUserObject get owner => throw _privateConstructorUsedError; List get images => throw _privateConstructorUsedError; + /// Serializes this SpotubeSimplePlaylistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeSimplePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeSimplePlaylistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -2794,6 +4419,8 @@ 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({ @@ -2832,6 +4459,8 @@ 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 { @@ -2872,6 +4501,8 @@ 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({ @@ -2967,12 +4598,14 @@ class _$SpotubeSimplePlaylistObjectImpl const DeepCollectionEquality().equals(other._images, _images)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, name, description, externalUri, owner, const DeepCollectionEquality().hash(_images)); - @JsonKey(ignore: true) + /// Create a copy of SpotubeSimplePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeSimplePlaylistObjectImplCopyWith<_$SpotubeSimplePlaylistObjectImpl> @@ -3013,8 +4646,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeSimplePlaylistObjectImplCopyWith<_$SpotubeSimplePlaylistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -3034,8 +4670,12 @@ mixin _$SpotubeSearchResponseObject { throw _privateConstructorUsedError; List get tracks => throw _privateConstructorUsedError; + /// Serializes this SpotubeSearchResponseObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeSearchResponseObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -3066,6 +4706,8 @@ 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({ @@ -3121,6 +4763,8 @@ 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({ @@ -3217,7 +4861,7 @@ class _$SpotubeSearchResponseObjectImpl const DeepCollectionEquality().equals(other._tracks, _tracks)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -3226,7 +4870,9 @@ class _$SpotubeSearchResponseObjectImpl const DeepCollectionEquality().hash(_playlists), const DeepCollectionEquality().hash(_tracks)); - @JsonKey(ignore: true) + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeSearchResponseObjectImplCopyWith<_$SpotubeSearchResponseObjectImpl> @@ -3261,8 +4907,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeSearchResponseObjectImplCopyWith<_$SpotubeSearchResponseObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -3378,8 +5027,13 @@ mixin _$SpotubeTrackObject { required TResult orElse(), }) => throw _privateConstructorUsedError; + + /// Serializes this SpotubeTrackObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeTrackObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -3411,6 +5065,8 @@ 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({ @@ -3449,6 +5105,8 @@ 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 { @@ -3490,6 +5148,8 @@ 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({ @@ -3598,12 +5258,14 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { (identical(other.path, path) || other.path == path)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, name, externalUri, const DeepCollectionEquality().hash(_artists), album, durationMs, path); - @JsonKey(ignore: true) + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeLocalTrackObjectImplCopyWith<_$SpotubeLocalTrackObjectImpl> @@ -3757,8 +5419,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeLocalTrackObjectImplCopyWith<_$SpotubeLocalTrackObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -3795,6 +5460,8 @@ 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({ @@ -3913,7 +5580,7 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { other.explicit == explicit)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -3926,7 +5593,9 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { isrc, explicit); - @JsonKey(ignore: true) + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeFullTrackObjectImplCopyWith<_$SpotubeFullTrackObjectImpl> @@ -4085,8 +5754,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeFullTrackObjectImplCopyWith<_$SpotubeFullTrackObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -4102,8 +5774,12 @@ mixin _$SpotubeUserObject { List get images => throw _privateConstructorUsedError; String get externalUri => throw _privateConstructorUsedError; + /// Serializes this SpotubeUserObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SpotubeUserObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SpotubeUserObjectCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -4131,6 +5807,8 @@ 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({ @@ -4183,6 +5861,8 @@ 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({ @@ -4258,12 +5938,14 @@ class _$SpotubeUserObjectImpl implements _SpotubeUserObject { other.externalUri == externalUri)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, id, name, const DeepCollectionEquality().hash(_images), externalUri); - @JsonKey(ignore: true) + /// Create a copy of SpotubeUserObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith => @@ -4296,8 +5978,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -4308,7 +5993,6 @@ PluginConfiguration _$PluginConfigurationFromJson(Map json) { /// @nodoc mixin _$PluginConfiguration { - PluginType get type => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; String get description => throw _privateConstructorUsedError; String get version => throw _privateConstructorUsedError; @@ -4319,8 +6003,12 @@ mixin _$PluginConfiguration { List get abilities => throw _privateConstructorUsedError; String? get repository => throw _privateConstructorUsedError; + /// Serializes this PluginConfiguration to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of PluginConfiguration + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $PluginConfigurationCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -4332,8 +6020,7 @@ abstract class $PluginConfigurationCopyWith<$Res> { _$PluginConfigurationCopyWithImpl<$Res, PluginConfiguration>; @useResult $Res call( - {PluginType type, - String name, + {String name, String description, String version, String author, @@ -4354,10 +6041,11 @@ 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({ - Object? type = null, Object? name = null, Object? description = null, Object? version = null, @@ -4369,10 +6057,6 @@ class _$PluginConfigurationCopyWithImpl<$Res, $Val extends PluginConfiguration> Object? repository = freezed, }) { return _then(_value.copyWith( - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as PluginType, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -4422,8 +6106,7 @@ abstract class _$$PluginConfigurationImplCopyWith<$Res> @override @useResult $Res call( - {PluginType type, - String name, + {String name, String description, String version, String author, @@ -4442,10 +6125,11 @@ 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({ - Object? type = null, Object? name = null, Object? description = null, Object? version = null, @@ -4457,10 +6141,6 @@ class __$$PluginConfigurationImplCopyWithImpl<$Res> Object? repository = freezed, }) { return _then(_$PluginConfigurationImpl( - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as PluginType, name: null == name ? _value.name : name // ignore: cast_nullable_to_non_nullable @@ -4505,8 +6185,7 @@ class __$$PluginConfigurationImplCopyWithImpl<$Res> @JsonSerializable() class _$PluginConfigurationImpl extends _PluginConfiguration { _$PluginConfigurationImpl( - {required this.type, - required this.name, + {required this.name, required this.description, required this.version, required this.author, @@ -4522,8 +6201,6 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { factory _$PluginConfigurationImpl.fromJson(Map json) => _$$PluginConfigurationImplFromJson(json); - @override - final PluginType type; @override final String name; @override @@ -4559,7 +6236,7 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { @override String toString() { - return 'PluginConfiguration(type: $type, name: $name, description: $description, version: $version, author: $author, entryPoint: $entryPoint, pluginApiVersion: $pluginApiVersion, apis: $apis, abilities: $abilities, repository: $repository)'; + return 'PluginConfiguration(name: $name, description: $description, version: $version, author: $author, entryPoint: $entryPoint, pluginApiVersion: $pluginApiVersion, apis: $apis, abilities: $abilities, repository: $repository)'; } @override @@ -4567,7 +6244,6 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PluginConfigurationImpl && - (identical(other.type, type) || other.type == type) && (identical(other.name, name) || other.name == name) && (identical(other.description, description) || other.description == description) && @@ -4584,11 +6260,10 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { other.repository == repository)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, - type, name, description, version, @@ -4599,7 +6274,9 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { const DeepCollectionEquality().hash(_abilities), repository); - @JsonKey(ignore: true) + /// Create a copy of PluginConfiguration + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith => @@ -4616,8 +6293,7 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { abstract class _PluginConfiguration extends PluginConfiguration { factory _PluginConfiguration( - {required final PluginType type, - required final String name, + {required final String name, required final String description, required final String version, required final String author, @@ -4631,8 +6307,6 @@ abstract class _PluginConfiguration extends PluginConfiguration { factory _PluginConfiguration.fromJson(Map json) = _$PluginConfigurationImpl.fromJson; - @override - PluginType get type; @override String get name; @override @@ -4651,8 +6325,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith => throw _privateConstructorUsedError; } @@ -4668,8 +6345,12 @@ mixin _$PluginUpdateAvailable { String get version => throw _privateConstructorUsedError; String? get changelog => throw _privateConstructorUsedError; + /// Serializes this PluginUpdateAvailable to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of PluginUpdateAvailable + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $PluginUpdateAvailableCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -4694,6 +6375,8 @@ 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({ @@ -4739,6 +6422,8 @@ 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({ @@ -4796,11 +6481,13 @@ class _$PluginUpdateAvailableImpl implements _PluginUpdateAvailable { other.changelog == changelog)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, downloadUrl, version, changelog); - @JsonKey(ignore: true) + /// Create a copy of PluginUpdateAvailable + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$PluginUpdateAvailableImplCopyWith<_$PluginUpdateAvailableImpl> @@ -4830,8 +6517,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$PluginUpdateAvailableImplCopyWith<_$PluginUpdateAvailableImpl> get copyWith => throw _privateConstructorUsedError; } @@ -4847,9 +6537,14 @@ mixin _$MetadataPluginRepository { String get owner => throw _privateConstructorUsedError; String get description => throw _privateConstructorUsedError; String get repoUrl => throw _privateConstructorUsedError; + List get topics => throw _privateConstructorUsedError; + /// Serializes this MetadataPluginRepository to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $MetadataPluginRepositoryCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -4860,7 +6555,12 @@ abstract class $MetadataPluginRepositoryCopyWith<$Res> { $Res Function(MetadataPluginRepository) then) = _$MetadataPluginRepositoryCopyWithImpl<$Res, MetadataPluginRepository>; @useResult - $Res call({String name, String owner, String description, String repoUrl}); + $Res call( + {String name, + String owner, + String description, + String repoUrl, + List topics}); } /// @nodoc @@ -4874,6 +6574,8 @@ 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({ @@ -4881,6 +6583,7 @@ class _$MetadataPluginRepositoryCopyWithImpl<$Res, Object? owner = null, Object? description = null, Object? repoUrl = null, + Object? topics = null, }) { return _then(_value.copyWith( name: null == name @@ -4899,6 +6602,10 @@ class _$MetadataPluginRepositoryCopyWithImpl<$Res, ? _value.repoUrl : repoUrl // ignore: cast_nullable_to_non_nullable as String, + topics: null == topics + ? _value.topics + : topics // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -4912,7 +6619,12 @@ abstract class _$$MetadataPluginRepositoryImplCopyWith<$Res> __$$MetadataPluginRepositoryImplCopyWithImpl<$Res>; @override @useResult - $Res call({String name, String owner, String description, String repoUrl}); + $Res call( + {String name, + String owner, + String description, + String repoUrl, + List topics}); } /// @nodoc @@ -4925,6 +6637,8 @@ 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({ @@ -4932,6 +6646,7 @@ class __$$MetadataPluginRepositoryImplCopyWithImpl<$Res> Object? owner = null, Object? description = null, Object? repoUrl = null, + Object? topics = null, }) { return _then(_$MetadataPluginRepositoryImpl( name: null == name @@ -4950,6 +6665,10 @@ class __$$MetadataPluginRepositoryImplCopyWithImpl<$Res> ? _value.repoUrl : repoUrl // ignore: cast_nullable_to_non_nullable as String, + topics: null == topics + ? _value._topics + : topics // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -4961,7 +6680,9 @@ class _$MetadataPluginRepositoryImpl implements _MetadataPluginRepository { {required this.name, required this.owner, required this.description, - required this.repoUrl}); + required this.repoUrl, + required final List topics}) + : _topics = topics; factory _$MetadataPluginRepositoryImpl.fromJson(Map json) => _$$MetadataPluginRepositoryImplFromJson(json); @@ -4974,10 +6695,17 @@ class _$MetadataPluginRepositoryImpl implements _MetadataPluginRepository { final String description; @override final String repoUrl; + final List _topics; + @override + List get topics { + if (_topics is EqualUnmodifiableListView) return _topics; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_topics); + } @override String toString() { - return 'MetadataPluginRepository(name: $name, owner: $owner, description: $description, repoUrl: $repoUrl)'; + return 'MetadataPluginRepository(name: $name, owner: $owner, description: $description, repoUrl: $repoUrl, topics: $topics)'; } @override @@ -4989,15 +6717,18 @@ class _$MetadataPluginRepositoryImpl implements _MetadataPluginRepository { (identical(other.owner, owner) || other.owner == owner) && (identical(other.description, description) || other.description == description) && - (identical(other.repoUrl, repoUrl) || other.repoUrl == repoUrl)); + (identical(other.repoUrl, repoUrl) || other.repoUrl == repoUrl) && + const DeepCollectionEquality().equals(other._topics, _topics)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, name, owner, description, repoUrl); + int get hashCode => Object.hash(runtimeType, name, owner, description, + repoUrl, const DeepCollectionEquality().hash(_topics)); - @JsonKey(ignore: true) + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$MetadataPluginRepositoryImplCopyWith<_$MetadataPluginRepositoryImpl> @@ -5017,7 +6748,8 @@ abstract class _MetadataPluginRepository implements MetadataPluginRepository { {required final String name, required final String owner, required final String description, - required final String repoUrl}) = _$MetadataPluginRepositoryImpl; + required final String repoUrl, + required final List topics}) = _$MetadataPluginRepositoryImpl; factory _MetadataPluginRepository.fromJson(Map json) = _$MetadataPluginRepositoryImpl.fromJson; @@ -5031,7 +6763,12 @@ abstract class _MetadataPluginRepository implements MetadataPluginRepository { @override String get repoUrl; @override - @JsonKey(ignore: true) + List get topics; + + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$MetadataPluginRepositoryImplCopyWith<_$MetadataPluginRepositoryImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 6f416330..56783d80 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -6,6 +6,123 @@ part of 'metadata.dart'; // JsonSerializableGenerator // ************************************************************************** +_$SpotubeAudioSourceContainerPresetLossyImpl + _$$SpotubeAudioSourceContainerPresetLossyImplFromJson(Map json) => + _$SpotubeAudioSourceContainerPresetLossyImpl( + type: $enumDecode(_$SpotubeMediaCompressionTypeEnumMap, json['type']), + name: json['name'] as String, + qualities: (json['qualities'] as List) + .map((e) => SpotubeAudioLossyContainerQuality.fromJson( + Map.from(e as Map))) + .toList(), + ); + +Map _$$SpotubeAudioSourceContainerPresetLossyImplToJson( + _$SpotubeAudioSourceContainerPresetLossyImpl instance) => + { + 'type': _$SpotubeMediaCompressionTypeEnumMap[instance.type]!, + 'name': instance.name, + 'qualities': instance.qualities.map((e) => e.toJson()).toList(), + }; + +const _$SpotubeMediaCompressionTypeEnumMap = { + SpotubeMediaCompressionType.lossy: 'lossy', + SpotubeMediaCompressionType.lossless: 'lossless', +}; + +_$SpotubeAudioSourceContainerPresetLosslessImpl + _$$SpotubeAudioSourceContainerPresetLosslessImplFromJson(Map json) => + _$SpotubeAudioSourceContainerPresetLosslessImpl( + type: $enumDecode(_$SpotubeMediaCompressionTypeEnumMap, json['type']), + name: json['name'] as String, + qualities: (json['qualities'] as List) + .map((e) => SpotubeAudioLosslessContainerQuality.fromJson( + Map.from(e as Map))) + .toList(), + ); + +Map _$$SpotubeAudioSourceContainerPresetLosslessImplToJson( + _$SpotubeAudioSourceContainerPresetLosslessImpl instance) => + { + 'type': _$SpotubeMediaCompressionTypeEnumMap[instance.type]!, + 'name': instance.name, + 'qualities': instance.qualities.map((e) => e.toJson()).toList(), + }; + +_$SpotubeAudioLossyContainerQualityImpl + _$$SpotubeAudioLossyContainerQualityImplFromJson(Map json) => + _$SpotubeAudioLossyContainerQualityImpl( + bitrate: (json['bitrate'] as num).toInt(), + ); + +Map _$$SpotubeAudioLossyContainerQualityImplToJson( + _$SpotubeAudioLossyContainerQualityImpl instance) => + { + 'bitrate': instance.bitrate, + }; + +_$SpotubeAudioLosslessContainerQualityImpl + _$$SpotubeAudioLosslessContainerQualityImplFromJson(Map json) => + _$SpotubeAudioLosslessContainerQualityImpl( + bitDepth: (json['bitDepth'] as num).toInt(), + sampleRate: (json['sampleRate'] as num).toInt(), + ); + +Map _$$SpotubeAudioLosslessContainerQualityImplToJson( + _$SpotubeAudioLosslessContainerQualityImpl instance) => + { + 'bitDepth': instance.bitDepth, + 'sampleRate': instance.sampleRate, + }; + +_$SpotubeAudioSourceMatchObjectImpl + _$$SpotubeAudioSourceMatchObjectImplFromJson(Map json) => + _$SpotubeAudioSourceMatchObjectImpl( + id: json['id'] as String, + title: json['title'] as String, + artists: (json['artists'] as List) + .map((e) => e as String) + .toList(), + duration: Duration(microseconds: (json['duration'] as num).toInt()), + thumbnail: json['thumbnail'] as String?, + externalUri: json['externalUri'] as String, + ); + +Map _$$SpotubeAudioSourceMatchObjectImplToJson( + _$SpotubeAudioSourceMatchObjectImpl instance) => + { + 'id': instance.id, + 'title': instance.title, + 'artists': instance.artists, + 'duration': instance.duration.inMicroseconds, + 'thumbnail': instance.thumbnail, + 'externalUri': instance.externalUri, + }; + +_$SpotubeAudioSourceStreamObjectImpl + _$$SpotubeAudioSourceStreamObjectImplFromJson(Map json) => + _$SpotubeAudioSourceStreamObjectImpl( + url: json['url'] as String, + container: json['container'] as String, + type: $enumDecode(_$SpotubeMediaCompressionTypeEnumMap, json['type']), + codec: json['codec'] as String?, + bitrate: (json['bitrate'] as num?)?.toDouble(), + bitDepth: (json['bitDepth'] as num?)?.toInt(), + sampleRate: (json['sampleRate'] as num?)?.toDouble(), + ); + +Map _$$SpotubeAudioSourceStreamObjectImplToJson( + _$SpotubeAudioSourceStreamObjectImpl instance) => + { + 'url': instance.url, + 'container': instance.container, + 'type': _$SpotubeMediaCompressionTypeEnumMap[instance.type]!, + 'codec': instance.codec, + 'bitrate': instance.bitrate, + 'bitDepth': instance.bitDepth, + 'sampleRate': instance.sampleRate, + }; + _$SpotubeFullAlbumObjectImpl _$$SpotubeFullAlbumObjectImplFromJson(Map json) => _$SpotubeFullAlbumObjectImpl( id: json['id'] as String, @@ -419,7 +536,6 @@ Map _$$SpotubeUserObjectImplToJson( _$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) => _$PluginConfigurationImpl( - type: $enumDecode(_$PluginTypeEnumMap, json['type']), name: json['name'] as String, description: json['description'] as String, version: json['version'] as String, @@ -440,7 +556,6 @@ _$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) => Map _$$PluginConfigurationImplToJson( _$PluginConfigurationImpl instance) => { - 'type': _$PluginTypeEnumMap[instance.type]!, 'name': instance.name, 'description': instance.description, 'version': instance.version, @@ -453,10 +568,6 @@ Map _$$PluginConfigurationImplToJson( 'repository': instance.repository, }; -const _$PluginTypeEnumMap = { - PluginType.metadata: 'metadata', -}; - const _$PluginApisEnumMap = { PluginApis.webview: 'webview', PluginApis.localstorage: 'localstorage', @@ -466,6 +577,8 @@ const _$PluginApisEnumMap = { const _$PluginAbilitiesEnumMap = { PluginAbilities.authentication: 'authentication', PluginAbilities.scrobbling: 'scrobbling', + PluginAbilities.metadata: 'metadata', + PluginAbilities.audioSource: 'audio-source', }; _$PluginUpdateAvailableImpl _$$PluginUpdateAvailableImplFromJson(Map json) => @@ -490,6 +603,8 @@ _$MetadataPluginRepositoryImpl _$$MetadataPluginRepositoryImplFromJson( owner: json['owner'] as String, description: json['description'] as String, repoUrl: json['repoUrl'] as String, + topics: + (json['topics'] as List).map((e) => e as String).toList(), ); Map _$$MetadataPluginRepositoryImplToJson( @@ -499,4 +614,5 @@ Map _$$MetadataPluginRepositoryImplToJson( 'owner': instance.owner, 'description': instance.description, 'repoUrl': instance.repoUrl, + 'topics': instance.topics, }; diff --git a/lib/models/metadata/plugin.dart b/lib/models/metadata/plugin.dart index ac6bb0b9..6bc84160 100644 --- a/lib/models/metadata/plugin.dart +++ b/lib/models/metadata/plugin.dart @@ -1,17 +1,20 @@ part of 'metadata.dart'; -enum PluginType { metadata } - enum PluginApis { webview, localstorage, timezone } -enum PluginAbilities { authentication, scrobbling } +enum PluginAbilities { + authentication, + scrobbling, + metadata, + @JsonValue('audio-source') + audioSource, +} @freezed class PluginConfiguration with _$PluginConfiguration { const PluginConfiguration._(); factory PluginConfiguration({ - required PluginType type, required String name, required String description, required String version, diff --git a/lib/models/metadata/repository.dart b/lib/models/metadata/repository.dart index 06151dee..2a83f791 100644 --- a/lib/models/metadata/repository.dart +++ b/lib/models/metadata/repository.dart @@ -7,6 +7,7 @@ class MetadataPluginRepository with _$MetadataPluginRepository { required String owner, required String description, required String repoUrl, + required List topics, }) = _MetadataPluginRepository; factory MetadataPluginRepository.fromJson(Map json) => diff --git a/lib/models/playback/track_sources.dart b/lib/models/playback/track_sources.dart index c9d089a6..677b34b8 100644 --- a/lib/models/playback/track_sources.dart +++ b/lib/models/playback/track_sources.dart @@ -1,121 +1,15 @@ 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'; part 'track_sources.g.dart'; -@freezed -class TrackSourceQuery with _$TrackSourceQuery { - TrackSourceQuery._(); - - factory TrackSourceQuery({ - required String id, - required String title, - required List artists, - required String album, - required int durationMs, - required String isrc, - required bool explicit, - }) = _TrackSourceQuery; - - factory TrackSourceQuery.fromJson(Map json) => - _$TrackSourceQueryFromJson(json); - - factory TrackSourceQuery.fromTrack(SpotubeFullTrackObject track) { - return TrackSourceQuery( - id: track.id, - title: track.name, - artists: track.artists.map((e) => e.name).toList(), - album: track.album.name, - durationMs: track.durationMs, - isrc: track.isrc, - explicit: track.explicit, - ); - } - - /// 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, - title: uri.queryParameters['title'] ?? '', - artists: uri.queryParameters['artists']?.split(',') ?? [], - album: uri.queryParameters['album'] ?? '', - durationMs: int.tryParse(uri.queryParameters['durationMs'] ?? '0') ?? 0, - isrc: uri.queryParameters['isrc'] ?? '', - explicit: uri.queryParameters['explicit']?.toLowerCase() == 'true', - ); - } - - String queryString() { - return toJson() - .entries - .map((e) => - "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List ? e.value.join(",") : e.value.toString())}") - .join("&"); - } -} - -@freezed -class TrackSourceInfo with _$TrackSourceInfo { - factory TrackSourceInfo({ - required String id, - required String title, - required String artists, - required String thumbnail, - required String pageUrl, - required int durationMs, - }) = _TrackSourceInfo; - - factory TrackSourceInfo.fromJson(Map json) => - _$TrackSourceInfoFromJson(json); -} - -@freezed -class TrackSource with _$TrackSource { - factory TrackSource({ - required String url, - required SourceQualities quality, - required SourceCodecs codec, - required String bitrate, - }) = _TrackSource; - - factory TrackSource.fromJson(Map json) => - _$TrackSourceFromJson(json); -} - @JsonSerializable() class BasicSourcedTrack { - final TrackSourceQuery query; - final AudioSource source; - final TrackSourceInfo info; - final List sources; - final List siblings; + final SpotubeFullTrackObject query; + final SpotubeAudioSourceMatchObject info; + final String source; + final List sources; + final List siblings; BasicSourcedTrack({ required this.query, required this.source, diff --git a/lib/models/playback/track_sources.freezed.dart b/lib/models/playback/track_sources.freezed.dart deleted file mode 100644 index 760037d8..00000000 --- a/lib/models/playback/track_sources.freezed.dart +++ /dev/null @@ -1,739 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'track_sources.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -TrackSourceQuery _$TrackSourceQueryFromJson(Map json) { - return _TrackSourceQuery.fromJson(json); -} - -/// @nodoc -mixin _$TrackSourceQuery { - String get id => throw _privateConstructorUsedError; - String get title => throw _privateConstructorUsedError; - List get artists => throw _privateConstructorUsedError; - String get album => throw _privateConstructorUsedError; - int get durationMs => throw _privateConstructorUsedError; - String get isrc => throw _privateConstructorUsedError; - bool get explicit => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TrackSourceQueryCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TrackSourceQueryCopyWith<$Res> { - factory $TrackSourceQueryCopyWith( - TrackSourceQuery value, $Res Function(TrackSourceQuery) then) = - _$TrackSourceQueryCopyWithImpl<$Res, TrackSourceQuery>; - @useResult - $Res call( - {String id, - String title, - List artists, - String album, - int durationMs, - String isrc, - bool explicit}); -} - -/// @nodoc -class _$TrackSourceQueryCopyWithImpl<$Res, $Val extends TrackSourceQuery> - implements $TrackSourceQueryCopyWith<$Res> { - _$TrackSourceQueryCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? artists = null, - Object? album = null, - Object? durationMs = null, - Object? isrc = null, - Object? explicit = null, - }) { - return _then(_value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String, - artists: null == artists - ? _value.artists - : artists // ignore: cast_nullable_to_non_nullable - as List, - album: null == album - ? _value.album - : album // ignore: cast_nullable_to_non_nullable - as String, - durationMs: null == durationMs - ? _value.durationMs - : durationMs // ignore: cast_nullable_to_non_nullable - as int, - isrc: null == isrc - ? _value.isrc - : isrc // ignore: cast_nullable_to_non_nullable - as String, - explicit: null == explicit - ? _value.explicit - : explicit // ignore: cast_nullable_to_non_nullable - as bool, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TrackSourceQueryImplCopyWith<$Res> - implements $TrackSourceQueryCopyWith<$Res> { - factory _$$TrackSourceQueryImplCopyWith(_$TrackSourceQueryImpl value, - $Res Function(_$TrackSourceQueryImpl) then) = - __$$TrackSourceQueryImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - String title, - List artists, - String album, - int durationMs, - String isrc, - bool explicit}); -} - -/// @nodoc -class __$$TrackSourceQueryImplCopyWithImpl<$Res> - extends _$TrackSourceQueryCopyWithImpl<$Res, _$TrackSourceQueryImpl> - implements _$$TrackSourceQueryImplCopyWith<$Res> { - __$$TrackSourceQueryImplCopyWithImpl(_$TrackSourceQueryImpl _value, - $Res Function(_$TrackSourceQueryImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? artists = null, - Object? album = null, - Object? durationMs = null, - Object? isrc = null, - Object? explicit = null, - }) { - return _then(_$TrackSourceQueryImpl( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String, - artists: null == artists - ? _value._artists - : artists // ignore: cast_nullable_to_non_nullable - as List, - album: null == album - ? _value.album - : album // ignore: cast_nullable_to_non_nullable - as String, - durationMs: null == durationMs - ? _value.durationMs - : durationMs // ignore: cast_nullable_to_non_nullable - as int, - isrc: null == isrc - ? _value.isrc - : isrc // ignore: cast_nullable_to_non_nullable - as String, - explicit: null == explicit - ? _value.explicit - : explicit // ignore: cast_nullable_to_non_nullable - as bool, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TrackSourceQueryImpl extends _TrackSourceQuery { - _$TrackSourceQueryImpl( - {required this.id, - required this.title, - required final List artists, - required this.album, - required this.durationMs, - required this.isrc, - required this.explicit}) - : _artists = artists, - super._(); - - factory _$TrackSourceQueryImpl.fromJson(Map json) => - _$$TrackSourceQueryImplFromJson(json); - - @override - final String id; - @override - final String title; - final List _artists; - @override - List get artists { - if (_artists is EqualUnmodifiableListView) return _artists; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_artists); - } - - @override - final String album; - @override - final int durationMs; - @override - final String isrc; - @override - final bool explicit; - - @override - String toString() { - return 'TrackSourceQuery(id: $id, title: $title, artists: $artists, album: $album, durationMs: $durationMs, isrc: $isrc, explicit: $explicit)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TrackSourceQueryImpl && - (identical(other.id, id) || other.id == id) && - (identical(other.title, title) || other.title == title) && - const DeepCollectionEquality().equals(other._artists, _artists) && - (identical(other.album, album) || other.album == album) && - (identical(other.durationMs, durationMs) || - other.durationMs == durationMs) && - (identical(other.isrc, isrc) || other.isrc == isrc) && - (identical(other.explicit, explicit) || - other.explicit == explicit)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, - id, - title, - const DeepCollectionEquality().hash(_artists), - album, - durationMs, - isrc, - explicit); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => - __$$TrackSourceQueryImplCopyWithImpl<_$TrackSourceQueryImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$TrackSourceQueryImplToJson( - this, - ); - } -} - -abstract class _TrackSourceQuery extends TrackSourceQuery { - factory _TrackSourceQuery( - {required final String id, - required final String title, - required final List artists, - required final String album, - required final int durationMs, - required final String isrc, - required final bool explicit}) = _$TrackSourceQueryImpl; - _TrackSourceQuery._() : super._(); - - factory _TrackSourceQuery.fromJson(Map json) = - _$TrackSourceQueryImpl.fromJson; - - @override - String get id; - @override - String get title; - @override - List get artists; - @override - String get album; - @override - int get durationMs; - @override - String get isrc; - @override - bool get explicit; - @override - @JsonKey(ignore: true) - _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => - throw _privateConstructorUsedError; -} - -TrackSourceInfo _$TrackSourceInfoFromJson(Map json) { - return _TrackSourceInfo.fromJson(json); -} - -/// @nodoc -mixin _$TrackSourceInfo { - String get id => throw _privateConstructorUsedError; - String get title => throw _privateConstructorUsedError; - String get artists => throw _privateConstructorUsedError; - String get thumbnail => throw _privateConstructorUsedError; - String get pageUrl => throw _privateConstructorUsedError; - int get durationMs => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TrackSourceInfoCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TrackSourceInfoCopyWith<$Res> { - factory $TrackSourceInfoCopyWith( - TrackSourceInfo value, $Res Function(TrackSourceInfo) then) = - _$TrackSourceInfoCopyWithImpl<$Res, TrackSourceInfo>; - @useResult - $Res call( - {String id, - String title, - String artists, - String thumbnail, - String pageUrl, - int durationMs}); -} - -/// @nodoc -class _$TrackSourceInfoCopyWithImpl<$Res, $Val extends TrackSourceInfo> - implements $TrackSourceInfoCopyWith<$Res> { - _$TrackSourceInfoCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? artists = null, - Object? thumbnail = null, - Object? pageUrl = null, - Object? durationMs = null, - }) { - return _then(_value.copyWith( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String, - artists: null == artists - ? _value.artists - : artists // ignore: cast_nullable_to_non_nullable - as String, - thumbnail: null == thumbnail - ? _value.thumbnail - : thumbnail // ignore: cast_nullable_to_non_nullable - as String, - pageUrl: null == pageUrl - ? _value.pageUrl - : pageUrl // ignore: cast_nullable_to_non_nullable - as String, - durationMs: null == durationMs - ? _value.durationMs - : durationMs // ignore: cast_nullable_to_non_nullable - as int, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TrackSourceInfoImplCopyWith<$Res> - implements $TrackSourceInfoCopyWith<$Res> { - factory _$$TrackSourceInfoImplCopyWith(_$TrackSourceInfoImpl value, - $Res Function(_$TrackSourceInfoImpl) then) = - __$$TrackSourceInfoImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - String title, - String artists, - String thumbnail, - String pageUrl, - int durationMs}); -} - -/// @nodoc -class __$$TrackSourceInfoImplCopyWithImpl<$Res> - extends _$TrackSourceInfoCopyWithImpl<$Res, _$TrackSourceInfoImpl> - implements _$$TrackSourceInfoImplCopyWith<$Res> { - __$$TrackSourceInfoImplCopyWithImpl( - _$TrackSourceInfoImpl _value, $Res Function(_$TrackSourceInfoImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? artists = null, - Object? thumbnail = null, - Object? pageUrl = null, - Object? durationMs = null, - }) { - return _then(_$TrackSourceInfoImpl( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String, - artists: null == artists - ? _value.artists - : artists // ignore: cast_nullable_to_non_nullable - as String, - thumbnail: null == thumbnail - ? _value.thumbnail - : thumbnail // ignore: cast_nullable_to_non_nullable - as String, - pageUrl: null == pageUrl - ? _value.pageUrl - : pageUrl // ignore: cast_nullable_to_non_nullable - as String, - durationMs: null == durationMs - ? _value.durationMs - : durationMs // ignore: cast_nullable_to_non_nullable - as int, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TrackSourceInfoImpl implements _TrackSourceInfo { - _$TrackSourceInfoImpl( - {required this.id, - required this.title, - required this.artists, - required this.thumbnail, - required this.pageUrl, - required this.durationMs}); - - factory _$TrackSourceInfoImpl.fromJson(Map json) => - _$$TrackSourceInfoImplFromJson(json); - - @override - final String id; - @override - final String title; - @override - final String artists; - @override - final String thumbnail; - @override - final String pageUrl; - @override - final int durationMs; - - @override - String toString() { - return 'TrackSourceInfo(id: $id, title: $title, artists: $artists, thumbnail: $thumbnail, pageUrl: $pageUrl, durationMs: $durationMs)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TrackSourceInfoImpl && - (identical(other.id, id) || other.id == id) && - (identical(other.title, title) || other.title == title) && - (identical(other.artists, artists) || other.artists == artists) && - (identical(other.thumbnail, thumbnail) || - other.thumbnail == thumbnail) && - (identical(other.pageUrl, pageUrl) || other.pageUrl == pageUrl) && - (identical(other.durationMs, durationMs) || - other.durationMs == durationMs)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash( - runtimeType, id, title, artists, thumbnail, pageUrl, durationMs); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => - __$$TrackSourceInfoImplCopyWithImpl<_$TrackSourceInfoImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$TrackSourceInfoImplToJson( - this, - ); - } -} - -abstract class _TrackSourceInfo implements TrackSourceInfo { - factory _TrackSourceInfo( - {required final String id, - required final String title, - required final String artists, - required final String thumbnail, - required final String pageUrl, - required final int durationMs}) = _$TrackSourceInfoImpl; - - factory _TrackSourceInfo.fromJson(Map json) = - _$TrackSourceInfoImpl.fromJson; - - @override - String get id; - @override - String get title; - @override - String get artists; - @override - String get thumbnail; - @override - String get pageUrl; - @override - int get durationMs; - @override - @JsonKey(ignore: true) - _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => - throw _privateConstructorUsedError; -} - -TrackSource _$TrackSourceFromJson(Map json) { - return _TrackSource.fromJson(json); -} - -/// @nodoc -mixin _$TrackSource { - String get url => throw _privateConstructorUsedError; - SourceQualities get quality => throw _privateConstructorUsedError; - SourceCodecs get codec => throw _privateConstructorUsedError; - String get bitrate => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $TrackSourceCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $TrackSourceCopyWith<$Res> { - factory $TrackSourceCopyWith( - TrackSource value, $Res Function(TrackSource) then) = - _$TrackSourceCopyWithImpl<$Res, TrackSource>; - @useResult - $Res call( - {String url, - SourceQualities quality, - SourceCodecs codec, - String bitrate}); -} - -/// @nodoc -class _$TrackSourceCopyWithImpl<$Res, $Val extends TrackSource> - implements $TrackSourceCopyWith<$Res> { - _$TrackSourceCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? url = null, - Object? quality = null, - Object? codec = null, - Object? bitrate = null, - }) { - return _then(_value.copyWith( - url: null == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String, - quality: null == quality - ? _value.quality - : quality // ignore: cast_nullable_to_non_nullable - as SourceQualities, - codec: null == codec - ? _value.codec - : codec // ignore: cast_nullable_to_non_nullable - as SourceCodecs, - bitrate: null == bitrate - ? _value.bitrate - : bitrate // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$TrackSourceImplCopyWith<$Res> - implements $TrackSourceCopyWith<$Res> { - factory _$$TrackSourceImplCopyWith( - _$TrackSourceImpl value, $Res Function(_$TrackSourceImpl) then) = - __$$TrackSourceImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String url, - SourceQualities quality, - SourceCodecs codec, - String bitrate}); -} - -/// @nodoc -class __$$TrackSourceImplCopyWithImpl<$Res> - extends _$TrackSourceCopyWithImpl<$Res, _$TrackSourceImpl> - implements _$$TrackSourceImplCopyWith<$Res> { - __$$TrackSourceImplCopyWithImpl( - _$TrackSourceImpl _value, $Res Function(_$TrackSourceImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? url = null, - Object? quality = null, - Object? codec = null, - Object? bitrate = null, - }) { - return _then(_$TrackSourceImpl( - url: null == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String, - quality: null == quality - ? _value.quality - : quality // ignore: cast_nullable_to_non_nullable - as SourceQualities, - codec: null == codec - ? _value.codec - : codec // ignore: cast_nullable_to_non_nullable - as SourceCodecs, - bitrate: null == bitrate - ? _value.bitrate - : bitrate // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$TrackSourceImpl implements _TrackSource { - _$TrackSourceImpl( - {required this.url, - required this.quality, - required this.codec, - required this.bitrate}); - - factory _$TrackSourceImpl.fromJson(Map json) => - _$$TrackSourceImplFromJson(json); - - @override - final String url; - @override - final SourceQualities quality; - @override - final SourceCodecs codec; - @override - final String bitrate; - - @override - String toString() { - return 'TrackSource(url: $url, quality: $quality, codec: $codec, bitrate: $bitrate)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$TrackSourceImpl && - (identical(other.url, url) || other.url == url) && - (identical(other.quality, quality) || other.quality == quality) && - (identical(other.codec, codec) || other.codec == codec) && - (identical(other.bitrate, bitrate) || other.bitrate == bitrate)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, url, quality, codec, bitrate); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith => - __$$TrackSourceImplCopyWithImpl<_$TrackSourceImpl>(this, _$identity); - - @override - Map toJson() { - return _$$TrackSourceImplToJson( - this, - ); - } -} - -abstract class _TrackSource implements TrackSource { - factory _TrackSource( - {required final String url, - required final SourceQualities quality, - required final SourceCodecs codec, - required final String bitrate}) = _$TrackSourceImpl; - - factory _TrackSource.fromJson(Map json) = - _$TrackSourceImpl.fromJson; - - @override - String get url; - @override - SourceQualities get quality; - @override - SourceCodecs get codec; - @override - String get bitrate; - @override - @JsonKey(ignore: true) - _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/models/playback/track_sources.g.dart b/lib/models/playback/track_sources.g.dart index 4676490d..3088493a 100644 --- a/lib/models/playback/track_sources.g.dart +++ b/lib/models/playback/track_sources.g.dart @@ -7,17 +7,18 @@ part of 'track_sources.dart'; // ************************************************************************** BasicSourcedTrack _$BasicSourcedTrackFromJson(Map json) => BasicSourcedTrack( - query: TrackSourceQuery.fromJson( + query: SpotubeFullTrackObject.fromJson( Map.from(json['query'] as Map)), - source: $enumDecode(_$AudioSourceEnumMap, json['source']), - info: TrackSourceInfo.fromJson( + source: json['source'] as String, + info: SpotubeAudioSourceMatchObject.fromJson( Map.from(json['info'] as Map)), sources: (json['sources'] as List) - .map((e) => TrackSource.fromJson(Map.from(e as Map))) + .map((e) => SpotubeAudioSourceStreamObject.fromJson( + Map.from(e as Map))) .toList(), siblings: (json['siblings'] as List?) - ?.map((e) => - TrackSourceInfo.fromJson(Map.from(e as Map))) + ?.map((e) => SpotubeAudioSourceMatchObject.fromJson( + Map.from(e as Map))) .toList() ?? const [], ); @@ -25,86 +26,8 @@ BasicSourcedTrack _$BasicSourcedTrackFromJson(Map json) => BasicSourcedTrack( Map _$BasicSourcedTrackToJson(BasicSourcedTrack instance) => { 'query': instance.query.toJson(), - 'source': _$AudioSourceEnumMap[instance.source]!, 'info': instance.info.toJson(), + 'source': instance.source, 'sources': instance.sources.map((e) => e.toJson()).toList(), 'siblings': instance.siblings.map((e) => e.toJson()).toList(), }; - -const _$AudioSourceEnumMap = { - AudioSource.youtube: 'youtube', - AudioSource.piped: 'piped', - AudioSource.jiosaavn: 'jiosaavn', - AudioSource.invidious: 'invidious', -}; - -_$TrackSourceQueryImpl _$$TrackSourceQueryImplFromJson(Map json) => - _$TrackSourceQueryImpl( - id: json['id'] as String, - title: json['title'] as String, - artists: - (json['artists'] as List).map((e) => e as String).toList(), - album: json['album'] as String, - durationMs: (json['durationMs'] as num).toInt(), - isrc: json['isrc'] as String, - explicit: json['explicit'] as bool, - ); - -Map _$$TrackSourceQueryImplToJson( - _$TrackSourceQueryImpl instance) => - { - 'id': instance.id, - 'title': instance.title, - 'artists': instance.artists, - 'album': instance.album, - 'durationMs': instance.durationMs, - 'isrc': instance.isrc, - 'explicit': instance.explicit, - }; - -_$TrackSourceInfoImpl _$$TrackSourceInfoImplFromJson(Map json) => - _$TrackSourceInfoImpl( - id: json['id'] as String, - title: json['title'] as String, - artists: json['artists'] as String, - thumbnail: json['thumbnail'] as String, - pageUrl: json['pageUrl'] as String, - durationMs: (json['durationMs'] as num).toInt(), - ); - -Map _$$TrackSourceInfoImplToJson( - _$TrackSourceInfoImpl instance) => - { - 'id': instance.id, - 'title': instance.title, - 'artists': instance.artists, - 'thumbnail': instance.thumbnail, - 'pageUrl': instance.pageUrl, - 'durationMs': instance.durationMs, - }; - -_$TrackSourceImpl _$$TrackSourceImplFromJson(Map json) => _$TrackSourceImpl( - url: json['url'] as String, - quality: $enumDecode(_$SourceQualitiesEnumMap, json['quality']), - codec: $enumDecode(_$SourceCodecsEnumMap, json['codec']), - bitrate: json['bitrate'] as String, - ); - -Map _$$TrackSourceImplToJson(_$TrackSourceImpl instance) => - { - 'url': instance.url, - 'quality': _$SourceQualitiesEnumMap[instance.quality]!, - 'codec': _$SourceCodecsEnumMap[instance.codec]!, - 'bitrate': instance.bitrate, - }; - -const _$SourceQualitiesEnumMap = { - SourceQualities.high: 'high', - SourceQualities.medium: 'medium', - SourceQualities.low: 'low', -}; - -const _$SourceCodecsEnumMap = { - SourceCodecs.m4a: 'm4a', - SourceCodecs.weba: 'weba', -}; diff --git a/lib/modules/home/sections/new_releases.dart b/lib/modules/home/sections/new_releases.dart index 2a1f2f91..be6d335d 100644 --- a/lib/modules/home/sections/new_releases.dart +++ b/lib/modules/home/sections/new_releases.dart @@ -28,7 +28,7 @@ class HomeNewReleasesSection extends HookConsumerWidget { if (newReleases.error case MetadataPluginException( - errorCode: MetadataPluginErrorCode.noDefaultPlugin, + errorCode: MetadataPluginErrorCode.noDefaultMetadataPlugin, message: _, )) { return const SizedBox.shrink(); diff --git a/lib/modules/home/sections/sections.dart b/lib/modules/home/sections/sections.dart index b04e7a9e..93055b74 100644 --- a/lib/modules/home/sections/sections.dart +++ b/lib/modules/home/sections/sections.dart @@ -48,7 +48,7 @@ class HomePageBrowseSection extends HookConsumerWidget { if (browseSections.error case MetadataPluginException( - errorCode: MetadataPluginErrorCode.noDefaultPlugin, + errorCode: MetadataPluginErrorCode.noDefaultMetadataPlugin, message: _, )) { return const SliverFillRemaining( diff --git a/lib/modules/library/local_folder/cache_export_dialog.dart b/lib/modules/library/local_folder/cache_export_dialog.dart index 0f10defc..4c86a8d5 100644 --- a/lib/modules/library/local_folder/cache_export_dialog.dart +++ b/lib/modules/library/local_folder/cache_export_dialog.dart @@ -6,9 +6,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path/path.dart' as path; import 'package:spotube/extensions/context.dart'; import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -final codecs = SourceCodecs.values.map((s) => s.name); +const containers = ["m4a", "mp3", "mp4", "ogg", "wav", "flac"]; class LocalFolderCacheExportDialog extends HookConsumerWidget { final Directory exportDir; @@ -30,7 +29,8 @@ class LocalFolderCacheExportDialog extends HookConsumerWidget { final stream = cacheDir.list().where( (event) => event is File && - codecs.contains(path.extension(event.path).replaceAll(".", "")), + containers + .contains(path.extension(event.path).replaceAll(".", "")), ); stream.listen( diff --git a/lib/modules/library/user_downloads/download_item.dart b/lib/modules/library/user_downloads/download_item.dart index 2dcfc28f..b1cd9f62 100644 --- a/lib/modules/library/user_downloads/download_item.dart +++ b/lib/modules/library/user_downloads/download_item.dart @@ -7,44 +7,19 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/download_manager_provider.dart'; -import 'package:spotube/services/download_manager/download_status.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; class DownloadItem extends HookConsumerWidget { - final SpotubeFullTrackObject track; + final DownloadTask task; const DownloadItem({ super.key, - required this.track, + required this.task, }); @override Widget build(BuildContext context, ref) { - final downloadManager = ref.watch(downloadManagerProvider); - - final taskStatus = useState(null); - - useEffect(() { - if (track is! SourcedTrack) return null; - final notifier = downloadManager.getStatusNotifier(track); - - taskStatus.value = notifier?.value; - - void listener() { - taskStatus.value = notifier?.value; - } - - notifier?.addListener(listener); - - return () { - notifier?.removeListener(listener); - }; - }, [track]); - - final isQueryingSourceInfo = - taskStatus.value == null || track is! SourcedTrack; + final downloadManager = ref.watch(downloadManagerProvider.notifier); return ButtonTile( style: ButtonVariance.ghost, @@ -55,90 +30,72 @@ class DownloadItem extends HookConsumerWidget { child: UniversalImage( height: 40, width: 40, - path: track.album.images.asUrlString( + path: task.track.album.images.asUrlString( placeholder: ImagePlaceholder.albumArt, ), ), ), ), - title: Text(track.name), + title: Text(task.track.name), subtitle: ArtistLink( - artists: track.artists, + artists: task.track.artists, mainAxisAlignment: WrapAlignment.start, onOverflowArtistClick: () { - context.navigateTo(TrackRoute(trackId: track.id)); + context.navigateTo(TrackRoute(trackId: task.track.id)); }, ), - trailing: isQueryingSourceInfo - ? Text(context.l10n.querying_info).small() - : switch (taskStatus.value!) { - DownloadStatus.downloading => HookBuilder(builder: (context) { - final taskProgress = useListenable(useMemoized( - () => downloadManager.getProgressNotifier(track), - [track], - )); + trailing: switch (task.status) { + DownloadStatus.downloading => HookBuilder(builder: (context) { + return StreamBuilder( + stream: task.downloadedBytesStream, + builder: (context, asyncSnapshot) { + final progress = + task.totalSizeBytes == null || task.totalSizeBytes == 0 + ? 0 + : (asyncSnapshot.data ?? 0) / task.totalSizeBytes!; + return Row( children: [ CircularProgressIndicator( - value: taskProgress?.value ?? 0, + value: progress.toDouble(), ), const SizedBox(width: 10), - IconButton.ghost( - icon: const Icon(SpotubeIcons.pause), - onPressed: () { - downloadManager.pause(track); - }), const SizedBox(width: 10), IconButton.ghost( icon: const Icon(SpotubeIcons.close), onPressed: () { - downloadManager.cancel(track); + downloadManager.cancel(task.track); }), ], ); - }), - DownloadStatus.paused => Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton.ghost( - icon: const Icon(SpotubeIcons.play), - onPressed: () { - downloadManager.resume(track); - }), - const SizedBox(width: 10), - IconButton.ghost( - icon: const Icon(SpotubeIcons.close), - onPressed: () { - downloadManager.cancel(track); - }) - ], + }); + }), + DownloadStatus.failed || DownloadStatus.canceled => SizedBox( + width: 100, + child: Row( + children: [ + Icon( + SpotubeIcons.error, + color: Colors.red[400], ), - DownloadStatus.failed || DownloadStatus.canceled => SizedBox( - width: 100, - child: Row( - children: [ - Icon( - SpotubeIcons.error, - color: Colors.red[400], - ), - const SizedBox(width: 10), - IconButton.ghost( - icon: const Icon(SpotubeIcons.refresh), - onPressed: () { - downloadManager.retry(track); - }, - ), - ], - ), - ), - DownloadStatus.completed => - Icon(SpotubeIcons.done, color: Colors.green[400]), - DownloadStatus.queued => IconButton.ghost( - icon: const Icon(SpotubeIcons.close), + const SizedBox(width: 10), + IconButton.ghost( + icon: const Icon(SpotubeIcons.refresh), onPressed: () { - downloadManager.removeFromQueue(track); - }), - }, + downloadManager.retry(task.track); + }, + ), + ], + ), + ), + DownloadStatus.completed => + Icon(SpotubeIcons.done, color: Colors.green[400]), + DownloadStatus.queued => IconButton.ghost( + icon: const Icon(SpotubeIcons.close), + onPressed: () { + downloadManager.cancel(task.track); + }), + }, ); } } diff --git a/lib/modules/metadata_plugins/installed_plugin.dart b/lib/modules/metadata_plugins/installed_plugin.dart index 4d050afc..7abda5ec 100644 --- a/lib/modules/metadata_plugins/installed_plugin.dart +++ b/lib/modules/metadata_plugins/installed_plugin.dart @@ -3,6 +3,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/constrains.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'; @@ -12,29 +13,60 @@ import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/updater/update_checker.dart'; import 'package:url_launcher/url_launcher.dart'; +final validAbilities = { + PluginAbilities.metadata: ("Metadata", SpotubeIcons.album), + PluginAbilities.audioSource: ("Audio Source", SpotubeIcons.music), +}; + class MetadataInstalledPluginItem extends HookConsumerWidget { final PluginConfiguration plugin; - final bool isDefault; + final bool isDefaultMetadata; + final bool isDefaultAudioSource; const MetadataInstalledPluginItem({ super.key, required this.plugin, - required this.isDefault, + required this.isDefaultMetadata, + required this.isDefaultAudioSource, }); @override Widget build(BuildContext context, ref) { + final mediaQuery = MediaQuery.sizeOf(context); + final metadataPlugin = ref.watch(metadataPluginProvider); - final isAuthenticatedSnapshot = - ref.watch(metadataPluginAuthenticatedProvider); + final audioSourcePlugin = ref.watch(audioSourcePluginProvider); + final pluginSnapshot = switch ((isDefaultMetadata, isDefaultAudioSource)) { + (true, _) => metadataPlugin, + (false, true) => audioSourcePlugin, + _ => null, + }; + final pluginsNotifier = ref.watch(metadataPluginsProvider.notifier); - final requiresAuth = - isDefault && plugin.abilities.contains(PluginAbilities.authentication); - final supportsScrobbling = - isDefault && plugin.abilities.contains(PluginAbilities.scrobbling); - final isAuthenticated = isAuthenticatedSnapshot.asData?.value == true; - final updateAvailable = - isDefault ? ref.watch(metadataPluginUpdateCheckerProvider) : null; - final hasUpdate = isDefault && updateAvailable?.asData?.value != null; + + final requiresAuth = (isDefaultMetadata || isDefaultAudioSource) && + plugin.abilities.contains(PluginAbilities.authentication); + final supportsScrobbling = isDefaultMetadata && + plugin.abilities.contains(PluginAbilities.scrobbling); + + final isMetadataAuthenticatedSnapshot = + ref.watch(metadataPluginAuthenticatedProvider); + final isAudioSourceAuthenticatedSnapshot = + ref.watch(audioSourcePluginAuthenticatedProvider); + final isAuthenticated = (isDefaultMetadata && + isMetadataAuthenticatedSnapshot.asData?.value == true) || + (isDefaultAudioSource && + isAudioSourceAuthenticatedSnapshot.asData?.value == true); + + final metadataUpdateAvailable = + ref.watch(metadataPluginUpdateCheckerProvider); + final audioSourceUpdateAvailable = + ref.watch(audioSourcePluginUpdateCheckerProvider); + final updateAvailable = switch ((isDefaultMetadata, isDefaultAudioSource)) { + (true, _) => metadataUpdateAvailable, + (false, true) => audioSourceUpdateAvailable, + _ => null, + }; + final hasUpdate = updateAvailable?.asData?.value != null; return Card( child: Column( @@ -79,6 +111,18 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { spacing: 8, children: [ Text(plugin.description), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + for (final ability in plugin.abilities) + if (validAbilities.keys.contains(ability)) + SecondaryBadge( + leading: Icon(validAbilities[ability]!.$2), + child: Text(validAbilities[ability]!.$1), + ), + ], + ), if (repoUrl != null) Wrap( spacing: 8, @@ -91,10 +135,27 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { ) else ...[ Text(context.l10n.author_name(plugin.author)), - DestructiveBadge( - leading: const Icon(SpotubeIcons.warning), - child: Text(context.l10n.third_party), - ) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 4, + children: [ + const Icon(SpotubeIcons.warning, size: 14), + Text( + context.l10n.third_party, + style: const TextStyle(color: Colors.white), + ).xSmall + ], + ), + ), ], SecondaryBadge( leading: const Icon(SpotubeIcons.connect), @@ -183,111 +244,158 @@ class MetadataInstalledPluginItem extends HookConsumerWidget { ], ), ), - Row( + Wrap( spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.spaceBetween, children: [ - Button.secondary( - enabled: !isDefault, - onPressed: () async { - await pluginsNotifier.setDefaultPlugin(plugin); - }, - child: Text( - isDefault - ? context.l10n.default_plugin - : context.l10n.set_default, - ), - ), - if (isDefault) - Consumer(builder: (context, ref, _) { - final supportTextSnapshot = - ref.watch(metadataPluginSupportTextProvider); - - if (supportTextSnapshot.hasValue && - supportTextSnapshot.value == null) { - return const SizedBox.shrink(); - } - - final bgColor = context.theme.brightness == Brightness.dark - ? const Color.fromARGB(255, 255, 145, 175) - : Colors.pink[600]; - final textColor = context.theme.brightness == Brightness.dark - ? Colors.pink[700] - : Colors.pink[50]; - - final mediaQuery = MediaQuery.sizeOf(context); - - return Button( - style: ButtonVariance.secondary.copyWith( - decoration: (context, states, value) { - return value.copyWithIfBoxDecoration( - color: bgColor, - ); - }, - textStyle: (context, states, value) { - return value.copyWith( - color: textColor, - ); - }, - iconTheme: (context, states, value) { - return value.copyWith( - color: textColor, - ); + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + if (plugin.abilities.contains(PluginAbilities.metadata)) + Button.secondary( + enabled: !isDefaultMetadata, + onPressed: () async { + await pluginsNotifier.setDefaultMetadataPlugin(plugin); }, + child: Text( + isDefaultMetadata + ? context.l10n.default_metadata_source + : context.l10n.set_default_metadata_source, + ), ), - leading: const Icon(SpotubeIcons.heartFilled), - child: Text(context.l10n.support), - onPressed: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: - Text(context.l10n.support_plugin_development), - content: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: mediaQuery.height * 0.8, - maxWidth: 720, - ), - child: SizedBox( - width: double.infinity, - child: SingleChildScrollView( - child: AppMarkdown( - data: supportTextSnapshot.value ?? "", + if (plugin.abilities.contains(PluginAbilities.audioSource)) + Button.secondary( + enabled: !isDefaultAudioSource, + onPressed: () async { + await pluginsNotifier + .setDefaultAudioSourcePlugin(plugin); + }, + child: Text( + isDefaultAudioSource + ? context.l10n.default_audio_source + : context.l10n.set_default_audio_source, + ), + ), + ], + ), + Row( + mainAxisSize: + mediaQuery.smAndUp ? MainAxisSize.min : MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + spacing: 8, + children: [ + if (isDefaultMetadata || isDefaultAudioSource) + Consumer(builder: (context, ref, _) { + final metadataSupportTextSnapshot = + ref.watch(metadataPluginSupportTextProvider); + final audioSourceSupportTextSnapshot = + ref.watch(audioSourcePluginSupportTextProvider); + + final supportTextSnapshot = + switch ((isDefaultMetadata, isDefaultAudioSource)) { + (true, _) => metadataSupportTextSnapshot, + (false, true) => audioSourceSupportTextSnapshot, + _ => null, + }; + + if ((supportTextSnapshot?.hasValue ?? false) && + supportTextSnapshot?.value == null) { + return const SizedBox.shrink(); + } + + final bgColor = + context.theme.brightness == Brightness.dark + ? const Color.fromARGB(255, 255, 145, 175) + : Colors.pink[600]; + final textColor = + context.theme.brightness == Brightness.dark + ? Colors.pink[700] + : Colors.pink[50]; + + final mediaQuery = MediaQuery.sizeOf(context); + + return Button( + style: ButtonVariance.secondary.copyWith( + decoration: (context, states, value) { + return value.copyWithIfBoxDecoration( + color: bgColor, + ); + }, + textStyle: (context, states, value) { + return value.copyWith( + color: textColor, + ); + }, + iconTheme: (context, states, value) { + return value.copyWith( + color: textColor, + ); + }, + ), + leading: const Icon(SpotubeIcons.heartFilled), + child: Text(context.l10n.support), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text( + context.l10n.support_plugin_development), + content: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: mediaQuery.height * 0.8, + maxWidth: 720, + ), + child: SizedBox( + width: double.infinity, + child: SingleChildScrollView( + child: AppMarkdown( + data: supportTextSnapshot + ?.asData?.value ?? + "", + ), + ), ), ), - ), - ), - actions: [ - Button.secondary( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(context.l10n.close), - ), - ], + actions: [ + Button.secondary( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(context.l10n.close), + ), + ], + ); + }, ); }, ); - }, - ); - }), - const Spacer(), - if (isDefault && requiresAuth && !isAuthenticated) - Button.primary( - onPressed: () async { - await metadataPlugin.asData?.value?.auth.authenticate(); - }, - leading: const Icon(SpotubeIcons.login), - child: Text(context.l10n.login), - ) - else if (isDefault && requiresAuth && isAuthenticated) - Button.destructive( - onPressed: () async { - await metadataPlugin.asData?.value?.auth.logout(); - }, - leading: const Icon(SpotubeIcons.logout), - child: Text(context.l10n.logout), - ) + }), + if ((isDefaultMetadata || isDefaultAudioSource) && + requiresAuth && + !isAuthenticated) + Button.primary( + onPressed: () async { + await pluginSnapshot?.asData?.value?.auth + .authenticate(); + }, + leading: const Icon(SpotubeIcons.login), + child: Text(context.l10n.login), + ) + else if ((isDefaultMetadata || isDefaultAudioSource) && + requiresAuth && + isAuthenticated) + Button.destructive( + onPressed: () async { + await pluginSnapshot?.asData?.value?.auth.logout(); + }, + leading: const Icon(SpotubeIcons.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 f140b9ee..9bd71f0a 100644 --- a/lib/modules/metadata_plugins/plugin_repository.dart +++ b/lib/modules/metadata_plugins/plugin_repository.dart @@ -1,3 +1,4 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; @@ -8,6 +9,12 @@ 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'; +import 'package:change_case/change_case.dart'; + +final validTopics = { + "spotube-metadata-plugin": ("Metadata", SpotubeIcons.album), + "spotube-audio-source-plugin": ("Audio Source", SpotubeIcons.music), +}; class MetadataPluginRepositoryItem extends HookConsumerWidget { final MetadataPluginRepository pluginRepo; @@ -26,144 +33,204 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget { final isInstalling = useState(false); return Card( - child: Basic( - title: Text( - "${pluginRepo.owner == "KRTirtho" ? "" : "${pluginRepo.owner}/"}${pluginRepo.name}"), - subtitle: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 8, - children: [ - Text(pluginRepo.description), - Row( - spacing: 8, - children: [ - if (pluginRepo.owner == "KRTirtho") ...[ - PrimaryBadge( - leading: Icon(SpotubeIcons.done), - child: Text(context.l10n.official), - ), - SecondaryBadge( - leading: host == "github.com" - ? const Icon(SpotubeIcons.github) - : null, - child: Text(host), - onPressed: () { - launchUrlString(pluginRepo.repoUrl); - }, - ), - ] else ...[ - Text(context.l10n.author_name(pluginRepo.owner)), - DestructiveBadge( - leading: const Icon(SpotubeIcons.warning), - child: Text(context.l10n.third_party), - ) - ] - ], + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 8, + children: [ + Basic( + title: Text( + pluginRepo.name.startsWith("spotube-plugin") + ? pluginRepo.name + .replaceFirst("spotube-plugin-", "") + .trim() + .toCapitalCase() + : pluginRepo.name.toCapitalCase(), ), - ], - ), - trailing: Button.primary( - enabled: !isInstalling.value, - onPressed: () async { - try { - isInstalling.value = true; - final pluginConfig = await pluginsNotifier - .downloadAndCachePlugin(pluginRepo.repoUrl); + subtitle: Text(pluginRepo.description), + trailing: Button.primary( + enabled: !isInstalling.value, + onPressed: () async { + try { + isInstalling.value = true; + final pluginConfig = await pluginsNotifier + .downloadAndCachePlugin(pluginRepo.repoUrl); - if (!context.mounted) return; - final isOfficialPlugin = pluginRepo.owner == "KRTirtho"; + if (!context.mounted) return; + final isOfficialPlugin = pluginRepo.owner == "KRTirtho"; - final isAllowed = isOfficialPlugin - ? true - : await showDialog( - context: context, - builder: (context) { - final pluginAbilities = pluginConfig.apis - .map( - (e) => context.l10n.can_access_name_api(e.name)) - .join("\n\n"); + final isAllowed = isOfficialPlugin + ? true + : await showDialog( + context: context, + builder: (context) { + final pluginAbilities = pluginConfig.apis + .map((e) => + context.l10n.can_access_name_api(e.name)) + .join("\n\n"); - return AlertDialog( - title: Text( - context.l10n.do_you_want_to_install_this_plugin), - content: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(context.l10n.third_party_plugin_warning), - const Gap(8), - FutureBuilder( - future: - pluginsNotifier.getLogoPath(pluginConfig), - builder: (context, snapshot) { - return Basic( - leading: snapshot.hasData - ? Image.file( - snapshot.data!, - width: 36, - height: 36, - ) - : Container( - height: 36, - width: 36, - alignment: Alignment.center, - decoration: BoxDecoration( - color: context - .theme.colorScheme.secondary, - borderRadius: - BorderRadius.circular(8), - ), - child: - const Icon(SpotubeIcons.plugin), - ), - title: Text(pluginConfig.name), - subtitle: Text(pluginConfig.description), - ); - }, + return AlertDialog( + title: Text( + context.l10n.do_you_want_to_install_this_plugin, ), - const Gap(8), - AppMarkdown( - 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", + content: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(context.l10n.third_party_plugin_warning), + const Gap(8), + FutureBuilder( + future: pluginsNotifier + .getLogoPath(pluginConfig), + builder: (context, snapshot) { + return Basic( + leading: snapshot.hasData + ? Image.file( + snapshot.data!, + width: 36, + height: 36, + ) + : Container( + height: 36, + width: 36, + alignment: Alignment.center, + decoration: BoxDecoration( + color: context.theme + .colorScheme.secondary, + borderRadius: + BorderRadius.circular(8), + ), + child: const Icon( + SpotubeIcons.plugin), + ), + title: Text(pluginConfig.name), + subtitle: + Text(pluginConfig.description), + ); + }, + ), + const Gap(8), + AppMarkdown( + 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", + ), + ], ), - ], - ), - actions: [ - Button.secondary( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text(context.l10n.decline), - ), - Button.primary( - onPressed: () { - Navigator.of(context).pop(true); - }, - child: Text(context.l10n.accept), - ), - ], + actions: [ + Button.secondary( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(context.l10n.decline), + ), + Button.primary( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text(context.l10n.accept), + ), + ], + ); + }, ); - }, - ); - if (isAllowed != true) return; - await pluginsNotifier.addPlugin(pluginConfig); - } finally { - if (context.mounted) { - isInstalling.value = false; - } - } - }, - leading: isInstalling.value - ? const CircularProgressIndicator() - : const Icon(SpotubeIcons.add), - child: Text(context.l10n.install), - ), + if (isAllowed != true) return; + await pluginsNotifier.addPlugin(pluginConfig); + } finally { + if (context.mounted) { + isInstalling.value = false; + } + } + }, + leading: isInstalling.value + ? SizedBox.square( + dimension: 20, + child: CircularProgressIndicator( + color: context.theme.colorScheme.primaryForeground, + ), + ) + : const Icon(SpotubeIcons.add), + child: Text(context.l10n.install), + ), + ), + if (pluginRepo.owner != "KRTirtho") + Text.rich( + TextSpan( + children: [ + TextSpan(text: context.l10n.source), + TextSpan( + text: pluginRepo.repoUrl.replaceAll("https://", ""), + style: const TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + launchUrlString(pluginRepo.repoUrl); + }, + ), + ], + ), + style: context.theme.typography.xSmall, + ), + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + if (pluginRepo.owner == "KRTirtho") + PrimaryBadge( + leading: const Icon(SpotubeIcons.done), + child: Text(context.l10n.official), + ) + else ...[ + Text( + context.l10n.author_name(pluginRepo.owner), + style: context.theme.typography.xSmall, + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + spacing: 4, + children: [ + const Icon(SpotubeIcons.warning, size: 14), + Text( + context.l10n.third_party, + style: const TextStyle(color: Colors.white), + ).xSmall + ], + ), + ), + ], + for (final topic in pluginRepo.topics) + if (validTopics.keys.contains(topic)) + SecondaryBadge( + leading: Icon(validTopics[topic]!.$2), + child: Text(validTopics[topic]!.$1), + ), + SecondaryBadge( + leading: host == "github.com" + ? const Icon(SpotubeIcons.github) + : null, + child: Text(host), + onPressed: () { + launchUrlString(pluginRepo.repoUrl); + }, + ), + ], + ), + ], ), ); } diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index ec903aab..5ea690e0 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.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/audio_source/quality_label.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:url_launcher/url_launcher_string.dart'; class PlayerView extends HookConsumerWidget { final PanelController panelController; @@ -45,6 +43,7 @@ class PlayerView extends HookConsumerWidget { final currentActiveTrackSource = sourcedCurrentTrack.asData?.value?.source; final isLocalTrack = currentActiveTrack is SpotubeLocalTrackObject; final mediaQuery = MediaQuery.sizeOf(context); + final qualityLabel = ref.watch(audioSourceQualityLabelProvider); final shouldHide = useState(true); @@ -109,22 +108,6 @@ class PlayerView extends HookConsumerWidget { ) ], trailing: [ - if (currentActiveTrackSource is YoutubeSourcedTrack) - TextButton( - size: const ButtonSize(1.2), - leading: Assets.images.logos.songlinkTransparent.image( - width: 20, - height: 20, - color: theme.colorScheme.foreground, - ), - onPressed: () { - final url = - "https://song.link/s/${currentActiveTrack?.id}"; - - launchUrlString(url); - }, - child: Text(context.l10n.song_link), - ), if (!isLocalTrack) Tooltip( tooltip: TooltipContainer( @@ -267,6 +250,20 @@ class PlayerView extends HookConsumerWidget { ); }), ), + const Gap(25), + OutlineBadge( + style: const ButtonStyle.outline( + size: ButtonSize.normal, + density: ButtonDensity.dense, + shape: ButtonShape.rectangle, + ).copyWith( + textStyle: (context, states, value) { + return value.copyWith(fontWeight: FontWeight.w500); + }, + ), + leading: const Icon(SpotubeIcons.lightningOutlined), + child: Text(qualityLabel), + ) ], ), ), diff --git a/lib/modules/player/player_actions.dart b/lib/modules/player/player_actions.dart index df1e2a2d..9f8639ec 100644 --- a/lib/modules/player/player_actions.dart +++ b/lib/modules/player/player_actions.dart @@ -43,8 +43,12 @@ class PlayerActions extends HookConsumerWidget { final downloader = ref.watch(downloadManagerProvider.notifier); final isInQueue = useMemoized(() { if (playlist.activeTrack is! SpotubeFullTrackObject) return false; - return downloader - .isActive(playlist.activeTrack! as SpotubeFullTrackObject); + final downloadTask = + downloader.getTaskByTrackId(playlist.activeTrack!.id); + return const [ + DownloadStatus.queued, + DownloadStatus.downloading, + ].contains(downloadTask?.status); }, [ playlist.activeTrack, downloader, diff --git a/lib/modules/player/player_queue.dart b/lib/modules/player/player_queue.dart index c9d5626f..bfb7a2e3 100644 --- a/lib/modules/player/player_queue.dart +++ b/lib/modules/player/player_queue.dart @@ -9,13 +9,16 @@ import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; +import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart'; import 'package:spotube/components/fallbacks/not_found.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; +import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart'; import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/modules/player/player_queue_actions.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; @@ -55,6 +58,9 @@ class PlayerQueue extends HookConsumerWidget { final controller = useAutoScrollController(); final searchText = useState(''); + final selectionMode = useState(false); + final selectedTrackIds = useState({}); + final isSearching = useState(false); final tracks = playlist.tracks; @@ -131,6 +137,91 @@ class PlayerQueue extends HookConsumerWidget { surfaceOpacity: 0, child: searchBar, ) + else if (selectionMode.value) + AppBar( + backgroundColor: Colors.transparent, + surfaceBlur: 0, + surfaceOpacity: 0, + leading: [ + IconButton.ghost( + icon: const Icon(SpotubeIcons.close), + onPressed: () { + selectedTrackIds.value = {}; + selectionMode.value = false; + }, + ) + ], + title: SizedBox( + height: 30, + child: AutoSizeText( + '${selectedTrackIds.value.length} selected', + maxLines: 1, + ), + ), + trailing: [ + PlayerQueueActionButton( + builder: (context, close) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Gap(12), + ButtonTile( + style: const ButtonStyle.ghost(), + leading: + const Icon(SpotubeIcons.selectionCheck), + title: Text(context.l10n.select_all), + onPressed: () { + selectedTrackIds.value = + filteredTracks.map((t) => t.id).toSet(); + Navigator.pop(context); + }, + ), + ButtonTile( + style: const ButtonStyle.ghost(), + leading: const Icon(SpotubeIcons.playlistAdd), + title: Text(context.l10n.add_to_playlist), + onPressed: () async { + final selected = filteredTracks + .where((t) => + selectedTrackIds.value.contains(t.id)) + .toList(); + close(); + if (selected.isEmpty) return; + final res = await showDialog( + context: context, + builder: (context) => + PlaylistAddTrackDialog( + tracks: selected, + openFromPlaylist: null, + ), + ); + if (res == true) { + selectedTrackIds.value = {}; + selectionMode.value = false; + } + }, + ), + ButtonTile( + style: const ButtonStyle.ghost(), + leading: const Icon(SpotubeIcons.trash), + title: Text(context.l10n.remove_from_queue), + onPressed: () async { + final ids = selectedTrackIds.value.toList(); + close(); + if (ids.isEmpty) return; + await Future.wait( + ids.map((id) => onRemove(id))); + if (context.mounted) { + selectedTrackIds.value = {}; + selectionMode.value = false; + } + }, + ), + const Gap(12), + ], + ), + ), + ], + ) else AppBar( trailingGap: 0, @@ -195,6 +286,20 @@ class PlayerQueue extends HookConsumerWidget { }, itemBuilder: (context, i) { final track = filteredTracks.elementAt(i); + + void toggleSelection(String id) { + final s = {...selectedTrackIds.value}; + if (s.contains(id)) { + s.remove(id); + } else { + s.add(id); + } + selectedTrackIds.value = s; + if (selectedTrackIds.value.isEmpty) { + selectionMode.value = false; + } + } + return AutoScrollTag( key: ValueKey(i), controller: controller, @@ -203,15 +308,34 @@ class PlayerQueue extends HookConsumerWidget { playlist: playlist, index: i, track: track, + selectionMode: selectionMode.value, + selected: + selectedTrackIds.value.contains(track.id), + onChanged: selectionMode.value + ? (_) => toggleSelection(track.id) + : null, onTap: () async { + if (selectionMode.value) { + toggleSelection(track.id); + return; + } if (playlist.activeTrack?.id == track.id) { return; } await onJump(track); }, + onLongPress: () { + if (!selectionMode.value) { + selectionMode.value = true; + selectedTrackIds.value = {track.id}; + } else { + toggleSelection(track.id); + } + }, leadingActions: [ if (!isSearching.value && - searchText.value.isEmpty) + searchText.value.isEmpty && + !selectionMode.value) Padding( padding: const EdgeInsets.only(left: 8.0), diff --git a/lib/modules/player/player_queue_actions.dart b/lib/modules/player/player_queue_actions.dart new file mode 100644 index 00000000..3d1666c2 --- /dev/null +++ b/lib/modules/player/player_queue_actions.dart @@ -0,0 +1,44 @@ +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/constrains.dart'; + +class PlayerQueueActionButton extends StatelessWidget { + final Widget Function(BuildContext context, VoidCallback close) builder; + + const PlayerQueueActionButton({ + super.key, + required this.builder, + }); + + @override + Widget build(BuildContext context) { + return IconButton.ghost( + onPressed: () { + final mediaQuery = MediaQuery.sizeOf(context); + + if (mediaQuery.lgAndUp) { + showDropdown( + context: context, + builder: (context) { + return SizedBox( + width: 220 * context.theme.scaling, + child: Card( + padding: EdgeInsets.zero, + child: builder(context, () => closeOverlay(context)), + ), + ); + }, + ); + } else { + openSheet( + context: context, + builder: (context) => builder(context, () => closeSheet(context)), + position: OverlayPosition.bottom, + ); + } + }, + icon: const Icon(SpotubeIcons.moreHorizontal), + ); + } +} diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index a6c3ae32..b9bd7631 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -1,60 +1,16 @@ -import 'package:collection/collection.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/spotube_icons.dart'; -import 'package:spotube/components/button/back_button.dart'; +import 'package:spotube/components/fallbacks/not_found.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; -import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; -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'; -import 'package:spotube/provider/youtube_engine/youtube_engine.dart'; -import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sources/jiosaavn.dart'; -import 'package:spotube/services/sourced_track/sources/youtube.dart'; -import 'package:spotube/utils/service_utils.dart'; - -final sourceInfoToIconMap = { - AudioSource.youtube: - const Icon(SpotubeIcons.youtube, color: Color(0xFFFF0000)), - AudioSource.jiosaavn: Container( - height: 30, - width: 30, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(90), - image: DecorationImage( - image: Assets.images.logos.jiosaavn.provider(), - fit: BoxFit.cover, - ), - ), - ), - AudioSource.piped: const Icon(SpotubeIcons.piped), - AudioSource.invidious: Container( - height: 18, - width: 18, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(90), - image: DecorationImage( - image: Assets.images.logos.invidious.provider(), - fit: BoxFit.cover, - ), - ), - ), -}; +import 'package:spotube/provider/server/sourced_track_provider.dart'; class SiblingTracksSheet extends HookConsumerWidget { final bool floating; @@ -65,305 +21,135 @@ class SiblingTracksSheet extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final theme = Theme.of(context); - final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); - 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); - final activeTrackNotifier = activeTrackSources.asData?.value?.notifier; - final activeTrack = activeTrackSources.asData?.value?.track; - final activeTrackSource = activeTrackSources.asData?.value?.source; - - final title = ServiceUtils.getTitle( - activeTrack?.name ?? "", - artists: activeTrack?.artists.map((e) => e.name).toList() ?? [], - onlyCleanArtist: true, - ).trim(); - - final defaultSearchTerm = - "$title - ${activeTrack?.artists.asString() ?? ""}"; - final searchController = useShadcnTextEditingController( - text: defaultSearchTerm, - ); - - final searchTerm = useDebounce( - useValueListenable(searchController).text, - ); - final controller = useScrollController(); - final searchRequest = useMemoized(() async { - if (searchTerm.trim().isEmpty || activeTrackSource == null) { - return []; - } - if (preferences.audioSource == AudioSource.jiosaavn) { - final resultsJioSaavn = - await jiosaavnClient.search.songs(searchTerm.trim()); - final results = await Future.wait( - resultsJioSaavn.results.mapIndexed((i, song) async { - final siblingType = JioSaavnSourcedTrack.toSiblingType(song); - return siblingType.info; - })); + final activeTrack = + ref.watch(audioPlayerProvider.select((e) => e.activeTrack)); - final activeSourceInfo = activeTrackSource.info; + if (activeTrack == null || activeTrack is! SpotubeFullTrackObject) { + return const SafeArea(child: NotFound()); + } - return results - ..removeWhere((element) => element.id == activeSourceInfo.id) - ..insert( - 0, - activeSourceInfo, - ); - } else { - final resultsYt = await youtubeEngine.searchVideos(searchTerm.trim()); + return HookBuilder(builder: (context) { + final sourcedTrack = ref.watch(sourcedTrackProvider(activeTrack)); + final sourcedTrackNotifier = + ref.watch(sourcedTrackProvider(activeTrack).notifier); - final searchResults = await Future.wait( - resultsYt - .map(YoutubeVideoInfo.fromVideo) - .mapIndexed((i, video) async { - if (!context.mounted) return null; - final siblingType = - await YoutubeSourcedTrack.toSiblingType(i, video, ref); - return siblingType.info; - }) - .whereType>() - .toList(), - ); - final activeSourceInfo = activeTrackSource.info; - return searchResults - ..removeWhere((element) => element.id == activeSourceInfo.id) - ..insert(0, activeSourceInfo); - } - }, [ - searchTerm, - searchMode.value, - activeTrack, - activeTrackSource, - preferences.audioSource, - youtubeEngine, - ]); + final siblings = useMemoized>( + () => !sourcedTrack.isLoading + ? [ + if (sourcedTrack.asData?.value != null) + sourcedTrack.asData!.value.info, + ...?sourcedTrack.asData?.value.siblings, + ] + : [], + [sourcedTrack], + ); - final siblings = useMemoized( - () => !isFetchingActiveTrack - ? [ - if (activeTrackSource != null) activeTrackSource.info, - ...?activeTrackSource?.siblings, - ] - : [], - [activeTrackSource, isFetchingActiveTrack], - ); + useEffect(() { + /// Populate sibling when active track changes + if (sourcedTrack.asData?.value != null && + sourcedTrack.asData?.value.siblings.isEmpty == true) { + sourcedTrackNotifier.copyWithSibling(); + } + return null; + }, [sourcedTrack]); - final previousActiveTrack = usePrevious(activeTrack); - useEffect(() { - /// Populate sibling when active track changes - if (previousActiveTrack?.id == activeTrack?.id) return; - if (activeTrackSource != null && activeTrackSource.siblings.isEmpty) { - activeTrackNotifier?.copyWithSibling(); - } - return null; - }, [activeTrack, previousActiveTrack]); - - final itemBuilder = useCallback( - (TrackSourceInfo sourceInfo, AudioSource source) { - final icon = sourceInfoToIconMap[source]; - return ButtonTile( - style: ButtonVariance.ghost, - padding: const EdgeInsets.symmetric(horizontal: 8), - title: Text( - sourceInfo.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - leading: UniversalImage( - path: sourceInfo.thumbnail, - height: 60, - width: 60, - ), - trailing: Text(Duration(milliseconds: sourceInfo.durationMs) - .toHumanReadableString()), - subtitle: Row( - children: [ - if (icon != null) icon, - Flexible( - child: Text( - " • ${sourceInfo.artists}", - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16), + child: Row( + spacing: 5, + children: [ + AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: Text( + context.l10n.alternative_track_sources, + ).bold()), + ], ), - ], - ), - enabled: !isFetchingActiveTrack && !isLoading.value, - selected: !isFetchingActiveTrack && - sourceInfo.id == activeTrackSource?.info.id, - onPressed: () async { - if (!isFetchingActiveTrack && - sourceInfo.id != activeTrackSource?.info.id) { - 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, - isFetchingActiveTrack, - isLoading.value, - ], - ); - - final scale = context.theme.scaling; - - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16), - child: Row( - spacing: 5, - children: [ - AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: !isSearching.value - ? Text( - context.l10n.alternative_track_sources, - ).bold() - : ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 320 * scale, - maxHeight: 38 * scale, - ), - child: TextField( - autofocus: true, - controller: searchController, - placeholder: Text(context.l10n.search), - style: theme.typography.bold, - ), - ), - ), - const Spacer(), - if (!isSearching.value) ...[ - IconButton.outline( - icon: const Icon(SpotubeIcons.search, size: 18), - onPressed: () { - isSearching.value = true; - }, - ), - if (!floating) const BackButton(icon: SpotubeIcons.angleDown) - ] else ...[ - if (preferences.audioSource == AudioSource.piped) - IconButton.outline( - icon: const Icon(SpotubeIcons.filter, size: 18), - onPressed: () { - showPopover( - context: context, - alignment: Alignment.bottomRight, - builder: (context) { - return DropdownMenu( - children: SearchMode.values - .map( - (e) => MenuButton( - onPressed: (context) { - searchMode.value = e; - }, - enabled: searchMode.value != e, - child: Text(e.label), - ), - ) - .toList(), - ); - }, - ); - }, - ), - IconButton.outline( - icon: const Icon(SpotubeIcons.close, size: 18), - onPressed: () { - isSearching.value = false; - }, - ), - ] - ], ), - ), - AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: isLoading.value - ? const SizedBox( - width: double.infinity, - child: LinearProgressIndicator(), - ) - : const SizedBox.shrink(), - ), - Expanded( - child: AnimatedSwitcher( + AnimatedSwitcher( duration: const Duration(milliseconds: 300), - transitionBuilder: (child, animation) => - FadeTransition(opacity: animation, child: child), - child: InterScrollbar( - controller: controller, - child: switch (isSearching.value) { - false => ListView.separated( - padding: const EdgeInsets.all(8.0), - controller: controller, - itemCount: siblings.length, - separatorBuilder: (context, index) => const Gap(8), - itemBuilder: (context, index) => itemBuilder( - siblings[index], - activeTrackSource!.source, - ), - ), - true => FutureBuilder( - future: searchRequest, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center( - child: Text(snapshot.error.toString()), - ); - } else if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator()); - } + child: sourcedTrack.isLoading + ? const SizedBox( + width: double.infinity, + child: LinearProgressIndicator(), + ) + : const SizedBox.shrink(), + ), + Expanded( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, animation) => + FadeTransition(opacity: animation, child: child), + child: InterScrollbar( + controller: controller, + child: ListView.separated( + padding: const EdgeInsets.all(8.0), + controller: controller, + itemCount: siblings.length, + separatorBuilder: (context, index) => const Gap(8), + itemBuilder: (context, index) { + final sourceInfo = siblings[index]; - return ListView.separated( - padding: const EdgeInsets.all(8.0), - controller: controller, - itemCount: snapshot.data!.length, - separatorBuilder: (context, index) => const Gap(8), - itemBuilder: (context, index) => itemBuilder( - snapshot.data![index], - preferences.audioSource, - ), - ); - }, - ), - }, + return ButtonTile( + style: ButtonVariance.ghost, + padding: const EdgeInsets.symmetric(horizontal: 8), + title: Text( + sourceInfo.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + leading: sourceInfo.thumbnail != null + ? UniversalImage( + path: sourceInfo.thumbnail!, + height: 60, + width: 60, + ) + : null, + trailing: + Text(sourceInfo.duration.toHumanReadableString()), + subtitle: Text( + sourceInfo.artists.join(", "), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + enabled: !sourcedTrack.isLoading, + selected: !sourcedTrack.isLoading && + sourceInfo.id == sourcedTrack.asData?.value.info.id, + onPressed: () async { + if (!sourcedTrack.isLoading && + sourceInfo.id != + sourcedTrack.asData?.value.info.id) { + await sourcedTrackNotifier + .swapWithSibling(sourceInfo); + await ref + .read(audioPlayerProvider.notifier) + .swapActiveSource(); + + if (context.mounted) { + if (MediaQuery.sizeOf(context).mdAndUp) { + closeOverlay(context); + } else { + closeDrawer(context); + } + } + } + }, + ); + }, + ), + ), ), ), - ), - ], - ), - ); + ], + ), + ); + }); } } diff --git a/lib/modules/root/sidebar/sidebar.dart b/lib/modules/root/sidebar/sidebar.dart index e4e7db3d..1538d624 100644 --- a/lib/modules/root/sidebar/sidebar.dart +++ b/lib/modules/root/sidebar/sidebar.dart @@ -22,7 +22,7 @@ class Sidebar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final ThemeData(:colorScheme) = Theme.of(context); - final mediaQuery = MediaQuery.of(context); + final mediaQuery = MediaQuery.sizeOf(context); final layoutMode = ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); diff --git a/lib/modules/root/sidebar/sidebar_footer.dart b/lib/modules/root/sidebar/sidebar_footer.dart index 4c46c13b..0f8ac9d8 100644 --- a/lib/modules/root/sidebar/sidebar_footer.dart +++ b/lib/modules/root/sidebar/sidebar_footer.dart @@ -24,7 +24,12 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { final theme = Theme.of(context); final router = AutoRouter.of(context, watch: true); final mediaQuery = MediaQuery.of(context); - final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; + final downloadCount = ref + .watch(downloadManagerProvider) + .where((e) => + e.status == DownloadStatus.downloading || + e.status == DownloadStatus.queued) + .length; final userSnapshot = ref.watch(metadataPluginUserProvider); final data = userSnapshot.asData?.value; diff --git a/lib/modules/root/spotube_navigation_bar.dart b/lib/modules/root/spotube_navigation_bar.dart index 15417fa6..47ea3ca3 100644 --- a/lib/modules/root/spotube_navigation_bar.dart +++ b/lib/modules/root/spotube_navigation_bar.dart @@ -25,7 +25,12 @@ class SpotubeNavigationBar extends HookConsumerWidget { Widget build(BuildContext context, ref) { final mediaQuery = MediaQuery.of(context); - final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; + final downloadCount = ref + .watch(downloadManagerProvider) + .where((e) => + e.status == DownloadStatus.downloading || + e.status == DownloadStatus.queued) + .length; final layoutMode = ref.watch(userPreferencesProvider.select((s) => s.layoutMode)); diff --git a/lib/modules/root/use_downloader_dialogs.dart b/lib/modules/root/use_downloader_dialogs.dart deleted file mode 100644 index e2f91043..00000000 --- a/lib/modules/root/use_downloader_dialogs.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/components/dialogs/replace_downloaded_dialog.dart'; -import 'package:spotube/provider/download_manager_provider.dart'; - -void useDownloaderDialogs(WidgetRef ref) { - final context = useContext(); - final showingDialogCompleter = useRef(Completer()..complete()); - final downloader = ref.watch(downloadManagerProvider); - - useEffect(() { - downloader.onFileExists = (track) async { - if (!context.mounted) return false; - - if (!showingDialogCompleter.value.isCompleted) { - await showingDialogCompleter.value.future; - } - - final replaceAll = ref.read(replaceDownloadedFileState); - - if (replaceAll != null) return replaceAll; - - showingDialogCompleter.value = Completer(); - - if (context.mounted) { - final result = await showDialog( - context: context, - builder: (context) => ReplaceDownloadedDialog( - track: track, - ), - ) ?? - false; - - showingDialogCompleter.value.complete(); - return result; - } - - // it'll never reach here as root_app is always mounted - return false; - }; - return null; - }, [downloader]); -} diff --git a/lib/modules/root/use_global_subscriptions.dart b/lib/modules/root/use_global_subscriptions.dart index 68f70b5a..9a492d31 100644 --- a/lib/modules/root/use_global_subscriptions.dart +++ b/lib/modules/root/use_global_subscriptions.dart @@ -31,7 +31,7 @@ void useGlobalSubscriptions(WidgetRef ref) { showDialog( context: context, builder: (context) => MetadataPluginUpdateAvailableDialog( - plugin: pluginConfig.defaultPluginConfig!, + plugin: pluginConfig.defaultMetadataPluginConfig!, update: pluginUpdate, ), ); diff --git a/lib/pages/getting_started/sections/playback.dart b/lib/pages/getting_started/sections/playback.dart index 43ff2c8e..699024b1 100644 --- a/lib/pages/getting_started/sections/playback.dart +++ b/lib/pages/getting_started/sections/playback.dart @@ -1,30 +1,11 @@ -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/spotube_icons.dart'; import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/string.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -final audioSourceToIconMap = { - AudioSource.youtube: const Icon( - SpotubeIcons.youtube, - color: Colors.red, - size: 20, - ), - AudioSource.piped: const Icon(SpotubeIcons.piped, size: 20), - AudioSource.invidious: ClipRRect( - borderRadius: BorderRadius.circular(26), - child: Assets.images.logos.invidious.image(width: 26, height: 26), - ), - AudioSource.jiosaavn: - Assets.images.logos.jiosaavn.image(width: 20, height: 20), -}; - class GettingStartedPagePlaybackSection extends HookConsumerWidget { final VoidCallback onNext; final VoidCallback onPrevious; @@ -40,17 +21,19 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { final preferences = ref.watch(userPreferencesProvider); final preferencesNotifier = ref.read(userPreferencesProvider.notifier); - final audioSourceToDescription = useMemoized( - () => { - AudioSource.youtube: "${context.l10n.youtube_source_description}\n" - "${context.l10n.highest_quality("148kbps mp4, 128kbps opus")}", - AudioSource.piped: context.l10n.piped_source_description, - AudioSource.jiosaavn: - "${context.l10n.jiosaavn_source_description}\n" - "${context.l10n.highest_quality("320kbps mp")}", - AudioSource.invidious: context.l10n.invidious_source_description, - }, - []); + // final audioSourceToDescription = useMemoized( + // () => { + // AudioSource.youtube: "${context.l10n.youtube_source_description}\n" + // "${context.l10n.highest_quality("148kbps mp4, 128kbps opus")}", + // AudioSource.piped: context.l10n.piped_source_description, + // AudioSource.jiosaavn: + // "${context.l10n.jiosaavn_source_description}\n" + // "${context.l10n.highest_quality("320kbps mp4")}", + // AudioSource.invidious: context.l10n.invidious_source_description, + // AudioSource.dabMusic: "${context.l10n.dab_music_source_description}\n" + // "${context.l10n.highest_quality("320kbps mp3, HI-RES 24bit 44.1kHz-96kHz flac")}", + // }, + // []); return Center( child: BlurCard( @@ -65,53 +48,44 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { ], ), const Gap(16), - Align( - alignment: Alignment.centerLeft, - child: Text(context.l10n.select_audio_source).semiBold().large(), - ), - const Gap(16), - Select( - value: preferences.audioSource, - onChanged: (value) { - if (value == null) return; - preferencesNotifier.setAudioSource(value); - }, - placeholder: Text(preferences.audioSource.name.capitalize()), - itemBuilder: (context, value) => Row( - mainAxisSize: MainAxisSize.min, - spacing: 6, - children: [ - audioSourceToIconMap[value]!, - Text(value.name.capitalize()), - ], - ), - popup: (context) { - return SelectPopup( - items: SelectItemBuilder( - childCount: AudioSource.values.length, - builder: (context, index) { - final source = AudioSource.values[index]; - - return SelectItemButton( - value: source, - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 6, - children: [ - audioSourceToIconMap[source]!, - Text(source.name.capitalize()), - ], - ), - ); - }, - ), - ); - }, - ), - const Gap(16), - Text( - audioSourceToDescription[preferences.audioSource]!, - ).small().muted(), + // Align( + // alignment: Alignment.centerLeft, + // child: Text(context.l10n.select_audio_source).semiBold().large(), + // ), + // const Gap(16), + // RadioGroup( + // value: preferences.audioSource, + // onChanged: (value) { + // preferencesNotifier.setAudioSource(value); + // }, + // child: Wrap( + // spacing: 6, + // runSpacing: 6, + // children: [ + // for (final source in AudioSource.values) + // Badge( + // isLabelVisible: source == AudioSource.dabMusic, + // label: const Text("NEW"), + // backgroundColor: Colors.lime[300], + // textColor: Colors.black, + // child: RadioCard( + // value: source, + // child: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // audioSourceToIconMap[source]!, + // Text(source.label), + // ], + // ), + // ), + // ), + // ], + // ), + // ), + // const Gap(16), + // Text( + // audioSourceToDescription[preferences.audioSource]!, + // ).small().muted(), const Gap(16), ButtonTile( title: Text(context.l10n.endless_playback), diff --git a/lib/pages/library/library.dart b/lib/pages/library/library.dart index 172d9af3..de438451 100644 --- a/lib/pages/library/library.dart +++ b/lib/pages/library/library.dart @@ -17,7 +17,12 @@ class LibraryPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final downloadingCount = ref.watch(downloadManagerProvider).$downloadCount; + final downloadingCount = ref + .watch(downloadManagerProvider) + .where((e) => + e.status == DownloadStatus.downloading || + e.status == DownloadStatus.queued) + .length; final router = context.watchRouter; final sidebarLibraryTileList = useMemoized( () => [ diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index 4f183346..2d989138 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -55,7 +55,7 @@ class UserAlbumsPage extends HookConsumerWidget { if (albumsQuery.error case MetadataPluginException( - errorCode: MetadataPluginErrorCode.noDefaultPlugin, + errorCode: MetadataPluginErrorCode.noDefaultMetadataPlugin, message: _, )) { return const Center(child: NoDefaultMetadataPlugin()); diff --git a/lib/pages/library/user_artists.dart b/lib/pages/library/user_artists.dart index d5df13e5..750cb50b 100644 --- a/lib/pages/library/user_artists.dart +++ b/lib/pages/library/user_artists.dart @@ -60,7 +60,7 @@ class UserArtistsPage extends HookConsumerWidget { if (artistQuery.error case MetadataPluginException( - errorCode: MetadataPluginErrorCode.noDefaultPlugin, + errorCode: MetadataPluginErrorCode.noDefaultMetadataPlugin, message: _, )) { return const Center(child: NoDefaultMetadataPlugin()); diff --git a/lib/pages/library/user_downloads.dart b/lib/pages/library/user_downloads.dart index 6566bed6..f6a130bb 100644 --- a/lib/pages/library/user_downloads.dart +++ b/lib/pages/library/user_downloads.dart @@ -14,9 +14,8 @@ class UserDownloadsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final downloadManager = ref.watch(downloadManagerProvider); - - final history = downloadManager.$backHistory; + final downloadQueue = ref.watch(downloadManagerProvider); + final downloadManagerNotifier = ref.watch(downloadManagerProvider.notifier); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -28,16 +27,15 @@ class UserDownloadsPage extends HookConsumerWidget { children: [ Expanded( child: AutoSizeText( - context.l10n - .currently_downloading(downloadManager.$downloadCount), + context.l10n.currently_downloading(downloadQueue.length), maxLines: 1, ).semiBold(), ), const SizedBox(width: 10), Button.destructive( - onPressed: downloadManager.$downloadCount == 0 + onPressed: downloadQueue.isEmpty ? null - : downloadManager.cancelAll, + : downloadManagerNotifier.clearAll, child: Text(context.l10n.cancel_all), ), ], @@ -46,9 +44,12 @@ class UserDownloadsPage extends HookConsumerWidget { Expanded( child: SafeArea( child: ListView.builder( - itemCount: history.length, + itemCount: downloadQueue.length, + padding: const EdgeInsets.only(bottom: 200), itemBuilder: (context, index) { - return DownloadItem(track: history.elementAt(index)); + return DownloadItem( + task: downloadQueue.elementAt(index), + ); }, ), ), diff --git a/lib/pages/library/user_local_tracks/local_folder.dart b/lib/pages/library/user_local_tracks/local_folder.dart index 27af0f57..523097e1 100644 --- a/lib/pages/library/user_local_tracks/local_folder.dart +++ b/lib/pages/library/user_local_tracks/local_folder.dart @@ -14,6 +14,7 @@ import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; +import 'package:spotube/components/track_presentation/presentation_actions.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/string.dart'; import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; @@ -68,6 +69,37 @@ class LocalLibraryPage extends HookConsumerWidget { } } + Future shufflePlayLocalTracks( + WidgetRef ref, + List tracks, + ) async { + final playlist = ref.read(audioPlayerProvider); + final playback = ref.read(audioPlayerProvider.notifier); + final isPlaylistPlaying = playlist.containsTracks(tracks); + final shuffledTracks = tracks.shuffled(); + if (isPlaylistPlaying) return; + + await playback.load( + shuffledTracks, + initialIndex: 0, + autoPlay: true, + ); + } + + Future addToQueueLocalTracks( + BuildContext context, + WidgetRef ref, + List tracks, + ) async { + final playlist = ref.read(audioPlayerProvider); + final playback = ref.read(audioPlayerProvider.notifier); + final isPlaylistPlaying = playlist.containsTracks(tracks); + if (isPlaylistPlaying) return; + await playback.addTracks(tracks); + if (!context.mounted) return; + showToastForAction(context, "add-to-queue", tracks.length); + } + @override Widget build(BuildContext context, ref) { final scale = context.theme.scaling; @@ -75,8 +107,12 @@ class LocalLibraryPage extends HookConsumerWidget { final sortBy = useState(SortBy.none); final playlist = ref.watch(audioPlayerProvider); final trackSnapshot = ref.watch(localTracksProvider); - final isPlaylistPlaying = playlist.containsTracks( - trackSnapshot.asData?.value.values.flattened.toList() ?? []); + final isPlaylistPlaying = useMemoized( + () => playlist.containsTracks( + trackSnapshot.asData?.value[location] ?? [], + ), + [playlist, trackSnapshot, location], + ); final searchController = useShadcnTextEditingController(); useValueListenable(searchController); @@ -162,7 +198,7 @@ class LocalLibraryPage extends HookConsumerWidget { ), ); - if (accepted ?? false) return; + if (accepted != true) return; final cacheDir = Directory( await UserPreferencesNotifier.getMusicCacheDir(), @@ -171,6 +207,8 @@ class LocalLibraryPage extends HookConsumerWidget { if (cacheDir.existsSync()) { await cacheDir.delete(recursive: true); } + + ref.invalidate(localTracksProvider); }, ), IconButton.outline( @@ -222,26 +260,79 @@ class LocalLibraryPage extends HookConsumerWidget { 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] ?? [], - ); + Tooltip( + tooltip: + TooltipContainer(child: Text(context.l10n.play)).call, + child: IconButton.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, + : null, + icon: Icon( + isPlaylistPlaying + ? SpotubeIcons.stop + : SpotubeIcons.play, + ), + ), + ), + const Gap(5), + Tooltip( + tooltip: + TooltipContainer(child: Text(context.l10n.shuffle)) + .call, + child: IconButton.outline( + onPressed: trackSnapshot.asData?.value != null + ? () async { + if (trackSnapshot.asData?.value.isNotEmpty == + true) { + if (!isPlaylistPlaying) { + await shufflePlayLocalTracks( + ref, + trackSnapshot.asData!.value[location] ?? + [], + ); + } + } + } + : null, + enabled: !isPlaylistPlaying, + icon: const Icon(SpotubeIcons.shuffle), + ), + ), + const Gap(5), + Tooltip( + tooltip: TooltipContainer( + child: Text(context.l10n.add_to_queue)) + .call, + child: IconButton.outline( + onPressed: trackSnapshot.asData?.value != null + ? () async { + if (trackSnapshot.asData?.value.isNotEmpty == + true) { + if (!isPlaylistPlaying) { + await addToQueueLocalTracks( + context, + ref, + trackSnapshot.asData!.value[location] ?? + [], + ); + } + } + } + : null, + enabled: !isPlaylistPlaying, + icon: const Icon(SpotubeIcons.queueAdd), ), - child: Text(context.l10n.play), ), const Spacer(), if (constraints.smAndDown) @@ -346,36 +437,41 @@ class LocalLibraryPage extends HookConsumerWidget { controller: controller, child: Skeletonizer( enabled: trackSnapshot.isLoading, - child: ListView.builder( + child: CustomScrollView( 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, - ); - } + slivers: [ + SliverList.builder( + 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, + final track = filteredTracks[index]; + return TrackTile( + index: index, + playlist: playlist, + track: track, + userPlaylist: false, + onTap: () async { + await playLocalTracks( + ref, + sortedTracks, + currentTrack: track, + ); + }, ); }, - ); - }, + ), + const SliverGap(200), + ], ), ), ), @@ -398,7 +494,7 @@ class LocalLibraryPage extends HookConsumerWidget { 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 43fa3cc9..5f7502e6 100644 --- a/lib/pages/library/user_local_tracks/user_local_tracks.dart +++ b/lib/pages/library/user_local_tracks/user_local_tracks.dart @@ -13,7 +13,6 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/utils/platform.dart'; -// ignore: depend_on_referenced_packages enum SortBy { none, diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index 9020d463..740bc947 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -83,7 +83,7 @@ class UserPlaylistsPage extends HookConsumerWidget { if (playlistsQuery.error case MetadataPluginException( - errorCode: MetadataPluginErrorCode.noDefaultPlugin, + errorCode: MetadataPluginErrorCode.noDefaultMetadataPlugin, message: _, )) { return const Center(child: NoDefaultMetadataPlugin()); diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index 4cd02881..44b8416f 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -9,7 +9,6 @@ import 'package:spotube/modules/root/bottom_player.dart'; import 'package:spotube/modules/root/sidebar/sidebar.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/hooks/configurators/use_endless_playback.dart'; -import 'package:spotube/modules/root/use_downloader_dialogs.dart'; import 'package:spotube/modules/root/use_global_subscriptions.dart'; import 'package:spotube/provider/glance/glance.dart'; @@ -25,7 +24,6 @@ class RootAppPage extends HookConsumerWidget { ref.listen(glanceProvider, (_, __) {}); useGlobalSubscriptions(ref); - useDownloaderDialogs(ref); useEndlessPlayback(ref); useCheckYtDlpInstalled(ref); diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index c6118a97..cb4f4a0b 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -85,7 +85,7 @@ class SearchPage extends HookConsumerWidget { child: Builder(builder: (context) { if (searchChipSnapshot.error case MetadataPluginException( - errorCode: MetadataPluginErrorCode.noDefaultPlugin, + errorCode: MetadataPluginErrorCode.noDefaultMetadataPlugin, message: _ )) { return const NoDefaultMetadataPlugin(); diff --git a/lib/pages/settings/blacklist.dart b/lib/pages/settings/blacklist.dart index 8ac2c1b9..2af899f3 100644 --- a/lib/pages/settings/blacklist.dart +++ b/lib/pages/settings/blacklist.dart @@ -64,7 +64,7 @@ class BlackListPage extends HookConsumerWidget { child: TextField( onChanged: (value) => searchText.value = value, placeholder: Text(context.l10n.search), - leading: const Icon(SpotubeIcons.search), + // prefixIcon: const Icon(SpotubeIcons.search), ), ), InterScrollbar( diff --git a/lib/pages/settings/metadata_plugins.dart b/lib/pages/settings/metadata_plugins.dart index 6698a67f..d4cb1ecf 100644 --- a/lib/pages/settings/metadata_plugins.dart +++ b/lib/pages/settings/metadata_plugins.dart @@ -30,6 +30,7 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { + final tabState = useState(0); final formKey = useMemoized(() => GlobalKey(), []); final plugins = ref.watch(metadataPluginsProvider); @@ -49,19 +50,50 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { final pluginRepos = pluginReposSnapshot.asData?.value.items ?? []; if (installedPluginIds.isEmpty) return pluginRepos; - return pluginRepos + final availablePlugins = pluginRepos .whereNot((repo) => installedPluginIds.contains(repo.repoUrl)) .toList(); + + if (tabState.value != 0) { + // metadata only plugins + return availablePlugins.where( + (d) { + return d.topics.contains( + tabState.value == 1 + ? "spotube-metadata-plugin" + : "spotube-audio-source-plugin", + ); + }, + ).toList(); + } + + return availablePlugins; // all plugins }, - [plugins.asData?.value.plugins, pluginReposSnapshot.asData?.value], + [ + plugins.asData?.value.plugins, + pluginReposSnapshot.asData?.value, + tabState.value, + ], ); + final installedPlugins = useMemoized?>(() { + if (tabState.value == 0) return plugins.asData?.value.plugins; + + return plugins.asData?.value.plugins.where((d) { + return d.abilities.contains( + tabState.value == 1 + ? PluginAbilities.metadata + : PluginAbilities.audioSource, + ); + }).toList(); + }, [tabState.value, plugins.asData?.value]); + return SafeArea( bottom: false, child: Scaffold( headers: [ TitleBar( - title: Text(context.l10n.metadata_provider_plugins), + title: Text(context.l10n.plugins), ) ], child: Padding( @@ -193,6 +225,20 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { ), ), const SliverGap(12), + SliverToBoxAdapter( + child: TabList( + index: tabState.value, + onChanged: (value) { + tabState.value = value; + }, + children: const [ + TabItem(child: Text("All")), + TabItem(child: Text("Metadata")), + TabItem(child: Text("Audio Source")), + ], + ), + ), + const SliverGap(12), if (plugins.asData?.value.plugins.isNotEmpty ?? false) SliverToBoxAdapter( child: Row( @@ -207,15 +253,20 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { ), const SliverGap(20), SliverList.separated( - itemCount: plugins.asData?.value.plugins.length ?? 0, + itemCount: installedPlugins?.length ?? 0, separatorBuilder: (context, index) => const Gap(12), itemBuilder: (context, index) { - final plugin = plugins.asData!.value.plugins[index]; - final isDefault = - plugins.asData!.value.defaultPlugin == index; + final plugin = installedPlugins![index]; + final isDefaultMetadata = + plugins.asData!.value.defaultMetadataPluginConfig?.slug == + plugin.slug; + final isDefaultAudioSource = plugins + .asData!.value.defaultAudioSourcePluginConfig?.slug == + plugin.slug; return MetadataInstalledPluginItem( plugin: plugin, - isDefault: isDefault, + isDefaultMetadata: isDefaultMetadata, + isDefaultAudioSource: isDefaultAudioSource, ); }, ), @@ -249,6 +300,7 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { description: "Loading...", repoUrl: "", owner: "", + topics: [], ), ), ); diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index af8e1b80..ca859ada 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -21,8 +21,8 @@ class SettingsAccountSection extends HookConsumerWidget { children: [ ListTile( leading: const Icon(SpotubeIcons.extensions), - title: Text(context.l10n.metadata_provider_plugins), - subtitle: Text(context.l10n.configure_your_own_metadata_plugin), + title: Text(context.l10n.plugins), + subtitle: Text(context.l10n.configure_plugins), onTap: () { context.pushRoute(const SettingsMetadataProviderRoute()); }, diff --git a/lib/pages/settings/sections/playback.dart b/lib/pages/settings/sections/playback.dart index 6acc70b1..0a29c991 100644 --- a/lib/pages/settings/sections/playback.dart +++ b/lib/pages/settings/sections/playback.dart @@ -1,30 +1,24 @@ import 'dart:io'; import 'package:auto_route/auto_route.dart'; -import 'package:collection/collection.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart' show ListTile; -import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:piped_client/piped_client.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/adaptive/adaptive_select_tile.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/settings/playback/edit_connect_port_dialog.dart'; -import 'package:spotube/modules/settings/playback/edit_instance_url_dialog.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; -import 'package:spotube/components/adaptive/adaptive_select_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/modules/settings/youtube_engine_not_installed_dialog.dart'; -import 'package:spotube/provider/audio_player/sources/invidious_instances_provider.dart'; -import 'package:spotube/provider/audio_player/sources/piped_instances_provider.dart'; +import 'package:spotube/provider/metadata_plugin/audio_source/quality_presets.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; - -import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart'; + import 'package:spotube/utils/platform.dart'; class SettingsPlaybackSection extends HookConsumerWidget { @@ -34,325 +28,107 @@ class SettingsPlaybackSection extends HookConsumerWidget { Widget build(BuildContext context, ref) { final preferences = ref.watch(userPreferencesProvider); final preferencesNotifier = ref.watch(userPreferencesProvider.notifier); + final sourcePresets = ref.watch(audioSourcePresetsProvider); + final sourcePresetsNotifier = + ref.watch(audioSourcePresetsProvider.notifier); final theme = Theme.of(context); return SectionCardWithHeading( heading: context.l10n.playback, children: [ - AdaptiveSelectTile( - secondary: const Icon(SpotubeIcons.audioQuality), - title: Text(context.l10n.audio_quality), - value: preferences.audioQuality, - options: [ - SelectItemButton( - value: SourceQualities.high, - child: Text(context.l10n.high), - ), - SelectItemButton( - value: SourceQualities.medium, - child: Text(context.l10n.medium), - ), - SelectItemButton( - value: SourceQualities.low, - child: Text(context.l10n.low), - ), - ], - onChanged: (value) { - if (value != null) { - preferencesNotifier.setAudioQuality(value); - } - }, - ), - AdaptiveSelectTile( - secondary: const Icon(SpotubeIcons.api), - title: Text(context.l10n.audio_source), - value: preferences.audioSource, - options: AudioSource.values + AdaptiveSelectTile( + secondary: const Icon(SpotubeIcons.engine), + title: Text(context.l10n.youtube_engine), + value: preferences.youtubeClientEngine, + options: YoutubeClientEngine.values + .where((e) => e.isAvailableForPlatform()) .map((e) => SelectItemButton( value: e, child: Text(e.label), )) .toList(), - onChanged: (value) { + onChanged: (value) async { if (value == null) return; - preferencesNotifier.setAudioSource(value); + if (value == YoutubeClientEngine.ytDlp) { + final customPath = KVStoreService.getYoutubeEnginePath(value); + if (!await YtDlpEngine.isInstalled() && + (customPath == null || !await File(customPath).exists()) && + context.mounted) { + final hasInstalled = await showDialog( + context: context, + builder: (context) => + YouTubeEngineNotInstalledDialog(engine: value), + ); + if (hasInstalled != true) return; + } + } + preferencesNotifier.setYoutubeClientEngine(value); }, ), - AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - crossFadeState: preferences.audioSource != AudioSource.piped - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: const SizedBox.shrink(), - secondChild: Consumer( - builder: (context, ref, child) { - final instanceList = ref.watch(pipedInstancesFutureProvider); - - return instanceList.when( - data: (data) { - return AdaptiveSelectTile( - secondary: const Icon(SpotubeIcons.piped), - title: Text(context.l10n.piped_instance), - subtitle: Text( - "${context.l10n.piped_description}\n" - "${context.l10n.piped_warning}", - ), - value: preferences.pipedInstance, - showValueWhenUnfolded: false, - trailing: [ - Tooltip( - tooltip: TooltipContainer( - child: Text(context.l10n.add_custom_url), - ).call, - child: IconButton.outline( - icon: const Icon(SpotubeIcons.edit), - size: ButtonSize.small, - onPressed: () { - showDialog( - context: context, - barrierColor: Colors.black.withValues(alpha: 0.5), - builder: (context) => - SettingsPlaybackEditInstanceUrlDialog( - title: context.l10n.piped_instance, - initialValue: preferences.pipedInstance, - onSave: (value) { - preferencesNotifier.setPipedInstance(value); - }, - ), - ); - }, - ), - ) - ], - options: [ - if (data - .none((e) => e.apiUrl == preferences.pipedInstance)) - SelectItemButton( - value: preferences.pipedInstance, - child: Text.rich( - TextSpan( - style: theme.typography.xSmall.copyWith( - color: theme.colorScheme.foreground, - ), - children: [ - TextSpan(text: context.l10n.custom), - const TextSpan(text: "\n"), - TextSpan(text: preferences.pipedInstance), - ], - ), - ), - ), - for (final e in data.sortedBy((e) => e.name)) - SelectItemButton( - value: e.apiUrl, - child: RichText( - text: TextSpan( - style: theme.typography.normal.copyWith( - color: theme.colorScheme.foreground, - ), - children: [ - TextSpan( - text: "${e.name.trim()}\n", - ), - TextSpan( - text: e.locations - .map(countryCodeToEmoji) - .join(""), - style: GoogleFonts.notoColorEmoji(), - ), - ], - ), - ), - ), - ], - onChanged: (value) { - if (value != null) { - preferencesNotifier.setPipedInstance(value); - } - }, - ); - }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (error, stackTrace) => Text(error.toString()), - ); + if (sourcePresets.presets.isNotEmpty) ...[ + AdaptiveSelectTile( + secondary: const Icon(SpotubeIcons.plugin), + title: Text(context.l10n.streaming_music_format), + value: sourcePresets.selectedStreamingContainerIndex, + options: [ + for (final MapEntry(:key, value: preset) + in sourcePresets.presets.asMap().entries) + SelectItemButton(value: key, child: Text(preset.name)), + ], + onChanged: (value) { + if (value == null) return; + sourcePresetsNotifier.setSelectedStreamingContainerIndex(value); }, ), - ), - AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - crossFadeState: preferences.audioSource != AudioSource.invidious - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: const SizedBox.shrink(), - secondChild: Consumer( - builder: (context, ref, child) { - final instanceList = ref.watch(invidiousInstancesProvider); - - return instanceList.when( - data: (data) { - return AdaptiveSelectTile( - secondary: const Icon(SpotubeIcons.piped), - title: Text(context.l10n.invidious_instance), - subtitle: Text( - "${context.l10n.invidious_description}\n" - "${context.l10n.invidious_warning}", - ), - trailing: [ - Tooltip( - tooltip: TooltipContainer( - child: Text(context.l10n.add_custom_url), - ).call, - child: IconButton.outline( - icon: const Icon(SpotubeIcons.edit), - size: ButtonSize.small, - onPressed: () { - showDialog( - context: context, - barrierColor: Colors.black.withValues(alpha: 0.5), - builder: (context) => - SettingsPlaybackEditInstanceUrlDialog( - title: context.l10n.invidious_instance, - initialValue: preferences.invidiousInstance, - onSave: (value) { - preferencesNotifier - .setInvidiousInstance(value); - }, - ), - ); - }, - ), - ) - ], - value: preferences.invidiousInstance, - showValueWhenUnfolded: false, - options: [ - if (data.none((e) => - e.details.uri == preferences.invidiousInstance)) - SelectItemButton( - value: preferences.invidiousInstance, - child: Text.rich( - TextSpan( - style: theme.typography.xSmall.copyWith( - color: theme.colorScheme.foreground, - ), - children: [ - TextSpan(text: context.l10n.custom), - const TextSpan(text: "\n"), - TextSpan(text: preferences.invidiousInstance), - ], - ), - ), - ), - for (final e in data.sortedBy((e) => e.name)) - SelectItemButton( - value: e.details.uri, - child: RichText( - text: TextSpan( - style: theme.typography.normal.copyWith( - color: theme.colorScheme.foreground, - ), - children: [ - TextSpan( - text: "${e.name.trim()}\n", - ), - TextSpan( - text: countryCodeToEmoji( - e.details.region, - ), - style: GoogleFonts.notoColorEmoji(), - ), - ], - ), - ), - ), - ], - onChanged: (value) { - if (value != null) { - preferencesNotifier.setInvidiousInstance(value); - } - }, - ); - }, - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (error, stackTrace) => Text(error.toString()), - ); + AdaptiveSelectTile( + secondary: const Icon(SpotubeIcons.audioQuality), + title: Text(context.l10n.streaming_music_quality), + value: sourcePresets.selectedStreamingQualityIndex, + options: [ + for (final MapEntry(:key, value: quality) in sourcePresets + .presets[sourcePresets.selectedStreamingContainerIndex] + .qualities + .asMap() + .entries) + SelectItemButton(value: key, child: Text(quality.toString())), + ], + onChanged: (value) { + if (value == null) return; + sourcePresetsNotifier.setSelectedStreamingQualityIndex(value); }, ), - ), - switch (preferences.audioSource) { - AudioSource.youtube => AdaptiveSelectTile( - secondary: const Icon(SpotubeIcons.engine), - title: Text(context.l10n.youtube_engine), - value: preferences.youtubeClientEngine, - options: YoutubeClientEngine.values - .where((e) => e.isAvailableForPlatform()) - .map((e) => SelectItemButton( - value: e, - child: Text(e.label), - )) - .toList(), - onChanged: (value) async { - if (value == null) return; - if (value == YoutubeClientEngine.ytDlp) { - final customPath = KVStoreService.getYoutubeEnginePath(value); - if (!await YtDlpEngine.isInstalled() && - (customPath == null || - !await File(customPath).exists()) && - context.mounted) { - final hasInstalled = await showDialog( - context: context, - builder: (context) => - YouTubeEngineNotInstalledDialog(engine: value), - ); - if (hasInstalled != true) return; - } - } - preferencesNotifier.setYoutubeClientEngine(value); - }, - ), - AudioSource.piped || - AudioSource.invidious => - AdaptiveSelectTile( - secondary: const Icon(SpotubeIcons.search), - title: Text(context.l10n.search_mode), - value: preferences.searchMode, - options: SearchMode.values - .map((e) => SelectItemButton( - value: e, - child: Text(e.label), - )) - .toList(), - onChanged: (value) { - if (value == null) return; - preferencesNotifier.setSearchMode(value); - }, - ), - _ => const SizedBox.shrink(), - }, - AnimatedCrossFade( - duration: const Duration(milliseconds: 300), - crossFadeState: preferences.searchMode == SearchMode.youtube && - (preferences.audioSource == AudioSource.piped || - preferences.audioSource == AudioSource.youtube || - preferences.audioSource == AudioSource.invidious) - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: ListTile( - leading: const Icon(SpotubeIcons.skip), - title: Text(context.l10n.skip_non_music), - trailing: Switch( - value: preferences.skipNonMusic, - onChanged: (state) { - preferencesNotifier.setSkipNonMusic(state); - }, - ), + AdaptiveSelectTile( + secondary: const Icon(SpotubeIcons.plugin), + title: Text(context.l10n.download_music_format), + value: sourcePresets.selectedDownloadingContainerIndex, + options: [ + for (final MapEntry(:key, value: preset) + in sourcePresets.presets.asMap().entries) + SelectItemButton(value: key, child: Text(preset.name)), + ], + onChanged: (value) { + if (value == null) return; + sourcePresetsNotifier.setSelectedDownloadingContainerIndex(value); + }, ), - secondChild: const SizedBox.shrink(), - ), + AdaptiveSelectTile( + secondary: const Icon(SpotubeIcons.audioQuality), + title: Text(context.l10n.download_music_quality), + value: sourcePresets.selectedStreamingQualityIndex, + options: [ + for (final MapEntry(:key, value: quality) in sourcePresets + .presets[sourcePresets.selectedDownloadingContainerIndex] + .qualities + .asMap() + .entries) + SelectItemButton(value: key, child: Text(quality.toString())), + ], + onChanged: (value) { + if (value == null) return; + sourcePresetsNotifier.setSelectedStreamingQualityIndex(value); + }, + ), + ], ListTile( title: Text(context.l10n.cache_music), subtitle: kIsMobile @@ -396,48 +172,6 @@ class SettingsPlaybackSection extends HookConsumerWidget { onChanged: preferencesNotifier.setNormalizeAudio, ), ), - if (preferences.audioSource != AudioSource.jiosaavn) ...[ - AdaptiveSelectTile( - popupConstraints: const BoxConstraints(maxWidth: 300), - secondary: const Icon(SpotubeIcons.stream), - title: Text(context.l10n.streaming_music_codec), - value: preferences.streamMusicCodec, - showValueWhenUnfolded: false, - options: SourceCodecs.values - .map((e) => SelectItemButton( - value: e, - child: Text( - e.label, - style: theme.typography.small, - ), - )) - .toList(), - onChanged: (value) { - if (value == null) return; - preferencesNotifier.setStreamMusicCodec(value); - }, - ), - AdaptiveSelectTile( - popupConstraints: const BoxConstraints(maxWidth: 300), - secondary: const Icon(SpotubeIcons.file), - title: Text(context.l10n.download_music_codec), - value: preferences.downloadMusicCodec, - showValueWhenUnfolded: false, - options: SourceCodecs.values - .map((e) => SelectItemButton( - value: e, - child: Text( - e.label, - style: theme.typography.small, - ), - )) - .toList(), - onChanged: (value) { - if (value == null) return; - preferencesNotifier.setDownloadMusicCodec(value); - }, - ), - ], ListTile( leading: const Icon(SpotubeIcons.repeat), title: Text(context.l10n.endless_playback), diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index cb53ca4f..66878714 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -7,12 +7,11 @@ import 'package:media_kit/media_kit.dart'; import 'package:spotube/extensions/list.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/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/discord_provider.dart'; -import 'package:spotube/provider/server/track_sources.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; @@ -145,40 +144,8 @@ class AudioPlayerNotifier extends Notifier { }), audioPlayer.playlistStream.listen((playlist) async { 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) { - 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 = []; - - 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(", ")}", - ); - } + final tracks = + playlist.medias.map((e) => SpotubeMedia.media(e).track).toList(); state = state.copyWith( tracks: tracks, @@ -259,11 +226,14 @@ class AudioPlayerNotifier extends Notifier { return addTracks(tracks); } - final addableTracks = _blacklist.filter(tracks).where( + final addableTracks = _blacklist + .filter(tracks) + .where( (track) => allowDuplicates || !state.tracks.any((element) => _compareTracks(element, track)), - ); + ) + .toList(); state = state.copyWith( tracks: [...addableTracks, ...state.tracks], @@ -371,13 +341,12 @@ class AudioPlayerNotifier extends Notifier { } bool _compareTracks(SpotubeTrackObject a, SpotubeTrackObject b) { - if ((a is SpotubeLocalTrackObject && b is! SpotubeLocalTrackObject) || - (a is! SpotubeLocalTrackObject && b is SpotubeLocalTrackObject)) { + if (a.runtimeType != b.runtimeType) { return false; } return a is SpotubeLocalTrackObject && b is SpotubeLocalTrackObject - ? (a).path == (b).path + ? a.path == b.path : a.id == b.id; } @@ -398,10 +367,9 @@ class AudioPlayerNotifier extends Notifier { // because of timeout final intendedActiveTrack = medias.elementAt(initialIndex); if (intendedActiveTrack.track is! SpotubeLocalTrackObject) { - await ref.read( - trackSourcesProvider( - TrackSourceQuery.fromTrack( - intendedActiveTrack.track as SpotubeFullTrackObject), + ref.read( + sourcedTrackProvider( + intendedActiveTrack.track as SpotubeFullTrackObject, ).future, ); } @@ -434,13 +402,31 @@ class AudioPlayerNotifier extends Notifier { return; } - final currentIndex = state.currentIndex; - final currentTrack = state.activeTrack as SpotubeFullTrackObject; - final swappedMedia = SpotubeMedia(currentTrack); + final oldState = state; + await audioPlayer.stop(); - await audioPlayer.addTrackAt(swappedMedia, currentIndex + 1); - await audioPlayer.skipToNext(); - await audioPlayer.removeTrack(currentIndex); + await load( + oldState.tracks, + initialIndex: oldState.currentIndex, + autoPlay: true, + ); + state = state.copyWith( + collections: oldState.collections, + loopMode: oldState.loopMode, + playing: oldState.playing, + shuffled: false, + ); + await audioPlayer.setLoopMode(oldState.loopMode); + await _updatePlayerState( + AudioPlayerStateTableCompanion( + tracks: Value(state.tracks), + currentIndex: Value(state.currentIndex), + collections: Value(state.collections), + loopMode: Value(state.loopMode), + playing: Value(state.playing), + shuffled: Value(state.shuffled), + ), + ); } Future jumpToTrack(SpotubeTrackObject track) async { diff --git a/lib/provider/audio_player/audio_player_streams.dart b/lib/provider/audio_player/audio_player_streams.dart index 507e9d49..eff13134 100644 --- a/lib/provider/audio_player/audio_player_streams.dart +++ b/lib/provider/audio_player/audio_player_streams.dart @@ -3,14 +3,13 @@ import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.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/state.dart'; import 'package:spotube/provider/discord_provider.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/metadata_plugin/core/scrobble.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; -import 'package:spotube/provider/server/track_sources.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; import 'package:spotube/provider/skip_segments/skip_segments.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -156,9 +155,7 @@ class AudioPlayerStreamListeners { try { await ref.read( - trackSourcesProvider( - TrackSourceQuery.fromTrack(nextTrack as SpotubeFullTrackObject), - ).future, + sourcedTrackProvider(nextTrack as SpotubeFullTrackObject).future, ); } finally { lastTrack = nextTrack.id; diff --git a/lib/provider/audio_player/querying_track_info.dart b/lib/provider/audio_player/querying_track_info.dart index ce99b261..06e9653c 100644 --- a/lib/provider/audio_player/querying_track_info.dart +++ b/lib/provider/audio_player/querying_track_info.dart @@ -1,8 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.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/server/track_sources.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; final queryingTrackInfoProvider = Provider((ref) { final audioPlayer = ref.watch(audioPlayerProvider); @@ -16,10 +15,9 @@ final queryingTrackInfoProvider = Provider((ref) { } return ref - .watch(trackSourcesProvider( - TrackSourceQuery.fromTrack( - audioPlayer.activeTrack! as SpotubeFullTrackObject, - ), - )) + .watch( + sourcedTrackProvider( + audioPlayer.activeTrack! as SpotubeFullTrackObject), + ) .isLoading; }); diff --git a/lib/provider/audio_player/sources/invidious_instances_provider.dart b/lib/provider/audio_player/sources/invidious_instances_provider.dart deleted file mode 100644 index c04ac765..00000000 --- a/lib/provider/audio_player/sources/invidious_instances_provider.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/services/sourced_track/sources/invidious.dart'; - -final invidiousInstancesProvider = FutureProvider((ref) async { - final invidious = ref.watch(invidiousProvider); - - final instances = await invidious.instances(); - - return instances - .where((instance) => instance.details.type == "https") - .toList(); -}); diff --git a/lib/provider/audio_player/sources/piped_instances_provider.dart b/lib/provider/audio_player/sources/piped_instances_provider.dart deleted file mode 100644 index 3c5d5f04..00000000 --- a/lib/provider/audio_player/sources/piped_instances_provider.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:spotube/services/logger/logger.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:piped_client/piped_client.dart'; -import 'package:spotube/services/sourced_track/sources/piped.dart'; - -final pipedInstancesFutureProvider = FutureProvider>( - (ref) async { - try { - final pipedClient = ref.watch(pipedProvider); - - return await pipedClient.instanceList(); - } catch (e, stack) { - AppLogger.reportError(e, stack); - return []; - } - }, -); diff --git a/lib/provider/audio_player/state.dart b/lib/provider/audio_player/state.dart index bb0527bf..d62155f3 100644 --- a/lib/provider/audio_player/state.dart +++ b/lib/provider/audio_player/state.dart @@ -51,11 +51,17 @@ class AudioPlayerState with _$AudioPlayerState { } bool containsTrack(SpotubeTrackObject track) { - return tracks.any((t) => t.id == track.id); + return tracks.isNotEmpty && + tracks.any( + (t) => + t is SpotubeLocalTrackObject && track is SpotubeLocalTrackObject + ? t.path == track.path + : t.id == track.id, + ); } bool containsTracks(List tracks) { - return tracks.every(containsTrack); + return this.tracks.isNotEmpty && tracks.every(containsTrack); } bool containsCollection(String collectionId) { diff --git a/lib/provider/audio_player/state.freezed.dart b/lib/provider/audio_player/state.freezed.dart index 146b0541..0299cd2f 100644 --- a/lib/provider/audio_player/state.freezed.dart +++ b/lib/provider/audio_player/state.freezed.dart @@ -27,8 +27,12 @@ mixin _$AudioPlayerState { int get currentIndex => throw _privateConstructorUsedError; List get tracks => throw _privateConstructorUsedError; + /// Serializes this AudioPlayerState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of AudioPlayerState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $AudioPlayerStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -58,6 +62,8 @@ 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({ @@ -122,6 +128,8 @@ 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({ @@ -226,7 +234,7 @@ class _$AudioPlayerStateImpl extends _AudioPlayerState { const DeepCollectionEquality().equals(other._tracks, _tracks)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -237,7 +245,9 @@ class _$AudioPlayerStateImpl extends _AudioPlayerState { currentIndex, const DeepCollectionEquality().hash(_tracks)); - @JsonKey(ignore: true) + /// Create a copy of AudioPlayerState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => @@ -277,8 +287,11 @@ 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(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/provider/download_manager_provider.dart b/lib/provider/download_manager_provider.dart index d7f28b67..0ca99ec1 100644 --- a/lib/provider/download_manager_provider.dart +++ b/lib/provider/download_manager_provider.dart @@ -1,277 +1,285 @@ import 'dart:async'; import 'dart:io'; -import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/server/track_sources.dart'; -import 'package:spotube/services/logger/logger.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:dio/dio.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:path/path.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart' hide join; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/components/dialogs/replace_downloaded_dialog.dart'; +import 'package:spotube/extensions/dio.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/audio_source/quality_presets.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/services/download_manager/download_manager.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:spotube/utils/primitive_utils.dart'; +import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/utils/service_utils.dart'; -class DownloadManagerProvider extends ChangeNotifier { - DownloadManagerProvider({required this.ref}) - : $history = {}, - $backHistory = {}, - dl = DownloadManager() { - dl.statusStream.listen((event) async { - try { - final (:request, :status) = event; +enum DownloadStatus { + queued, + downloading, + completed, + failed, + canceled, +} - final sourcedTrack = $history.firstWhereOrNull( - (element) => element.getUrlOfCodec(downloadCodec) == request.url, - ); - if (sourcedTrack == null) return; - final track = $backHistory.firstWhereOrNull( - (element) => element.id == sourcedTrack.query.id, - ); - if (track == null) return; +class DownloadTask { + final SpotubeFullTrackObject track; + final DownloadStatus status; + final CancelToken cancelToken; + final int? totalSizeBytes; + final StreamController _downloadedBytesStreamController; - final savePath = getTrackFileUrl(sourcedTrack); - // related to onFileExists - final oldFile = File("$savePath.old"); + Stream get downloadedBytesStream => + _downloadedBytesStreamController.stream; - // if download failed and old file exists, rename it back - if ((status == DownloadStatus.failed || - status == DownloadStatus.canceled) && - await oldFile.exists()) { - await oldFile.rename(savePath); - } - if (status != DownloadStatus.completed || - //? WebA audiotagging is not supported yet - //? Although in future by converting weba to opus & then tagging it - //? is possible using vorbis comments - downloadCodec == SourceCodecs.weba) { - return; - } + DownloadTask({ + required this.track, + required this.status, + required this.cancelToken, + this.totalSizeBytes, + StreamController? downloadedBytesStreamController, + }) : _downloadedBytesStreamController = + downloadedBytesStreamController ?? StreamController.broadcast(); - final file = File(request.path); - - if (await oldFile.exists()) { - await oldFile.delete(); - } - - final imageBytes = await ServiceUtils.downloadImage( - (track.album.images).asUrlString( - placeholder: ImagePlaceholder.albumArt, - index: 1, - ), - ); - - final metadata = track.toMetadata( - fileLength: await file.length(), - imageBytes: imageBytes, - ); - - await MetadataGod.writeMetadata( - file: file.path, - metadata: metadata, - ); - } catch (e, stack) { - AppLogger.reportError(e, stack); - } - }); - } - - Future Function(SpotubeFullTrackObject track) onFileExists = - (SpotubeFullTrackObject track) async => true; - - final Ref ref; - - String get downloadDirectory => - ref.read(userPreferencesProvider.select((s) => s.downloadLocation)); - SourceCodecs get downloadCodec => - ref.read(userPreferencesProvider.select((s) => s.downloadMusicCodec)); - - int get $downloadCount => dl - .getAllDownloads() - .where( - (download) => - download.status.value == DownloadStatus.downloading || - download.status.value == DownloadStatus.paused || - download.status.value == DownloadStatus.queued, - ) - .length; - - final Set $history; - // these are the tracks which metadata hasn't been fetched yet - final Set $backHistory; - final DownloadManager dl; - - String getTrackFileUrl(SourcedTrack track) { - final name = - "${track.query.title} - ${track.query.artists.join(", ")}.${downloadCodec.name}"; - return join(downloadDirectory, PrimitiveUtils.toSafeFileName(name)); - } - - bool isActive(SpotubeFullTrackObject track) { - if ($backHistory.contains(track)) return true; - - final sourcedTrack = $history.firstWhereOrNull( - (element) => element.query.id == track.id, + DownloadTask copyWith({ + SpotubeFullTrackObject? track, + DownloadStatus? status, + CancelToken? cancelToken, + int? totalSizeBytes, + StreamController? downloadedBytesStreamController, + }) { + return DownloadTask( + track: track ?? this.track, + status: status ?? this.status, + cancelToken: cancelToken ?? this.cancelToken, + totalSizeBytes: totalSizeBytes ?? this.totalSizeBytes, + downloadedBytesStreamController: + downloadedBytesStreamController ?? _downloadedBytesStreamController, ); - - if (sourcedTrack == null) return false; - - return dl - .getAllDownloads() - .where( - (download) => - download.status.value == DownloadStatus.downloading || - download.status.value == DownloadStatus.paused || - download.status.value == DownloadStatus.queued, - ) - .map((e) => e.request.url) - .contains(sourcedTrack.getUrlOfCodec(downloadCodec)!); - } - - /// For singular downloads - Future addToQueue(SpotubeFullTrackObject track) async { - final sourcedTrack = await ref.read( - trackSourcesProvider(TrackSourceQuery.fromTrack(track)).future, - ); - - final savePath = getTrackFileUrl(sourcedTrack); - - final oldFile = File(savePath); - if (await oldFile.exists() && !await onFileExists(track)) { - return; - } - - if (await oldFile.exists()) { - await oldFile.rename("$savePath.old"); - } - - if (sourcedTrack.codec == downloadCodec) { - final downloadTask = await dl.addDownload( - sourcedTrack.getUrlOfCodec(downloadCodec)!, - savePath, - ); - if (downloadTask != null) { - $history.add(sourcedTrack); - } - } else { - $backHistory.add(track); - final sourcedTrack = await ref - .read( - trackSourcesProvider( - TrackSourceQuery.fromTrack(track), - ).future, - ) - .then((d) { - $backHistory.remove(track); - return d; - }); - final downloadTask = await dl.addDownload( - sourcedTrack.getUrlOfCodec(downloadCodec)!, - savePath, - ); - if (downloadTask != null) { - $history.add(sourcedTrack); - } - } - - notifyListeners(); - } - - Future batchAddToQueue(List tracks) async { - $backHistory.addAll(tracks); - notifyListeners(); - for (final track in tracks) { - try { - if (track == tracks.first) { - await addToQueue(track); - } else { - await Future.delayed( - const Duration(seconds: 1), - () => addToQueue(track), - ); - } - } catch (e) { - AppLogger.reportError(e, StackTrace.current); - continue; - } - } - } - - Future removeFromQueue(SpotubeFullTrackObject track) async { - final sourcedTrack = await mapToSourcedTrack(track); - 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)!); - } - - Future resume(SpotubeFullTrackObject track) async { - final sourcedTrack = await mapToSourcedTrack(track); - return dl.resumeDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!); - } - - Future retry(SpotubeFullTrackObject track) { - return addToQueue(track); - } - - void cancel(SpotubeFullTrackObject track) async { - final sourcedTrack = await mapToSourcedTrack(track); - return dl.cancelDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!); - } - - void cancelAll() { - for (final download in dl.getAllDownloads()) { - if (download.status.value == DownloadStatus.completed) continue; - dl.cancelDownload(download.request.url); - } - } - - Future mapToSourcedTrack(SpotubeFullTrackObject track) async { - final historicTrack = - $history.firstWhereOrNull((element) => element.query.id == track.id); - - if (historicTrack != null) { - return historicTrack; - } - - final sourcedTrack = await ref.read( - trackSourcesProvider(TrackSourceQuery.fromTrack(track)).future, - ); - - return sourcedTrack; - } - - ValueNotifier? getStatusNotifier( - SpotubeFullTrackObject track, - ) { - final sourcedTrack = $history.firstWhereOrNull( - (element) => element.query.id == track.id, - ); - if (sourcedTrack == null) { - return null; - } - return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!)?.status; - } - - ValueNotifier? getProgressNotifier(SpotubeFullTrackObject track) { - final sourcedTrack = $history.firstWhereOrNull( - (element) => element.query.id == track.id, - ); - if (sourcedTrack == null) { - return null; - } - return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec)!)?.progress; } } -final downloadManagerProvider = ChangeNotifierProvider( - (ref) => DownloadManagerProvider(ref: ref), +class DownloadManagerNotifier extends Notifier> { + final Dio dio; + DownloadManagerNotifier() + : dio = Dio(), + super(); + + @override + build() { + ref.onDispose(() { + for (final task in state) { + if (task.status == DownloadStatus.downloading) { + task.cancelToken.cancel(); + } + task._downloadedBytesStreamController.close(); + } + }); + + return []; + } + + DownloadTask? getTaskByTrackId(String trackId) { + return state.firstWhereOrNull((element) => element.track.id == trackId); + } + + void addToQueue(SpotubeFullTrackObject track) { + if (state.any((element) => element.track.id == track.id)) return; + state = [ + ...state, + DownloadTask( + track: track, + status: DownloadStatus.queued, + cancelToken: CancelToken(), + ), + ]; + + ref.read(sourcedTrackProvider(track)); + + _startDownloading(); // No await should be invoked to avoid stuck UI + } + + void addAllToQueue(List tracks) { + state = [ + ...state, + ...tracks.map((e) => DownloadTask( + track: e, + status: DownloadStatus.queued, + cancelToken: CancelToken(), + )), + ]; + + ref.read(sourcedTrackProvider(tracks.first)); + _startDownloading(); // No await should be invoked to avoid stuck UI + } + + void retry(SpotubeFullTrackObject track) { + if (state.firstWhereOrNull((e) => e.track.id == track.id)?.status + case DownloadStatus.canceled || DownloadStatus.failed) { + _setStatus(track, DownloadStatus.queued); + _startDownloading(); // No await should be invoked to avoid stuck UI + } + } + + void cancel(SpotubeFullTrackObject track) { + if (state.firstWhereOrNull((e) => e.track.id == track.id)?.status == + DownloadStatus.failed) { + return; + } + _setStatus(track, DownloadStatus.canceled); + } + + void clearAll() { + for (final task in state) { + if (task.status == DownloadStatus.downloading) { + task.cancelToken.cancel(); + } + } + state = []; + } + + void _setStatus(SpotubeFullTrackObject track, DownloadStatus status) { + state = state.map((e) { + if (e.track.id == track.id) { + if ((status == DownloadStatus.canceled) && e.cancelToken.isCancelled) { + e.cancelToken.cancel(); + } + + return e.copyWith(status: status); + } + return e; + }).toList(); + } + + bool _isShowingDialog = false; + + Future _shouldReplaceFileOnExist(DownloadTask task) async { + if (rootNavigatorKey.currentContext == null || _isShowingDialog) { + return false; + } + final replaceAll = ref.read(replaceDownloadedFileState); + if (replaceAll != null) return replaceAll; + _isShowingDialog = true; + try { + return await showDialog( + context: rootNavigatorKey.currentContext!, + builder: (context) => ReplaceDownloadedDialog( + track: task.track, + ), + ) ?? + false; + } finally { + _isShowingDialog = false; + } + } + + Future _downloadTrack(DownloadTask task) async { + try { + _setStatus(task.track, DownloadStatus.downloading); + final track = await ref.read(sourcedTrackProvider(task.track).future); + if (task.cancelToken.isCancelled) { + _setStatus(task.track, DownloadStatus.canceled); + } + final presets = ref.read(audioSourcePresetsProvider); + final container = + presets.presets[presets.selectedDownloadingContainerIndex]; + final downloadLocation = ref.read( + userPreferencesProvider.select((value) => value.downloadLocation)); + + final url = track.getUrlOfQuality( + container, + presets.selectedDownloadingQualityIndex, + ); + + if (url == null) { + throw Exception("No download URL found for selected codec"); + } + + final savePath = join( + downloadLocation, + ServiceUtils.sanitizeFilename( + "${track.query.name} - ${track.query.artists.map((e) => e.name).join(", ")}.${container.getFileExtension()}", + ), + ); + + final savePathFile = File(savePath); + if (await savePathFile.exists()) { + // dio automatically replaces the file if it exists so no deletion required + if (!await _shouldReplaceFileOnExist(task)) { + _setStatus(track.query, DownloadStatus.completed); + return; + } + } + + final response = await dio.chunkDownload( + url, + savePath, + cancelToken: task.cancelToken, + onReceiveProgress: (count, total) { + if (task.totalSizeBytes == null) { + state = state.map((e) { + if (e.track.id == track.query.id) { + return e.copyWith(totalSizeBytes: total); + } + return e; + }).toList(); + } + task._downloadedBytesStreamController.add(count); + }, + deleteOnError: true, + fileAccessMode: FileAccessMode.write, + ); + if (response.statusCode != null && response.statusCode! < 400) { + _setStatus(track.query, DownloadStatus.completed); + } else { + _setStatus(track.query, DownloadStatus.failed); + return; + } + + if (container.getFileExtension() == "weba") return; + + final imageBytes = await ServiceUtils.downloadImage( + (task.track.album.images).asUrlString( + placeholder: ImagePlaceholder.albumArt, + index: 1, + ), + ); + await MetadataGod.writeMetadata( + file: savePath, + metadata: task.track.toMetadata( + fileLength: await savePathFile.length(), + imageBytes: imageBytes, + ), + ); + } catch (e, stack) { + if (e is! DioException || e.type != DioExceptionType.cancel) { + _setStatus(task.track, DownloadStatus.failed); + AppLogger.reportError(e, stack); + } + } + } + + Future _startDownloading() async { + for (final task in state) { + if (task.status == DownloadStatus.downloading) return; + + if (task.status == DownloadStatus.queued) { + try { + await _downloadTrack(task); + } finally { + // After completion, check for more queued tasks + // Ignore errors of the prior task to allow next task to complete + await _startDownloading(); + } + } + } + } +} + +final downloadManagerProvider = + NotifierProvider>( + DownloadManagerNotifier.new, ); diff --git a/lib/provider/history/summary.dart b/lib/provider/history/summary.dart index 99df4c11..5ced7559 100644 --- a/lib/provider/history/summary.dart +++ b/lib/provider/history/summary.dart @@ -53,7 +53,7 @@ class PlaybackHistorySummaryNotifier database.historyTable.itemId.count(distinct: true); final itemIdCountingCol = database.historyTable.itemId.count(); final durationSumJsonColumn = - database.historyTable.data.jsonExtract(r"$.duration_ms").sum(); + database.historyTable.data.jsonExtract(r"$.durationMs").sum(); final artistCountingCol = database.historyTable.data.jsonExtract(r"$.artists"); diff --git a/lib/provider/metadata_plugin/album/album.dart b/lib/provider/metadata_plugin/album/album.dart index 3a386236..394f6eb0 100644 --- a/lib/provider/metadata_plugin/album/album.dart +++ b/lib/provider/metadata_plugin/album/album.dart @@ -12,7 +12,7 @@ final metadataPluginAlbumProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } return metadataPlugin.album.getAlbum(id); diff --git a/lib/provider/metadata_plugin/artist/artist.dart b/lib/provider/metadata_plugin/artist/artist.dart index f1691657..e66309d4 100644 --- a/lib/provider/metadata_plugin/artist/artist.dart +++ b/lib/provider/metadata_plugin/artist/artist.dart @@ -12,7 +12,7 @@ final metadataPluginArtistProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } return metadataPlugin.artist.getArtist(artistId); diff --git a/lib/provider/metadata_plugin/audio_source/quality_label.dart b/lib/provider/metadata_plugin/audio_source/quality_label.dart new file mode 100644 index 00000000..113ed54e --- /dev/null +++ b/lib/provider/metadata_plugin/audio_source/quality_label.dart @@ -0,0 +1,12 @@ +import 'package:riverpod/riverpod.dart'; +import 'package:spotube/provider/metadata_plugin/audio_source/quality_presets.dart'; + +final audioSourceQualityLabelProvider = Provider((ref) { + final sourceQuality = ref.watch(audioSourcePresetsProvider); + final sourceContainer = sourceQuality.presets + .elementAtOrNull(sourceQuality.selectedStreamingContainerIndex); + final quality = sourceContainer?.qualities + .elementAtOrNull(sourceQuality.selectedStreamingQualityIndex); + + return "${sourceContainer?.name ?? "Unknown"} • ${quality?.toString() ?? "Unknown"}"; +}); diff --git a/lib/provider/metadata_plugin/audio_source/quality_presets.dart b/lib/provider/metadata_plugin/audio_source/quality_presets.dart new file mode 100644 index 00000000..ba88fed6 --- /dev/null +++ b/lib/provider/metadata_plugin/audio_source/quality_presets.dart @@ -0,0 +1,132 @@ +import 'dart:convert'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; +import 'package:spotube/services/metadata/metadata.dart'; + +part 'quality_presets.g.dart'; +part 'quality_presets.freezed.dart'; + +@freezed +class AudioSourcePresetsState with _$AudioSourcePresetsState { + factory AudioSourcePresetsState({ + @Default([]) final List presets, + @Default(0) final int selectedStreamingQualityIndex, + @Default(0) final int selectedStreamingContainerIndex, + @Default(0) final int selectedDownloadingQualityIndex, + @Default(0) final int selectedDownloadingContainerIndex, + }) = _AudioSourcePresetsState; + + factory AudioSourcePresetsState.fromJson(Map json) => + _$AudioSourcePresetsStateFromJson(json); +} + +class AudioSourceAvailableQualityPresetsNotifier + extends Notifier { + @override + build() { + final audioSourceSnapshot = ref.watch(audioSourcePluginProvider); + final audioSourceConfigSnapshot = ref.watch( + metadataPluginsProvider.select((data) => + data.whenData((value) => value.defaultAudioSourcePluginConfig)), + ); + + _initialize(audioSourceSnapshot, audioSourceConfigSnapshot); + + listenSelf((previous, next) { + final isNewLossless = + next.presets.elementAtOrNull(next.selectedStreamingContainerIndex) + is SpotubeAudioSourceContainerPresetLossless; + final isOldLossless = previous?.presets + .elementAtOrNull(previous.selectedStreamingContainerIndex) + is SpotubeAudioSourceContainerPresetLossless; + if (!isOldLossless && isNewLossless) { + audioPlayer.setDemuxerBufferSize(6 * 1024 * 1024); // 6MB + } else if (isOldLossless && !isNewLossless) { + audioPlayer.setDemuxerBufferSize(4 * 1024 * 1024); // 4MB + } + }); + + return AudioSourcePresetsState(); + } + + void _initialize( + AsyncValue audioSourceSnapshot, + AsyncValue audioSourceConfigSnapshot, + ) async { + audioSourceConfigSnapshot.whenData((audioSourceConfig) { + audioSourceSnapshot.whenData((audioSource) async { + if (audioSource == null || audioSourceConfig == null) { + throw MetadataPluginException.noDefaultAudioSourcePlugin(); + } + final preferences = await SharedPreferences.getInstance(); + final persistedStateStr = + preferences.getString("audioSourceState-${audioSourceConfig.slug}"); + + if (persistedStateStr != null) { + state = + AudioSourcePresetsState.fromJson(jsonDecode(persistedStateStr)) + .copyWith( + presets: audioSource.audioSource.supportedPresets, + ); + } else { + state = AudioSourcePresetsState( + presets: audioSource.audioSource.supportedPresets, + ); + } + }); + }); + } + + void setSelectedStreamingContainerIndex(int index) { + state = state.copyWith( + selectedStreamingContainerIndex: index, + selectedStreamingQualityIndex: + 0, // Resetting both because it's a different quality + ); + _updatePreferences(); + } + + void setSelectedStreamingQualityIndex(int index) { + state = state.copyWith(selectedStreamingQualityIndex: index); + _updatePreferences(); + } + + void setSelectedDownloadingContainerIndex(int index) { + state = state.copyWith( + selectedDownloadingContainerIndex: index, + selectedDownloadingQualityIndex: + 0, // Resetting both because it's a different quality + ); + _updatePreferences(); + } + + void setSelectedDownloadingQualityIndex(int index) { + state = state.copyWith(selectedDownloadingQualityIndex: index); + _updatePreferences(); + } + + void _updatePreferences() async { + final audioSourceConfig = await ref.read(metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig)); + if (audioSourceConfig == null) { + throw MetadataPluginException.noDefaultAudioSourcePlugin(); + } + + final preferences = await SharedPreferences.getInstance(); + await preferences.setString( + "audioSourceState-${audioSourceConfig.slug}", + jsonEncode(state), + ); + } +} + +final audioSourcePresetsProvider = NotifierProvider< + AudioSourceAvailableQualityPresetsNotifier, AudioSourcePresetsState>( + () => AudioSourceAvailableQualityPresetsNotifier(), +); diff --git a/lib/provider/metadata_plugin/audio_source/quality_presets.freezed.dart b/lib/provider/metadata_plugin/audio_source/quality_presets.freezed.dart new file mode 100644 index 00000000..a8e0c9f7 --- /dev/null +++ b/lib/provider/metadata_plugin/audio_source/quality_presets.freezed.dart @@ -0,0 +1,289 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'quality_presets.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +AudioSourcePresetsState _$AudioSourcePresetsStateFromJson( + Map json) { + return _AudioSourcePresetsState.fromJson(json); +} + +/// @nodoc +mixin _$AudioSourcePresetsState { + List get presets => + throw _privateConstructorUsedError; + int get selectedStreamingQualityIndex => throw _privateConstructorUsedError; + int get selectedStreamingContainerIndex => throw _privateConstructorUsedError; + int get selectedDownloadingQualityIndex => throw _privateConstructorUsedError; + int get selectedDownloadingContainerIndex => + throw _privateConstructorUsedError; + + /// Serializes this AudioSourcePresetsState to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of AudioSourcePresetsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AudioSourcePresetsStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AudioSourcePresetsStateCopyWith<$Res> { + factory $AudioSourcePresetsStateCopyWith(AudioSourcePresetsState value, + $Res Function(AudioSourcePresetsState) then) = + _$AudioSourcePresetsStateCopyWithImpl<$Res, AudioSourcePresetsState>; + @useResult + $Res call( + {List presets, + int selectedStreamingQualityIndex, + int selectedStreamingContainerIndex, + int selectedDownloadingQualityIndex, + int selectedDownloadingContainerIndex}); +} + +/// @nodoc +class _$AudioSourcePresetsStateCopyWithImpl<$Res, + $Val extends AudioSourcePresetsState> + implements $AudioSourcePresetsStateCopyWith<$Res> { + _$AudioSourcePresetsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AudioSourcePresetsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? presets = null, + Object? selectedStreamingQualityIndex = null, + Object? selectedStreamingContainerIndex = null, + Object? selectedDownloadingQualityIndex = null, + Object? selectedDownloadingContainerIndex = null, + }) { + return _then(_value.copyWith( + presets: null == presets + ? _value.presets + : presets // ignore: cast_nullable_to_non_nullable + as List, + selectedStreamingQualityIndex: null == selectedStreamingQualityIndex + ? _value.selectedStreamingQualityIndex + : selectedStreamingQualityIndex // ignore: cast_nullable_to_non_nullable + as int, + selectedStreamingContainerIndex: null == selectedStreamingContainerIndex + ? _value.selectedStreamingContainerIndex + : selectedStreamingContainerIndex // ignore: cast_nullable_to_non_nullable + as int, + selectedDownloadingQualityIndex: null == selectedDownloadingQualityIndex + ? _value.selectedDownloadingQualityIndex + : selectedDownloadingQualityIndex // ignore: cast_nullable_to_non_nullable + as int, + selectedDownloadingContainerIndex: null == + selectedDownloadingContainerIndex + ? _value.selectedDownloadingContainerIndex + : selectedDownloadingContainerIndex // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AudioSourcePresetsStateImplCopyWith<$Res> + implements $AudioSourcePresetsStateCopyWith<$Res> { + factory _$$AudioSourcePresetsStateImplCopyWith( + _$AudioSourcePresetsStateImpl value, + $Res Function(_$AudioSourcePresetsStateImpl) then) = + __$$AudioSourcePresetsStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List presets, + int selectedStreamingQualityIndex, + int selectedStreamingContainerIndex, + int selectedDownloadingQualityIndex, + int selectedDownloadingContainerIndex}); +} + +/// @nodoc +class __$$AudioSourcePresetsStateImplCopyWithImpl<$Res> + extends _$AudioSourcePresetsStateCopyWithImpl<$Res, + _$AudioSourcePresetsStateImpl> + implements _$$AudioSourcePresetsStateImplCopyWith<$Res> { + __$$AudioSourcePresetsStateImplCopyWithImpl( + _$AudioSourcePresetsStateImpl _value, + $Res Function(_$AudioSourcePresetsStateImpl) _then) + : super(_value, _then); + + /// Create a copy of AudioSourcePresetsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? presets = null, + Object? selectedStreamingQualityIndex = null, + Object? selectedStreamingContainerIndex = null, + Object? selectedDownloadingQualityIndex = null, + Object? selectedDownloadingContainerIndex = null, + }) { + return _then(_$AudioSourcePresetsStateImpl( + presets: null == presets + ? _value._presets + : presets // ignore: cast_nullable_to_non_nullable + as List, + selectedStreamingQualityIndex: null == selectedStreamingQualityIndex + ? _value.selectedStreamingQualityIndex + : selectedStreamingQualityIndex // ignore: cast_nullable_to_non_nullable + as int, + selectedStreamingContainerIndex: null == selectedStreamingContainerIndex + ? _value.selectedStreamingContainerIndex + : selectedStreamingContainerIndex // ignore: cast_nullable_to_non_nullable + as int, + selectedDownloadingQualityIndex: null == selectedDownloadingQualityIndex + ? _value.selectedDownloadingQualityIndex + : selectedDownloadingQualityIndex // ignore: cast_nullable_to_non_nullable + as int, + selectedDownloadingContainerIndex: null == + selectedDownloadingContainerIndex + ? _value.selectedDownloadingContainerIndex + : selectedDownloadingContainerIndex // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AudioSourcePresetsStateImpl implements _AudioSourcePresetsState { + _$AudioSourcePresetsStateImpl( + {final List presets = const [], + this.selectedStreamingQualityIndex = 0, + this.selectedStreamingContainerIndex = 0, + this.selectedDownloadingQualityIndex = 0, + this.selectedDownloadingContainerIndex = 0}) + : _presets = presets; + + factory _$AudioSourcePresetsStateImpl.fromJson(Map json) => + _$$AudioSourcePresetsStateImplFromJson(json); + + final List _presets; + @override + @JsonKey() + List get presets { + if (_presets is EqualUnmodifiableListView) return _presets; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_presets); + } + + @override + @JsonKey() + final int selectedStreamingQualityIndex; + @override + @JsonKey() + final int selectedStreamingContainerIndex; + @override + @JsonKey() + final int selectedDownloadingQualityIndex; + @override + @JsonKey() + final int selectedDownloadingContainerIndex; + + @override + String toString() { + return 'AudioSourcePresetsState(presets: $presets, selectedStreamingQualityIndex: $selectedStreamingQualityIndex, selectedStreamingContainerIndex: $selectedStreamingContainerIndex, selectedDownloadingQualityIndex: $selectedDownloadingQualityIndex, selectedDownloadingContainerIndex: $selectedDownloadingContainerIndex)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AudioSourcePresetsStateImpl && + const DeepCollectionEquality().equals(other._presets, _presets) && + (identical(other.selectedStreamingQualityIndex, + selectedStreamingQualityIndex) || + other.selectedStreamingQualityIndex == + selectedStreamingQualityIndex) && + (identical(other.selectedStreamingContainerIndex, + selectedStreamingContainerIndex) || + other.selectedStreamingContainerIndex == + selectedStreamingContainerIndex) && + (identical(other.selectedDownloadingQualityIndex, + selectedDownloadingQualityIndex) || + other.selectedDownloadingQualityIndex == + selectedDownloadingQualityIndex) && + (identical(other.selectedDownloadingContainerIndex, + selectedDownloadingContainerIndex) || + other.selectedDownloadingContainerIndex == + selectedDownloadingContainerIndex)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_presets), + selectedStreamingQualityIndex, + selectedStreamingContainerIndex, + selectedDownloadingQualityIndex, + selectedDownloadingContainerIndex); + + /// Create a copy of AudioSourcePresetsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AudioSourcePresetsStateImplCopyWith<_$AudioSourcePresetsStateImpl> + get copyWith => __$$AudioSourcePresetsStateImplCopyWithImpl< + _$AudioSourcePresetsStateImpl>(this, _$identity); + + @override + Map toJson() { + return _$$AudioSourcePresetsStateImplToJson( + this, + ); + } +} + +abstract class _AudioSourcePresetsState implements AudioSourcePresetsState { + factory _AudioSourcePresetsState( + {final List presets, + final int selectedStreamingQualityIndex, + final int selectedStreamingContainerIndex, + final int selectedDownloadingQualityIndex, + final int selectedDownloadingContainerIndex}) = + _$AudioSourcePresetsStateImpl; + + factory _AudioSourcePresetsState.fromJson(Map json) = + _$AudioSourcePresetsStateImpl.fromJson; + + @override + List get presets; + @override + int get selectedStreamingQualityIndex; + @override + int get selectedStreamingContainerIndex; + @override + int get selectedDownloadingQualityIndex; + @override + int get selectedDownloadingContainerIndex; + + /// Create a copy of AudioSourcePresetsState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AudioSourcePresetsStateImplCopyWith<_$AudioSourcePresetsStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/provider/metadata_plugin/audio_source/quality_presets.g.dart b/lib/provider/metadata_plugin/audio_source/quality_presets.g.dart new file mode 100644 index 00000000..f3d8fd41 --- /dev/null +++ b/lib/provider/metadata_plugin/audio_source/quality_presets.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'quality_presets.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AudioSourcePresetsStateImpl _$$AudioSourcePresetsStateImplFromJson( + Map json) => + _$AudioSourcePresetsStateImpl( + presets: (json['presets'] as List?) + ?.map((e) => SpotubeAudioSourceContainerPreset.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + selectedStreamingQualityIndex: + (json['selectedStreamingQualityIndex'] as num?)?.toInt() ?? 0, + selectedStreamingContainerIndex: + (json['selectedStreamingContainerIndex'] as num?)?.toInt() ?? 0, + selectedDownloadingQualityIndex: + (json['selectedDownloadingQualityIndex'] as num?)?.toInt() ?? 0, + selectedDownloadingContainerIndex: + (json['selectedDownloadingContainerIndex'] as num?)?.toInt() ?? 0, + ); + +Map _$$AudioSourcePresetsStateImplToJson( + _$AudioSourcePresetsStateImpl instance) => + { + 'presets': instance.presets.map((e) => e.toJson()).toList(), + 'selectedStreamingQualityIndex': instance.selectedStreamingQualityIndex, + 'selectedStreamingContainerIndex': + instance.selectedStreamingContainerIndex, + 'selectedDownloadingQualityIndex': + instance.selectedDownloadingQualityIndex, + 'selectedDownloadingContainerIndex': + instance.selectedDownloadingContainerIndex, + }; diff --git a/lib/provider/metadata_plugin/core/auth.dart b/lib/provider/metadata_plugin/core/auth.dart index 9aa696fc..dc5e7eb6 100644 --- a/lib/provider/metadata_plugin/core/auth.dart +++ b/lib/provider/metadata_plugin/core/auth.dart @@ -8,7 +8,7 @@ class MetadataPluginAuthenticatedNotifier extends AsyncNotifier { @override FutureOr build() async { final defaultPluginConfig = ref.watch(metadataPluginsProvider); - if (defaultPluginConfig.asData?.value.defaultPluginConfig?.abilities + if (defaultPluginConfig.asData?.value.defaultMetadataPluginConfig?.abilities .contains(PluginAbilities.authentication) != true) { return false; @@ -35,3 +35,36 @@ final metadataPluginAuthenticatedProvider = AsyncNotifierProvider( MetadataPluginAuthenticatedNotifier.new, ); + +class AudioSourcePluginAuthenticatedNotifier extends AsyncNotifier { + @override + FutureOr build() async { + final defaultPluginConfig = ref.watch(metadataPluginsProvider); + if (defaultPluginConfig + .asData?.value.defaultAudioSourcePluginConfig?.abilities + .contains(PluginAbilities.authentication) != + true) { + return false; + } + + final defaultPlugin = await ref.watch(audioSourcePluginProvider.future); + if (defaultPlugin == null) { + return false; + } + + final sub = defaultPlugin.auth.authStateStream.listen((event) { + state = AsyncData(defaultPlugin.auth.isAuthenticated()); + }); + + ref.onDispose(() { + sub.cancel(); + }); + + return defaultPlugin.auth.isAuthenticated(); + } +} + +final audioSourcePluginAuthenticatedProvider = + AsyncNotifierProvider( + AudioSourcePluginAuthenticatedNotifier.new, +); diff --git a/lib/provider/metadata_plugin/core/repositories.dart b/lib/provider/metadata_plugin/core/repositories.dart index 55c11ed2..a78f63d9 100644 --- a/lib/provider/metadata_plugin/core/repositories.dart +++ b/lib/provider/metadata_plugin/core/repositories.dart @@ -49,6 +49,7 @@ class MetadataPluginRepositoriesNotifier owner: repo["owner"]["login"] ?? "", description: repo["description"] ?? "", repoUrl: repo["html_url"] ?? "", + topics: repo["topics"].cast() ?? [], ); }).toList(); diff --git a/lib/provider/metadata_plugin/core/scrobble.dart b/lib/provider/metadata_plugin/core/scrobble.dart index 376572ad..0f8fcc19 100644 --- a/lib/provider/metadata_plugin/core/scrobble.dart +++ b/lib/provider/metadata_plugin/core/scrobble.dart @@ -10,8 +10,10 @@ class MetadataPluginScrobbleNotifier @override build() { final metadataPlugin = ref.watch(metadataPluginProvider); - final pluginConfig = - ref.watch(metadataPluginsProvider).valueOrNull?.defaultPluginConfig; + final pluginConfig = ref + .watch(metadataPluginsProvider) + .valueOrNull + ?.defaultMetadataPluginConfig; if (metadataPlugin.valueOrNull == null || pluginConfig == null || diff --git a/lib/provider/metadata_plugin/core/support.dart b/lib/provider/metadata_plugin/core/support.dart index 88bfbf5c..8864f1b1 100644 --- a/lib/provider/metadata_plugin/core/support.dart +++ b/lib/provider/metadata_plugin/core/support.dart @@ -9,3 +9,13 @@ final metadataPluginSupportTextProvider = FutureProvider((ref) async { } return await metadataPlugin.core.support; }); + +final audioSourcePluginSupportTextProvider = + FutureProvider((ref) async { + final audioSourcePlugin = await ref.watch(audioSourcePluginProvider.future); + + if (audioSourcePlugin == null) { + throw 'No metadata plugin available'; + } + return await audioSourcePlugin.core.support; +}); diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart index 6350d610..5793eb57 100644 --- a/lib/provider/metadata_plugin/library/playlists.dart +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -131,7 +131,7 @@ final metadataPluginIsSavedPlaylistProvider = final plugin = await ref.watch(metadataPluginProvider.future); if (plugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } final savedPlaylists = diff --git a/lib/provider/metadata_plugin/metadata_plugin_provider.dart b/lib/provider/metadata_plugin/metadata_plugin_provider.dart index b61c0255..cdc96c41 100644 --- a/lib/provider/metadata_plugin/metadata_plugin_provider.dart +++ b/lib/provider/metadata_plugin/metadata_plugin_provider.dart @@ -4,12 +4,14 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; import 'package:drift/drift.dart'; +import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.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/metadata/errors/exceptions.dart'; @@ -24,18 +26,28 @@ final allowedDomainsRegex = RegExp( class MetadataPluginState { final List plugins; - final int defaultPlugin; + final int defaultMetadataPlugin; + final int defaultAudioSourcePlugin; const MetadataPluginState({ this.plugins = const [], - this.defaultPlugin = -1, + this.defaultMetadataPlugin = -1, + this.defaultAudioSourcePlugin = -1, }); - PluginConfiguration? get defaultPluginConfig { - if (defaultPlugin < 0 || defaultPlugin >= plugins.length) { + PluginConfiguration? get defaultMetadataPluginConfig { + if (defaultMetadataPlugin < 0 || defaultMetadataPlugin >= plugins.length) { return null; } - return plugins[defaultPlugin]; + return plugins[defaultMetadataPlugin]; + } + + PluginConfiguration? get defaultAudioSourcePluginConfig { + if (defaultAudioSourcePlugin < 0 || + defaultAudioSourcePlugin >= plugins.length) { + return null; + } + return plugins[defaultAudioSourcePlugin]; } factory MetadataPluginState.fromJson(Map json) { @@ -43,24 +55,30 @@ class MetadataPluginState { plugins: (json["plugins"] as List) .map((e) => PluginConfiguration.fromJson(e)) .toList(), - defaultPlugin: json["default_plugin"] ?? -1, + defaultMetadataPlugin: json["default_metadata_plugin"] ?? -1, + defaultAudioSourcePlugin: json['default_audio_source_plugin'], ); } Map toJson() { return { "plugins": plugins.map((e) => e.toJson()).toList(), - "default_plugin": defaultPlugin, + "default_metadata_plugin": defaultMetadataPlugin, + "default_audio_source_plugin": defaultAudioSourcePlugin }; } MetadataPluginState copyWith({ List? plugins, - int? defaultPlugin, + int? defaultMetadataPlugin, + int? defaultAudioSourcePlugin, }) { return MetadataPluginState( plugins: plugins ?? this.plugins, - defaultPlugin: defaultPlugin ?? this.defaultPlugin, + defaultMetadataPlugin: + defaultMetadataPlugin ?? this.defaultMetadataPlugin, + defaultAudioSourcePlugin: + defaultAudioSourcePlugin ?? this.defaultAudioSourcePlugin, ); } } @@ -72,7 +90,7 @@ class MetadataPluginNotifier extends AsyncNotifier { build() async { final database = ref.watch(databaseProvider); - final subscription = database.metadataPluginsTable.select().watch().listen( + final subscription = database.pluginsTable.select().watch().listen( (event) async { state = AsyncValue.data(await toStatePlugins(event)); }, @@ -82,22 +100,26 @@ class MetadataPluginNotifier extends AsyncNotifier { subscription.cancel(); }); - final plugins = await database.metadataPluginsTable.select().get(); + final plugins = await database.pluginsTable.select().get(); - return await toStatePlugins(plugins); + final pluginState = await toStatePlugins(plugins); + + await _loadDefaultPlugins(pluginState); + + return pluginState; } Future toStatePlugins( - List plugins, + List plugins, ) async { - int defaultPlugin = -1; + int defaultMetadataPlugin = -1; + int defaultAudioSourcePlugin = -1; 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, @@ -133,23 +155,66 @@ class MetadataPluginNotifier extends AsyncNotifier { !await pluginJsonFile.exists() || !await pluginBinaryFile.exists()) { // Delete the plugin entry from DB if the plugin files are not there. - await database.metadataPluginsTable.deleteOne(plugin); + await database.pluginsTable.deleteOne(plugin); continue; } pluginConfigs.add(pluginConfig); - if (plugin.selected) { - defaultPlugin = pluginConfigs.length - 1; + if (plugin.selectedForMetadata) { + defaultMetadataPlugin = pluginConfigs.length - 1; + } + if (plugin.selectedForAudioSource) { + defaultAudioSourcePlugin = pluginConfigs.length - 1; } } return MetadataPluginState( plugins: pluginConfigs, - defaultPlugin: defaultPlugin, + defaultMetadataPlugin: defaultMetadataPlugin, + defaultAudioSourcePlugin: defaultAudioSourcePlugin, ); } + Future _loadDefaultPlugins(MetadataPluginState pluginState) async { + const plugins = [ + "spotube-plugin-musicbrainz-listenbrainz", + "spotube-plugin-youtube-audio", + ]; + + for (final plugin in plugins) { + final byteData = await rootBundle.load( + "assets/plugins/$plugin/plugin.smplug", + ); + final pluginConfig = + await extractPluginArchive(byteData.buffer.asUint8List()); + try { + await addPlugin(pluginConfig); + } on MetadataPluginException catch (e) { + if (e.errorCode == MetadataPluginErrorCode.duplicatePlugin && + await isPluginUpdate(pluginConfig)) { + final oldConfig = pluginState.plugins + .firstWhereOrNull((p) => p.slug == pluginConfig.slug); + if (oldConfig == null) continue; + final isDefaultMetadata = + oldConfig == pluginState.defaultMetadataPluginConfig; + final isDefaultAudioSource = + oldConfig == pluginState.defaultAudioSourcePluginConfig; + + await removePlugin(pluginConfig); + await addPlugin(pluginConfig); + + if (isDefaultMetadata) { + await setDefaultMetadataPlugin(pluginConfig); + } + if (isDefaultAudioSource) { + await setDefaultAudioSourcePlugin(pluginConfig); + } + } + } + } + } + Uri _getGithubReleasesUrl(String repoUrl) { final parsedUri = Uri.parse(repoUrl); final uri = parsedUri.replace( @@ -214,7 +279,7 @@ class MetadataPluginNotifier extends AsyncNotifier { /// Root directory where all metadata plugins are stored. Future _getPluginRootDir() async => Directory( join( - (await getApplicationCacheDirectory()).path, + (await getApplicationSupportDirectory()).path, "metadata-plugins", ), ); @@ -327,7 +392,7 @@ class MetadataPluginNotifier extends AsyncNotifier { Future addPlugin(PluginConfiguration plugin) async { _assertPluginApiCompatibility(plugin); - final pluginRes = await (database.metadataPluginsTable.select() + final pluginRes = await (database.pluginsTable.select() ..where( (tbl) => tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author), @@ -339,8 +404,8 @@ class MetadataPluginNotifier extends AsyncNotifier { throw MetadataPluginException.duplicatePlugin(); } - await database.metadataPluginsTable.insertOne( - MetadataPluginsTableCompanion.insert( + await database.pluginsTable.insertOne( + PluginsTableCompanion.insert( name: plugin.name, author: plugin.author, description: plugin.description, @@ -350,6 +415,23 @@ class MetadataPluginNotifier extends AsyncNotifier { abilities: plugin.abilities.map((e) => e.name).toList(), pluginApiVersion: Value(plugin.pluginApiVersion), repository: Value(plugin.repository), + // Setting the very first plugin as the default plugin + selectedForMetadata: Value( + (state.valueOrNull?.plugins + .where( + (d) => d.abilities.contains(PluginAbilities.metadata)) + .isEmpty ?? + true) && + plugin.abilities.contains(PluginAbilities.metadata), + ), + selectedForAudioSource: Value( + (state.valueOrNull?.plugins + .where((d) => + d.abilities.contains(PluginAbilities.audioSource)) + .isEmpty ?? + true) && + plugin.abilities.contains(PluginAbilities.audioSource), + ), ), ); } @@ -360,15 +442,65 @@ class MetadataPluginNotifier extends AsyncNotifier { if (pluginExtractionDir.existsSync()) { await pluginExtractionDir.delete(recursive: true); } - await database.metadataPluginsTable.deleteWhere((tbl) => + await database.pluginsTable.deleteWhere((tbl) => tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author)); + + // Same here, if the removed plugin is the default plugin + // set the first available plugin as the default plugin + // only when there is 1 remaining plugin + if (state.valueOrNull?.defaultMetadataPluginConfig == plugin) { + final remainingPlugins = state.valueOrNull?.plugins.where( + (p) => + p != plugin && p.abilities.contains(PluginAbilities.metadata), + ) ?? + []; + if (remainingPlugins.length == 1) { + await setDefaultMetadataPlugin(remainingPlugins.first); + } + } + + if (state.valueOrNull?.defaultAudioSourcePluginConfig == plugin) { + final remainingPlugins = state.valueOrNull?.plugins.where( + (p) => + p != plugin && + p.abilities.contains(PluginAbilities.audioSource), + ) ?? + []; + if (remainingPlugins.length == 1) { + await setDefaultAudioSourcePlugin(remainingPlugins.first); + } + } + } + + Future isPluginUpdate(PluginConfiguration newPlugin) async { + final pluginRes = await (database.pluginsTable.select() + ..where( + (tbl) => + tbl.name.equals(newPlugin.name) & + tbl.author.equals(newPlugin.author), + ) + ..limit(1)) + .get(); + + if (pluginRes.isEmpty) { + return false; + } + + final oldPlugin = pluginRes.first; + final oldPluginApiVersion = Version.parse(oldPlugin.pluginApiVersion); + final newPluginApiVersion = Version.parse(newPlugin.pluginApiVersion); + + return newPluginApiVersion > oldPluginApiVersion; } Future updatePlugin( PluginConfiguration plugin, PluginUpdateAvailable update, ) async { - final isDefault = plugin == state.valueOrNull?.defaultPluginConfig; + final isDefaultMetadata = + plugin == state.valueOrNull?.defaultMetadataPluginConfig; + final isDefaultAudioSource = + plugin == state.valueOrNull?.defaultAudioSourcePluginConfig; final pluginUpdatedConfig = await downloadAndCachePlugin(update.downloadUrl); @@ -381,21 +513,46 @@ class MetadataPluginNotifier extends AsyncNotifier { await removePlugin(plugin); await addPlugin(pluginUpdatedConfig); - if (isDefault) { - await setDefaultPlugin(pluginUpdatedConfig); + if (isDefaultMetadata) { + await setDefaultMetadataPlugin(pluginUpdatedConfig); + } + if (isDefaultAudioSource) { + await setDefaultAudioSourcePlugin(pluginUpdatedConfig); } } - Future setDefaultPlugin(PluginConfiguration plugin) async { - await database.metadataPluginsTable - .update() - .write(const MetadataPluginsTableCompanion(selected: Value(false))); + Future setDefaultMetadataPlugin(PluginConfiguration plugin) async { + assert( + plugin.abilities.contains(PluginAbilities.metadata), + "Must be a metadata plugin", + ); - await (database.metadataPluginsTable.update() + await database.pluginsTable + .update() + .write(const PluginsTableCompanion(selectedForMetadata: Value(false))); + + await (database.pluginsTable.update() ..where((tbl) => tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author))) .write( - const MetadataPluginsTableCompanion(selected: Value(true)), + const PluginsTableCompanion(selectedForMetadata: Value(true)), + ); + } + + Future setDefaultAudioSourcePlugin(PluginConfiguration plugin) async { + assert( + plugin.abilities.contains(PluginAbilities.audioSource), + "Must be an audio-source plugin", + ); + + await database.pluginsTable.update().write( + const PluginsTableCompanion(selectedForAudioSource: Value(false))); + + await (database.pluginsTable.update() + ..where((tbl) => + tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author))) + .write( + const PluginsTableCompanion(selectedForAudioSource: Value(true)), ); } @@ -432,8 +589,10 @@ final metadataPluginsProvider = final metadataPluginProvider = FutureProvider( (ref) async { final defaultPlugin = await ref.watch( - metadataPluginsProvider.selectAsync((data) => data.defaultPluginConfig), + metadataPluginsProvider + .selectAsync((data) => data.defaultMetadataPluginConfig), ); + final youtubeEngine = ref.read(youtubeEngineProvider); if (defaultPlugin == null) { return null; @@ -443,6 +602,34 @@ final metadataPluginProvider = FutureProvider( final pluginByteCode = await pluginsNotifier.getPluginByteCode(defaultPlugin); - return await MetadataPlugin.create(defaultPlugin, pluginByteCode); + return await MetadataPlugin.create( + youtubeEngine, + defaultPlugin, + pluginByteCode, + ); + }, +); + +final audioSourcePluginProvider = FutureProvider( + (ref) async { + final defaultPlugin = await ref.watch( + metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig), + ); + final youtubeEngine = ref.watch(youtubeEngineProvider); + + if (defaultPlugin == null) { + return null; + } + + final pluginsNotifier = ref.read(metadataPluginsProvider.notifier); + final pluginByteCode = + await pluginsNotifier.getPluginByteCode(defaultPlugin); + + return await MetadataPlugin.create( + youtubeEngine, + defaultPlugin, + pluginByteCode, + ); }, ); diff --git a/lib/provider/metadata_plugin/playlist/playlist.dart b/lib/provider/metadata_plugin/playlist/playlist.dart index 71062b95..9a41340d 100644 --- a/lib/provider/metadata_plugin/playlist/playlist.dart +++ b/lib/provider/metadata_plugin/playlist/playlist.dart @@ -13,7 +13,7 @@ class MetadataPluginPlaylistNotifier final metadataPlugin = await ref.read(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } return metadataPlugin; diff --git a/lib/provider/metadata_plugin/search/all.dart b/lib/provider/metadata_plugin/search/all.dart index b40ee78a..4b051e58 100644 --- a/lib/provider/metadata_plugin/search/all.dart +++ b/lib/provider/metadata_plugin/search/all.dart @@ -9,7 +9,7 @@ final metadataPluginSearchAllProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } return metadataPlugin.search.all(query); @@ -20,7 +20,7 @@ final metadataPluginSearchChipsProvider = FutureProvider((ref) async { final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } return metadataPlugin.search.chips; }); diff --git a/lib/provider/metadata_plugin/tracks/track.dart b/lib/provider/metadata_plugin/tracks/track.dart index 502780e1..1beac43a 100644 --- a/lib/provider/metadata_plugin/tracks/track.dart +++ b/lib/provider/metadata_plugin/tracks/track.dart @@ -8,7 +8,7 @@ final metadataPluginTrackProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } return metadataPlugin.track.getTrack(trackId); diff --git a/lib/provider/metadata_plugin/updater/update_checker.dart b/lib/provider/metadata_plugin/updater/update_checker.dart index b53ab2b5..6a7dc589 100644 --- a/lib/provider/metadata_plugin/updater/update_checker.dart +++ b/lib/provider/metadata_plugin/updater/update_checker.dart @@ -8,10 +8,25 @@ final metadataPluginUpdateCheckerProvider = final metadataPlugin = await ref.watch(metadataPluginProvider.future); if (metadataPlugin == null || - metadataPluginConfigs.defaultPluginConfig == null) { + metadataPluginConfigs.defaultMetadataPluginConfig == null) { return null; } return metadataPlugin.core - .checkUpdate(metadataPluginConfigs.defaultPluginConfig!); + .checkUpdate(metadataPluginConfigs.defaultMetadataPluginConfig!); +}); + +final audioSourcePluginUpdateCheckerProvider = + FutureProvider((ref) async { + final audioSourcePluginConfigs = + await ref.watch(metadataPluginsProvider.future); + final audioSourcePlugin = await ref.watch(audioSourcePluginProvider.future); + + if (audioSourcePlugin == null || + audioSourcePluginConfigs.defaultAudioSourcePluginConfig == null) { + return null; + } + + return audioSourcePlugin.core + .checkUpdate(audioSourcePluginConfigs.defaultAudioSourcePluginConfig!); }); diff --git a/lib/provider/metadata_plugin/utils/common.dart b/lib/provider/metadata_plugin/utils/common.dart index 087b8a1b..dc56e494 100644 --- a/lib/provider/metadata_plugin/utils/common.dart +++ b/lib/provider/metadata_plugin/utils/common.dart @@ -20,7 +20,7 @@ mixin MetadataPluginMixin final plugin = await ref.read(metadataPluginProvider.future); if (plugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } return plugin; diff --git a/lib/provider/server/active_track_sources.dart b/lib/provider/server/active_track_sources.dart index 5b64dc26..603ca0e4 100644 --- a/lib/provider/server/active_track_sources.dart +++ b/lib/provider/server/active_track_sources.dart @@ -1,14 +1,13 @@ import 'package:hooks_riverpod/hooks_riverpod.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/server/track_sources.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; final activeTrackSourcesProvider = FutureProvider< ({ SourcedTrack? source, - TrackSourcesNotifier? notifier, + SourcedTrackNotifier? notifier, SpotubeTrackObject track, })?>((ref) async { final audioPlayerState = ref.watch(audioPlayerProvider); @@ -25,13 +24,15 @@ final activeTrackSourcesProvider = FutureProvider< ); } - final trackQuery = TrackSourceQuery.fromTrack( - audioPlayerState.activeTrack! as SpotubeFullTrackObject, + final sourcedTrack = await ref.watch( + sourcedTrackProvider( + audioPlayerState.activeTrack! as SpotubeFullTrackObject, + ).future, ); - - final sourcedTrack = await ref.watch(trackSourcesProvider(trackQuery).future); final sourcedTrackNotifier = ref.watch( - trackSourcesProvider(trackQuery).notifier, + sourcedTrackProvider( + audioPlayerState.activeTrack! as SpotubeFullTrackObject, + ).notifier, ); return ( diff --git a/lib/provider/server/router.dart b/lib/provider/server/router.dart index 06ff4a24..f103ea8c 100644 --- a/lib/provider/server/router.dart +++ b/lib/provider/server/router.dart @@ -12,6 +12,7 @@ final serverRouterProvider = Provider((ref) { router.get("/ping", (Request request) => Response.ok("pong")); + router.head("/stream/", playbackRoutes.headStreamTrackId); router.get("/stream/", playbackRoutes.getStreamTrackId); router.get("/playback/toggle-playback", playbackRoutes.togglePlayback); diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index c81d968f..db6bf8f5 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -1,7 +1,7 @@ +import 'dart:async'; import 'dart:io'; import 'dart:math'; -import 'package:collection/collection.dart'; import 'package:dio/dio.dart' hide Response; import 'package:dio/dio.dart' as dio_lib; import 'package:flutter/foundation.dart'; @@ -11,16 +11,14 @@ import 'package:path/path.dart'; import 'package:shelf/shelf.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/parser/range_headers.dart'; -import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/server/active_track_sources.dart'; -import 'package:spotube/provider/server/track_sources.dart'; +import 'package:spotube/provider/server/sourced_track_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.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'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/service_utils.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; @@ -46,49 +44,125 @@ class ServerPlaybackRoutes { ServerPlaybackRoutes(this.ref) : dio = Dio(); - Future<({dio_lib.Response response, Uint8List? bytes})> - streamTrack( + Future _getTrackCacheFilePath(SourcedTrack track) async { + return join( + await UserPreferencesNotifier.getMusicCacheDir(), + ServiceUtils.sanitizeFilename( + '${track.query.name} - ${track.query.artists.map((d) => d.name).join(",")} (${track.info.id}).${track.qualityPreset!.getFileExtension()}', + ), + ); + } + + Future _getSourcedTrack( + Request request, + String trackId, + ) async { + final track = + playlist.tracks.firstWhere((element) => element.id == trackId); + + final activeSourcedTrack = + await ref.read(activeTrackSourcesProvider.future); + + final media = audioPlayer.playlist.medias + .firstWhere((e) => e.uri == request.requestedUri.toString()); + final spotubeMedia = + media is SpotubeMedia ? media : SpotubeMedia.media(media); + final sourcedTrack = activeSourcedTrack?.track.id == track.id + ? activeSourcedTrack?.source + : await ref.read( + sourcedTrackProvider(spotubeMedia.track as SpotubeFullTrackObject) + .future, + ); + + return sourcedTrack; + } + + Future streamTrackInformation( + Request request, + SourcedTrack track, + ) async { + AppLogger.log.i( + "HEAD request for track: ${track.query.name}\n" + "Headers: ${request.headers}", + ); + + final trackCacheFile = File(await _getTrackCacheFilePath(track)); + + if (await trackCacheFile.exists() && userPreferences.cacheMusic) { + final fileLength = await trackCacheFile.length(); + + return dio_lib.Response( + statusCode: 200, + headers: Headers.fromMap({ + "content-type": ["audio/${track.qualityPreset!.name}"], + "content-length": ["$fileLength"], + "accept-ranges": ["bytes"], + "content-range": ["bytes 0-$fileLength/$fileLength"], + }), + requestOptions: RequestOptions(path: request.requestedUri.toString()), + ); + } + + String url = track.url ?? + await ref + .read(sourcedTrackProvider(track.query).notifier) + .swapWithNextSibling() + .then((track) => track.url!); + + final options = Options( + headers: { + "user-agent": _randomUserAgent, + "Cache-Control": "max-age=3600", + "Connection": "keep-alive", + "host": Uri.parse(url).host, + }, + validateStatus: (status) => status! < 400, + ); + + final res = await dio.head(url, options: options); + + return res; + } + + Future streamTrack( Request request, SourcedTrack track, Map headers, ) async { - final trackCacheFile = File( - join( - await UserPreferencesNotifier.getMusicCacheDir(), - ServiceUtils.sanitizeFilename( - '${track.query.title} - ${track.query.artists.join(",")} (${track.info.id}).${track.codec.name}', - ), - ), + AppLogger.log.i( + "GET request for track: ${track.query.name}\n" + "Headers: ${request.headers}", ); + final trackCacheFile = File(await _getTrackCacheFilePath(track)); + if (await trackCacheFile.exists() && userPreferences.cacheMusic) { final bytes = await trackCacheFile.readAsBytes(); final cachedFileLength = bytes.length; - return ( - response: dio_lib.Response( - statusCode: 200, - headers: Headers.fromMap({ - "content-type": ["audio/${track.codec.name}"], - "content-length": ["$cachedFileLength"], - "accept-ranges": ["bytes"], - "content-range": ["bytes 0-$cachedFileLength/$cachedFileLength"], - }), - requestOptions: RequestOptions(path: request.requestedUri.toString()), - ), - bytes: bytes, + return dio_lib.Response( + statusCode: 200, + headers: Headers.fromMap({ + "content-type": ["audio/${track.qualityPreset!.name}"], + "content-length": ["${cachedFileLength - 1}"], + "accept-ranges": ["bytes"], + "content-range": [ + "bytes 0-${cachedFileLength - 1}/$cachedFileLength" + ], + "connection": ["close"], + }), + requestOptions: RequestOptions(path: request.requestedUri.toString()), + data: bytes, ); } - final trackPartialCacheFile = File("${trackCacheFile.path}.part"); - String url = track.url ?? await ref - .read(trackSourcesProvider(track.query).notifier) + .read(sourcedTrackProvider(track.query).notifier) .swapWithNextSibling() .then((track) => track.url!); - var options = Options( + final options = Options( headers: { ...headers, "user-agent": _randomUserAgent, @@ -96,20 +170,20 @@ class ServerPlaybackRoutes { "Connection": "keep-alive", "host": Uri.parse(url).host, }, - responseType: ResponseType.bytes, + responseType: ResponseType.stream, validateStatus: (status) => status! < 400, ); final contentLengthRes = await Future.value( dio.head( url, - options: options, + options: options.copyWith(responseType: ResponseType.bytes), ), ).catchError((e, stack) async { AppLogger.reportError(e, stack); final sourcedTrack = await ref - .read(trackSourcesProvider(track.query).notifier) + .read(sourcedTrackProvider(track.query).notifier) .refreshStreamingUrl(); url = sourcedTrack.url!; @@ -120,126 +194,136 @@ class ServerPlaybackRoutes { // 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, + return 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, ); } - final contentLength = contentLengthRes?.headers.value("content-length"); + final res = await dio.get(url, options: options); - /// Forcing partial content range as mpv sometimes greedily wants - /// everything at one go. Slows down overall streaming. - final range = RangeHeader.parse(headers["range"] ?? ""); - final contentPartialLength = int.tryParse(contentLength ?? ""); - if ((range.end == null) && - contentPartialLength != null && - range.start == 0) { - options = options.copyWith( - headers: { - ...?options.headers, - "range": "$range${(contentPartialLength * 0.3).ceil()}", - }, - ); + AppLogger.log.i( + "Response for track: ${track.query.name}\n" + "Status Code: ${res.statusCode}\n" + "Headers: ${res.headers.map}", + ); + + if (!userPreferences.cacheMusic) { + return res; } - final res = await dio.get(url, options: options); - - final bytes = res.data; - - if (bytes == null || !userPreferences.cacheMusic) { - return (response: res, bytes: bytes); - } - - final contentRange = - ContentRangeHeader.parse(res.headers.value("content-range") ?? ""); + final resStream = res.data!.stream.asBroadcastStream(); + final trackPartialCacheFile = File("${trackCacheFile.path}.part"); if (!await trackPartialCacheFile.exists()) { await trackPartialCacheFile.create(recursive: true); } // Write the stream to the file based on the range - final partialCacheFile = - await trackPartialCacheFile.open(mode: FileMode.writeOnlyAppend); - int fileLength = 0; - try { - await partialCacheFile.setPosition(contentRange.start); - await partialCacheFile.writeFrom(bytes); - fileLength = await partialCacheFile.length(); - } finally { - await partialCacheFile.close(); - } + final partialCacheFileSink = + trackPartialCacheFile.openWrite(mode: FileMode.writeOnlyAppend); + final contentRange = res.headers.value("content-range") != null + ? ContentRangeHeader.parse(res.headers.value("content-range") ?? "") + : ContentRangeHeader(0, 0, 0); - if (fileLength == contentRange.total) { - await trackPartialCacheFile.rename(trackCacheFile.path); - } + resStream.listen( + (data) { + partialCacheFileSink.add(data); + }, + onError: (e, stack) { + partialCacheFileSink.close(); + }, + onDone: () async { + await partialCacheFileSink.close(); - if (contentRange.total == fileLength && track.codec != SourceCodecs.weba) { - final playlistTrack = playlist.tracks.firstWhereOrNull( - (element) => element.id == track.query.id, - ); - if (playlistTrack == null) { - AppLogger.log.e( - "Track ${track.query.id} not found in playlist, cannot write metadata.", + final fileLength = await trackPartialCacheFile.length(); + if (fileLength != contentRange.total) return; + + await trackPartialCacheFile.rename(trackCacheFile.path); + + if (track.qualityPreset!.getFileExtension() == "weba") return; + + final imageBytes = await ServiceUtils.downloadImage( + track.query.album.images.asUrlString( + placeholder: ImagePlaceholder.albumArt, + index: 1, + ), ); - return (response: res, bytes: bytes); + + await MetadataGod.writeMetadata( + file: trackCacheFile.path, + metadata: track.query.toMetadata( + imageBytes: imageBytes, + fileLength: fileLength, + ), + ).catchError((e, stackTrace) { + AppLogger.reportError(e, stackTrace); + }); + }, + cancelOnError: true, + ); + + res.data?.stream = + resStream; // To avoid Stream has been already listened to exception + return res; + } + + /// @head('/stream/') + Future headStreamTrackId(Request request, String trackId) async { + try { + final sourcedTrack = await _getSourcedTrack(request, trackId); + + if (sourcedTrack == null) { + return Response.notFound("Track not found in the current queue"); } - final imageBytes = await ServiceUtils.downloadImage( - (playlistTrack.album.images).asUrlString( - placeholder: ImagePlaceholder.albumArt, - index: 1, - ), + final res = await streamTrackInformation( + request, + sourcedTrack, ); - await MetadataGod.writeMetadata( - file: trackCacheFile.path, - metadata: (playlistTrack as SpotubeFullTrackObject).toMetadata( - imageBytes: imageBytes, - fileLength: fileLength, - ), + return Response( + res.statusCode!, + headers: res.headers.map, ); + } catch (e, stack) { + AppLogger.reportError(e, stack); + return Response.internalServerError(); } - - return (bytes: bytes, response: res); } /// @get('/stream/') Future getStreamTrackId(Request request, String trackId) async { try { - final track = - playlist.tracks.firstWhere((element) => element.id == trackId); + final sourcedTrack = await _getSourcedTrack(request, trackId); - final activeSourcedTrack = - await ref.read(activeTrackSourcesProvider.future); - final sourcedTrack = activeSourcedTrack?.track.id == track.id - ? activeSourcedTrack?.source - : await ref.read( - trackSourcesProvider( - //! Use [Request.requestedUri] as it contains full https url. - //! [Request.url] will exclude and starts relatively. (streams/... basically) - TrackSourceQuery.parseUri(request.requestedUri.toString()), - ).future, - ); + if (sourcedTrack == null) { + return Response.notFound("Track not found in the current queue"); + } - final (bytes: audioBytes, response: res) = await streamTrack( + final res = await streamTrack( request, - sourcedTrack!, + sourcedTrack, request.headers, ); + if (res.data is ResponseBody) { + return Response( + res.statusCode!, + body: (res.data as ResponseBody).stream, + headers: res.headers.map, + ); + } + return Response( res.statusCode!, - body: audioBytes, + body: res.data, headers: res.headers.map, ); } catch (e, stack) { diff --git a/lib/provider/server/sourced_track_provider.dart b/lib/provider/server/sourced_track_provider.dart new file mode 100644 index 00000000..7934ecc7 --- /dev/null +++ b/lib/provider/server/sourced_track_provider.dart @@ -0,0 +1,49 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/audio_source/quality_presets.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/services/sourced_track/sourced_track.dart'; + +class SourcedTrackNotifier + extends FamilyAsyncNotifier { + @override + FutureOr build(query) { + ref.watch(audioSourcePluginProvider); + ref.watch(audioSourcePresetsProvider); + + return SourcedTrack.fetchFromTrack(query: query, ref: ref); + } + + Future refreshStreamingUrl() async { + return await update((prev) async { + return await prev.refreshStream(); + }); + } + + Future copyWithSibling() async { + return await update((prev) async { + return prev.copyWithSibling(); + }); + } + + Future swapWithSibling( + SpotubeAudioSourceMatchObject sibling, + ) async { + return await update((prev) async { + return await prev.swapWithSibling(sibling) ?? prev; + }); + } + + Future swapWithNextSibling() async { + return await update((prev) async { + return await prev.swapWithSibling(prev.siblings.first) as SourcedTrack; + }); + } +} + +final sourcedTrackProvider = AsyncNotifierProviderFamily( + () => SourcedTrackNotifier(), +); diff --git a/lib/provider/server/track_sources.dart b/lib/provider/server/track_sources.dart deleted file mode 100644 index 24502471..00000000 --- a/lib/provider/server/track_sources.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:async'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; - -class TrackSourcesNotifier - extends FamilyAsyncNotifier { - @override - FutureOr build(query) { - ref.watch(userPreferencesProvider.select((p) => p.audioQuality)); - ref.watch(userPreferencesProvider.select((p) => p.audioSource)); - ref.watch(userPreferencesProvider.select((p) => p.streamMusicCodec)); - ref.watch(userPreferencesProvider.select((p) => p.downloadMusicCodec)); - - return SourcedTrack.fetchFromQuery(query: query, ref: ref); - } - - Future refreshStreamingUrl() async { - return await update((prev) async { - return await prev.refreshStream(); - }); - } - - Future copyWithSibling() async { - return await update((prev) async { - return prev.copyWithSibling(); - }); - } - - Future swapWithSibling(TrackSourceInfo sibling) async { - return await update((prev) async { - 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( - () => TrackSourcesNotifier(), -); diff --git a/lib/provider/skip_segments/skip_segments.dart b/lib/provider/skip_segments/skip_segments.dart index accccddd..dc06f326 100644 --- a/lib/provider/skip_segments/skip_segments.dart +++ b/lib/provider/skip_segments/skip_segments.dart @@ -86,18 +86,10 @@ final segmentProvider = FutureProvider( if (snapshot == null) return null; final (:track, :source, :notifier) = snapshot; if (track is SpotubeLocalTrackObject) return null; - if (source!.source case AudioSource.jiosaavn) return null; + if (!source!.source.toLowerCase().contains("youtube")) return null; - final skipNonMusic = ref.watch( - userPreferencesProvider.select( - (s) { - final isPipedYTMusicMode = s.audioSource == AudioSource.piped && - s.searchMode == SearchMode.youtubeMusic; - - return s.skipNonMusic && !isPipedYTMusicMode; - }, - ), - ); + final skipNonMusic = + ref.watch(userPreferencesProvider.select((s) => s.skipNonMusic)); if (!skipNonMusic) { return SourcedSegments(segments: [], source: source.info.id); diff --git a/lib/provider/track_options/track_options_provider.dart b/lib/provider/track_options/track_options_provider.dart index 7e6bc16e..5aebf39c 100644 --- a/lib/provider/track_options/track_options_provider.dart +++ b/lib/provider/track_options/track_options_provider.dart @@ -21,12 +21,10 @@ import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/services/metadata/errors/exceptions.dart'; -import 'package:url_launcher/url_launcher_string.dart'; enum TrackOptionValue { album, share, - songlink, addToPlaylist, addToQueue, removeFromPlaylist, @@ -51,7 +49,7 @@ class TrackOptionsActions { ref.read(metadataPluginSavedTracksProvider.notifier); MetadataPluginSavedPlaylistsNotifier get favoritePlaylistsNotifier => ref.read(metadataPluginSavedPlaylistsProvider.notifier); - DownloadManagerProvider get downloadManager => + DownloadManagerNotifier get downloadManager => ref.read(downloadManagerProvider.notifier); BlackListNotifier get blacklist => ref.read(blacklistProvider.notifier); @@ -97,7 +95,7 @@ class TrackOptionsActions { final metadataPlugin = await ref.read(metadataPluginProvider.future); if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin(); + throw MetadataPluginException.noDefaultMetadataPlugin(); } final tracks = await metadataPlugin.track.radio(track.id); @@ -166,7 +164,7 @@ class TrackOptionsActions { } break; case TrackOptionValue.playNext: - playback.addTracksAtFirst([track]); + await playback.addTracksAtFirst([track]); if (context.mounted) { showToast( @@ -237,10 +235,6 @@ class TrackOptionsActions { case TrackOptionValue.share: actionShare(context); break; - case TrackOptionValue.songlink: - final url = "https://song.link/s/${track.id}"; - await launchUrlString(url); - break; case TrackOptionValue.details: if (track is! SpotubeFullTrackObject) break; showDialog( @@ -252,8 +246,8 @@ class TrackOptionsActions { ); break; case TrackOptionValue.download: - if (track is! SpotubeFullTrackObject) break; - await downloadManager.addToQueue(track as SpotubeFullTrackObject); + if (track is SpotubeLocalTrackObject) break; + downloadManager.addToQueue(track as SpotubeFullTrackObject); break; case TrackOptionValue.startRadio: actionStartRadio(context); @@ -269,7 +263,7 @@ typedef TrackOptionFlags = ({ bool isActiveTrack, bool isAuthenticated, bool isLiked, - ValueNotifier? progressNotifier, + DownloadTask? downloadTask, }); final trackOptionActionsProvider = @@ -289,15 +283,16 @@ final trackOptionsStateProvider = final isBlacklisted = blacklist.contains(track); final isSavedTrack = ref.watch(metadataPluginIsSavedTrackProvider(track.id)); + final downloadTask = playlist.activeTrack?.id == null + ? null + : downloadManager.getTaskByTrackId(playlist.activeTrack!.id); final isInDownloadQueue = playlist.activeTrack == null || playlist.activeTrack! is SpotubeLocalTrackObject ? false - : downloadManager - .isActive(playlist.activeTrack! as SpotubeFullTrackObject); - - final progressNotifier = track is SpotubeLocalTrackObject - ? null - : downloadManager.getProgressNotifier(track as SpotubeFullTrackObject); + : const [ + DownloadStatus.queued, + DownloadStatus.downloading, + ].contains(downloadTask?.status); return ( isInQueue: playlist.containsTrack(track), @@ -306,6 +301,6 @@ final trackOptionsStateProvider = isActiveTrack: playlist.activeTrack?.id == track.id, isAuthenticated: authenticated.asData?.value ?? false, isLiked: isSavedTrack.asData?.value ?? false, - progressNotifier: progressNotifier, + downloadTask: downloadTask, ); }); diff --git a/lib/provider/user_preferences/user_preferences_provider.dart b/lib/provider/user_preferences/user_preferences_provider.dart index a5be97e2..0b43d043 100644 --- a/lib/provider/user_preferences/user_preferences_provider.dart +++ b/lib/provider/user_preferences/user_preferences_provider.dart @@ -10,7 +10,6 @@ import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:spotube/provider/database/database.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'; import 'package:spotube/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; import 'package:open_file/open_file.dart'; @@ -119,14 +118,6 @@ class UserPreferencesNotifier extends Notifier { } } - void setStreamMusicCodec(SourceCodecs codec) { - setData(PreferencesTableCompanion(streamMusicCodec: Value(codec))); - } - - void setDownloadMusicCodec(SourceCodecs codec) { - setData(PreferencesTableCompanion(downloadMusicCodec: Value(codec))); - } - void setThemeMode(ThemeMode mode) { setData(PreferencesTableCompanion(themeMode: Value(mode))); } @@ -153,10 +144,6 @@ class UserPreferencesNotifier extends Notifier { setData(PreferencesTableCompanion(checkUpdate: Value(check))); } - void setAudioQuality(SourceQualities quality) { - setData(PreferencesTableCompanion(audioQuality: Value(quality))); - } - void setDownloadLocation(String downloadDir) { if (downloadDir.isEmpty) return; setData(PreferencesTableCompanion(downloadLocation: Value(downloadDir))); @@ -187,14 +174,6 @@ class UserPreferencesNotifier extends Notifier { setData(PreferencesTableCompanion(locale: Value(locale))); } - void setPipedInstance(String instance) { - setData(PreferencesTableCompanion(pipedInstance: Value(instance))); - } - - void setInvidiousInstance(String instance) { - setData(PreferencesTableCompanion(invidiousInstance: Value(instance))); - } - void setSearchMode(SearchMode mode) { setData(PreferencesTableCompanion(searchMode: Value(mode))); } @@ -203,10 +182,6 @@ class UserPreferencesNotifier extends Notifier { setData(PreferencesTableCompanion(skipNonMusic: Value(skip))); } - void setAudioSource(AudioSource type) { - setData(PreferencesTableCompanion(audioSource: Value(type))); - } - void setYoutubeClientEngine(YoutubeClientEngine engine) { setData(PreferencesTableCompanion(youtubeClientEngine: Value(engine))); } diff --git a/lib/services/audio_player/audio_player.dart b/lib/services/audio_player/audio_player.dart index 93a6417e..2693f13a 100644 --- a/lib/services/audio_player/audio_player.dart +++ b/lib/services/audio_player/audio_player.dart @@ -2,7 +2,6 @@ import 'dart:io'; import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter/foundation.dart'; import 'package:spotube/services/audio_player/custom_player.dart'; @@ -22,21 +21,9 @@ class SpotubeMedia extends mk.Media { static String get _host => kIsWindows ? "localhost" : InternetAddress.anyIPv4.address; - static String _queries(SpotubeFullTrackObject track) { - final params = TrackSourceQuery.fromTrack(track).toJson(); - - return params.entries - .map((e) => - "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List ? e.value.join(",") : e.value.toString())}") - .join("&"); - } - final SpotubeTrackObject track; - SpotubeMedia( - this.track, { - Map? extras, - super.httpHeaders, - }) : assert( + SpotubeMedia(this.track) + : assert( track is SpotubeLocalTrackObject || track is SpotubeFullTrackObject, "Track must be a either a local track or a full track object with ISRC", ), @@ -44,8 +31,14 @@ class SpotubeMedia extends mk.Media { super( track is SpotubeLocalTrackObject ? track.path - : "http://$_host:$serverPort/stream/${track.id}?${_queries(track as SpotubeFullTrackObject)}", + : "http://$_host:$serverPort/stream/${track.id}", + extras: track.toJson(), ); + + factory SpotubeMedia.media(Media media) { + assert(media.extras != null, "[Media] must have extra metadata set"); + return SpotubeMedia(SpotubeTrackObject.fromJson(media.extras!)); + } } abstract class AudioPlayerInterface { @@ -56,6 +49,7 @@ abstract class AudioPlayerInterface { configuration: const mk.PlayerConfiguration( title: "Spotube", logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error, + async: true, ), ) { _mkPlayer.stream.error.listen((event) { diff --git a/lib/services/audio_player/audio_player_impl.dart b/lib/services/audio_player/audio_player_impl.dart index 82c8c906..afd209a3 100644 --- a/lib/services/audio_player/audio_player_impl.dart +++ b/lib/services/audio_player/audio_player_impl.dart @@ -131,4 +131,8 @@ class SpotubeAudioPlayer extends AudioPlayerInterface Future setAudioNormalization(bool normalize) async { await _mkPlayer.setAudioNormalization(normalize); } + + Future setDemuxerBufferSize(int sizeInBytes) async { + await _mkPlayer.setDemuxerBufferSize(sizeInBytes); + } } diff --git a/lib/services/audio_player/custom_player.dart b/lib/services/audio_player/custom_player.dart index 5258696b..7cbd51a5 100644 --- a/lib/services/audio_player/custom_player.dart +++ b/lib/services/audio_player/custom_player.dart @@ -121,9 +121,23 @@ class CustomPlayer extends Player { NativePlayer get nativePlayer => platform as NativePlayer; Future insert(int index, Media media) async { - await add(media); - await Future.delayed(const Duration(milliseconds: 100)); - await move(state.playlist.medias.length - 1, index); + final addedMediaCompleter = Completer(); + final playlistStream = stream.playlist.listen( + (event) { + final mediaAddedIndex = + event.medias.indexWhere((m) => m.uri == media.uri); + if (mediaAddedIndex != -1 && !addedMediaCompleter.isCompleted) { + addedMediaCompleter.complete(mediaAddedIndex); + } + }, + ); + try { + await add(media); + final mediaAddedIndex = await addedMediaCompleter.future; + await move(mediaAddedIndex, index); + } finally { + playlistStream.cancel(); + } } Future setAudioNormalization(bool normalize) async { @@ -133,4 +147,12 @@ class CustomPlayer extends Player { await nativePlayer.setProperty('af', ''); } } + + Future setDemuxerBufferSize(int sizeInBytes) async { + await nativePlayer.setProperty('demuxer-max-bytes', sizeInBytes.toString()); + await nativePlayer.setProperty( + 'demuxer-max-back-bytes', + sizeInBytes.toString(), + ); + } } diff --git a/lib/services/download_manager/chunked_download.dart b/lib/services/download_manager/chunked_download.dart deleted file mode 100644 index 80a3e78f..00000000 --- a/lib/services/download_manager/chunked_download.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:dio/dio.dart'; - -/// Downloading by spiting as file in chunks -extension ChunkDownload on Dio { - Future chunkedDownload( - url, { - Map? queryParameters, - required String savePath, - ProgressCallback? onReceiveProgress, - CancelToken? cancelToken, - bool deleteOnError = true, - int chunkSize = 102400, // 100KB - int maxConcurrentChunk = 3, - String tempExtension = ".temp", - }) async { - int total = 0; - var progress = []; - - ProgressCallback createCallback(int chunkIndex) { - return (int received, _) { - progress[chunkIndex] = received; - if (onReceiveProgress != null && total != 0) { - onReceiveProgress(progress.reduce((a, b) => a + b), total); - } - }; - } - - // this is the last response - // status & headers will the last chunk's status & headers - final completer = Completer(); - - Future downloadChunk( - String url, { - required int start, - required int end, - required int chunkIndex, - }) async { - progress.add(0); - --end; - final res = await download( - url, - savePath + tempExtension + chunkIndex.toString(), - onReceiveProgress: createCallback(chunkIndex), - cancelToken: cancelToken, - queryParameters: queryParameters, - deleteOnError: deleteOnError, - options: Options( - responseType: ResponseType.bytes, - headers: {"range": "bytes=$start-$end"}, - ), - ); - - return res; - } - - Future mergeTempFiles(int chunk) async { - File headFile = File("$savePath${tempExtension}0"); - var raf = await headFile.open(mode: FileMode.writeOnlyAppend); - for (int i = 1; i < chunk; ++i) { - File chunkFile = File(savePath + tempExtension + i.toString()); - raf = await raf.writeFrom(await chunkFile.readAsBytes()); - await chunkFile.delete(); - } - await raf.close(); - - headFile = await headFile.rename(savePath); - } - - final firstResponse = await downloadChunk( - url, - start: 0, - end: chunkSize, - chunkIndex: 0, - ); - - final responses = [firstResponse]; - - if (firstResponse.statusCode == HttpStatus.partialContent) { - total = int.parse( - firstResponse.headers - .value(HttpHeaders.contentRangeHeader) - ?.split("/") - .lastOrNull ?? - '0', - ); - - final reserved = total - - int.parse( - firstResponse.headers.value(HttpHeaders.contentLengthHeader) ?? - // since its a partial content, the content length will be the chunk size - chunkSize.toString(), - ); - - int chunk = (reserved / chunkSize).ceil() + 1; - - if (chunk > 1) { - int currentChunkSize = chunkSize; - if (chunk > maxConcurrentChunk + 1) { - chunk = maxConcurrentChunk + 1; - currentChunkSize = (reserved / maxConcurrentChunk).ceil(); - } - - responses.addAll( - await Future.wait( - List.generate(maxConcurrentChunk, (i) { - int start = chunkSize + i * currentChunkSize; - return downloadChunk( - url, - start: start, - end: start + currentChunkSize, - chunkIndex: i + 1, - ); - }), - ), - ); - } - - await mergeTempFiles(chunk).then((_) { - final response = responses.last; - final isPartialStatus = - response.statusCode == HttpStatus.partialContent; - - completer.complete( - Response( - data: response.data, - headers: response.headers, - requestOptions: response.requestOptions, - statusCode: isPartialStatus ? HttpStatus.ok : response.statusCode, - statusMessage: isPartialStatus ? 'Ok' : response.statusMessage, - extra: response.extra, - isRedirect: response.isRedirect, - redirects: response.redirects, - ), - ); - }).catchError((e) { - completer.completeError(e); - }); - } - - return completer.future; - } -} diff --git a/lib/services/download_manager/download_manager.dart b/lib/services/download_manager/download_manager.dart deleted file mode 100644 index d2072bd7..00000000 --- a/lib/services/download_manager/download_manager.dart +++ /dev/null @@ -1,416 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'dart:io'; -import 'package:collection/collection.dart'; - -import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; -import 'package:path/path.dart' as path; -import 'package:path_provider/path_provider.dart'; - -import 'package:spotube/services/download_manager/chunked_download.dart'; -import 'package:spotube/services/download_manager/download_request.dart'; -import 'package:spotube/services/download_manager/download_status.dart'; -import 'package:spotube/services/download_manager/download_task.dart'; -import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/utils/primitive_utils.dart'; - -export './download_request.dart'; -export './download_status.dart'; -export './download_task.dart'; - -typedef DownloadStatusEvent = ({ - DownloadStatus status, - DownloadRequest request -}); - -class DownloadManager { - final Map _cache = {}; - final Queue _queue = Queue(); - var dio = Dio(); - static const partialExtension = ".partial"; - static const tempExtension = ".temp"; - - // var tasks = StreamController(); - - final _statusStreamController = - StreamController.broadcast(); - Stream get statusStream => - _statusStreamController.stream; - - int maxConcurrentTasks = 2; - int runningTasks = 0; - - static final DownloadManager _dm = DownloadManager._internal(); - - DownloadManager._internal(); - - factory DownloadManager({int? maxConcurrentTasks}) { - if (maxConcurrentTasks != null) { - _dm.maxConcurrentTasks = maxConcurrentTasks; - } - return _dm; - } - - void Function(int, int) createCallback(url, int partialFileLength) => - (int received, int total) { - getDownload(url)?.progress.value = - (received + partialFileLength) / (total + partialFileLength); - - if (total == -1) {} - }; - - Future download( - String url, - String savePath, - CancelToken cancelToken, { - forceDownload = false, - }) async { - late String partialFilePath; - late File partialFile; - try { - final task = getDownload(url); - - if (task == null || task.status.value == DownloadStatus.canceled) { - return; - } - setStatus(task, DownloadStatus.downloading); - - final file = File(savePath.toString()); - - await Directory(path.dirname(savePath)).create(recursive: true); - - final tmpDirPath = await Directory( - path.join( - (await getTemporaryDirectory()).path, - "spotube-downloads", - ), - ).create(recursive: true); - - partialFilePath = path.join( - tmpDirPath.path, - path.basename(savePath) + partialExtension, - ); - partialFile = File(partialFilePath); - - final fileExist = await file.exists(); - final partialFileExist = await partialFile.exists(); - - if (fileExist) { - setStatus(task, DownloadStatus.completed); - } else if (partialFileExist) { - final partialFileLength = await partialFile.length(); - - final response = await dio.download( - url, - partialFilePath + tempExtension, - onReceiveProgress: createCallback(url, partialFileLength), - options: Options( - headers: { - HttpHeaders.rangeHeader: 'bytes=$partialFileLength-', - HttpHeaders.connectionHeader: "close", - }, - ), - cancelToken: cancelToken, - deleteOnError: true, - ); - - if (response.statusCode == HttpStatus.partialContent) { - final ioSink = partialFile.openWrite(mode: FileMode.writeOnlyAppend); - final partialChunkFile = File(partialFilePath + tempExtension); - await ioSink.addStream(partialChunkFile.openRead()); - await partialChunkFile.delete(); - await ioSink.close(); - - await partialFile.copy(savePath); - await partialFile.delete(); - - setStatus(task, DownloadStatus.completed); - } - } else { - final response = await dio.chunkedDownload( - url, - savePath: partialFilePath, - onReceiveProgress: createCallback(url, 0), - cancelToken: cancelToken, - deleteOnError: true, - ); - - if (response.statusCode == HttpStatus.ok) { - await partialFile.copy(savePath); - await partialFile.delete(); - setStatus(task, DownloadStatus.completed); - } - } - } catch (e, stackTrace) { - AppLogger.reportError(e, stackTrace); - - var task = getDownload(url)!; - if (task.status.value != DownloadStatus.canceled && - task.status.value != DownloadStatus.paused) { - setStatus(task, DownloadStatus.failed); - runningTasks--; - - if (_queue.isNotEmpty) { - _startExecution(); - } - rethrow; - } else if (task.status.value == DownloadStatus.paused) { - final ioSink = partialFile.openWrite(mode: FileMode.writeOnlyAppend); - final f = File(partialFilePath + tempExtension); - if (await f.exists()) { - await ioSink.addStream(f.openRead()); - } - await ioSink.close(); - } - } - - runningTasks--; - - if (_queue.isNotEmpty) { - _startExecution(); - } - } - - void disposeNotifiers(DownloadTask task) { - // task.status.dispose(); - // task.progress.dispose(); - } - - void setStatus(DownloadTask? task, DownloadStatus status) { - if (task != null) { - task.status.value = status; - - // tasks.add(task); - if (status.isCompleted) { - disposeNotifiers(task); - } - - _statusStreamController.add((status: status, request: task.request)); - } - } - - Future addDownload(String url, String savedPath) async { - if (url.isEmpty) throw Exception("Invalid Url. Url is empty: $url"); - return _addDownloadRequest(DownloadRequest(url, savedPath)); - } - - Future _addDownloadRequest( - DownloadRequest downloadRequest, - ) async { - if (_cache[downloadRequest.url] != null) { - if (!_cache[downloadRequest.url]!.status.value.isCompleted && - _cache[downloadRequest.url]!.request == downloadRequest) { - // Do nothing - return _cache[downloadRequest.url]!; - } else { - _queue.remove(_cache[downloadRequest.url]?.request); - } - } - - _queue.add(DownloadRequest(downloadRequest.url, downloadRequest.path)); - - final task = DownloadTask(_queue.last); - - _cache[downloadRequest.url] = task; - - _startExecution(); - - return task; - } - - Future pauseDownload(String url) async { - var task = getDownload(url)!; - setStatus(task, DownloadStatus.paused); - task.request.cancelToken.cancel(); - - _queue.remove(task.request); - } - - Future cancelDownload(String url) async { - var task = getDownload(url)!; - setStatus(task, DownloadStatus.canceled); - _queue.remove(task.request); - task.request.cancelToken.cancel(); - } - - Future resumeDownload(String url) async { - var task = getDownload(url)!; - setStatus(task, DownloadStatus.downloading); - task.request.cancelToken = CancelToken(); - _queue.add(task.request); - - _startExecution(); - } - - Future removeDownload(String url) async { - cancelDownload(url); - _cache.remove(url); - } - - // Do not immediately call getDownload After addDownload, rather use the returned DownloadTask from addDownload - DownloadTask? getDownload(String url) { - return _cache[url]; - } - - Future whenDownloadComplete(String url, - {Duration timeout = const Duration(hours: 2)}) async { - DownloadTask? task = getDownload(url); - - if (task != null) { - return task.whenDownloadComplete(timeout: timeout); - } else { - return Future.error("Not found"); - } - } - - List getAllDownloads() { - return _cache.values.toList(); - } - - // Batch Download Mechanism - Future addBatchDownloads(List urls, String savePath) async { - for (final url in urls) { - addDownload(url, savePath); - } - } - - List getBatchDownloads(List urls) { - return urls.map((e) => _cache[e]).toList(); - } - - Future pauseBatchDownloads(List urls) async { - for (var element in urls) { - pauseDownload(element); - } - } - - Future cancelBatchDownloads(List urls) async { - for (var element in urls) { - cancelDownload(element); - } - } - - Future resumeBatchDownloads(List urls) async { - for (var element in urls) { - resumeDownload(element); - } - } - - ValueNotifier getBatchDownloadProgress(List urls) { - ValueNotifier progress = ValueNotifier(0); - var total = urls.length; - - if (total == 0) { - return progress; - } - - if (total == 1) { - return getDownload(urls.first)?.progress ?? progress; - } - - var progressMap = {}; - - for (var url in urls) { - DownloadTask? task = getDownload(url); - - if (task != null) { - progressMap[url] = 0.0; - - if (task.status.value.isCompleted) { - progressMap[url] = 1.0; - progress.value = progressMap.values.sum / total; - } - - void progressListener() { - progressMap[url] = task.progress.value; - progress.value = progressMap.values.sum / total; - } - - task.progress.addListener(progressListener); - - void listener() { - if (task.status.value.isCompleted) { - progressMap[url] = 1.0; - progress.value = progressMap.values.sum / total; - task.status.removeListener(listener); - task.progress.removeListener(progressListener); - } - } - - task.status.addListener(listener); - } else { - total--; - } - } - - return progress; - } - - Future?> whenBatchDownloadsComplete(List urls, - {Duration timeout = const Duration(hours: 2)}) async { - var completer = Completer?>(); - - var completed = 0; - var total = urls.length; - - for (final url in urls) { - DownloadTask? task = getDownload(url); - - if (task != null) { - if (task.status.value.isCompleted) { - completed++; - - if (completed == total) { - completer.complete(getBatchDownloads(urls)); - } - } - - void listener() { - if (task.status.value.isCompleted) { - completed++; - - if (completed == total) { - completer.complete(getBatchDownloads(urls)); - task.status.removeListener(listener); - } - } - } - - task.status.addListener(listener); - } else { - total--; - - if (total == 0) { - completer.complete(null); - } - } - } - - return completer.future.timeout(timeout); - } - - void _startExecution() async { - if (runningTasks == maxConcurrentTasks || _queue.isEmpty) { - return; - } - - while (_queue.isNotEmpty && runningTasks < maxConcurrentTasks) { - runningTasks++; - var currentRequest = _queue.removeFirst(); - - await download( - currentRequest.url, - currentRequest.path, - currentRequest.cancelToken, - ); - - await Future.delayed(const Duration(milliseconds: 500), null); - } - } - - /// This function is used for get file name with extension from url - String getFileNameFromUrl(String url) { - return PrimitiveUtils.toSafeFileName(url.split('/').last); - } -} diff --git a/lib/services/download_manager/download_request.dart b/lib/services/download_manager/download_request.dart deleted file mode 100644 index 80c4af37..00000000 --- a/lib/services/download_manager/download_request.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:dio/dio.dart'; - -class DownloadRequest { - final String url; - final String path; - var cancelToken = CancelToken(); - var forceDownload = false; - - DownloadRequest( - this.url, - this.path, - ); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is DownloadRequest && - runtimeType == other.runtimeType && - url == other.url && - path == other.path; - - @override - int get hashCode => url.hashCode ^ path.hashCode; -} diff --git a/lib/services/download_manager/download_status.dart b/lib/services/download_manager/download_status.dart deleted file mode 100644 index b97080fa..00000000 --- a/lib/services/download_manager/download_status.dart +++ /dev/null @@ -1,26 +0,0 @@ -enum DownloadStatus { - queued, - downloading, - completed, - failed, - paused, - canceled; - - bool get isCompleted { - switch (this) { - case DownloadStatus.queued: - return false; - case DownloadStatus.downloading: - return false; - case DownloadStatus.paused: - return false; - case DownloadStatus.completed: - return true; - case DownloadStatus.failed: - return true; - - case DownloadStatus.canceled: - return true; - } - } -} diff --git a/lib/services/download_manager/download_task.dart b/lib/services/download_manager/download_task.dart deleted file mode 100644 index d79cf95b..00000000 --- a/lib/services/download_manager/download_task.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:spotube/services/download_manager/download_request.dart'; -import 'package:spotube/services/download_manager/download_status.dart'; - -class DownloadTask { - final DownloadRequest request; - ValueNotifier status = ValueNotifier(DownloadStatus.queued); - ValueNotifier progress = ValueNotifier(0); - - DownloadTask( - this.request, - ); - - Future whenDownloadComplete( - {Duration timeout = const Duration(hours: 2)}) async { - var completer = Completer(); - - if (status.value.isCompleted) { - completer.complete(status.value); - } - - void listener() { - if (status.value.isCompleted) { - completer.complete(status.value); - status.removeListener(listener); - } - } - - status.addListener(listener); - - return completer.future.timeout(timeout); - } -} diff --git a/lib/services/metadata/endpoints/audio_source.dart b/lib/services/metadata/endpoints/audio_source.dart new file mode 100644 index 00000000..d22449c6 --- /dev/null +++ b/lib/services/metadata/endpoints/audio_source.dart @@ -0,0 +1,38 @@ +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_script/values.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class MetadataPluginAudioSourceEndpoint { + final Hetu hetu; + MetadataPluginAudioSourceEndpoint(this.hetu); + + HTInstance get hetuMetadataAudioSource => + (hetu.fetch("metadataPlugin") as HTInstance).memberGet("audioSource") + as HTInstance; + + List get supportedPresets { + final raw = hetuMetadataAudioSource.memberGet("supportedPresets") as List; + + return raw + .map((e) => SpotubeAudioSourceContainerPreset.fromJson(e)) + .toList(); + } + + Future> matches( + SpotubeFullTrackObject track, + ) async { + final raw = await hetuMetadataAudioSource + .invoke("matches", positionalArgs: [track.toJson()]) as List; + + return raw.map((e) => SpotubeAudioSourceMatchObject.fromJson(e)).toList(); + } + + Future> streams( + SpotubeAudioSourceMatchObject match, + ) async { + final raw = await hetuMetadataAudioSource + .invoke("streams", positionalArgs: [match.toJson()]) as List; + + return raw.map((e) => SpotubeAudioSourceStreamObject.fromJson(e)).toList(); + } +} diff --git a/lib/services/metadata/errors/exceptions.dart b/lib/services/metadata/errors/exceptions.dart index 62cc3779..5bb5ac57 100644 --- a/lib/services/metadata/errors/exceptions.dart +++ b/lib/services/metadata/errors/exceptions.dart @@ -9,7 +9,8 @@ enum MetadataPluginErrorCode { pluginDownloadFailed, duplicatePlugin, pluginByteCodeFileNotFound, - noDefaultPlugin, + noDefaultMetadataPlugin, + noDefaultAudiSourcePlugin, } class MetadataPluginException implements Exception { @@ -68,10 +69,15 @@ class MetadataPluginException implements Exception { 'Plugin byte code file, plugin.out not found. Please ensure the plugin is correctly packaged.', errorCode: MetadataPluginErrorCode.pluginByteCodeFileNotFound, ); - MetadataPluginException.noDefaultPlugin() + MetadataPluginException.noDefaultMetadataPlugin() : this._( 'No default metadata plugin is set. Please set a default plugin in the settings.', - errorCode: MetadataPluginErrorCode.noDefaultPlugin, + errorCode: MetadataPluginErrorCode.noDefaultMetadataPlugin, + ); + MetadataPluginException.noDefaultAudioSourcePlugin() + : this._( + 'No default audio source plugin is set. Please set a default plugin in the settings.', + errorCode: MetadataPluginErrorCode.noDefaultAudiSourcePlugin, ); @override diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index ab2290f6..5860e0d6 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -3,7 +3,9 @@ import 'dart:typed_data'; import 'package:auto_route/auto_route.dart'; import 'package:hetu_otp_util/hetu_otp_util.dart'; import 'package:hetu_script/hetu_script.dart'; -import 'package:hetu_spotube_plugin/hetu_spotube_plugin.dart'; +import 'package:hetu_spotube_plugin/hetu_spotube_plugin.dart' as spotube_plugin; +import 'package:hetu_spotube_plugin/hetu_spotube_plugin.dart' + hide YouTubeEngine; import 'package:hetu_std/hetu_std.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; @@ -15,6 +17,7 @@ import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart'; import 'package:spotube/services/metadata/endpoints/album.dart'; import 'package:spotube/services/metadata/endpoints/artist.dart'; +import 'package:spotube/services/metadata/endpoints/audio_source.dart'; import 'package:spotube/services/metadata/endpoints/auth.dart'; import 'package:spotube/services/metadata/endpoints/browse.dart'; import 'package:spotube/services/metadata/endpoints/playlist.dart'; @@ -22,13 +25,15 @@ import 'package:spotube/services/metadata/endpoints/search.dart'; import 'package:spotube/services/metadata/endpoints/track.dart'; import 'package:spotube/services/metadata/endpoints/core.dart'; import 'package:spotube/services/metadata/endpoints/user.dart'; +import 'package:spotube/services/youtube_engine/youtube_engine.dart'; const defaultMetadataLimit = "20"; class MetadataPlugin { - static final pluginApiVersion = Version.parse("1.0.0"); + static final pluginApiVersion = Version.parse("2.0.0"); static Future create( + YouTubeEngine youtubeEngine, PluginConfiguration config, Uint8List byteCode, ) async { @@ -76,6 +81,58 @@ class MetadataPlugin { ), ); }, + createYoutubeEngine: () { + return spotube_plugin.YouTubeEngine( + search: (query) async { + final result = await youtubeEngine.searchVideos(query); + return result + .map((video) => { + 'id': video.id.value, + 'title': video.title, + 'author': video.author, + 'duration': video.duration?.inSeconds, + 'description': video.description, + 'uploadDate': video.uploadDate?.toIso8601String(), + 'viewCount': video.engagement.viewCount, + 'likeCount': video.engagement.likeCount, + 'isLive': video.isLive, + }) + .toList(); + }, + getVideo: (videoId) async { + final video = await youtubeEngine.getVideo(videoId); + return { + 'id': video.id.value, + 'title': video.title, + 'author': video.author, + 'duration': video.duration?.inSeconds, + 'description': video.description, + 'uploadDate': video.uploadDate?.toIso8601String(), + 'viewCount': video.engagement.viewCount, + 'likeCount': video.engagement.likeCount, + 'isLive': video.isLive, + }; + }, + streamManifest: (videoId) { + return youtubeEngine.getStreamManifest(videoId).then( + (manifest) { + final streams = manifest.audioOnly + .map( + (stream) => { + 'url': stream.url.toString(), + 'quality': stream.qualityLabel, + 'bitrate': stream.bitrate.bitsPerSecond, + 'container': stream.container.name, + 'videoId': stream.videoId, + }, + ) + .toList(); + return streams; + }, + ); + }, + ); + }, ); await HetuStdLoader.loadBytecodeFlutter(hetu); @@ -98,6 +155,7 @@ class MetadataPlugin { late final MetadataAuthEndpoint auth; + late final MetadataPluginAudioSourceEndpoint audioSource; late final MetadataPluginAlbumEndpoint album; late final MetadataPluginArtistEndpoint artist; late final MetadataPluginBrowseEndpoint browse; @@ -110,6 +168,7 @@ class MetadataPlugin { MetadataPlugin._(this.hetu) { auth = MetadataAuthEndpoint(hetu); + audioSource = MetadataPluginAudioSourceEndpoint(hetu); artist = MetadataPluginArtistEndpoint(hetu); album = MetadataPluginAlbumEndpoint(hetu); browse = MetadataPluginBrowseEndpoint(hetu); diff --git a/lib/services/song_link/model.dart b/lib/services/song_link/model.dart deleted file mode 100644 index ae9d3833..00000000 --- a/lib/services/song_link/model.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of './song_link.dart'; - -@freezed -class SongLink with _$SongLink { - const factory SongLink({ - required String displayName, - required String linkId, - required String platform, - required bool show, - required String? uniqueId, - required String? country, - required String? url, - required String? nativeAppUriMobile, - required String? nativeAppUriDesktop, - }) = _SongLink; - - factory SongLink.fromJson(Map json) => - _$SongLinkFromJson(json); -} diff --git a/lib/services/song_link/song_link.dart b/lib/services/song_link/song_link.dart deleted file mode 100644 index e3cffa52..00000000 --- a/lib/services/song_link/song_link.dart +++ /dev/null @@ -1,54 +0,0 @@ -library song_link; - -import 'dart:convert'; - -import 'package:spotube/services/logger/logger.dart'; -import 'package:dio/dio.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:html/parser.dart'; - -part 'model.dart'; - -part 'song_link.freezed.dart'; -part 'song_link.g.dart'; - -abstract class SongLinkService { - static final dio = Dio(); - static Future> links(String spotifyId) async { - try { - final res = await dio.get( - "https://song.link/s/$spotifyId", - options: Options( - headers: { - "Accept": - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" - }, - responseType: ResponseType.plain, - ), - ); - - final document = parse(res.data); - - final script = document.getElementById("__NEXT_DATA__")?.text; - - if (script == null) { - return []; - } - - final pageProps = jsonDecode(script) as Map; - final songLinks = pageProps["props"]?["pageProps"]?["pageData"] - ?["sections"] - ?.firstWhere( - (section) => section?["sectionId"] == "section|auto|links|listen", - )?["links"] as List?; - - return songLinks?.map((link) => SongLink.fromJson(link)).toList() ?? - []; - } catch (e, stackTrace) { - AppLogger.reportError(e, stackTrace); - return []; - } - } -} diff --git a/lib/services/song_link/song_link.freezed.dart b/lib/services/song_link/song_link.freezed.dart deleted file mode 100644 index 0a1af8a9..00000000 --- a/lib/services/song_link/song_link.freezed.dart +++ /dev/null @@ -1,320 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'song_link.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); - -SongLink _$SongLinkFromJson(Map json) { - return _SongLink.fromJson(json); -} - -/// @nodoc -mixin _$SongLink { - String get displayName => throw _privateConstructorUsedError; - String get linkId => throw _privateConstructorUsedError; - String get platform => throw _privateConstructorUsedError; - bool get show => throw _privateConstructorUsedError; - String? get uniqueId => throw _privateConstructorUsedError; - String? get country => throw _privateConstructorUsedError; - String? get url => throw _privateConstructorUsedError; - String? get nativeAppUriMobile => throw _privateConstructorUsedError; - String? get nativeAppUriDesktop => throw _privateConstructorUsedError; - - Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) - $SongLinkCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SongLinkCopyWith<$Res> { - factory $SongLinkCopyWith(SongLink value, $Res Function(SongLink) then) = - _$SongLinkCopyWithImpl<$Res, SongLink>; - @useResult - $Res call( - {String displayName, - String linkId, - String platform, - bool show, - String? uniqueId, - String? country, - String? url, - String? nativeAppUriMobile, - String? nativeAppUriDesktop}); -} - -/// @nodoc -class _$SongLinkCopyWithImpl<$Res, $Val extends SongLink> - implements $SongLinkCopyWith<$Res> { - _$SongLinkCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? displayName = null, - Object? linkId = null, - Object? platform = null, - Object? show = null, - Object? uniqueId = freezed, - Object? country = freezed, - Object? url = freezed, - Object? nativeAppUriMobile = freezed, - Object? nativeAppUriDesktop = freezed, - }) { - return _then(_value.copyWith( - displayName: null == displayName - ? _value.displayName - : displayName // ignore: cast_nullable_to_non_nullable - as String, - linkId: null == linkId - ? _value.linkId - : linkId // ignore: cast_nullable_to_non_nullable - as String, - platform: null == platform - ? _value.platform - : platform // ignore: cast_nullable_to_non_nullable - as String, - show: null == show - ? _value.show - : show // ignore: cast_nullable_to_non_nullable - as bool, - uniqueId: freezed == uniqueId - ? _value.uniqueId - : uniqueId // ignore: cast_nullable_to_non_nullable - as String?, - country: freezed == country - ? _value.country - : country // ignore: cast_nullable_to_non_nullable - as String?, - url: freezed == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String?, - nativeAppUriMobile: freezed == nativeAppUriMobile - ? _value.nativeAppUriMobile - : nativeAppUriMobile // ignore: cast_nullable_to_non_nullable - as String?, - nativeAppUriDesktop: freezed == nativeAppUriDesktop - ? _value.nativeAppUriDesktop - : nativeAppUriDesktop // ignore: cast_nullable_to_non_nullable - as String?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SongLinkImplCopyWith<$Res> - implements $SongLinkCopyWith<$Res> { - factory _$$SongLinkImplCopyWith( - _$SongLinkImpl value, $Res Function(_$SongLinkImpl) then) = - __$$SongLinkImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String displayName, - String linkId, - String platform, - bool show, - String? uniqueId, - String? country, - String? url, - String? nativeAppUriMobile, - String? nativeAppUriDesktop}); -} - -/// @nodoc -class __$$SongLinkImplCopyWithImpl<$Res> - extends _$SongLinkCopyWithImpl<$Res, _$SongLinkImpl> - implements _$$SongLinkImplCopyWith<$Res> { - __$$SongLinkImplCopyWithImpl( - _$SongLinkImpl _value, $Res Function(_$SongLinkImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? displayName = null, - Object? linkId = null, - Object? platform = null, - Object? show = null, - Object? uniqueId = freezed, - Object? country = freezed, - Object? url = freezed, - Object? nativeAppUriMobile = freezed, - Object? nativeAppUriDesktop = freezed, - }) { - return _then(_$SongLinkImpl( - displayName: null == displayName - ? _value.displayName - : displayName // ignore: cast_nullable_to_non_nullable - as String, - linkId: null == linkId - ? _value.linkId - : linkId // ignore: cast_nullable_to_non_nullable - as String, - platform: null == platform - ? _value.platform - : platform // ignore: cast_nullable_to_non_nullable - as String, - show: null == show - ? _value.show - : show // ignore: cast_nullable_to_non_nullable - as bool, - uniqueId: freezed == uniqueId - ? _value.uniqueId - : uniqueId // ignore: cast_nullable_to_non_nullable - as String?, - country: freezed == country - ? _value.country - : country // ignore: cast_nullable_to_non_nullable - as String?, - url: freezed == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String?, - nativeAppUriMobile: freezed == nativeAppUriMobile - ? _value.nativeAppUriMobile - : nativeAppUriMobile // ignore: cast_nullable_to_non_nullable - as String?, - nativeAppUriDesktop: freezed == nativeAppUriDesktop - ? _value.nativeAppUriDesktop - : nativeAppUriDesktop // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SongLinkImpl implements _SongLink { - const _$SongLinkImpl( - {required this.displayName, - required this.linkId, - required this.platform, - required this.show, - required this.uniqueId, - required this.country, - required this.url, - required this.nativeAppUriMobile, - required this.nativeAppUriDesktop}); - - factory _$SongLinkImpl.fromJson(Map json) => - _$$SongLinkImplFromJson(json); - - @override - final String displayName; - @override - final String linkId; - @override - final String platform; - @override - final bool show; - @override - final String? uniqueId; - @override - final String? country; - @override - final String? url; - @override - final String? nativeAppUriMobile; - @override - final String? nativeAppUriDesktop; - - @override - String toString() { - return 'SongLink(displayName: $displayName, linkId: $linkId, platform: $platform, show: $show, uniqueId: $uniqueId, country: $country, url: $url, nativeAppUriMobile: $nativeAppUriMobile, nativeAppUriDesktop: $nativeAppUriDesktop)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SongLinkImpl && - (identical(other.displayName, displayName) || - other.displayName == displayName) && - (identical(other.linkId, linkId) || other.linkId == linkId) && - (identical(other.platform, platform) || - other.platform == platform) && - (identical(other.show, show) || other.show == show) && - (identical(other.uniqueId, uniqueId) || - other.uniqueId == uniqueId) && - (identical(other.country, country) || other.country == country) && - (identical(other.url, url) || other.url == url) && - (identical(other.nativeAppUriMobile, nativeAppUriMobile) || - other.nativeAppUriMobile == nativeAppUriMobile) && - (identical(other.nativeAppUriDesktop, nativeAppUriDesktop) || - other.nativeAppUriDesktop == nativeAppUriDesktop)); - } - - @JsonKey(ignore: true) - @override - int get hashCode => Object.hash(runtimeType, displayName, linkId, platform, - show, uniqueId, country, url, nativeAppUriMobile, nativeAppUriDesktop); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith => - __$$SongLinkImplCopyWithImpl<_$SongLinkImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SongLinkImplToJson( - this, - ); - } -} - -abstract class _SongLink implements SongLink { - const factory _SongLink( - {required final String displayName, - required final String linkId, - required final String platform, - required final bool show, - required final String? uniqueId, - required final String? country, - required final String? url, - required final String? nativeAppUriMobile, - required final String? nativeAppUriDesktop}) = _$SongLinkImpl; - - factory _SongLink.fromJson(Map json) = - _$SongLinkImpl.fromJson; - - @override - String get displayName; - @override - String get linkId; - @override - String get platform; - @override - bool get show; - @override - String? get uniqueId; - @override - String? get country; - @override - String? get url; - @override - String? get nativeAppUriMobile; - @override - String? get nativeAppUriDesktop; - @override - @JsonKey(ignore: true) - _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/services/song_link/song_link.g.dart b/lib/services/song_link/song_link.g.dart deleted file mode 100644 index 7658a74c..00000000 --- a/lib/services/song_link/song_link.g.dart +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'song_link.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$SongLinkImpl _$$SongLinkImplFromJson(Map json) => _$SongLinkImpl( - displayName: json['displayName'] as String, - linkId: json['linkId'] as String, - platform: json['platform'] as String, - show: json['show'] as bool, - uniqueId: json['uniqueId'] as String?, - country: json['country'] as String?, - url: json['url'] as String?, - nativeAppUriMobile: json['nativeAppUriMobile'] as String?, - nativeAppUriDesktop: json['nativeAppUriDesktop'] as String?, - ); - -Map _$$SongLinkImplToJson(_$SongLinkImpl instance) => - { - 'displayName': instance.displayName, - 'linkId': instance.linkId, - 'platform': instance.platform, - 'show': instance.show, - 'uniqueId': instance.uniqueId, - 'country': instance.country, - 'url': instance.url, - 'nativeAppUriMobile': instance.nativeAppUriMobile, - 'nativeAppUriDesktop': instance.nativeAppUriDesktop, - }; diff --git a/lib/services/sourced_track/enums.dart b/lib/services/sourced_track/enums.dart deleted file mode 100644 index d9ea079c..00000000 --- a/lib/services/sourced_track/enums.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:spotube/models/playback/track_sources.dart'; - -enum SourceCodecs { - m4a._("M4a (Best for downloaded music)"), - weba._("WebA (Best for streamed music)\nDoesn't support audio metadata"); - - final String label; - const SourceCodecs._(this.label); -} - -enum SourceQualities { - 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 = ({ - T info, - List? source -}); diff --git a/lib/services/sourced_track/exceptions.dart b/lib/services/sourced_track/exceptions.dart index c841e1e2..4817c9fb 100644 --- a/lib/services/sourced_track/exceptions.dart +++ b/lib/services/sourced_track/exceptions.dart @@ -1,12 +1,12 @@ -import 'package:spotube/models/playback/track_sources.dart'; +import 'package:spotube/models/metadata/metadata.dart'; class TrackNotFoundError extends Error { - final TrackSourceQuery track; + final SpotubeTrackObject track; TrackNotFoundError(this.track); @override String toString() { - return '[TrackNotFoundError] ${track.title} - ${track.artists.join(", ")}'; + return '[TrackNotFoundError] ${track.name} - ${track.artists.join(", ")}'; } } diff --git a/lib/services/sourced_track/models/video_info.dart b/lib/services/sourced_track/models/video_info.dart deleted file mode 100644 index e3452c61..00000000 --- a/lib/services/sourced_track/models/video_info.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'package:invidious/invidious.dart'; -import 'package:piped_client/piped_client.dart'; -import 'package:spotube/models/database/database.dart'; - -import 'package:youtube_explode_dart/youtube_explode_dart.dart'; - -class YoutubeVideoInfo { - final SearchMode searchMode; - final String title; - final Duration duration; - final String thumbnailUrl; - final String id; - final int likes; - final int dislikes; - final int views; - final String channelName; - final String channelId; - final DateTime publishedAt; - - YoutubeVideoInfo({ - required this.searchMode, - required this.title, - required this.duration, - required this.thumbnailUrl, - required this.id, - required this.likes, - required this.dislikes, - required this.views, - required this.channelName, - required this.publishedAt, - required this.channelId, - }); - - YoutubeVideoInfo.fromJson(Map json) - : title = json['title'], - searchMode = SearchMode.fromString(json['searchMode']), - duration = Duration(seconds: json['duration']), - thumbnailUrl = json['thumbnailUrl'], - id = json['id'], - likes = json['likes'], - dislikes = json['dislikes'], - views = json['views'], - channelName = json['channelName'], - channelId = json['channelId'], - publishedAt = DateTime.tryParse(json['publishedAt']) ?? DateTime.now(); - - Map toJson() => { - 'title': title, - 'duration': duration.inSeconds, - 'thumbnailUrl': thumbnailUrl, - 'id': id, - 'likes': likes, - 'dislikes': dislikes, - 'views': views, - 'channelName': channelName, - 'channelId': channelId, - 'publishedAt': publishedAt.toIso8601String(), - 'searchMode': searchMode.name, - }; - - factory YoutubeVideoInfo.fromVideo(Video video) { - return YoutubeVideoInfo( - searchMode: SearchMode.youtube, - title: video.title, - duration: video.duration ?? Duration.zero, - thumbnailUrl: video.thumbnails.mediumResUrl, - id: video.id.value, - likes: video.engagement.likeCount ?? 0, - dislikes: video.engagement.dislikeCount ?? 0, - views: video.engagement.viewCount, - channelName: video.author, - channelId: '/c/${video.channelId.value}', - publishedAt: video.uploadDate ?? DateTime(2003, 9, 9), - ); - } - - factory YoutubeVideoInfo.fromSearchItemStream( - PipedSearchItemStream searchItem, - SearchMode searchMode, - ) { - return YoutubeVideoInfo( - searchMode: searchMode, - title: searchItem.title, - duration: searchItem.duration, - thumbnailUrl: searchItem.thumbnail, - id: searchItem.id, - likes: 0, - dislikes: 0, - views: searchItem.views, - channelName: searchItem.uploaderName, - channelId: searchItem.uploaderUrl ?? "", - publishedAt: searchItem.uploadedDate != null - ? DateTime.tryParse(searchItem.uploadedDate!) ?? DateTime(2003, 9, 9) - : DateTime(2003, 9, 9), - ); - } - - factory YoutubeVideoInfo.fromStreamResponse( - PipedStreamResponse stream, SearchMode searchMode) { - return YoutubeVideoInfo( - searchMode: searchMode, - title: stream.title, - duration: stream.duration, - thumbnailUrl: stream.thumbnailUrl, - id: stream.id, - likes: stream.likes, - dislikes: stream.dislikes, - views: stream.views, - channelName: stream.uploader, - publishedAt: stream.uploadedDate != null - ? DateTime.tryParse(stream.uploadedDate!) ?? DateTime(2003, 9, 9) - : DateTime(2003, 9, 9), - channelId: stream.uploaderUrl, - ); - } - - factory YoutubeVideoInfo.fromSearchResponse( - InvidiousSearchResponseVideo searchResponse, - SearchMode searchMode, - ) { - return YoutubeVideoInfo( - searchMode: searchMode, - title: searchResponse.title, - duration: Duration(seconds: searchResponse.lengthSeconds), - thumbnailUrl: searchResponse.videoThumbnails.first.url, - id: searchResponse.videoId, - likes: 0, - dislikes: 0, - views: searchResponse.viewCount, - channelName: searchResponse.author, - channelId: searchResponse.authorId, - publishedAt: - DateTime.fromMillisecondsSinceEpoch(searchResponse.published * 1000), - ); - } -} diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index d979c007..385e5be6 100644 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -1,17 +1,28 @@ +import 'dart:convert'; + 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'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/provider/metadata_plugin/audio_source/quality_presets.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/services/dio/dio.dart'; +import 'package:spotube/services/logger/logger.dart'; +import 'package:spotube/services/metadata/errors/exceptions.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/sources/invidious.dart'; -import 'package:spotube/services/sourced_track/sources/jiosaavn.dart'; -import 'package:spotube/services/sourced_track/sources/piped.dart'; -import 'package:spotube/services/sourced_track/sources/youtube.dart'; +import 'package:spotube/services/sourced_track/exceptions.dart'; import 'package:spotube/utils/service_utils.dart'; -abstract class SourcedTrack extends BasicSourcedTrack { +final officialMusicRegex = RegExp( + r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)", + caseSensitive: false, +); + +class SourcedTrack extends BasicSourcedTrack { final Ref ref; SourcedTrack({ @@ -23,132 +34,277 @@ abstract class SourcedTrack extends BasicSourcedTrack { required super.sources, }); - static SourcedTrack fromJson( - Map json, { - required Ref ref, - }) { - final preferences = ref.read(userPreferencesProvider); - - final info = TrackSourceInfo.fromJson(json["info"]); - final query = TrackSourceQuery.fromJson(json["query"]); - final source = AudioSource.values.firstWhereOrNull( - (source) => source.name == json["source"], - ) ?? - preferences.audioSource; - final siblings = (json["siblings"] as List) - .map((s) => TrackSourceInfo.fromJson(s)) - .toList(); - final sources = - (json["sources"] as List).map((s) => TrackSource.fromJson(s)).toList(); - - return switch (preferences.audioSource) { - AudioSource.youtube => YoutubeSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - AudioSource.piped => PipedSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - AudioSource.jiosaavn => JioSaavnSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - AudioSource.invidious => InvidiousSourcedTrack( - ref: ref, - source: source, - siblings: siblings, - info: info, - query: query, - sources: sources, - ), - }; - } - - static String getSearchTerm(TrackSourceQuery track) { - final title = ServiceUtils.getTitle( - track.title, - artists: track.artists, - onlyCleanArtist: true, - ).trim(); - - assert(title.trim().isNotEmpty, "Title should not be empty"); - - return "$title - ${track.artists.join(", ")}"; - } - - static Future fetchFromQuery({ - required TrackSourceQuery query, + static Future fetchFromTrack({ + required SpotubeFullTrackObject query, required Ref ref, }) async { - final preferences = ref.read(userPreferencesProvider); - try { - return switch (preferences.audioSource) { - AudioSource.youtube => - await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref), - AudioSource.piped => - await PipedSourcedTrack.fetchFromTrack(query: query, ref: ref), - AudioSource.invidious => - await InvidiousSourcedTrack.fetchFromTrack(query: query, ref: ref), - AudioSource.jiosaavn => - await JioSaavnSourcedTrack.fetchFromTrack(query: query, ref: ref), - }; - } catch (e) { - if (preferences.audioSource == AudioSource.youtube) { - rethrow; + final audioSource = await ref.read(audioSourcePluginProvider.future); + final audioSourceConfig = await ref.read(metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig)); + if (audioSource == null || audioSourceConfig == null) { + throw MetadataPluginException.noDefaultAudioSourcePlugin(); + } + + final database = ref.read(databaseProvider); + final cachedSource = await (database.select(database.sourceMatchTable) + ..where((s) => + s.trackId.equals(query.id) & + s.sourceType.equals(audioSourceConfig.slug)) + ..limit(1) + ..orderBy([ + (s) => + OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), + ])) + .get() + .then((s) => s.firstOrNull); + + if (cachedSource == null) { + final siblings = await fetchSiblings(ref: ref, query: query); + if (siblings.isEmpty) { + throw TrackNotFoundError(query); } - return await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref); + await database.into(database.sourceMatchTable).insert( + SourceMatchTableCompanion.insert( + trackId: query.id, + sourceInfo: Value(jsonEncode(siblings.first)), + sourceType: audioSourceConfig.slug, + ), + ); + + final manifest = await audioSource.audioSource.streams(siblings.first); + + return SourcedTrack( + ref: ref, + siblings: siblings.skip(1).toList(), + info: siblings.first, + source: audioSourceConfig.slug, + sources: manifest, + query: query, + ); } + final item = SpotubeAudioSourceMatchObject.fromJson( + jsonDecode(cachedSource.sourceInfo), + ); + final manifest = await audioSource.audioSource.streams(item); + + final sourcedTrack = SourcedTrack( + ref: ref, + siblings: [], + sources: manifest, + info: item, + query: query, + source: audioSourceConfig.slug, + ); + + AppLogger.log.i("${query.name}: ${sourcedTrack.url}"); + + return sourcedTrack; } - static Future> fetchSiblings({ - required TrackSourceQuery query, + static List rankResults( + List results, + SpotubeFullTrackObject track, + ) { + return results + .map((sibling) { + int score = 0; + + for (final artist in track.artists) { + final isSameChannelArtist = + sibling.artists.any((a) => a.toLowerCase() == artist.name); + + if (isSameChannelArtist) { + score += 1; + } + + final titleContainsArtist = + sibling.title.toLowerCase().contains(artist.name.toLowerCase()); + + if (titleContainsArtist) { + score += 1; + } + } + + final titleContainsTrackName = + sibling.title.toLowerCase().contains(track.name.toLowerCase()); + + final hasOfficialFlag = + officialMusicRegex.hasMatch(sibling.title.toLowerCase()); + + if (titleContainsTrackName) { + score += 3; + } + + if (hasOfficialFlag) { + score += 1; + } + + if (hasOfficialFlag && titleContainsTrackName) { + score += 2; + } + + return (sibling: sibling, score: score); + }) + .sorted((a, b) => b.score.compareTo(a.score)) + .map((e) => e.sibling) + .toList(); + } + + static Future> fetchSiblings({ + required SpotubeFullTrackObject query, required Ref ref, - }) { - final preferences = ref.read(userPreferencesProvider); + }) async { + final audioSource = await ref.read(audioSourcePluginProvider.future); - return switch (preferences.audioSource) { - AudioSource.piped => - PipedSourcedTrack.fetchSiblings(query: query, ref: ref), - AudioSource.youtube => - YoutubeSourcedTrack.fetchSiblings(query: query, ref: ref), - AudioSource.jiosaavn => - JioSaavnSourcedTrack.fetchSiblings(query: query, ref: ref), - AudioSource.invidious => - InvidiousSourcedTrack.fetchSiblings(query: query, ref: ref), - }; + if (audioSource == null) { + throw MetadataPluginException.noDefaultAudioSourcePlugin(); + } + + final videoResults = []; + + final searchResults = await audioSource.audioSource.matches(query); + + if (ServiceUtils.onlyContainsEnglish(query.name)) { + videoResults.addAll(searchResults); + } else { + videoResults.addAll(rankResults(searchResults, query)); + } + + return videoResults.toSet().toList(); } - Future copyWithSibling(); + Future copyWithSibling() async { + if (siblings.isNotEmpty) { + return this; + } + final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - Future swapWithSibling(TrackSourceInfo sibling); + return SourcedTrack( + ref: ref, + siblings: fetchedSiblings.where((s) => s.id != info.id).toList(), + source: source, + sources: sources, + info: info, + query: query, + ); + } + + Future swapWithSibling( + SpotubeAudioSourceMatchObject sibling, + ) async { + if (sibling.id == info.id) { + return null; + } + + final audioSource = await ref.read(audioSourcePluginProvider.future); + final audioSourceConfig = await ref.read(metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig)); + if (audioSource == null || audioSourceConfig == null) { + throw MetadataPluginException.noDefaultAudioSourcePlugin(); + } + + // a sibling source that was fetched from the search results + final isStepSibling = siblings.none((s) => s.id == sibling.id); + + final newSourceInfo = isStepSibling + ? sibling + : siblings.firstWhere((s) => s.id == sibling.id); + + final newSiblings = siblings.where((s) => s.id != sibling.id).toList() + ..insert(0, info); + + final manifest = await audioSource.audioSource.streams(newSourceInfo); + + final database = ref.read(databaseProvider); + + // Delete the old Entry + await (database.sourceMatchTable.delete() + ..where( + (table) => + table.trackId.equals(query.id) & + table.sourceType.equals(audioSourceConfig.slug), + )) + .go(); + + await database.into(database.sourceMatchTable).insert( + SourceMatchTableCompanion.insert( + trackId: query.id, + sourceInfo: Value(jsonEncode(sibling)), + sourceType: audioSourceConfig.slug, + createdAt: Value(DateTime.now()), + ), + mode: InsertMode.replace, + ); + + return SourcedTrack( + ref: ref, + source: source, + siblings: newSiblings, + sources: manifest, + info: newSourceInfo, + query: query, + ); + } Future swapWithSiblingOfIndex(int index) { return swapWithSibling(siblings[index]); } - Future refreshStream(); + Future refreshStream() async { + final audioSource = await ref.read(audioSourcePluginProvider.future); + final audioSourceConfig = await ref.read(metadataPluginsProvider + .selectAsync((data) => data.defaultAudioSourcePluginConfig)); + if (audioSource == null || audioSourceConfig == null) { + throw MetadataPluginException.noDefaultAudioSourcePlugin(); + } + + 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.container} ${source.codec} ${source.bitrate}", + ); + + if (res.statusCode! < 400) { + validStreams.add(source); + } + } + + AppLogger.log.d(stringBuffer.toString()); + + if (validStreams.isEmpty) { + validStreams = await audioSource.audioSource.streams(info); + } + + final sourcedTrack = SourcedTrack( + ref: ref, + siblings: siblings, + source: source, + sources: validStreams, + info: info, + query: query, + ); + + AppLogger.log.i("Refreshing ${query.name}: ${sourcedTrack.url}"); + + return sourcedTrack; + } + String? get url { - final preferences = ref.read(userPreferencesProvider); + final preferences = ref.read(audioSourcePresetsProvider); - final codec = preferences.audioSource == AudioSource.jiosaavn - ? SourceCodecs.m4a - : preferences.streamMusicCodec; - - return getUrlOfCodec(codec); + return getUrlOfQuality( + preferences.presets[preferences.selectedStreamingContainerIndex], + preferences.selectedStreamingQualityIndex, + ); } /// Returns the URL of the track based on the codec and quality preferences. @@ -157,57 +313,62 @@ 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) { - final preferences = ref.read(userPreferencesProvider); + SpotubeAudioSourceStreamObject? getStreamOfQuality( + SpotubeAudioSourceContainerPreset preset, + int qualityIndex, + ) { + if (sources.isEmpty) return null; + + final quality = preset.qualities[qualityIndex]; final exactMatch = sources.firstWhereOrNull( - (source) => - source.codec == codec && source.quality == preferences.audioQuality, + (source) { + if (source.container != preset.name) return false; + + if (quality case SpotubeAudioLosslessContainerQuality()) { + return source.sampleRate == quality.sampleRate && + source.bitDepth == quality.bitDepth; + } else { + return source.bitrate == + (quality as SpotubeAudioLossyContainerQuality).bitrate; + } + }, ); if (exactMatch != null) { - return exactMatch.url; + return exactMatch; } - final sameCodecSources = sources - .where((source) => source.codec == codec) - .toList() - .sorted((a, b) { - final aDiff = (a.quality.index - preferences.audioQuality.index).abs(); - final bDiff = (b.quality.index - preferences.audioQuality.index).abs(); - return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index; - }).toList(); - - if (sameCodecSources.isNotEmpty) { - return preferences.audioQuality > SourceQualities.low - ? sameCodecSources.first.url - : sameCodecSources.last.url; - } - - final fallbackSource = sources.sorted((a, b) { - final aDiff = (a.quality.index - preferences.audioQuality.index).abs(); - final bDiff = (b.quality.index - preferences.audioQuality.index).abs(); - return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index; + // Find the preset with closest quality to the supplied quality + return sources.where((source) { + return source.container == preset.name; + }).reduce((prev, curr) { + if (quality is SpotubeAudioLosslessContainerQuality) { + final prevDiff = ((prev.sampleRate ?? 0) - quality.sampleRate).abs() + + ((prev.bitDepth ?? 0) - quality.bitDepth).abs(); + final currDiff = ((curr.sampleRate ?? 0) - quality.sampleRate).abs() + + ((curr.bitDepth ?? 0) - quality.bitDepth).abs(); + return currDiff < prevDiff ? curr : prev; + } else { + final prevDiff = ((prev.bitrate ?? 0) - + (quality as SpotubeAudioLossyContainerQuality).bitrate) + .abs(); + final currDiff = ((curr.bitrate ?? 0) - quality.bitrate).abs(); + return currDiff < prevDiff ? curr : prev; + } }); - - return preferences.audioQuality > SourceQualities.low - ? fallbackSource.firstOrNull?.url - : fallbackSource.lastOrNull?.url; } - SourceCodecs get codec { - final preferences = ref.read(userPreferencesProvider); - - return preferences.audioSource == AudioSource.jiosaavn - ? SourceCodecs.m4a - : preferences.streamMusicCodec; + String? getUrlOfQuality( + SpotubeAudioSourceContainerPreset preset, + int qualityIndex, + ) { + return getStreamOfQuality(preset, qualityIndex)?.url; } - TrackSource get activeTrackSource { - final audioQuality = ref.read(userPreferencesProvider).audioQuality; - return sources.firstWhereOrNull( - (source) => source.codec == codec && source.quality == audioQuality, - ) ?? - sources.first; + SpotubeAudioSourceContainerPreset? get qualityPreset { + final presetState = ref.read(audioSourcePresetsProvider); + return presetState.presets + .elementAtOrNull(presetState.selectedStreamingContainerIndex); } } diff --git a/lib/services/sourced_track/sources/invidious.dart b/lib/services/sourced_track/sources/invidious.dart deleted file mode 100644 index 82e001f5..00000000 --- a/lib/services/sourced_track/sources/invidious.dart +++ /dev/null @@ -1,261 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/models/database/database.dart'; -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/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:invidious/invidious.dart'; -import 'package:spotube/services/sourced_track/sources/youtube.dart'; -import 'package:spotube/utils/service_utils.dart'; - -final invidiousProvider = Provider( - (ref) { - final invidiousInstance = ref.watch( - userPreferencesProvider.select((s) => s.invidiousInstance), - ); - return InvidiousClient(server: invidiousInstance); - }, -); - -class InvidiousSourcedTrack extends SourcedTrack { - InvidiousSourcedTrack({ - required super.ref, - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final audioSource = ref.read(userPreferencesProvider).audioSource; - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .getSingleOrNull(); - final invidiousClient = ref.read(invidiousProvider); - - if (cachedSource == null) { - final siblings = await fetchSiblings(ref: ref, query: query); - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: const Value(SourceType.youtube), - ), - ); - - return InvidiousSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - sources: siblings.first.source as List, - info: siblings.first.info, - query: query, - source: audioSource, - ); - } else { - final manifest = - await invidiousClient.videos.get(cachedSource.sourceId, local: true); - - return InvidiousSourcedTrack( - ref: ref, - siblings: [], - sources: toSources(manifest), - info: TrackSourceInfo( - id: manifest.videoId, - artists: manifest.author, - pageUrl: "https://www.youtube.com/watch?v=${manifest.videoId}", - thumbnail: manifest.videoThumbnails.first.url, - title: manifest.title, - durationMs: Duration(seconds: manifest.lengthSeconds).inMilliseconds, - ), - query: query, - source: audioSource, - ); - } - } - - static List toSources(InvidiousVideoResponse manifest) { - return manifest.adaptiveFormats.map((stream) { - return TrackSource( - url: stream.url.toString(), - quality: switch (stream.qualityLabel) { - "high" => SourceQualities.high, - "medium" => SourceQualities.medium, - _ => SourceQualities.low, - }, - codec: stream.type.contains("audio/webm") - ? SourceCodecs.weba - : SourceCodecs.m4a, - bitrate: stream.bitrate, - ); - }).toList(); - } - - static Future toSiblingType( - int index, - YoutubeVideoInfo item, - InvidiousClient invidiousClient, - ) async { - List? sourceMap; - if (index == 0) { - final manifest = await invidiousClient.videos.get(item.id, local: true); - sourceMap = toSources(manifest); - } - - final SiblingType sibling = ( - info: TrackSourceInfo( - id: item.id, - artists: item.channelName, - pageUrl: "https://www.youtube.com/watch?v=${item.id}", - thumbnail: item.thumbnailUrl, - title: item.title, - durationMs: item.duration.inMilliseconds, - ), - source: sourceMap, - ); - - return sibling; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final invidiousClient = ref.read(invidiousProvider); - final preference = ref.read(userPreferencesProvider); - - final searchQuery = SourcedTrack.getSearchTerm(query); - - final searchResults = await invidiousClient.search.list( - searchQuery, - type: InvidiousSearchType.video, - ); - - if (ServiceUtils.onlyContainsEnglish(searchQuery)) { - return await Future.wait( - searchResults - .whereType() - .map( - (result) => YoutubeVideoInfo.fromSearchResponse( - result, - preference.searchMode, - ), - ) - .mapIndexed((i, r) => toSiblingType(i, r, invidiousClient)), - ); - } - - final rankedSiblings = YoutubeSourcedTrack.rankResults( - searchResults - .whereType() - .map( - (result) => YoutubeVideoInfo.fromSearchResponse( - result, - preference.searchMode, - ), - ) - .toList(), - query, - ); - - return await Future.wait( - rankedSiblings.mapIndexed((i, r) => toSiblingType(i, r, invidiousClient)), - ); - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return InvidiousSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - info: info, - query: query, - sources: sources, - ); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, info); - - final pipedClient = ref.read(invidiousProvider); - - final manifest = - await pipedClient.videos.get(newSourceInfo.id, local: true); - - final database = ref.read(databaseProvider); - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: newSourceInfo.id, - sourceType: const Value(SourceType.youtube), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return InvidiousSourcedTrack( - ref: ref, - siblings: newSiblings, - sources: toSources(manifest), - info: newSourceInfo, - query: query, - source: source, - ); - } - - @override - Future refreshStream() async { - final manifest = - await ref.read(invidiousProvider).videos.get(info.id, local: true); - - return InvidiousSourcedTrack( - ref: ref, - siblings: siblings, - sources: toSources(manifest), - info: info, - query: query, - source: source, - ); - } -} diff --git a/lib/services/sourced_track/sources/jiosaavn.dart b/lib/services/sourced_track/sources/jiosaavn.dart deleted file mode 100644 index 02e97479..00000000 --- a/lib/services/sourced_track/sources/jiosaavn.dart +++ /dev/null @@ -1,230 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/playback/track_sources.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:jiosaavn/jiosaavn.dart'; -import 'package:spotube/extensions/string.dart'; - -final jiosaavnClient = JioSaavnClient(); - -class JioSaavnSourcedTrack extends SourcedTrack { - JioSaavnSourcedTrack({ - required super.ref, - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - bool weakMatch = false, - }) async { - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .getSingleOrNull(); - - if (cachedSource == null || - cachedSource.sourceType != SourceType.jiosaavn) { - final siblings = - await fetchSiblings(ref: ref, query: query, weakMatch: weakMatch); - - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: const Value(SourceType.jiosaavn), - ), - ); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - sources: siblings.first.source!, - info: siblings.first.info, - query: query, - source: AudioSource.jiosaavn, - ); - } - - final [item] = - await jiosaavnClient.songs.detailsById([cachedSource.sourceId]); - - final (:info, :source) = toSiblingType(item); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: [], - sources: source!, - query: query, - info: info, - source: AudioSource.jiosaavn, - ); - } - - static SiblingType toSiblingType(SongResponse result) { - final SiblingType sibling = ( - info: TrackSourceInfo( - artists: [ - result.primaryArtists, - if (result.featuredArtists.isNotEmpty) ", ", - result.featuredArtists - ].join("").unescapeHtml(), - durationMs: - Duration(seconds: int.parse(result.duration)).inMilliseconds, - id: result.id, - pageUrl: result.url, - thumbnail: result.image?.last.link ?? "", - title: result.name!.unescapeHtml(), - ), - source: result.downloadUrl!.map((link) { - return TrackSource( - url: link.link, - quality: link.quality == "320kbps" - ? SourceQualities.high - : link.quality == "160kbps" - ? SourceQualities.medium - : SourceQualities.low, - codec: SourceCodecs.m4a, - bitrate: link.quality, - ); - }).toList() - ); - - return sibling; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - bool weakMatch = false, - }) async { - final searchQuery = SourcedTrack.getSearchTerm(query); - - final SongSearchResponse(:results) = - await jiosaavnClient.search.songs(searchQuery, limit: 20); - - final trackArtistNames = query.artists; - - final matchedResults = results - .where( - (s) { - s.name?.unescapeHtml().contains(query.title) ?? false; - - final sameName = s.name?.unescapeHtml() == query.title; - final artistNames = [ - s.primaryArtists, - if (s.featuredArtists.isNotEmpty) ", ", - s.featuredArtists - ].join("").unescapeHtml(); - final sameArtists = artistNames.split(", ").any( - (artist) => trackArtistNames.any((ar) => artist == ar), - ); - if (weakMatch) { - final containsName = - s.name?.unescapeHtml().contains(query.title) ?? false; - final containsPrimaryArtist = s.primaryArtists - .unescapeHtml() - .contains(trackArtistNames.first); - - return containsName && containsPrimaryArtist; - } - - return sameName && sameArtists; - }, - ) - .map(toSiblingType) - .toList(); - - if (weakMatch && matchedResults.isEmpty) { - return results.map(toSiblingType).toList(); - } - - return matchedResults; - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - info: info, - query: query, - sources: sources, - ); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == this.info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, this.info); - - final [item] = await jiosaavnClient.songs.detailsById([newSourceInfo.id]); - - final (:info, :source) = toSiblingType(item); - - final database = ref.read(databaseProvider); - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: info.id, - sourceType: const Value(SourceType.jiosaavn), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return JioSaavnSourcedTrack( - ref: ref, - siblings: newSiblings, - sources: source!, - info: newSourceInfo, - query: query, - source: AudioSource.jiosaavn, - ); - } - - @override - Future refreshStream() async { - // There's no need to refresh the stream for JioSaavnSourcedTrack - return this; - } -} diff --git a/lib/services/sourced_track/sources/piped.dart b/lib/services/sourced_track/sources/piped.dart deleted file mode 100644 index 78beda10..00000000 --- a/lib/services/sourced_track/sources/piped.dart +++ /dev/null @@ -1,290 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:piped_client/piped_client.dart'; -import 'package:spotube/models/database/database.dart'; -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/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:spotube/services/sourced_track/sources/youtube.dart'; -import 'package:spotube/utils/service_utils.dart'; - -final pipedProvider = Provider( - (ref) { - final instance = - ref.watch(userPreferencesProvider.select((s) => s.pipedInstance)); - return PipedClient(instance: instance); - }, -); - -class PipedSourcedTrack extends SourcedTrack { - PipedSourcedTrack({ - required super.ref, - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final audioSource = ref.read(userPreferencesProvider).audioSource; - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .getSingleOrNull(); - final preferences = ref.read(userPreferencesProvider); - final pipedClient = ref.read(pipedProvider); - - if (cachedSource == null) { - final siblings = await fetchSiblings(ref: ref, query: query); - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: Value( - preferences.searchMode == SearchMode.youtube - ? SourceType.youtube - : SourceType.youtubeMusic, - ), - ), - ); - - return PipedSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - source: audioSource, - info: siblings.first.info, - query: query, - sources: siblings.first.source!, - ); - } else { - final manifest = await pipedClient.streams(cachedSource.sourceId); - - return PipedSourcedTrack( - ref: ref, - siblings: [], - sources: toSources(manifest), - info: TrackSourceInfo( - id: manifest.id, - artists: manifest.uploader, - pageUrl: "https://www.youtube.com/watch?v=${manifest.id}", - thumbnail: manifest.thumbnailUrl, - title: manifest.title, - durationMs: manifest.duration.inMilliseconds, - ), - query: query, - source: audioSource, - ); - } - } - - static List toSources(PipedStreamResponse manifest) { - return manifest.audioStreams.map((audio) { - return TrackSource( - url: audio.url.toString(), - quality: switch (audio.quality) { - "high" => SourceQualities.high, - "medium" => SourceQualities.medium, - _ => SourceQualities.low, - }, - codec: audio.format == PipedAudioStreamFormat.m4a - ? SourceCodecs.m4a - : SourceCodecs.weba, - bitrate: audio.bitrate.toString(), - ); - }).toList(); - } - - static Future toSiblingType( - int index, - YoutubeVideoInfo item, - PipedClient pipedClient, - ) async { - List? sources; - if (index == 0) { - final manifest = await pipedClient.streams(item.id); - sources = toSources(manifest); - } - - final SiblingType sibling = ( - info: TrackSourceInfo( - id: item.id, - artists: item.channelName, - pageUrl: "https://www.youtube.com/watch?v=${item.id}", - thumbnail: item.thumbnailUrl, - title: item.title, - durationMs: item.duration.inMilliseconds, - ), - source: sources, - ); - - return sibling; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final pipedClient = ref.read(pipedProvider); - final preference = ref.read(userPreferencesProvider); - - final searchQuery = SourcedTrack.getSearchTerm(query); - - final PipedSearchResult(items: searchResults) = await pipedClient.search( - searchQuery, - preference.searchMode == SearchMode.youtube - ? PipedFilter.videos - : PipedFilter.musicSongs, - ); - - // when falling back to piped API make sure to use the YouTube mode - final isYouTubeMusic = preference.audioSource != AudioSource.piped - ? false - : preference.searchMode == SearchMode.youtubeMusic; - - if (isYouTubeMusic) { - final artists = query.artists; - - return await Future.wait( - searchResults - .map( - (result) => YoutubeVideoInfo.fromSearchItemStream( - result as PipedSearchItemStream, - preference.searchMode, - ), - ) - .sorted((a, b) => b.views.compareTo(a.views)) - .where( - (item) => artists.any( - (artist) => - artist.toLowerCase() == item.channelName.toLowerCase(), - ), - ) - .mapIndexed((i, r) => toSiblingType(i, r, pipedClient)), - ); - } - - if (ServiceUtils.onlyContainsEnglish(searchQuery)) { - return await Future.wait( - searchResults - .whereType() - .map( - (result) => YoutubeVideoInfo.fromSearchItemStream( - result, - preference.searchMode, - ), - ) - .mapIndexed((i, r) => toSiblingType(i, r, pipedClient)), - ); - } - - final rankedSiblings = YoutubeSourcedTrack.rankResults( - searchResults - .map( - (result) => YoutubeVideoInfo.fromSearchItemStream( - result as PipedSearchItemStream, - preference.searchMode, - ), - ) - .toList(), - query, - ); - - return await Future.wait( - rankedSiblings.mapIndexed((i, r) => toSiblingType(i, r, pipedClient)), - ); - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return PipedSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - info: info, - query: query, - sources: sources, - ); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, info); - - final pipedClient = ref.read(pipedProvider); - - final manifest = await pipedClient.streams(newSourceInfo.id); - - final database = ref.read(databaseProvider); - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: newSourceInfo.id, - sourceType: const Value(SourceType.youtube), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return PipedSourcedTrack( - ref: ref, - siblings: newSiblings, - sources: toSources(manifest), - info: newSourceInfo, - query: query, - source: source, - ); - } - - @override - Future refreshStream() async { - final manifest = await ref.read(pipedProvider).streams(info.id); - return PipedSourcedTrack( - ref: ref, - siblings: siblings, - info: info, - source: source, - query: query, - sources: toSources(manifest), - ); - } -} diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart deleted file mode 100644 index 399d5e10..00000000 --- a/lib/services/sourced_track/sources/youtube.dart +++ /dev/null @@ -1,437 +0,0 @@ -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'; -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'; -import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:spotube/utils/service_utils.dart'; -import 'package:youtube_explode_dart/youtube_explode_dart.dart'; - -final officialMusicRegex = RegExp( - r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)", - caseSensitive: false, -); - -class YoutubeSourcedTrack extends SourcedTrack { - YoutubeSourcedTrack({ - required super.source, - required super.siblings, - required super.info, - required super.query, - required super.sources, - required super.ref, - }); - - static Future fetchFromTrack({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final audioSource = ref.read(userPreferencesProvider).audioSource; - final database = ref.read(databaseProvider); - final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(query.id)) - ..limit(1) - ..orderBy([ - (s) => - OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), - ])) - .get() - .then((s) => s.firstOrNull); - - if (cachedSource == null || cachedSource.sourceType != SourceType.youtube) { - final siblings = await fetchSiblings(ref: ref, query: query); - if (siblings.isEmpty) { - throw TrackNotFoundError(query); - } - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: siblings.first.info.id, - sourceType: const Value(SourceType.youtube), - ), - ); - - return YoutubeSourcedTrack( - ref: ref, - siblings: siblings.map((s) => s.info).skip(1).toList(), - info: siblings.first.info, - source: audioSource, - sources: siblings.first.source ?? [], - query: query, - ); - } - final (item, manifest) = await ref - .read(youtubeEngineProvider) - .getVideoWithStreamInfo(cachedSource.sourceId); - - final sourcedTrack = YoutubeSourcedTrack( - ref: ref, - siblings: [], - sources: toTrackSources(manifest), - info: TrackSourceInfo( - id: item.id.value, - artists: item.author, - pageUrl: item.url, - thumbnail: item.thumbnails.highResUrl, - title: item.title, - durationMs: item.duration?.inMilliseconds ?? 0, - ), - query: query, - source: audioSource, - ); - - AppLogger.log.i("${query.title}: ${sourcedTrack.url}"); - - return sourcedTrack; - } - - static List toTrackSources(StreamManifest manifest) { - return manifest.audioOnly.map((streamInfo) { - return TrackSource( - url: streamInfo.url.toString(), - quality: switch (streamInfo.qualityLabel) { - "medium" => SourceQualities.medium, - "high" => SourceQualities.high, - "low" => SourceQualities.low, - _ => SourceQualities.high, - }, - codec: streamInfo.codec.mimeType == "audio/webm" - ? SourceCodecs.weba - : SourceCodecs.m4a, - bitrate: streamInfo.bitrate.bitsPerSecond.toString(), - ); - }).toList(); - } - - static Future toSiblingType( - int index, - YoutubeVideoInfo item, - dynamic ref, - ) async { - assert(ref is WidgetRef || ref is Ref, "Invalid ref type"); - - List? sourceMap; - if (index == 0) { - final manifest = - await ref.read(youtubeEngineProvider).getStreamManifest(item.id); - sourceMap = toTrackSources(manifest); - } - - final SiblingType sibling = ( - info: TrackSourceInfo( - id: item.id, - artists: item.channelName, - pageUrl: "https://www.youtube.com/watch?v=${item.id}", - thumbnail: item.thumbnailUrl, - title: item.title, - durationMs: item.duration.inMilliseconds, - ), - source: sourceMap, - ); - - return sibling; - } - - static List rankResults( - List results, TrackSourceQuery track) { - return results - .sorted((a, b) => b.views.compareTo(a.views)) - .map((sibling) { - int score = 0; - - for (final artist in track.artists) { - final isSameChannelArtist = - sibling.channelName.toLowerCase() == artist.toLowerCase(); - final channelContainsArtist = sibling.channelName - .toLowerCase() - .contains(artist.toLowerCase()); - - if (isSameChannelArtist || channelContainsArtist) { - score += 1; - } - - final titleContainsArtist = - sibling.title.toLowerCase().contains(artist.toLowerCase()); - - if (titleContainsArtist) { - score += 1; - } - } - - final titleContainsTrackName = - sibling.title.toLowerCase().contains(track.title.toLowerCase()); - - final hasOfficialFlag = - officialMusicRegex.hasMatch(sibling.title.toLowerCase()); - - if (titleContainsTrackName) { - score += 3; - } - - if (hasOfficialFlag) { - score += 1; - } - - if (hasOfficialFlag && titleContainsTrackName) { - score += 2; - } - - return (sibling: sibling, score: score); - }) - .sorted((a, b) => b.score.compareTo(a.score)) - .map((e) => e.sibling) - .toList(); - } - - static Future> fetchFromIsrc({ - required TrackSourceQuery track, - required Ref ref, - }) async { - final isrcResults = []; - final isrc = track.isrc; - if (isrc.isNotEmpty) { - final searchedVideos = - await ref.read(youtubeEngineProvider).searchVideos(isrc.toString()); - if (searchedVideos.isNotEmpty) { - 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 - .toLowerCase() - .replaceAll(RegExp(r'[^\p{L}\p{N}\p{Z}]+', unicode: true), '') - .split(RegExp(r'\p{Z}+', unicode: true)) - .where((item) => item.isNotEmpty); - final spWords = track.title - .toLowerCase() - .replaceAll(RegExp(r'[^\p{L}\p{N}\p{Z}]+', unicode: true), '') - .split(RegExp(r'\p{Z}+', unicode: true)) - .where((item) => item.isNotEmpty); - // Single word and duration match with 3 second tolerance - if (ytWords.any((word) => spWords.contains(word)) && - (videoInfo.duration - - Duration(milliseconds: track.durationMs)) - .abs() - .inMilliseconds <= - 3000) { - stringBuffer.writeln( - "ISRC MATCH: ${videoInfo.id} ${videoInfo.title} by ${videoInfo.channelName} ${videoInfo.duration}", - ); - - return videoInfo; - } - return null; - }) - .nonNulls - .toList(); - - AppLogger.log.d(stringBuffer.toString()); - - isrcResults.addAll(filteredMatches); - } - } - return isrcResults; - } - - static Future> fetchSiblings({ - required TrackSourceQuery query, - required Ref ref, - }) async { - final videoResults = []; - - if (query is! SourcedTrack) { - final isrcResults = await fetchFromIsrc( - track: query, - ref: ref, - ); - - 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", - ); - if (ytLink?.url != null) { - try { - videoResults.add( - YoutubeVideoInfo.fromVideo(await ref - .read(youtubeEngineProvider) - .getVideo(Uri.parse(ytLink!.url!).queryParameters["v"]!)), - ); - } on VideoUnplayableException catch (e, stack) { - // Ignore this error and continue with the search - AppLogger.reportError(e, stack); - } - } else { - AppLogger.log.w("No YouTube link found in SongLink results"); - } - } - } - - final searchQuery = SourcedTrack.getSearchTerm(query); - - final searchResults = - await ref.read(youtubeEngineProvider).searchVideos(searchQuery); - - if (ServiceUtils.onlyContainsEnglish(searchQuery)) { - videoResults - .addAll(searchResults.map(YoutubeVideoInfo.fromVideo).toList()); - } else { - videoResults.addAll(rankResults( - searchResults.map(YoutubeVideoInfo.fromVideo).toList(), - query, - )); - } - - final seenIds = {}; - int index = 0; - return await Future.wait( - videoResults.map((videoResult) async { - // Deduplicate results - if (!seenIds.contains(videoResult.id)) { - seenIds.add(videoResult.id); - return await toSiblingType(index++, videoResult, ref); - } - return null; - }), - ).then((s) => s.whereType().toList()); - } - - @override - Future swapWithSibling(TrackSourceInfo sibling) async { - if (sibling.id == info.id) { - return null; - } - - // a sibling source that was fetched from the search results - final isStepSibling = siblings.none((s) => s.id == sibling.id); - - final newSourceInfo = isStepSibling - ? sibling - : siblings.firstWhere((s) => s.id == sibling.id); - - final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, info); - - final manifest = await ref - .read(youtubeEngineProvider) - .getStreamManifest(newSourceInfo.id); - - final database = ref.read(databaseProvider); - - await database.into(database.sourceMatchTable).insert( - SourceMatchTableCompanion.insert( - trackId: query.id, - sourceId: newSourceInfo.id, - sourceType: const Value(SourceType.youtube), - // Because we're sorting by createdAt in the query - // we have to update it to indicate priority - createdAt: Value(DateTime.now()), - ), - mode: InsertMode.replace, - ); - - return YoutubeSourcedTrack( - ref: ref, - source: source, - siblings: newSiblings, - sources: toTrackSources(manifest), - info: newSourceInfo, - query: query, - ); - } - - @override - Future copyWithSibling() async { - if (siblings.isNotEmpty) { - return this; - } - final fetchedSiblings = await fetchSiblings(ref: ref, query: query); - - return YoutubeSourcedTrack( - ref: ref, - siblings: fetchedSiblings - .where((s) => s.info.id != info.id) - .map((s) => s.info) - .toList(), - source: source, - sources: sources, - info: info, - query: query, - ); - } - - @override - Future refreshStream() async { - 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: validStreams, - info: info, - query: query, - ); - - AppLogger.log.i("Refreshing ${query.title}: ${sourcedTrack.url}"); - - return sourcedTrack; - } -} diff --git a/lib/services/youtube_engine/newpipe_engine.dart b/lib/services/youtube_engine/newpipe_engine.dart index ae451e22..d6445a19 100644 --- a/lib/services/youtube_engine/newpipe_engine.dart +++ b/lib/services/youtube_engine/newpipe_engine.dart @@ -6,7 +6,7 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:http_parser/http_parser.dart'; class NewPipeEngine implements YouTubeEngine { - static bool get isAvailableForPlatform => kIsAndroid; + static bool get isAvailableForPlatform => kIsAndroid || kIsDesktop; AudioOnlyStreamInfo _parseAudioStream(AudioStream stream, String videoId) { return AudioOnlyStreamInfo( diff --git a/lib/services/youtube_engine/quickjs_solver.dart b/lib/services/youtube_engine/quickjs_solver.dart new file mode 100644 index 00000000..a7c032f6 --- /dev/null +++ b/lib/services/youtube_engine/quickjs_solver.dart @@ -0,0 +1,167 @@ +// import 'dart:async'; +// import 'dart:collection'; +// import 'dart:convert'; +// import 'package:flutter/foundation.dart'; +// import 'package:http/http.dart' as http; +// import 'package:youtube_explode_dart/js_challenge.dart'; +// // ignore: implementation_imports +// import 'package:youtube_explode_dart/src/reverse_engineering/challenges/ejs/ejs.dart'; +// import 'package:jsf/jsf.dart'; + +// /// [WIP] +// class QuickJSEJSSolver extends BaseJSChallengeSolver { +// final _playerCache = {}; +// final _sigCache = <(String, String, JSChallengeType), String>{}; +// final QuickJSRuntime qjs; +// QuickJSEJSSolver._(this.qjs); + +// static Future init() async { +// final modules = await EJSBuilder.getJSModules(); +// final deno = await QuickJSRuntime.init(modules); +// return QuickJSEJSSolver._(deno); +// } + +// @override +// Future solve( +// String playerUrl, JSChallengeType type, String challenge) async { +// final key = (playerUrl, challenge, type); +// if (_sigCache.containsKey(key)) { +// return _sigCache[key]!; +// } + +// var playerScript = _playerCache[playerUrl]; +// if (playerScript == null) { +// final resp = await http.get(Uri.parse(playerUrl)); +// playerScript = _playerCache[playerUrl] = resp.body; +// } +// final jsCall = EJSBuilder.buildJSCall(playerScript, { +// type: [challenge], +// }); + +// final result = await qjs.eval(jsCall); +// // Trim the first and last characters (' delimiters of the JS string) +// final data = json.decode(result.substring(1, result.length - 1)) +// as Map; + +// if (data['type'] != 'result') { +// throw Exception('Unexpected response type: ${data['type']}'); +// } +// final response = data['responses'][0]; +// if (response['type'] != 'result') { +// throw Exception('Unexpected item response type: ${response['type']}'); +// } +// final decoded = response['data'][challenge]; +// if (decoded == null) { +// throw Exception('No data for challenge: $challenge'); +// } + +// _sigCache[key] = decoded; + +// return decoded; +// } + +// @override +// void dispose() { +// qjs.dispose(); +// } +// } + +// class _EvalRequest { +// final String code; +// final Completer completer; + +// _EvalRequest(this.code, this.completer); +// } + +// class QuickJSRuntime { +// final JsRuntime _runtime; +// final StreamController _stdoutController = +// StreamController.broadcast(); + +// // Queue for incoming eval requests +// final Queue<_EvalRequest> _evalQueue = Queue<_EvalRequest>(); +// bool _isProcessing = false; // Flag to indicate if an eval is currently active + +// QuickJSRuntime(this._runtime); + +// /// Disposes the Deno process. +// void dispose() { +// _stdoutController.close(); +// _runtime.dispose(); +// } + +// /// Sends JavaScript code to Deno for evaluation. +// /// Assumes single-line input produces single-line output. +// Future eval(String code) { +// final completer = Completer(); +// final request = _EvalRequest(code, completer); +// _evalQueue.addLast(request); // Add request to the end of the queue +// _processQueue(); // Attempt to process the queue + +// return completer.future; +// } + +// // Processes the eval queue. +// void _processQueue() { +// if (_isProcessing || _evalQueue.isEmpty) { +// return; // Already processing or nothing in queue +// } + +// _isProcessing = true; +// final request = +// _evalQueue.first; // Get the next request without removing it yet + +// StreamSubscription? currentOutputSubscription; +// Completer lineReceived = Completer(); + +// currentOutputSubscription = _stdoutController.stream.listen((data) { +// if (!lineReceived.isCompleted) { +// // Assuming single line output per eval. +// // This will capture the first full line or chunk received after sending the code. +// request.completer.complete(data.trim()); +// lineReceived.complete(); +// currentOutputSubscription +// ?.cancel(); // Cancel subscription for this request +// _evalQueue.removeFirst(); // Remove the processed request +// _isProcessing = false; // Mark as no longer processing +// _processQueue(); // Attempt to process next item in queue +// } +// }, onError: (e) { +// if (!request.completer.isCompleted) { +// request.completer.completeError(e); +// lineReceived.completeError(e); +// currentOutputSubscription?.cancel(); +// _evalQueue.removeFirst(); +// _isProcessing = false; +// _processQueue(); +// } +// }, onDone: () { +// if (!request.completer.isCompleted) { +// request.completer.completeError( +// StateError('Deno process closed while awaiting eval result.')); +// lineReceived.completeError( +// StateError('Deno process closed while awaiting eval result.')); +// currentOutputSubscription?.cancel(); +// _evalQueue.removeFirst(); +// _isProcessing = false; +// _processQueue(); +// } +// }); + +// debugPrint("[QuickJS Solver] Evaluate ${request.code}"); +// final result = _runtime.eval(request.code); +// debugPrint("[QuickJS Solver] Evaluation Result $result"); +// _stdoutController.add(result); +// } + +// static Future init(String initCode) async { +// debugPrint("[QuickJS Solver] Initializing"); +// debugPrint("[QuickJS Solver] script $initCode"); + +// final runtime = JsRuntime(); + +// runtime.execInitScript(initCode); + +// return QuickJSRuntime(runtime); +// } +// } diff --git a/lib/services/youtube_engine/youtube_explode_engine.dart b/lib/services/youtube_engine/youtube_explode_engine.dart index fa58314c..f8587ca6 100644 --- a/lib/services/youtube_engine/youtube_explode_engine.dart +++ b/lib/services/youtube_engine/youtube_explode_engine.dart @@ -2,6 +2,7 @@ import 'dart:isolate'; import 'package:flutter/foundation.dart'; import 'package:spotube/services/youtube_engine/youtube_engine.dart'; +// import 'package:youtube_explode_dart/solvers.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'dart:async'; @@ -55,8 +56,9 @@ class IsolatedYoutubeExplode { } } - static void _isolateEntry(SendPort mainSendPort) { + static Future _isolateEntry(SendPort mainSendPort) async { final receivePort = ReceivePort(); + // final solver = await DenoEJSSolver.init(); final youtubeExplode = YoutubeExplode(); final stopWatch = kDebugMode ? Stopwatch() : null; @@ -162,8 +164,8 @@ class YouTubeExplodeEngine implements YouTubeEngine { requireWatchPage: false, ytClients: [ YoutubeApiClient.ios, - YoutubeApiClient.android, YoutubeApiClient.androidVr, + YoutubeApiClient.android, ], ); diff --git a/lib/utils/service_utils.dart b/lib/utils/service_utils.dart index 81c4bfe4..738e4033 100644 --- a/lib/utils/service_utils.dart +++ b/lib/utils/service_utils.dart @@ -10,11 +10,9 @@ import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; import 'package:spotube/modules/root/update_dialog.dart'; -import 'package:spotube/models/lyrics.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/services/dio/dio.dart'; import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/primitive_utils.dart'; import 'package:collection/collection.dart'; @@ -189,95 +187,6 @@ abstract class ServiceUtils { return lyrics; } - @Deprecated("In favor spotify lyrics api, this isn't needed anymore") - static const baseUri = "https://www.rentanadviser.com/subtitles"; - - @Deprecated("In favor spotify lyrics api, this isn't needed anymore") - static Future getTimedLyrics(SourcedTrack track) async { - final artistNames = track.query.artists; - final query = getTitle( - track.query.title, - artists: artistNames, - ); - - final searchUri = Uri.parse("$baseUri/subtitles4songs.aspx").replace( - queryParameters: {"q": query}, - ); - - final res = await globalDio.getUri( - searchUri, - options: Options(responseType: ResponseType.plain), - ); - final document = parser.parse(res.data); - final results = - document.querySelectorAll("#tablecontainer table tbody tr td a"); - - final rateSortedResults = results.map((result) { - final title = result.text.trim().toLowerCase(); - int points = 0; - final hasAllArtists = track.query.artists - .every((artist) => title.contains(artist.toLowerCase())); - final hasTrackName = title.contains(track.query.title.toLowerCase()); - final isNotLive = !PrimitiveUtils.containsTextInBracket(title, "live"); - final exactYtMatch = title == track.info.title.toLowerCase(); - if (exactYtMatch) points = 7; - for (final criteria in [hasTrackName, hasAllArtists, isNotLive]) { - if (criteria) points++; - } - return {"result": result, "points": points}; - }).sorted((a, b) => (b["points"] as int).compareTo(a["points"] as int)); - - // not result was found at all - if (rateSortedResults.first["points"] == 0) { - return Future.error("Subtitle lookup failed", StackTrace.current); - } - - final topResult = rateSortedResults.first["result"] as Element; - final subtitleUri = - Uri.parse("$baseUri/${topResult.attributes["href"]}&type=lrc"); - - final lrcDocument = parser.parse((await globalDio.getUri( - subtitleUri, - options: Options(responseType: ResponseType.plain), - )) - .data); - final lrcList = lrcDocument - .querySelector("#ctl00_ContentPlaceHolder1_lbllyrics") - ?.innerHtml - .replaceAll(RegExp(r'

.*

'), "") - .split("
") - .map((e) { - e = e.trim(); - final regexp = RegExp(r'\[.*\]'); - final timeStr = regexp - .firstMatch(e) - ?.group(0) - ?.replaceAll(RegExp(r'\[|\]'), "") - .trim() - .split(":"); - final minuteSeconds = timeStr?.last.split("."); - - return LyricSlice( - time: Duration( - minutes: int.parse(timeStr?.first ?? "0"), - seconds: int.parse(minuteSeconds?.first ?? "0"), - milliseconds: int.parse(minuteSeconds?.last ?? "0"), - ), - text: e.split(regexp).last); - }).toList() ?? - []; - - final subtitle = SubtitleSimple( - name: topResult.text.trim(), - uri: subtitleUri, - lyrics: lrcList, - rating: rateSortedResults.first["points"] as int, - provider: "Rent An Adviser", - ); - - return subtitle; - } - static DateTime parseSpotifyAlbumDate(SpotubeFullAlbumObject? album) { if (album == null) { return DateTime.parse("1975-01-01"); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index eda2d021..d1ecafdf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,17 +8,15 @@ #include #include +#include #include #include #include -#include #include #include #include #include #include -#include -#include #include #include #include @@ -30,6 +28,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) flutter_new_pipe_extractor_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterNewPipeExtractorPlugin"); + flutter_new_pipe_extractor_plugin_register_with_registrar(flutter_new_pipe_extractor_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); @@ -39,9 +40,6 @@ 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); @@ -57,12 +55,6 @@ 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); g_autoptr(FlPluginRegistrar) tray_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin"); tray_manager_plugin_register_with_registrar(tray_manager_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b9ca593f..fa74793f 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,17 +5,15 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_window file_selector_linux + flutter_new_pipe_extractor 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 window_manager diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 9385ed14..a7d025e8 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -15,9 +15,9 @@ import device_info_plus import file_picker import file_selector_macos import flutter_inappwebview_macos +import flutter_new_pipe_extractor 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 @@ -27,8 +27,6 @@ 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 import window_manager @@ -44,9 +42,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) + FlutterNewPipeExtractorPlugin.register(with: registry.registrar(forPlugin: "FlutterNewPipeExtractorPlugin")) 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")) @@ -56,8 +54,6 @@ 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")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) diff --git a/pubspec.lock b/pubspec.lock index a6720c55..f5eea18c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -82,7 +82,7 @@ packages: source: hosted version: "1.6.5" async: - dependency: "direct main" + dependency: transitive description: name: async sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" @@ -154,7 +154,7 @@ packages: source: hosted version: "3.0.0" base32: - dependency: transitive + dependency: "direct main" description: name: base32 sha256: "37548444aaee8bd5e91db442ce69ee3a79d3652ed47c1fa7568aa3bb9af0aea5" @@ -315,7 +315,7 @@ packages: source: hosted version: "1.3.1" change_case: - dependency: transitive + dependency: "direct main" description: name: change_case sha256: f4e08feaa845e75e4f5ad2b0e15f24813d7ea6c27e7b78252f0c17f752cf1157 @@ -422,10 +422,10 @@ packages: dependency: transitive description: name: country_flags - sha256: "78a7bf8aabd7ae1a90087f0c517471ac9ebfe07addc652692f58da0f0f833196" + sha256: "714f2d415e74828eb08787d552a05e94cdf2cbe0607a5656f3e70087cd7bb7e0" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "4.1.0" coverage: dependency: transitive description: @@ -446,10 +446,10 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" csslib: dependency: transitive description: @@ -458,14 +458,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - dart_des: - dependency: transitive - description: - name: dart_des - sha256: "0a66afb8883368c824497fd2a1fd67bdb1a785965a3956728382c03d40747c33" - url: "https://pub.dev" - source: hosted - version: "1.0.2" dart_mappable: dependency: transitive description: @@ -539,14 +531,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.9.0" - dio_http2_adapter: - dependency: "direct main" - description: - name: dio_http2_adapter - sha256: b8bd5d587fd228a461711f8b82f378ccd4bf1fbf7802e7663ca60d7b5ce0e3aa - url: "https://pub.dev" - source: hosted - version: "2.6.0" dio_web_adapter: dependency: transitive description: @@ -963,7 +947,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: "916bde44cbead75125e8db842eb46bdcf211a79a" + resolved-ref: "898fd4ebcef77f5177b08aa6f9b9047bd02c6b9b" url: "https://github.com/KRTirtho/flutter_new_pipe_extractor.git" source: git version: "0.1.0" @@ -1090,14 +1074,6 @@ packages: url: "https://pub.dev" source: hosted version: "11.2.0" - form_validator: - dependency: "direct main" - description: - name: form_validator - sha256: "8cbe91b7d5260870d6fb9e23acd55d5d1d1fdf2397f0279a4931ac3c0c7bf8fb" - url: "https://pub.dev" - source: hosted - version: "2.1.1" freezed: dependency: "direct dev" description: @@ -1136,7 +1112,7 @@ packages: source: hosted version: "1.2.0" gap: - dependency: "direct main" + dependency: transitive description: name: gap sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d @@ -1159,14 +1135,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" - google_fonts: - dependency: "direct main" - description: - name: google_fonts - sha256: ebc94ed30fd13cefd397cb1658b593f21571f014b7d1197eeb41fb95f05d899a - url: "https://pub.dev" - source: hosted - version: "6.3.1" graphs: dependency: transitive description: @@ -1221,16 +1189,16 @@ packages: description: path: "." ref: main - resolved-ref: "01935a75640092af7947bfb21a497240376f0c83" + resolved-ref: "32828156bc111d147709f8d644804227bbdfe8f1" url: "https://github.com/KRTirtho/hetu_spotube_plugin.git" source: git - version: "0.0.1" + version: "0.0.2" hetu_std: dependency: "direct main" description: path: "." ref: main - resolved-ref: "577ad115dce0514afc53e2b3ab7b96bcd88d3be3" + resolved-ref: "401fde426339cf8f1e00dee22cc95f64c3e60053" url: "https://github.com/hetu-community/hetu_std.git" source: git version: "1.0.0" @@ -1274,14 +1242,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" - http2: - dependency: transitive - description: - name: http2 - sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa" - url: "https://pub.dev" - source: hosted - version: "2.3.1" http_methods: dependency: transitive description: @@ -1399,14 +1359,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" - invidious: - dependency: "direct main" - description: - name: invidious - sha256: "27ef3a001df875665de15535dbc9099f44d12a59480018fb1e17377d4af0308d" - url: "https://pub.dev" - source: hosted - version: "0.1.1" io: dependency: "direct dev" description: @@ -1415,30 +1367,6 @@ 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: - name: jiosaavn - sha256: d32b4f43f26488f942f5d7d19d748a1f2664ae3d41ff9c7d50eeb81705174bd2 - url: "https://pub.dev" - source: hosted - version: "0.1.0" jovial_misc: dependency: transitive description: @@ -1464,7 +1392,7 @@ packages: source: hosted version: "0.6.7" json_annotation: - dependency: "direct main" + dependency: transitive description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" @@ -1572,34 +1500,34 @@ packages: description: path: media_kit ref: HEAD - resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + resolved-ref: d310049f24196250d876efb02b9ff56fa9ef5068 url: "https://github.com/media-kit/media-kit" source: git - version: "1.2.0" + version: "1.2.1" media_kit_libs_android_audio: dependency: "direct overridden" description: path: "libs/android/media_kit_libs_android_audio" ref: HEAD - resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + resolved-ref: d310049f24196250d876efb02b9ff56fa9ef5068 url: "https://github.com/media-kit/media-kit" source: git - version: "1.3.7" + version: "1.3.8" media_kit_libs_audio: dependency: "direct main" description: path: "libs/universal/media_kit_libs_audio" ref: HEAD - resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + resolved-ref: d310049f24196250d876efb02b9ff56fa9ef5068 url: "https://github.com/media-kit/media-kit" source: git - version: "1.0.6" + version: "1.0.7" media_kit_libs_ios_audio: dependency: "direct overridden" description: path: "libs/ios/media_kit_libs_ios_audio" ref: HEAD - resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + resolved-ref: d310049f24196250d876efb02b9ff56fa9ef5068 url: "https://github.com/media-kit/media-kit" source: git version: "1.1.4" @@ -1608,7 +1536,7 @@ packages: description: path: "libs/linux/media_kit_libs_linux" ref: HEAD - resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + resolved-ref: d310049f24196250d876efb02b9ff56fa9ef5068 url: "https://github.com/media-kit/media-kit" source: git version: "1.2.1" @@ -1617,7 +1545,7 @@ packages: description: path: "libs/macos/media_kit_libs_macos_audio" ref: HEAD - resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + resolved-ref: d310049f24196250d876efb02b9ff56fa9ef5068 url: "https://github.com/media-kit/media-kit" source: git version: "1.1.4" @@ -1626,7 +1554,7 @@ packages: description: path: "libs/windows/media_kit_libs_windows_audio" ref: HEAD - resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1 + resolved-ref: d310049f24196250d876efb02b9ff56fa9ef5068 url: "https://github.com/media-kit/media-kit" source: git version: "1.0.9" @@ -1758,14 +1686,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.3" - otp_util: - dependency: "direct main" - description: - name: otp_util - sha256: dd8956c6472bacc3ffabe62c03f8a9782d1e5a5a3f2674420970f549d642b1cf - url: "https://pub.dev" - source: hosted - version: "1.0.2" package_config: dependency: transitive description: @@ -1926,22 +1846,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.4" - piped_client: - dependency: "direct main" - description: - name: piped_client - sha256: "947613e2a8d368b72cb36473de2c5c2784e4e72b2d3f17e5a5181b98b1a5436e" - 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: @@ -2034,10 +1938,10 @@ packages: dependency: transitive description: name: random_user_agents - sha256: "19facde509a2482dababb454faf2aceff797a6ae08e80f91268c0c8a7420f03b" + sha256: "95647149687167e82a7b39e1b4616fdebb574981b71b6f0cfca21b69f36293a8" url: "https://pub.dev" source: hosted - version: "1.0.15" + version: "1.0.17" recase: dependency: transitive description: @@ -2131,10 +2035,10 @@ packages: dependency: "direct main" description: name: shadcn_flutter - sha256: af83de199b7c3a965ab24e293cfcafe2764c12b7f911f5b1a427c332029262d9 + sha256: "1fd4f798c39d6308dc8f7e94d9e870b5db39fbf417ea95c423c7555ce8227a1c" url: "https://pub.dev" source: hosted - version: "0.0.44" + version: "0.0.47" shared_preferences: dependency: "direct main" description: @@ -2444,30 +2348,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" - stroke_text: - dependency: "direct main" - description: - name: stroke_text - sha256: "783fee071e3a3c5d3fe24011d7d776ce3cd64792e01b650c6b727ac3f38cb37b" - 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: @@ -2484,30 +2364,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.0" - syntax_highlight: - dependency: transitive - description: - name: syntax_highlight - sha256: "4d3ba40658cadba6ba55d697f29f00b43538ebb6eb4a0ca0e895c568eaced138" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - system_theme: - dependency: "direct main" - description: - name: system_theme - sha256: "5f93485401689601d4636a695f99f7c70a30873ee68c1d95025d908a3386be7e" - url: "https://pub.dev" - source: hosted - version: "3.1.2" - system_theme_web: - dependency: transitive - description: - name: system_theme_web - sha256: "900c92c5c050ce58048f241ef9a17e5cd8629808325a05b473dc62a6e99bae77" - url: "https://pub.dev" - source: hosted - version: "0.0.3" term_glyph: dependency: transitive description: @@ -2517,7 +2373,7 @@ packages: source: hosted version: "1.2.2" test: - dependency: "direct main" + dependency: "direct dev" description: name: test sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" @@ -2853,7 +2709,7 @@ packages: source: hosted version: "1.1.0" xml: - dependency: "direct dev" + dependency: transitive description: name: xml sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 @@ -2872,10 +2728,10 @@ packages: dependency: "direct main" description: name: youtube_explode_dart - sha256: "9ff345caf8351c59eb1b7560837f761e08d2beaea3b4187637942715a31a6f58" + sha256: add33de45d80c7f71a5e3dd464dd82fafd7fb5ab875fd303c023f30f76618325 url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "3.0.0" yt_dlp_dart: dependency: "direct main" description: @@ -2887,4 +2743,4 @@ packages: version: "1.0.0" sdks: dart: ">=3.9.0 <4.0.0" - flutter: ">=3.35.1" + flutter: ">=3.32.3" diff --git a/pubspec.yaml b/pubspec.yaml index c3044aad..add4a2a1 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: 5.0.0+42 +version: 5.1.0+43 homepage: https://spotube.krtirtho.dev repository: https://github.com/KRTirtho/spotube @@ -15,7 +15,6 @@ environment: dependencies: app_links: ^6.4.0 args: ^2.5.0 - async: ^2.11.0 audio_service: ^0.18.13 audio_service_mpris: ^0.2.0 audio_session: ^0.1.19 @@ -61,15 +60,11 @@ dependencies: sdk: flutter flutter_native_splash: ^2.4.6 flutter_riverpod: ^2.5.1 - flutter_secure_storage: ^9.2.4 flutter_sharing_intent: ^1.1.0 flutter_undraw: ^0.2.1 form_builder_validators: ^11.1.1 - form_validator: ^2.1.1 freezed_annotation: ^2.4.1 fuzzywuzzy: ^1.1.6 - gap: ^3.0.1 - google_fonts: ^6.2.1 home_widget: ^0.7.0 hooks_riverpod: ^2.5.1 html: ^0.15.1 @@ -77,21 +72,10 @@ dependencies: http: ^1.2.1 image_picker: ^1.1.0 intl: any - invidious: ^0.1.1 - jiosaavn: ^0.1.0 - json_annotation: ^4.8.1 local_notifier: ^0.1.6 logger: ^2.0.2 logging: ^1.3.0 lrc: ^1.0.2 - 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 @@ -100,14 +84,13 @@ dependencies: path: ^1.9.0 path_provider: ^2.1.3 permission_handler: ^11.3.1 - piped_client: ^0.1.2 riverpod: ^2.5.1 scrobblenaut: git: ref: dart-3-support url: https://github.com/KRTirtho/scrobblenaut.git scroll_to_index: ^3.0.1 - shadcn_flutter: ^0.0.42 + shadcn_flutter: ^0.0.47 shared_preferences: ^2.2.3 shelf: ^1.4.1 shelf_router: ^1.1.4 @@ -119,9 +102,6 @@ dependencies: smtc_windows: ^1.1.0 sqlite3: ^2.4.3 sqlite3_flutter_libs: ^0.5.23 - stroke_text: ^0.0.2 - system_theme: ^3.1.2 - test: ^1.25.7 timezone: ^0.10.0 titlebar_buttons: ^1.0.0 tray_manager: ^0.5.0 @@ -134,7 +114,7 @@ dependencies: wikipedia_api: ^0.1.0 win32_registry: ^1.1.5 window_manager: ^0.4.3 - youtube_explode_dart: ^2.5.1 + youtube_explode_dart: ^3.0.0 yt_dlp_dart: git: url: https://github.com/KRTirtho/yt_dlp_dart.git @@ -144,8 +124,6 @@ dependencies: url: https://github.com/KRTirtho/flutter_new_pipe_extractor.git http_parser: ^4.1.2 collection: any - otp_util: ^1.0.2 - dio_http2_adapter: ^2.6.0 archive: ^4.0.7 hetu_script: ^0.4.2+1 hetu_std: @@ -163,6 +141,18 @@ dependencies: get_it: ^8.0.3 flutter_markdown_plus: ^1.0.3 pub_semver: ^2.2.0 + change_case: ^1.1.0 + flutter_secure_storage: ^9.2.4 + # Have to use the git version due to unresponsive .move() after .add() + 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 + base32: ^2.2.0 dev_dependencies: build_runner: ^2.4.13 @@ -179,9 +169,9 @@ dev_dependencies: process_run: ^0.14.2 pubspec_parse: ^1.3.0 pub_api_client: ^3.0.0 - xml: ^6.5.0 io: ^1.0.4 drift_dev: ^2.21.0 + test: ^1.25.7 auto_route_generator: ^9.0.0 dependency_overrides: @@ -194,12 +184,12 @@ dependency_overrides: flutter_svg: ^2.0.17 intl: any collection: any + flutter_secure_storage_platform_interface: 2.0.0 flutter_secure_storage_linux: git: url: https://github.com/m-berto/flutter_secure_storage.git 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 @@ -230,6 +220,8 @@ flutter: - assets/branding/spotube-logo.png - assets/branding/spotube-logo-light.png - assets/branding/spotube-logo.ico + - assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug + - assets/plugins/spotube-plugin-youtube-audio/plugin.smplug - LICENSE - packages/flutter_undraw/assets/undraw/access_denied.svg - packages/flutter_undraw/assets/undraw/fixing_bugs.svg diff --git a/test/drift/app_db/generated/schema.dart b/test/drift/app_db/generated/schema.dart index dfd3edf3..76573e49 100644 --- a/test/drift/app_db/generated/schema.dart +++ b/test/drift/app_db/generated/schema.dart @@ -1,41 +1,47 @@ +// 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_v5.dart' as v5; -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_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_v7.dart' as v7; +import 'schema_v8.dart' as v8; +import 'schema_v9.dart' as v9; +import 'schema_v10.dart' as v10; class GeneratedHelper implements SchemaInstantiationHelper { @override GeneratedDatabase databaseForVersion(QueryExecutor db, int version) { switch (version) { - case 5: - return v5.DatabaseAtV5(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 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 7: + return v7.DatabaseAtV7(db); + case 8: + return v8.DatabaseAtV8(db); + case 9: + return v9.DatabaseAtV9(db); + case 10: + return v10.DatabaseAtV10(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3, 4, 5, 6, 7, 8]; + static const versions = const [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; } diff --git a/test/drift/app_db/generated/schema_v1.dart b/test/drift/app_db/generated/schema_v1.dart index 7a849d18..ca848561 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_v10.dart b/test/drift/app_db/generated/schema_v10.dart new file mode 100644 index 00000000..2811ad02 --- /dev/null +++ b/test/drift/app_db/generated/schema_v10.dart @@ -0,0 +1,3407 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +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 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 themeMode = GeneratedColumn( + 'theme_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(ThemeMode.system.name)); + late final GeneratedColumn audioSourceId = GeneratedColumn( + 'audio_source_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn youtubeClientEngine = + GeneratedColumn('youtube_client_engine', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(YoutubeClientEngine.youtubeExplode.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, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + themeMode, + audioSourceId, + youtubeClientEngine, + 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'])!, + 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'])!, + themeMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}theme_mode'])!, + audioSourceId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}audio_source_id']), + youtubeClientEngine: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}youtube_client_engine'])!, + 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 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 themeMode; + final String? audioSourceId; + final String youtubeClientEngine; + final bool discordPresence; + final bool endlessPlayback; + final bool enableConnect; + final int connectPort; + final bool cacheMusic; + const PreferencesTableData( + {required this.id, + 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.themeMode, + this.audioSourceId, + required this.youtubeClientEngine, + 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['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['theme_mode'] = Variable(themeMode); + if (!nullToAbsent || audioSourceId != null) { + map['audio_source_id'] = Variable(audioSourceId); + } + map['youtube_client_engine'] = Variable(youtubeClientEngine); + 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), + 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), + themeMode: Value(themeMode), + audioSourceId: audioSourceId == null && nullToAbsent + ? const Value.absent() + : Value(audioSourceId), + youtubeClientEngine: Value(youtubeClientEngine), + 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']), + 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']), + themeMode: serializer.fromJson(json['themeMode']), + audioSourceId: serializer.fromJson(json['audioSourceId']), + youtubeClientEngine: + serializer.fromJson(json['youtubeClientEngine']), + 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), + '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), + 'themeMode': serializer.toJson(themeMode), + 'audioSourceId': serializer.toJson(audioSourceId), + 'youtubeClientEngine': serializer.toJson(youtubeClientEngine), + 'discordPresence': serializer.toJson(discordPresence), + 'endlessPlayback': serializer.toJson(endlessPlayback), + 'enableConnect': serializer.toJson(enableConnect), + 'connectPort': serializer.toJson(connectPort), + 'cacheMusic': serializer.toJson(cacheMusic), + }; + } + + PreferencesTableData copyWith( + {int? id, + 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? themeMode, + Value audioSourceId = const Value.absent(), + String? youtubeClientEngine, + bool? discordPresence, + bool? endlessPlayback, + bool? enableConnect, + int? connectPort, + bool? cacheMusic}) => + PreferencesTableData( + id: id ?? this.id, + 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, + themeMode: themeMode ?? this.themeMode, + audioSourceId: + audioSourceId.present ? audioSourceId.value : this.audioSourceId, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + 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, + 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, + themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode, + audioSourceId: data.audioSourceId.present + ? data.audioSourceId.value + : this.audioSourceId, + youtubeClientEngine: data.youtubeClientEngine.present + ? data.youtubeClientEngine.value + : this.youtubeClientEngine, + 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('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('themeMode: $themeMode, ') + ..write('audioSourceId: $audioSourceId, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..write('discordPresence: $discordPresence, ') + ..write('endlessPlayback: $endlessPlayback, ') + ..write('enableConnect: $enableConnect, ') + ..write('connectPort: $connectPort, ') + ..write('cacheMusic: $cacheMusic') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + id, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + themeMode, + audioSourceId, + youtubeClientEngine, + discordPresence, + endlessPlayback, + enableConnect, + connectPort, + cacheMusic + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PreferencesTableData && + other.id == this.id && + 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.themeMode == this.themeMode && + other.audioSourceId == this.audioSourceId && + other.youtubeClientEngine == this.youtubeClientEngine && + 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 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 themeMode; + final Value audioSourceId; + final Value youtubeClientEngine; + final Value discordPresence; + final Value endlessPlayback; + final Value enableConnect; + final Value connectPort; + final Value cacheMusic; + const PreferencesTableCompanion({ + this.id = 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.themeMode = const Value.absent(), + this.audioSourceId = const Value.absent(), + this.youtubeClientEngine = 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.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.themeMode = const Value.absent(), + this.audioSourceId = const Value.absent(), + this.youtubeClientEngine = 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? 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? themeMode, + Expression? audioSourceId, + Expression? youtubeClientEngine, + Expression? discordPresence, + Expression? endlessPlayback, + Expression? enableConnect, + Expression? connectPort, + Expression? cacheMusic, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (themeMode != null) 'theme_mode': themeMode, + if (audioSourceId != null) 'audio_source_id': audioSourceId, + if (youtubeClientEngine != null) + 'youtube_client_engine': youtubeClientEngine, + 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? 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? themeMode, + Value? audioSourceId, + Value? youtubeClientEngine, + Value? discordPresence, + Value? endlessPlayback, + Value? enableConnect, + Value? connectPort, + Value? cacheMusic}) { + return PreferencesTableCompanion( + id: id ?? this.id, + 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, + themeMode: themeMode ?? this.themeMode, + audioSourceId: audioSourceId ?? this.audioSourceId, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + 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 (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 (themeMode.present) { + map['theme_mode'] = Variable(themeMode.value); + } + if (audioSourceId.present) { + map['audio_source_id'] = Variable(audioSourceId.value); + } + if (youtubeClientEngine.present) { + map['youtube_client_engine'] = + Variable(youtubeClientEngine.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('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('themeMode: $themeMode, ') + ..write('audioSourceId: $audioSourceId, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..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 sourceInfo = GeneratedColumn( + 'source_info', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("{}")); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', 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, trackId, sourceInfo, 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'])!, + sourceInfo: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_info'])!, + 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 sourceInfo; + final String sourceType; + final DateTime createdAt; + const SourceMatchTableData( + {required this.id, + required this.trackId, + required this.sourceInfo, + required this.sourceType, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['track_id'] = Variable(trackId); + map['source_info'] = Variable(sourceInfo); + map['source_type'] = Variable(sourceType); + map['created_at'] = Variable(createdAt); + return map; + } + + SourceMatchTableCompanion toCompanion(bool nullToAbsent) { + return SourceMatchTableCompanion( + id: Value(id), + trackId: Value(trackId), + sourceInfo: Value(sourceInfo), + 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']), + sourceInfo: serializer.fromJson(json['sourceInfo']), + 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), + 'sourceInfo': serializer.toJson(sourceInfo), + 'sourceType': serializer.toJson(sourceType), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SourceMatchTableData copyWith( + {int? id, + String? trackId, + String? sourceInfo, + String? sourceType, + DateTime? createdAt}) => + SourceMatchTableData( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceInfo: sourceInfo ?? this.sourceInfo, + 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, + sourceInfo: + data.sourceInfo.present ? data.sourceInfo.value : this.sourceInfo, + 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('sourceInfo: $sourceInfo, ') + ..write('sourceType: $sourceType, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, trackId, sourceInfo, sourceType, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SourceMatchTableData && + other.id == this.id && + other.trackId == this.trackId && + other.sourceInfo == this.sourceInfo && + other.sourceType == this.sourceType && + other.createdAt == this.createdAt); +} + +class SourceMatchTableCompanion extends UpdateCompanion { + final Value id; + final Value trackId; + final Value sourceInfo; + final Value sourceType; + final Value createdAt; + const SourceMatchTableCompanion({ + this.id = const Value.absent(), + this.trackId = const Value.absent(), + this.sourceInfo = const Value.absent(), + this.sourceType = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SourceMatchTableCompanion.insert({ + this.id = const Value.absent(), + required String trackId, + this.sourceInfo = const Value.absent(), + required String sourceType, + this.createdAt = const Value.absent(), + }) : trackId = Value(trackId), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? trackId, + Expression? sourceInfo, + Expression? sourceType, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (trackId != null) 'track_id': trackId, + if (sourceInfo != null) 'source_info': sourceInfo, + if (sourceType != null) 'source_type': sourceType, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SourceMatchTableCompanion copyWith( + {Value? id, + Value? trackId, + Value? sourceInfo, + Value? sourceType, + Value? createdAt}) { + return SourceMatchTableCompanion( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceInfo: sourceInfo ?? this.sourceInfo, + 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 (sourceInfo.present) { + map['source_info'] = Variable(sourceInfo.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('sourceInfo: $sourceInfo, ') + ..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 PluginsTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PluginsTable(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 selectedForMetadata = GeneratedColumn( + 'selected_for_metadata', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_metadata" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn selectedForAudioSource = + GeneratedColumn('selected_for_audio_source', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_audio_source" 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('2.0.0')); + @override + List get $columns => [ + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selectedForMetadata, + selectedForAudioSource, + repository, + pluginApiVersion + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'plugins_table'; + @override + Set get $primaryKey => {id}; + @override + PluginsTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PluginsTableData( + 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'])!, + selectedForMetadata: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}selected_for_metadata'])!, + selectedForAudioSource: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}selected_for_audio_source'])!, + repository: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}repository']), + pluginApiVersion: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}plugin_api_version'])!, + ); + } + + @override + PluginsTable createAlias(String alias) { + return PluginsTable(attachedDatabase, alias); + } +} + +class PluginsTableData 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 selectedForMetadata; + final bool selectedForAudioSource; + final String? repository; + final String pluginApiVersion; + const PluginsTableData( + {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.selectedForMetadata, + required this.selectedForAudioSource, + 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_for_metadata'] = Variable(selectedForMetadata); + map['selected_for_audio_source'] = Variable(selectedForAudioSource); + if (!nullToAbsent || repository != null) { + map['repository'] = Variable(repository); + } + map['plugin_api_version'] = Variable(pluginApiVersion); + return map; + } + + PluginsTableCompanion toCompanion(bool nullToAbsent) { + return PluginsTableCompanion( + id: Value(id), + name: Value(name), + description: Value(description), + version: Value(version), + author: Value(author), + entryPoint: Value(entryPoint), + apis: Value(apis), + abilities: Value(abilities), + selectedForMetadata: Value(selectedForMetadata), + selectedForAudioSource: Value(selectedForAudioSource), + repository: repository == null && nullToAbsent + ? const Value.absent() + : Value(repository), + pluginApiVersion: Value(pluginApiVersion), + ); + } + + factory PluginsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PluginsTableData( + 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']), + selectedForMetadata: + serializer.fromJson(json['selectedForMetadata']), + selectedForAudioSource: + serializer.fromJson(json['selectedForAudioSource']), + 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), + 'selectedForMetadata': serializer.toJson(selectedForMetadata), + 'selectedForAudioSource': serializer.toJson(selectedForAudioSource), + 'repository': serializer.toJson(repository), + 'pluginApiVersion': serializer.toJson(pluginApiVersion), + }; + } + + PluginsTableData copyWith( + {int? id, + String? name, + String? description, + String? version, + String? author, + String? entryPoint, + String? apis, + String? abilities, + bool? selectedForMetadata, + bool? selectedForAudioSource, + Value repository = const Value.absent(), + String? pluginApiVersion}) => + PluginsTableData( + 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, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, + repository: repository.present ? repository.value : this.repository, + pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, + ); + PluginsTableData copyWithCompanion(PluginsTableCompanion data) { + return PluginsTableData( + 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, + selectedForMetadata: data.selectedForMetadata.present + ? data.selectedForMetadata.value + : this.selectedForMetadata, + selectedForAudioSource: data.selectedForAudioSource.present + ? data.selectedForAudioSource.value + : this.selectedForAudioSource, + repository: + data.repository.present ? data.repository.value : this.repository, + pluginApiVersion: data.pluginApiVersion.present + ? data.pluginApiVersion.value + : this.pluginApiVersion, + ); + } + + @override + String toString() { + return (StringBuffer('PluginsTableData(') + ..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('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selectedForMetadata, + selectedForAudioSource, + repository, + pluginApiVersion); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PluginsTableData && + 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.selectedForMetadata == this.selectedForMetadata && + other.selectedForAudioSource == this.selectedForAudioSource && + other.repository == this.repository && + other.pluginApiVersion == this.pluginApiVersion); +} + +class PluginsTableCompanion 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 selectedForMetadata; + final Value selectedForAudioSource; + final Value repository; + final Value pluginApiVersion; + const PluginsTableCompanion({ + 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.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = const Value.absent(), + this.repository = const Value.absent(), + this.pluginApiVersion = const Value.absent(), + }); + PluginsTableCompanion.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.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = 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? selectedForMetadata, + Expression? selectedForAudioSource, + 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 (selectedForMetadata != null) + 'selected_for_metadata': selectedForMetadata, + if (selectedForAudioSource != null) + 'selected_for_audio_source': selectedForAudioSource, + if (repository != null) 'repository': repository, + if (pluginApiVersion != null) 'plugin_api_version': pluginApiVersion, + }); + } + + PluginsTableCompanion copyWith( + {Value? id, + Value? name, + Value? description, + Value? version, + Value? author, + Value? entryPoint, + Value? apis, + Value? abilities, + Value? selectedForMetadata, + Value? selectedForAudioSource, + Value? repository, + Value? pluginApiVersion}) { + return PluginsTableCompanion( + 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, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, + 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 (selectedForMetadata.present) { + map['selected_for_metadata'] = Variable(selectedForMetadata.value); + } + if (selectedForAudioSource.present) { + map['selected_for_audio_source'] = + Variable(selectedForAudioSource.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('PluginsTableCompanion(') + ..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('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV10 extends GeneratedDatabase { + DatabaseAtV10(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 PluginsTable pluginsTable = PluginsTable(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_info, source_type)'); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + historyTable, + lyricsTable, + pluginsTable, + uniqueBlacklist, + uniqTrackMatch + ]; + @override + int get schemaVersion => 10; +} diff --git a/test/drift/app_db/generated/schema_v2.dart b/test/drift/app_db/generated/schema_v2.dart index 4b28750d..c9642f86 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 7ddf4d2b..f6416823 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 c8f07c6e..4206abdb 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 72c48612..4283aa98 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 9e556976..c0ef0442 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 b28397ab..b476efbd 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 index 33fb4dad..7008eaff 100644 --- a/test/drift/app_db/generated/schema_v8.dart +++ b/test/drift/app_db/generated/schema_v8.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_v9.dart b/test/drift/app_db/generated/schema_v9.dart new file mode 100644 index 00000000..cde63c2f --- /dev/null +++ b/test/drift/app_db/generated/schema_v9.dart @@ -0,0 +1,3568 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +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 PluginsTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PluginsTable(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 selectedForMetadata = GeneratedColumn( + 'selected_for_metadata', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_metadata" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn selectedForAudioSource = + GeneratedColumn('selected_for_audio_source', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("selected_for_audio_source" 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('2.0.0')); + @override + List get $columns => [ + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selectedForMetadata, + selectedForAudioSource, + repository, + pluginApiVersion + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'plugins_table'; + @override + Set get $primaryKey => {id}; + @override + PluginsTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PluginsTableData( + 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'])!, + selectedForMetadata: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}selected_for_metadata'])!, + selectedForAudioSource: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}selected_for_audio_source'])!, + repository: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}repository']), + pluginApiVersion: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}plugin_api_version'])!, + ); + } + + @override + PluginsTable createAlias(String alias) { + return PluginsTable(attachedDatabase, alias); + } +} + +class PluginsTableData 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 selectedForMetadata; + final bool selectedForAudioSource; + final String? repository; + final String pluginApiVersion; + const PluginsTableData( + {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.selectedForMetadata, + required this.selectedForAudioSource, + 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_for_metadata'] = Variable(selectedForMetadata); + map['selected_for_audio_source'] = Variable(selectedForAudioSource); + if (!nullToAbsent || repository != null) { + map['repository'] = Variable(repository); + } + map['plugin_api_version'] = Variable(pluginApiVersion); + return map; + } + + PluginsTableCompanion toCompanion(bool nullToAbsent) { + return PluginsTableCompanion( + id: Value(id), + name: Value(name), + description: Value(description), + version: Value(version), + author: Value(author), + entryPoint: Value(entryPoint), + apis: Value(apis), + abilities: Value(abilities), + selectedForMetadata: Value(selectedForMetadata), + selectedForAudioSource: Value(selectedForAudioSource), + repository: repository == null && nullToAbsent + ? const Value.absent() + : Value(repository), + pluginApiVersion: Value(pluginApiVersion), + ); + } + + factory PluginsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PluginsTableData( + 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']), + selectedForMetadata: + serializer.fromJson(json['selectedForMetadata']), + selectedForAudioSource: + serializer.fromJson(json['selectedForAudioSource']), + 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), + 'selectedForMetadata': serializer.toJson(selectedForMetadata), + 'selectedForAudioSource': serializer.toJson(selectedForAudioSource), + 'repository': serializer.toJson(repository), + 'pluginApiVersion': serializer.toJson(pluginApiVersion), + }; + } + + PluginsTableData copyWith( + {int? id, + String? name, + String? description, + String? version, + String? author, + String? entryPoint, + String? apis, + String? abilities, + bool? selectedForMetadata, + bool? selectedForAudioSource, + Value repository = const Value.absent(), + String? pluginApiVersion}) => + PluginsTableData( + 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, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, + repository: repository.present ? repository.value : this.repository, + pluginApiVersion: pluginApiVersion ?? this.pluginApiVersion, + ); + PluginsTableData copyWithCompanion(PluginsTableCompanion data) { + return PluginsTableData( + 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, + selectedForMetadata: data.selectedForMetadata.present + ? data.selectedForMetadata.value + : this.selectedForMetadata, + selectedForAudioSource: data.selectedForAudioSource.present + ? data.selectedForAudioSource.value + : this.selectedForAudioSource, + repository: + data.repository.present ? data.repository.value : this.repository, + pluginApiVersion: data.pluginApiVersion.present + ? data.pluginApiVersion.value + : this.pluginApiVersion, + ); + } + + @override + String toString() { + return (StringBuffer('PluginsTableData(') + ..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('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selectedForMetadata, + selectedForAudioSource, + repository, + pluginApiVersion); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PluginsTableData && + 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.selectedForMetadata == this.selectedForMetadata && + other.selectedForAudioSource == this.selectedForAudioSource && + other.repository == this.repository && + other.pluginApiVersion == this.pluginApiVersion); +} + +class PluginsTableCompanion 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 selectedForMetadata; + final Value selectedForAudioSource; + final Value repository; + final Value pluginApiVersion; + const PluginsTableCompanion({ + 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.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = const Value.absent(), + this.repository = const Value.absent(), + this.pluginApiVersion = const Value.absent(), + }); + PluginsTableCompanion.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.selectedForMetadata = const Value.absent(), + this.selectedForAudioSource = 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? selectedForMetadata, + Expression? selectedForAudioSource, + 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 (selectedForMetadata != null) + 'selected_for_metadata': selectedForMetadata, + if (selectedForAudioSource != null) + 'selected_for_audio_source': selectedForAudioSource, + if (repository != null) 'repository': repository, + if (pluginApiVersion != null) 'plugin_api_version': pluginApiVersion, + }); + } + + PluginsTableCompanion copyWith( + {Value? id, + Value? name, + Value? description, + Value? version, + Value? author, + Value? entryPoint, + Value? apis, + Value? abilities, + Value? selectedForMetadata, + Value? selectedForAudioSource, + Value? repository, + Value? pluginApiVersion}) { + return PluginsTableCompanion( + 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, + selectedForMetadata: selectedForMetadata ?? this.selectedForMetadata, + selectedForAudioSource: + selectedForAudioSource ?? this.selectedForAudioSource, + 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 (selectedForMetadata.present) { + map['selected_for_metadata'] = Variable(selectedForMetadata.value); + } + if (selectedForAudioSource.present) { + map['selected_for_audio_source'] = + Variable(selectedForAudioSource.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('PluginsTableCompanion(') + ..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('selectedForMetadata: $selectedForMetadata, ') + ..write('selectedForAudioSource: $selectedForAudioSource, ') + ..write('repository: $repository, ') + ..write('pluginApiVersion: $pluginApiVersion') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV9 extends GeneratedDatabase { + DatabaseAtV9(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 PluginsTable pluginsTable = PluginsTable(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, + pluginsTable, + uniqueBlacklist, + uniqTrackMatch + ]; + @override + int get schemaVersion => 9; +} diff --git a/untranslated_messages.json b/untranslated_messages.json index af89bb78..9e26dfee 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1,5 +1 @@ -{ - "nl": [ - "audio_source" - ] -} +{} \ No newline at end of file diff --git a/website/package.json b/website/package.json index 9f1eb71a..74026b97 100644 --- a/website/package.json +++ b/website/package.json @@ -17,7 +17,7 @@ "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "astro": "^5.12.8", - "astro-pagefind": "^1.8.3", + "astro-pagefind": "1.8.3", "date-fns": "^4.1.0", "markdown-it": "^14.1.0", "react": "^19.1.1", @@ -36,4 +36,4 @@ "@types/markdown-it": "^14.1.2", "@types/sanitize-html": "^2.16.0" } -} \ No newline at end of file +} diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index d297e8c6..173f5e5d 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -33,7 +33,7 @@ importers: specifier: ^5.12.8 version: 5.12.8(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.9.2) astro-pagefind: - specifier: ^1.8.3 + specifier: 1.8.3 version: 1.8.3(astro@5.12.8(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.9.2)) date-fns: specifier: ^4.1.0 diff --git a/website/public/android-chrome-192x192.png b/website/public/android-chrome-192x192.png index 2ef2f3e7..9f1d8bcc 100644 Binary files a/website/public/android-chrome-192x192.png and b/website/public/android-chrome-192x192.png differ diff --git a/website/public/android-chrome-512x512.png b/website/public/android-chrome-512x512.png index 41bda9c4..c5834ae7 100644 Binary files a/website/public/android-chrome-512x512.png and b/website/public/android-chrome-512x512.png differ diff --git a/website/public/apple-touch-icon.png b/website/public/apple-touch-icon.png index c171ce48..211e35de 100644 Binary files a/website/public/apple-touch-icon.png and b/website/public/apple-touch-icon.png differ diff --git a/website/public/favicon-16x16.png b/website/public/favicon-16x16.png index 6c14fa32..1b971dc1 100644 Binary files a/website/public/favicon-16x16.png and b/website/public/favicon-16x16.png differ diff --git a/website/public/favicon-32x32.png b/website/public/favicon-32x32.png index 8106368f..9853456f 100644 Binary files a/website/public/favicon-32x32.png and b/website/public/favicon-32x32.png differ diff --git a/website/public/favicon.ico b/website/public/favicon.ico index e8e1c26b..6324292a 100644 Binary files a/website/public/favicon.ico and b/website/public/favicon.ico differ diff --git a/website/public/images/spotube-logo.png b/website/public/images/spotube-logo.png index b24a8c23..d46d9ce5 100644 Binary files a/website/public/images/spotube-logo.png and b/website/public/images/spotube-logo.png differ diff --git a/website/public/images/spotube-logo.svg b/website/public/images/spotube-logo.svg deleted file mode 100644 index 5cd88f8e..00000000 --- a/website/public/images/spotube-logo.svg +++ /dev/null @@ -1,349 +0,0 @@ - - diff --git a/website/src/collections/app.ts b/website/src/collections/app.ts index 3ae86c8a..6f021abe 100644 --- a/website/src/collections/app.ts +++ b/website/src/collections/app.ts @@ -1,105 +1,132 @@ import type { IconType } from "react-icons"; import { - FaAndroid, - FaApple, - FaDebian, - FaFedora, - FaOpensuse, - FaUbuntu, - FaWindows, - FaRedhat, + FaAndroid, + FaApple, + FaDebian, + FaFedora, + FaOpensuse, + FaUbuntu, + FaWindows, + FaRedhat, + FaLinux, } from "react-icons/fa6"; import { LuHouse, LuNewspaper, LuDownload, LuBook } from "react-icons/lu"; -export const routes: Record = { - "/": ["Home", LuHouse], - "/blog": ["Blog", LuNewspaper], - "/docs": ["Docs", LuBook], - "/downloads": ["Downloads", LuDownload], - "/about": ["About", null], +export const routes: Record = { + "/": ["Home", LuHouse], + "/blog": ["Blog", LuNewspaper], + "/docs": ["Docs", LuBook], + "/downloads": ["Downloads", LuDownload], + "/about": ["About", null], }; const releasesUrl = - "https://github.com/KRTirtho/Spotube/releases/latest/download"; + "https://github.com/KRTirtho/Spotube/releases/latest/download"; export const downloadLinks: Record = { - "Android Apk": [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid]], - "Windows Executable": [ - `${releasesUrl}/Spotube-windows-x86_64-setup.exe`, - [FaWindows], - ], - "macOS Dmg": [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple]], - "Ubuntu, Debian": [ - `${releasesUrl}/Spotube-linux-x86_64.deb`, - [FaUbuntu, FaDebian], - ], - "Fedora, Redhat, Opensuse": [ - `${releasesUrl}/Spotube-linux-x86_64.rpm`, - [FaFedora, FaRedhat, FaOpensuse], - ], - "iPhone Ipa": [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple]], + "Android Apk": [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid]], + "Windows Executable": [ + `${releasesUrl}/Spotube-windows-x86_64-setup.exe`, + [FaWindows], + ], + "macOS Dmg": [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple]], + "Ubuntu, Debian": [ + `${releasesUrl}/Spotube-linux-x86_64.deb`, + [FaUbuntu, FaDebian], + ], + "Fedora, Redhat, Opensuse": [ + `${releasesUrl}/Spotube-linux-x86_64.rpm`, + [FaFedora, FaRedhat, FaOpensuse], + ], + "iPhone Ipa": [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple]], }; export const extendedDownloadLinks: Record< - string, - [string, IconType[], string] + string, + [string, IconType[], string] > = { - Android: [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid], "apk"], - Windows: [ - `${releasesUrl}/Spotube-windows-x86_64-setup.exe`, - [FaWindows], - "exe", - ], - macOS: [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"], - "Ubuntu, Debian": [ - `${releasesUrl}/Spotube-linux-x86_64.deb`, - [FaUbuntu, FaDebian], - "deb", - ], - "Fedora, Redhat, Opensuse": [ - `${releasesUrl}/Spotube-linux-x86_64.rpm`, - [FaFedora, FaRedhat, FaOpensuse], - "rpm", - ], - iPhone: [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"], + Android: [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid], "apk"], + Windows: [ + `${releasesUrl}/Spotube-windows-x86_64-setup.exe`, + [FaWindows], + "exe", + ], + macOS: [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"], + "Ubuntu, Debian (x64)": [ + `${releasesUrl}/Spotube-linux-x86_64.deb`, + [FaUbuntu, FaDebian], + "deb", + ], + "Ubuntu, Debian (arm64)": [ + `${releasesUrl}/Spotube-linux-aarch64.deb`, + [FaUbuntu, FaDebian], + "deb", + ], + "Fedora, Redhat, Opensuse": [ + `${releasesUrl}/Spotube-linux-x86_64.rpm`, + [FaFedora, FaRedhat, FaOpensuse], + "rpm", + ], + "Linux AppImage (x64)": [ + `${releasesUrl}/Spotube-linux-x86_64.AppImage`, + [FaLinux], + "AppImage", + ], + "Linux AppImage (arm64)": [ + `${releasesUrl}/Spotube-linux-aarch64.AppImage`, + [FaLinux], + "AppImage", + ], + iPhone: [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"], }; const nightlyReleaseUrl = - "https://github.com/KRTirtho/Spotube/releases/download/nightly"; + "https://github.com/KRTirtho/Spotube/releases/download/nightly"; export const extendedNightlyDownloadLinks: Record< - string, - [string, IconType[], string] + string, + [string, IconType[], string] > = { - Android: [ - `${nightlyReleaseUrl}/Spotube-android-all-arch.apk`, - [FaAndroid], - "apk", - ], - Windows: [ - `${nightlyReleaseUrl}/Spotube-windows-x86_64-setup.exe`, - [FaWindows], - "exe", - ], - macOS: [`${nightlyReleaseUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"], - "Ubuntu, Debian": [ - `${nightlyReleaseUrl}/Spotube-linux-x86_64.deb`, - [FaUbuntu, FaDebian], - "deb", - ], - "Fedora, Redhat, Opensuse": [ - `${nightlyReleaseUrl}/Spotube-linux-x86_64.rpm`, - [FaFedora, FaRedhat, FaOpensuse], - "rpm", - ], - iPhone: [`${nightlyReleaseUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"], + Android: [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid], "apk"], + Windows: [ + `${releasesUrl}/Spotube-windows-x86_64-setup.exe`, + [FaWindows], + "exe", + ], + macOS: [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"], + "Ubuntu, Debian (x64)": [ + `${releasesUrl}/Spotube-linux-x86_64.deb`, + [FaUbuntu, FaDebian], + "deb", + ], + "Ubuntu, Debian (arm64)": [ + `${releasesUrl}/Spotube-linux-aarch64.deb`, + [FaUbuntu, FaDebian], + "deb", + ], + "Fedora, Redhat, Opensuse": [ + `${releasesUrl}/Spotube-linux-x86_64.rpm`, + [FaFedora, FaRedhat, FaOpensuse], + "rpm", + ], + "Linux AppImage (x64)": [ + `${releasesUrl}/Spotube-linux-x86_64.AppImage`, + [FaLinux], + "AppImage", + ], + "Linux AppImage (arm64)": [ + `${releasesUrl}/Spotube-linux-aarch64.AppImage`, + [FaLinux], + "AppImage", + ], + iPhone: [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"], }; export const ADS_SLOTS = Object.freeze({ - rootPageDisplay: 5979549631, - blogPageInFeed: 3386010031, - downloadPageDisplay: 9928443050, + rootPageDisplay: 5979549631, + blogPageInFeed: 3386010031, + downloadPageDisplay: 9928443050, packagePageArticle: 9119323068, // This is being used for rehype-auto-ads in svelte.config.js blogArticlePageArticle: 6788673194, -}); \ No newline at end of file +}); diff --git a/website/src/components/ads/Ads.astro b/website/src/components/ads/Ads.astro index b2e4bb27..9d3daf1c 100644 --- a/website/src/components/ads/Ads.astro +++ b/website/src/components/ads/Ads.astro @@ -12,7 +12,7 @@ const { adSlot, adFormat, fullWidthResponsive = true, - style, + style = "display:block", adLayout, adLayoutKey, } = Astro.props; @@ -22,7 +22,7 @@ const AD_CLIENT = "ca-pub-6419300932495863"; ; })} -

+

{link[1][2]}

diff --git a/website/src/pages/downloads/index.astro b/website/src/pages/downloads/index.astro index 72479a82..23fe938e 100644 --- a/website/src/pages/downloads/index.astro +++ b/website/src/pages/downloads/index.astro @@ -22,34 +22,7 @@ const otherDownloads: [string, string, IconType][] = [

Spotube is available for every platform

- -

- Versions of Spotube (<=v4.0.2) are ceased to work with Spotify™ API. -
- So users can no longer use/download those versions. -
- Please wait for the next version that will remedy this issue by not using such - APIs. -

-

- Spotube has no affiliation with Spotify™ or any of its subsidiaries. -

-
-
- -
- The new Spotube v5 is still under beta. Please use the Nightly version - until stable release. -
- - -
- +

diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 686e435e..c2acd0fa 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -53,11 +53,11 @@ import { ADS_SLOTS } from "~/collections/app"; diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 87b34e37..2441d2e2 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,16 +12,14 @@ #include #include #include +#include #include #include -#include #include #include #include #include #include -#include -#include #include #include #include @@ -39,12 +37,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + FlutterNewPipeExtractorPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterNewPipeExtractorPluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); FlutterTimezonePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi")); - IrondashEngineContextPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); LocalNotifierPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalNotifierPlugin")); MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar( @@ -55,10 +53,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); Sqlite3FlutterLibsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); - SuperNativeExtensionsPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SuperNativeExtensionsPluginCApi")); - SystemThemePluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("SystemThemePlugin")); TrayManagerPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("TrayManagerPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 798e47c8..9990b87b 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,16 +9,14 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_window file_selector_windows flutter_inappwebview_windows + flutter_new_pipe_extractor 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 window_manager