diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e0031d17..64ee89d2 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -71,3 +71,10 @@ body: description: Anything else you'd like to include? validations: required: false + - type: checkboxes + attributes: + label: Self grab + description: If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. We welcome contributions! + options: + - label: I'm ready to work on this issue! + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/new_feature.yml b/.github/ISSUE_TEMPLATE/new_feature.yml index 9742f91f..7f02ea38 100644 --- a/.github/ISSUE_TEMPLATE/new_feature.yml +++ b/.github/ISSUE_TEMPLATE/new_feature.yml @@ -35,4 +35,11 @@ body: label: Additional information description: Anything else you'd like to include? validations: - required: false \ No newline at end of file + required: false + - type: checkboxes + attributes: + label: Self grab + description: If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. We welcome contributions! + options: + - label: I'm ready to work on this issue! + required: false \ No newline at end of file diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 00000000..e4fb55c5 --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,32 @@ +name: Lint + +on: + pull_request: + +env: + FLUTTER_VERSION: '3.16.0' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + + - name: Configure repo + run: | + flutter pub get + echo '${{ secrets.DOTENV_NIGHTLY }}' > .env + dart run build_runner build --delete-conflicting-outputs + + - name: Lint Dart files + run: | + dart analyze --no-fatal-warnings + + - name: Lint translations & config files + run: | + npm install -g @prantlf/jsonlint + jsonlint -q -D --enforce-double-quotes ./lib/l10n/*.arb + jsonlint -q -D --enforce-double-quotes -T .vscode/*.json \ No newline at end of file diff --git a/.github/workflows/spotube-release-binary.yml b/.github/workflows/spotube-release-binary.yml index 62dbcace..8fa0e2f5 100644 --- a/.github/workflows/spotube-release-binary.yml +++ b/.github/workflows/spotube-release-binary.yml @@ -4,7 +4,7 @@ on: inputs: version: description: Version to release (x.x.x) - default: 3.2.0 + default: 3.4.0 required: true channel: type: choice @@ -26,19 +26,14 @@ on: default: true env: - FLUTTER_VERSION: '3.16.0' + FLUTTER_VERSION: '3.16.3' jobs: windows: runs-on: windows-latest steps: - uses: actions/checkout@v4 - - uses: actions/checkout@v4 - with: - repository: KRTirtho/flutter_distributor - path: flutter_distributor - ref: fix-windows-build - - uses: subosito/flutter-action@v2.10.0 + - uses: subosito/flutter-action@v2.12.0 with: cache: true flutter-version: ${{ env.FLUTTER_VERSION }} @@ -79,10 +74,9 @@ jobs: - name: Build Windows Executable run: | - dart pub global activate melos - cd flutter_distributor && melos bs && cd .. + dart pub global activate flutter_distributor make innoinstall - dart run ./flutter_distributor/packages/flutter_distributor/bin/main.dart package --platform=windows --targets=exe --skip-clean + flutter_distributor package --platform=windows --targets=exe --skip-clean mv dist/**/spotube-*-windows-setup.exe dist/Spotube-windows-x86_64-setup.exe - name: Create Chocolatey Package and set hash @@ -113,7 +107,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: subosito/flutter-action@v2.10.0 + - uses: subosito/flutter-action@v2.12.0 with: cache: true flutter-version: ${{ env.FLUTTER_VERSION }} @@ -169,7 +163,6 @@ jobs: dart pub global activate flutter_distributor alias dpkg-deb="dpkg-deb --Zxz" flutter_distributor package --platform=linux --targets=deb - flutter_distributor package --platform=linux --targets=appimage flutter_distributor package --platform=linux --targets=rpm - name: Create tar.xz (stable) @@ -185,7 +178,6 @@ jobs: mv build/spotube-linux-*-x86_64.tar.xz dist/ mv dist/**/spotube-*-linux.deb dist/Spotube-linux-x86_64.deb mv dist/**/spotube-*-linux.rpm dist/Spotube-linux-x86_64.rpm - mv dist/**/spotube-*-linux.AppImage dist/Spotube-linux-x86_64.AppImage - uses: actions/upload-artifact@v3 @@ -193,7 +185,6 @@ jobs: if-no-files-found: error name: Spotube-Release-Binaries path: | - dist/Spotube-linux-x86_64.AppImage dist/Spotube-linux-x86_64.deb dist/Spotube-linux-x86_64.rpm dist/spotube-linux-${{ env.BUILD_VERSION }}-x86_64.tar.xz @@ -209,7 +200,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: subosito/flutter-action@v2.10.0 + - uses: subosito/flutter-action@v2.12.0 with: cache: true flutter-version: ${{ env.FLUTTER_VERSION }} @@ -285,7 +276,7 @@ jobs: runs-on: macos-12 steps: - uses: actions/checkout@v4 - - uses: subosito/flutter-action@v2.10.0 + - uses: subosito/flutter-action@v2.12.0 with: cache: true flutter-version: ${{ env.FLUTTER_VERSION }} @@ -330,13 +321,54 @@ jobs: mkdir -p build/${{ env.BUILD_VERSION }} appdmg appdmg.json build/Spotube-macos-universal.dmg + iOS: + + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2.10.0 + with: + cache: true + flutter-version: ${{ env.FLUTTER_VERSION }} + + - name: Replace pubspec version and BUILD_VERSION Env (nightly) + if: ${{ inputs.channel == 'nightly' }} + run: | + brew install yq + yq -i '.version |= sub("\+\d+", "+${{ inputs.channel }}.")' pubspec.yaml + yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml + echo "BUILD_VERSION=${{ inputs.version }}+${{ inputs.channel }}.${{ github.run_number }}" >> $GITHUB_ENV + + - name: BUILD_VERSION Env (stable) + if: ${{ inputs.channel == 'stable' }} + run: | + echo "BUILD_VERSION=${{ inputs.version }}" >> $GITHUB_ENV + + - name: Create Stable .env + if: ${{ inputs.channel == 'stable' }} + run: echo '${{ secrets.DOTENV_RELEASE }}' > .env + + - name: Create Nightly .env + if: ${{ inputs.channel == 'nightly' }} + run: echo '${{ secrets.DOTENV_NIGHTLY }}' > .env + + - name: Generate Secrets + run: | + flutter pub get + dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns + + - name: Build iOS iPA + run: | + flutter build ios --release --no-codesign --flavor ${{ inputs.channel }} + ln -sf ./build/ios/iphoneos Payload + zip -r9 Spotube-iOS.ipa Payload/${{ inputs.channel }}.app - uses: actions/upload-artifact@v3 with: if-no-files-found: error name: Spotube-Release-Binaries path: | - build/Spotube-macos-universal.dmg + Spotube-iOS.ipa - name: Debug With SSH When fails if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }} @@ -352,6 +384,7 @@ jobs: - linux - android - macos + - iOS steps: - uses: actions/download-artifact@v3 with: diff --git a/.vscode/launch.json b/.vscode/launch.json index 9add0735..7a1e8b9b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,6 +6,12 @@ "type": "dart", "request": "launch", "program": "lib/main.dart", + }, + { + "name": "spotube (mobile)", + "type": "dart", + "request": "launch", + "program": "lib/main.dart", "args": [ "--flavor", "dev" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3710d812..ea429caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,71 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [3.4.0](https://github.com/KRTirtho/spotube/compare/v3.3.0...v3.4.0) (2023-12-30) + + +### Features + +* Add Go to Album option in track option [#917](https://github.com/KRTirtho/spotube/issues/917) ([b0beeca](https://github.com/KRTirtho/spotube/commit/b0beeca0cbaf810fae27832cff98cfda95715050)) +* **translations:** add Italian language translations ([#818](https://github.com/KRTirtho/spotube/issues/818)) ([e4eb0e2](https://github.com/KRTirtho/spotube/commit/e4eb0e2596ade2bb5195e183f03af42742fc8486)), closes [#676](https://github.com/KRTirtho/spotube/issues/676) [#676](https://github.com/KRTirtho/spotube/issues/676) +* compact genre view in home page ([82ed5e9](https://github.com/KRTirtho/spotube/commit/82ed5e90576b57ef32e61a65015e04862ab15461)) +* Deep link support ([#950](https://github.com/KRTirtho/spotube/issues/950)) ([4050f55](https://github.com/KRTirtho/spotube/commit/4050f556400aaec5515231578512cf1a6b990110)) +* improve loading animations ([b92583d](https://github.com/KRTirtho/spotube/commit/b92583d0df7b8dee0d121cd2bb666b14c77d8c86)) +* toggle for discord rpc ([24a2294](https://github.com/KRTirtho/spotube/commit/24a2294512bb0c4aff77bc8dcad9b4de3e8b45c6)) +* **translations:** add Dutch Language ([#969](https://github.com/KRTirtho/spotube/issues/969)) ([3ad7ba6](https://github.com/KRTirtho/spotube/commit/3ad7ba66b56e93e69d2181d47029b7549ed225fc)) + + +### Bug Fixes + +* add safe area in home ([9ee6067](https://github.com/KRTirtho/spotube/commit/9ee60677f6d50df7468e12dc6653ecedefa2494f)) +* amoled mode and color scheme can't be changed ([840e014](https://github.com/KRTirtho/spotube/commit/840e014f2b18f193d040baef0e0cd595088a4a84)) +* doesn't minimize to tray when system title bar close button is used [#866](https://github.com/KRTirtho/spotube/issues/866) ([bb8f250](https://github.com/KRTirtho/spotube/commit/bb8f250f5f351c1a353791b77b25b9de7586191f)) +* genre border issues ([2fb16e6](https://github.com/KRTirtho/spotube/commit/2fb16e64e9cdfca54d633cdf287b0544ecdda3b6)) +* Incorrect "Artist" label/heading on Search Results Page [#920](https://github.com/KRTirtho/spotube/issues/920) ([f86d544](https://github.com/KRTirtho/spotube/commit/f86d5449168068e338f769d7f504d2146b86dc79)) +* metadata not getting added for YouTube tracks [#916](https://github.com/KRTirtho/spotube/issues/916) and Wrong duration of downloaded tracks [#912](https://github.com/KRTirtho/spotube/issues/912) ([a7b9398](https://github.com/KRTirtho/spotube/commit/a7b9398708ede865dc2c25fb791c8e98eeff7a38)) +* Playlist refresh not working [#915](https://github.com/KRTirtho/spotube/issues/915) ([5f1df5a](https://github.com/KRTirtho/spotube/commit/5f1df5a87d8fb7980b52cf57b7b6bedea57a1269)) +* track view header title overflow and player view drag glitch ([b04d884](https://github.com/KRTirtho/spotube/commit/b04d8849e7169824ec5b980236b5d61b2629f56e)) +* wrong artist name sent while scrobbling [#958](https://github.com/KRTirtho/spotube/issues/958) ([dcbe729](https://github.com/KRTirtho/spotube/commit/dcbe7294b742d43fbff4e89ab4c4825e94421dd9)) + +## [3.3.0](https://github.com/KRTirtho/spotube/compare/v3.2.0...v3.3.0) (2023-11-27) + + +### Features + +* Add JioSaavn as audio source ([#881](https://github.com/KRTirtho/spotube/issues/881)) ([14069cd](https://github.com/KRTirtho/spotube/commit/14069cd4fe08597c8d9aa0810270fb4c386c1d55)) +* **android:** better quick scroll/drag to scroll implementation ([2e2c44f](https://github.com/KRTirtho/spotube/commit/2e2c44f0afef69bf9bc485db97d45127a0847c8e)) +* **artist:** modularize page and add wikipedia section ([2a69886](https://github.com/KRTirtho/spotube/commit/2a698865567883271471ace9a44123bbfd8fcd2f)) +* discord RPC integration [#98](https://github.com/KRTirtho/spotube/issues/98) ([88b8785](https://github.com/KRTirtho/spotube/commit/88b8785cb86a19900f3a867b044c1ccb2fe400bb)) +* **mini_player:** show/hide lyrics [#851](https://github.com/KRTirtho/spotube/issues/851) ([dcbb156](https://github.com/KRTirtho/spotube/commit/dcbb1568337969841acc0abe0e7185ee5e4c3590)) +* paginated playlist and album page ([28a5d6b](https://github.com/KRTirtho/spotube/commit/28a5d6bb3820ab0bd4007664f73d685f6e1d2c90)) +* **translations:** add Turkish translations ([0c22469](https://github.com/KRTirtho/spotube/commit/0c22469503f32dbbf1a5d31419c1b76c699fa966)) + + +### Bug Fixes + +* "Add () to Playlist" option not showing in favorited playlists [#904](https://github.com/KRTirtho/spotube/issues/904) ([96021e1](https://github.com/KRTirtho/spotube/commit/96021e1a49d22bd25fd052c122f49f439c2bea43)) +* 0:00 media duration in queue after application restart [#782](https://github.com/KRTirtho/spotube/issues/782) ([83c0b49](https://github.com/KRTirtho/spotube/commit/83c0b49da962d9f3d40de9525f90f0b320e8f7b8)) +* Add to Playlist Dialog memory leak [#817](https://github.com/KRTirtho/spotube/issues/817) ([fed36ec](https://github.com/KRTirtho/spotube/commit/fed36ecdd81e8a0f8358693eff0a6233dea32e5d)) +* **album_card:** show loading state during adding track to queue/play ([5633367](https://github.com/KRTirtho/spotube/commit/5633367397812148f6d712d06e97a4f84033f968)) +* alternative track source safearea overflow [#876](https://github.com/KRTirtho/spotube/issues/876) ([7b72a90](https://github.com/KRTirtho/spotube/commit/7b72a90bc65b541cbe2e24ef2234524b522ad71d)) +* android invalid download location Download not starting or not explaining error [#720](https://github.com/KRTirtho/spotube/issues/720) ([d056dbf](https://github.com/KRTirtho/spotube/commit/d056dbf9eeef7033dbc012d0c05800063e820042)) +* changed settings are not persisting after force stop [#821](https://github.com/KRTirtho/spotube/issues/821) ([e29a38d](https://github.com/KRTirtho/spotube/commit/e29a38dfa43ddf7a38046d1d40424f01dbe62261)) +* check for unsynced lyrics and error handling for timed lyrics query ([1d77556](https://github.com/KRTirtho/spotube/commit/1d77556157d158600f29cf2ea5f26c567607dec7)) +* **genres:** lag while scrolling ([dc980b0](https://github.com/KRTirtho/spotube/commit/dc980b024edad3132e72cbb2f0087297a4b76469)) +* infinite list disappearing for a moment everytime new page is fetched ([1334a62](https://github.com/KRTirtho/spotube/commit/1334a62aaea31f97031b3ebf455e94c583f37314)) +* last track of queue keeps repeating [#718](https://github.com/KRTirtho/spotube/issues/718) ([58e5698](https://github.com/KRTirtho/spotube/commit/58e569864dddd74c3064624998dfc184046e97eb)) +* Navigating to settings, redirects to home page [#812](https://github.com/KRTirtho/spotube/issues/812) ([da04f06](https://github.com/KRTirtho/spotube/commit/da04f068f9b7effff8d50cb5714d93ea80c22b7f)) +* new releases section flickering on scroll glitch ([ee94b7c](https://github.com/KRTirtho/spotube/commit/ee94b7cbb24e0f0bc22a6d49c830d4055aa02895)) +* **playbutton_card:** annoying animation ([574406d](https://github.com/KRTirtho/spotube/commit/574406dd5fc410914b27e7fce374323696845012)) +* scrobbling not working for first track or single track ([0a6b54d](https://github.com/KRTirtho/spotube/commit/0a6b54da367345b73fe6e954f1d9368d9f9ead71)) +* settings page scrollbar position ([ee82290](https://github.com/KRTirtho/spotube/commit/ee8229020b3b03fc074b316db4b322af13b807bd)) +* shuffle doesn't move active track to top ([4956bf3](https://github.com/KRTirtho/spotube/commit/4956bf367baae39c88b5de7c6c136513a14f8ad2)) +* spotube doesn't exit properly, hangs in infinite loop [#768](https://github.com/KRTirtho/spotube/issues/768) ([353ca79](https://github.com/KRTirtho/spotube/commit/353ca79be334077c3ac27b4f64e8b4b15eca7175)) +* trim login field padding ([286ef83](https://github.com/KRTirtho/spotube/commit/286ef83e8ec516db70019398d9e3e724437a4172)) +* use CustomScrollView for personalized page ([7d05c40](https://github.com/KRTirtho/spotube/commit/7d05c40dc0d04208b059f2483c1e4de199c8b51d)) +* user_playlists layout, track tile index, ([487c2ed](https://github.com/KRTirtho/spotube/commit/487c2ed6bdc4af33006ba52532eb4eaaa261dceb)) +* **windows:** media control not working [#641](https://github.com/KRTirtho/spotube/issues/641) ([7818574](https://github.com/KRTirtho/spotube/commit/7818574356d0fb8ff567e1f6a83fd0b6f2ee7c8a)) + ## [3.2.0](https://github.com/KRTirtho/spotube/compare/v3.1.2...v3.2.0) (2023-10-16) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index b2823e62..13996cea 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -145,7 +145,7 @@ Do the following: flutter run -d )> ``` -Do debugging/testing/build etc then submit to us with PR against the development branch (master) & we'll review your code +Do debugging/testing/build etc then submit to us with PR against the development branch (dev) & we'll review your code ### Submit Translations @@ -163,4 +163,4 @@ Make sure you're familiar with [Flutter localization](https://docs.flutter.dev/u - Now restart (hot restart if running already) the app in debug mode & go to "Settings" > "Language" & see if your locale shows up - If it does, select it & see if the app is translated properly - Now git commit the changes & push -- Finally, submit a PR against the development branch (dev) & we'll review your code \ No newline at end of file +- Finally, submit a PR against the development branch (dev) & we'll review your code diff --git a/README.md b/README.md index d82af783..791d5da0 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ Spotube Logo An open source, cross-platform Spotify client compatible across multiple platforms
-utilizing Spotify's data API and YouTube (or Piped.video) as an audio source,
+utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source,
eliminating the need for Spotify Premium -Btw it's not another Electron app😉 +Btw it's not just another Electron app 😉 Visit the website Discord Server @@ -26,7 +26,7 @@ Btw it's not another Electron app😉 ## 🌃 Features - 🚫 No ads, thanks to the use of public & free Spotify and YT Music APIs¹ -- ⬇️ Downloadable tracks +- ⬇️ Freely downloadable tracks - 🖥️ 📱 Cross-platform support - 🪶 Small size & less data usage - 🕵️ Anonymous/guest login @@ -36,17 +36,17 @@ Btw it's not another Electron app😉 - 📖 Open source/libre software - 🔉 Playback control is done locally, not on the server -**¹** It is still **recommended** to support the creators by watching/liking/subscribing to the artists' YouTube channels or liking their tracks on Spotify (or purchasing a Spotify Premium subscription too). +**¹** It is still **recommended** to support creators by engaging with their YouTube channels/Spotify tracks (or preferably by buying their merch/concert tickets/physical media). ### ❌ Unsupported features -- 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts can **never be supported** because the audio tracks are _only_ available on Spotify and accessing them would require Spotify Premium. +- 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts will **never be supported** because the audio tracks are _only_ available on Spotify and accessing them would require Spotify Premium. - 🎧 **Spotify Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8) ## 📜 ⬇️ Installation guide -New releases usually appear after 3-4 months.
-This handy table lists all methods you can use to install Spotube: +New versions usually release every 3-4 months.
+This handy table lists all the methods you can use to install Spotube: @@ -184,19 +184,24 @@ If you are concerned, you can [read the reason of choosing this license](https:/
-

[Click to show] 🙏 Library/Plugin/Framework Credits

+

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

+### Services 1. [Flutter](https://flutter.dev) - Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase 1. [Spotify API](https://developer.spotify.com/documentation/web-api) - The Spotify Web API is a RESTful API that provides access to Spotify data 1. [Piped](https://piped-docs.kavin.rocks/) - Piped is a privacy friendly alternative YouTube frontend, which is efficient and scalable by design. 1. [YouTube](https://youtube.com/) - YouTube is an American online video-sharing platform headquartered in San Bruno, California. Three former PayPal employees—Chad Hurley, Steve Chen, and Jawed Karim—created the service in February 2005 +1. [JioSaavn](https://www.jiosaavn.com) - JioSaavn is an Indian online music streaming service and a digital distributor of Bollywood, English and other regional Indian music across the world. Since it was founded in 2007 as Saavn, the company has acquired rights to over 5 crore (50 million) music tracks in 15 languages 1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution 1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users 1. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux 1. [SponsorBlock](https://sponsor.ajay.app) - SponsorBlock is an open-source crowdsourced browser extension and open API for skipping sponsor segments in YouTube videos. 1. [Inno Setup](https://jrsoftware.org/isinfo.php) - Inno Setup is a free installer for Windows programs by Jordan Russell and Martijn Laan 1. [F-Droid](https://f-droid.org) - F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device +1. [LastFM](https://last.fm) - Last.fm is a music streaming and discovery platform that helps users discover and share new music. It tracks users' music listening habits across many devices and platforms. + +### Dependencies 1. [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. @@ -204,7 +209,7 @@ If you are concerned, you can [read the reason of choosing this license](https:/ 1. [auto_size_text](https://github.com/leisim/auto_size_text) - Flutter widget that automatically resizes text to fit perfectly within its bounds. 1. [buttons_tabbar](https://afonsoraposo.com) - A Flutter package that implements a TabBar where each label is a toggle button. 1. [cached_network_image](https://github.com/Baseflow/flutter_cached_network_image) - Flutter library to load and cache network images. Can also be used with placeholder and error widgets. -1. [catcher_2](https://github.com/ThexXTURBOXx/catcher_2) - Plugin for error catching which provides multiple handlers for dealing with errors when they are not caught by the developer. +1. [catcher_2](https://github.com/ThexXTURBOXx/catcher_2) - Plugin for error catching which provides multiple handlers for dealing with errors when they are not caught by the developer. 1. [collection](https://pub.dev/packages/collection) - Collections and utilities functions and classes related to collections. 1. [cupertino_icons](https://pub.dev/packages/cupertino_icons) - Default icons asset for Cupertino widgets based on Apple styled icons 1. [curved_navigation_bar](https://github.com/rafalbednarczuk/curved_navigation_bar) - Stunning Animating Curved Shape Navigation Bar. Adjustable color, background color, animation curve, animation duration. @@ -216,7 +221,10 @@ If you are concerned, you can [read the reason of choosing this license](https:/ 1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration. 1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times. 1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI. -1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft. +1. [fl_query](https://fl-query.krtirtho.dev) - Asynchronous data caching, refetching & invalidation library for Flutter +1. [fl_query_hooks](https://fl-query.krtirtho.dev) - Elite flutter_hooks compatible library for fl_query, the Asynchronous data caching, refetching & invalidation library for Flutter +1. [fl_query_devtools](https://fl-query.krtirtho.dev) - Devtools support for Fl-Query +1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft. 1. [flutter_cache_manager](https://github.com/Baseflow/flutter_cache_manager/tree/develop/flutter_cache_manager) - Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite. 1. [flutter_displaymode](https://github.com/ajinasokan/flutter_displaymode) - A Flutter plugin to set display mode (resolution, refresh rate) on Android platform. Allows to enable high refresh rate on supported devices. 1. [flutter_feather_icons](https://github.com/muj-programmer/flutter_feather_icons) - Feather is a collection of simply beautiful open source icons. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and usability. @@ -227,7 +235,7 @@ If you are concerned, you can [read the reason of choosing this license](https:/ 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_svg](https://pub.dev/packages/flutter_svg) - An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files. 1. [form_validator](https://github.com/TheMisir/form-validator) - Simplest form validation library for flutter's form field widgets -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. [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. [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. [go_router](https://pub.dev/packages/go_router) - A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more 1. [hive](https://github.com/hivedb/hive/tree/master/hive) - Lightweight and blazing fast key-value database written in pure Dart. Strongly encrypted using AES-256. @@ -247,7 +255,7 @@ If you are concerned, you can [read the reason of choosing this license](https:/ 1. [package_info_plus](https://plus.fluttercommunity.dev/) - Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android. 1. [palette_generator](https://pub.dev/packages/palette_generator) - Flutter package for generating palette colors from a source image. 1. [path](https://pub.dev/packages/path) - A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web. -1. [path_provider](https://pub.dev/packages/path_provider) - Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories. +1. [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. [popover](https://github.com/minikin/popover) - A popover is a transient view that appears above other content onscreen when you tap a control or in an area. @@ -258,7 +266,6 @@ If you are concerned, you can [read the reason of choosing this license](https:/ 1. [smtc_windows](https://github.com/KRTirtho/smtc_windows) - Windows `SystemMediaTransportControls` implementation for Flutter giving access to Windows OS Media Control applet. 1. [spotify](https://github.com/rinukkusu/spotify-dart) - An incomplete dart library for interfacing with the Spotify Web API. 1. [stroke_text](https://github.com/MohamedAbd0/stroke_text) - A Simple Flutter plugin for applying stroke (border) style to a text widget -1. [supabase](https://supabase.com) - A dart client for Supabase. This client makes it simple for developers to build secure and scalable products. 1. [system_theme](https://pub.dev/packages/system_theme) - A plugin to get the current system theme info. Supports Android, Web, Windows, Linux and macOS 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. [url_launcher](https://pub.dev/packages/url_launcher) - Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes. @@ -267,8 +274,19 @@ If you are concerned, you can [read the reason of choosing this license](https:/ 1. [visibility_detector](https://pub.dev/packages/visibility_detector) - A widget that detects the visibility of its child and notifies a callback. 1. [window_manager](https://github.com/leanflutter/window_manager) - This plugin allows Flutter desktop apps to resizing and repositioning the window. 1. [youtube_explode_dart](https://github.com/Hexer10/youtube_explode_dart) - A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key. -1. [simple_icons](https://jlnrrg.github.io/) - The Simple Icon pack available as Flutter Icons. Provides over 1500 Free SVG icons for popular brands. +1. [simple_icons](https://teavelopment.com/) - The Simple Icon pack available as Flutter Icons. Provides over 1500 Free SVG icons for popular brands. 1. [audio_service_mpris](https://github.com/bdrazhzhov/audio-service-mpris) - audio_service platform interface supporting Media Player Remote Interfacing Specification. +1. [file_picker](https://github.com/miguelpruivo/plugins_flutter_file_picker) - A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support. +1. [jiosaavn](https://github.com/KRTirtho/jiosaavn) - Unofficial API client for jiosaavn.com +1. [very_good_infinite_list](https://github.com/VeryGoodOpenSource/very_good_infinite_list) - A library for easily displaying paginated data, created by Very Good Ventures. Great for activity feeds, news feeds, and more. +1. [gap](https://github.com/letsar/gap) - Flutter widgets for easily adding gaps inside Flex widgets such as Columns and Rows or scrolling views. +1. [sliver_tools](https://github.com/Kavantix) - A set of useful sliver tools that are missing from the flutter framework +1. [html_unescape](https://github.com/filiph/html_unescape) - A small library for un-escaping HTML. Supports all Named Character References, Decimal Character References and Hexadecimal Character References. +1. [wikipedia_api](https://github.com/KRTirtho/wikipedia_api) - Wikipedia API for dart and flutter +1. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort. +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. [win32_registry](https://win32.pub) - A package that provides a friendly Dart API for accessing the Windows Registry. +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. [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_distributor](https://distributor.leanflutter.org) - A complete tool for packaging and publishing your Flutter apps. @@ -279,12 +297,11 @@ If you are concerned, you can [read the reason of choosing this license](https:/ 1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes. 1. [pub_api_client](https://github.com/leoafarias/pub_api_client) - An API Client for Pub to interact with public package information. 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. [fl_query](https://fl-query.vercel.app) - Asynchronous data caching, refetching & invalidation library for Flutter -1. [fl_query_hooks](https://fl-query.vercel.app) - Elite flutter_hooks compatible library for fl_query, the Asynchronous data caching, refetching & invalidation library for Flutter -1. [fl_query_devtools](https://fl-query.vercel.app) - Devtools support for Fl-Query 1. [flutter_desktop_tools](https://github.com/KRTirtho/flutter_desktop_tools) - Essential collection of tools for flutter desktop app development 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. [window_size](https://github.com/google/flutter-desktop-embedding.git) - Allows resizing and repositioning the window containing Flutter. +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. [dart_discord_rpc](https://github.com/alexmercerind/dart_discord_rpc) - Discord Rich Presence for Flutter & Dart apps & games.
-

© Copyright Spotube 2023

+

© Copyright Spotube 2024

diff --git a/android/app/build.gradle b/android/app/build.gradle index cd6bc457..df13c9f4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -78,19 +78,19 @@ android { productFlavors { nightly { dimension "default" - resValue "string", "app_name", "Spotube Nightly" + resValue "string", "app_name_en", "Spotube Nightly" applicationIdSuffix ".nightly" versionNameSuffix "-nightly" } dev { dimension "default" - resValue "string", "app_name", "Spotube Dev" + resValue "string", "app_name_en", "Spotube Dev" applicationIdSuffix ".dev" versionNameSuffix "-dev" } stable { dimension "default" - resValue "string", "app_name", "Spotube" + resValue "string", "app_name_en", "Spotube" } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bfb51226..5ab7a0b5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -18,7 +18,7 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/jiosaavn.png b/assets/jiosaavn.png new file mode 100644 index 00000000..4d2d46e4 Binary files /dev/null and b/assets/jiosaavn.png differ diff --git a/assets/liked-tracks.jpg b/assets/liked-tracks.jpg new file mode 100644 index 00000000..62dad65e Binary files /dev/null and b/assets/liked-tracks.jpg differ diff --git a/bin/gen-credits.dart b/bin/gen-credits.dart index 43e1e53d..f8975335 100644 --- a/bin/gen-credits.dart +++ b/bin/gen-credits.dart @@ -1,7 +1,7 @@ +import 'dart:developer'; import 'dart:io'; import 'package:collection/collection.dart'; -import 'package:path/path.dart'; import 'package:http/http.dart'; import 'package:html/parser.dart'; import 'package:pub_api_client/pub_api_client.dart'; @@ -33,15 +33,20 @@ void main() async { final gitDeps = gitDepsList.map( (d) { + final uri = Uri.parse( + d.value.url.toString().replaceAll('.git', ''), + ); return MapEntry( d.key, - join( - d.value.url.toString().replaceAll('.git', ''), - 'raw', - d.value.ref ?? 'main', - d.value.path ?? '', - 'pubspec.yaml', - ), + uri.replace( + pathSegments: [ + ...uri.pathSegments, + 'raw', + d.value.ref ?? 'main', + d.value.path ?? '', + 'pubspec.yaml', + ], + ).toString(), ); }, ).toList(); @@ -55,7 +60,10 @@ void main() async { } catch (e) { final document = parse(res.body); final pre = document.querySelector('pre'); - if (pre == null) rethrow; + if (pre == null) { + log(d.toString()); + rethrow; + } return Pubspec.parse(pre.text); } } diff --git a/bin/untranslated_messages.dart b/bin/untranslated_messages.dart index 172f218f..e19f9a07 100644 --- a/bin/untranslated_messages.dart +++ b/bin/untranslated_messages.dart @@ -35,6 +35,11 @@ void main(List args) { ); } + print( + "Prompt:\n" + "Translate following to their appropriate locale for flutter arb translations files." + " Put the respective new translations in a map of their corresponding locale.", + ); // ignore: avoid_print print( const JsonEncoder.withIndent(' ').convert( diff --git a/flutter_launcher_icons-nightly.yaml b/flutter_launcher_icons-nightly.yaml index e531efd4..c6892d4b 100644 --- a/flutter_launcher_icons-nightly.yaml +++ b/flutter_launcher_icons-nightly.yaml @@ -1,5 +1,6 @@ flutter_launcher_icons: android: true + ios: true image_path: "assets/spotube-nightly-logo.png" adaptive_icon_foreground: "assets/spotube-nightly-logo-foreground.jpg" adaptive_icon_background: "#242832" diff --git a/ios/Podfile b/ios/Podfile index cfd01b62..5b0d5a2c 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -34,6 +34,27 @@ target 'Runner' do flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end +target 'dev' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +target 'stable' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +target 'nightly' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 68fcdacc..35f3dc18 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,13 +1,12 @@ PODS: + - app_links (0.0.1): + - Flutter - audio_service (0.0.1): - Flutter - audio_session (0.0.1): - Flutter - - audioplayers_darwin (0.0.1): + - device_info_plus (0.0.1): - Flutter - - connectivity_plus (0.0.1): - - Flutter - - ReachabilitySwift - DKImagePickerController/Core (4.3.4): - DKImagePickerController/ImageDataManager - DKImagePickerController/Resource @@ -42,6 +41,8 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter + - file_selector_ios (0.0.1): + - Flutter - Flutter (1.0.0) - flutter_inappwebview (0.0.1): - Flutter @@ -50,44 +51,77 @@ PODS: - flutter_inappwebview/Core (0.0.1): - Flutter - OrderedSet (~> 5.0) + - flutter_keyboard_visibility (0.0.1): + - Flutter + - flutter_mailer (0.0.1): + - Flutter + - flutter_native_splash (0.0.1): + - Flutter + - flutter_secure_storage (6.0.0): + - Flutter + - flutter_sharing_intent (0.0.1): + - Flutter + - fluttertoast (0.0.2): + - Flutter + - Toast - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) - - metadata_god (0.0.1): + - image_picker_ios (0.0.1): - Flutter + - integration_test (0.0.1): + - Flutter + - media_kit_libs_ios_audio (1.0.4): + - Flutter + - media_kit_native_event_loop (1.0.0): + - Flutter + - metadata_god (0.0.1) - OrderedSet (5.0.0) - package_info_plus (0.4.5): - Flutter - - path_provider_ios (0.0.1): + - path_provider_foundation (0.0.1): - Flutter - - permission_handler_apple (9.0.4): + - FlutterMacOS + - permission_handler_apple (9.1.1): - Flutter - - ReachabilitySwift (5.0.0) - - SDWebImage (5.13.4): - - SDWebImage/Core (= 5.13.4) - - SDWebImage/Core (5.13.4) - - shared_preferences_ios (0.0.1): + - SDWebImage (5.18.8): + - SDWebImage/Core (= 5.18.8) + - SDWebImage/Core (5.18.8) + - shared_preferences_foundation (0.0.1): - Flutter - - sqflite (0.0.2): + - FlutterMacOS + - sqflite (0.0.3): - Flutter - FMDB (>= 2.7.5) - - SwiftyGif (5.4.3) + - SwiftyGif (5.4.4) + - Toast (4.0.0) - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: + - app_links (from `.symlinks/plugins/app_links/ios`) - audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`) - - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`) - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) + - file_selector_ios (from `.symlinks/plugins/file_selector_ios/ios`) - Flutter (from `Flutter`) - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) + - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) + - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) + - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) + - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - flutter_sharing_intent (from `.symlinks/plugins/flutter_sharing_intent/ios`) + - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - media_kit_libs_ios_audio (from `.symlinks/plugins/media_kit_libs_ios_audio/ios`) + - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) - metadata_god (from `.symlinks/plugins/metadata_god/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -97,63 +131,96 @@ SPEC REPOS: - DKPhotoGallery - FMDB - OrderedSet - - ReachabilitySwift - SDWebImage - SwiftyGif + - Toast EXTERNAL SOURCES: + app_links: + :path: ".symlinks/plugins/app_links/ios" audio_service: :path: ".symlinks/plugins/audio_service/ios" audio_session: :path: ".symlinks/plugins/audio_session/ios" - audioplayers_darwin: - :path: ".symlinks/plugins/audioplayers_darwin/ios" - connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" + file_selector_ios: + :path: ".symlinks/plugins/file_selector_ios/ios" Flutter: :path: Flutter flutter_inappwebview: :path: ".symlinks/plugins/flutter_inappwebview/ios" + flutter_keyboard_visibility: + :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" + flutter_mailer: + :path: ".symlinks/plugins/flutter_mailer/ios" + flutter_native_splash: + :path: ".symlinks/plugins/flutter_native_splash/ios" + flutter_secure_storage: + :path: ".symlinks/plugins/flutter_secure_storage/ios" + flutter_sharing_intent: + :path: ".symlinks/plugins/flutter_sharing_intent/ios" + fluttertoast: + :path: ".symlinks/plugins/fluttertoast/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + media_kit_libs_ios_audio: + :path: ".symlinks/plugins/media_kit_libs_ios_audio/ios" + media_kit_native_event_loop: + :path: ".symlinks/plugins/media_kit_native_event_loop/ios" metadata_god: :path: ".symlinks/plugins/metadata_god/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_ios: - :path: ".symlinks/plugins/path_provider_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" - shared_preferences_ios: - :path: ".symlinks/plugins/shared_preferences_ios/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" sqflite: :path: ".symlinks/plugins/sqflite/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: + app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875 audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_session: 4f3e461722055d21515cf3261b64c973c062f345 - audioplayers_darwin: 387322cb364026a1782298c982693b1b6aa9fa1b - connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e + device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 + file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de + file_selector_ios: 8c25d700d625e1dcdd6599f2d927072f2254647b Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 + flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62 + flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 + flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 + flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef + flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be + flutter_sharing_intent: e35380d0e1501d7111dbb7e46d5ac6339da6da98 + fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - metadata_god: cebcc48708aca3e9d1ef60c74b23404ff3730d5e + image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 + integration_test: 13825b8a9334a850581300559b8839134b124670 + media_kit_libs_ios_audio: 8f39d96a9c630685dfb844c289bd1d114c486fb3 + media_kit_native_event_loop: 99111eded5acbdc9c2738021ea6550dd36ca8837 + metadata_god: eceae399d0020475069a5cebc35943ce8562b5d7 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 - shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad - sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 - SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 - url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 + SDWebImage: a81bbb3ba4ea5f810f4069c68727cb118467a04a + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a + SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f + Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 + url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 -PODFILE CHECKSUM: e9ba2289804955e1370e293b204c6e8651354f4a +PODFILE CHECKSUM: e36c7ad9836dfd8d22934c7680185432a658e28f -COCOAPODS: 1.11.3 +COCOAPODS: 1.14.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index d9cf2138..8b7c2b1e 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,17 +3,39 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ + 051977801F58E8DBB6712352 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7E9EBDD27997A73A4D38EE1 /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 17438EB903776D8D0E926C9B /* Pods_nightly.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BAC36FC304DBD4E8A8C00694 /* Pods_nightly.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 46249B26D47C5DB81A4F972E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7E9EBDD27997A73A4D38EE1 /* Pods_Runner.framework */; }; + 4E86E0C42011EDB42C34AF9A /* Pods_stable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5F91A319C771EEC978B238A /* Pods_stable.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B536BD902B405DB1009B3CE4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + B536BD912B405DB1009B3CE4 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + B536BD952B405DB1009B3CE4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B536BD962B405DB1009B3CE4 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + B536BD972B405DB1009B3CE4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + B536BD982B405DB1009B3CE4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + B536BDAE2B405FDE009B3CE4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + B536BDAF2B405FDE009B3CE4 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + B536BDB22B405FDE009B3CE4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B536BDB32B405FDE009B3CE4 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + B536BDB42B405FDE009B3CE4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + B536BDB52B405FDE009B3CE4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + B536BDD02B4060B3009B3CE4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + B536BDD12B4060B3009B3CE4 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + B536BDD42B4060B3009B3CE4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B536BDD52B4060B3009B3CE4 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + B536BDD62B4060B3009B3CE4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + B536BDD72B4060B3009B3CE4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + C36A05AD330BBFAED75A62D5 /* Pods_dev.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4238A4985255EC9F93067739 /* Pods_dev.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -27,17 +49,78 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + B536BD992B405DB1009B3CE4 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDB62B405FDE009B3CE4 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDD82B4060B3009B3CE4 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 04C104D3779B4D1635D939BF /* Pods-Runner.profile-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-nightly.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-nightly.xcconfig"; sourceTree = ""; }; + 0F8FB58820FF492BD3CF9315 /* Pods-nightly.debug-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.debug-stable.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.debug-stable.xcconfig"; sourceTree = ""; }; + 126B91CED32FAD3C40A67A23 /* Pods-dev.debug-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.debug-stable.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.debug-stable.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 171073CFF94F5751BC2B78DD /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1C9810F8B3FD927ED8C94791 /* Pods-dev.profile-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.profile-nightly.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.profile-nightly.xcconfig"; sourceTree = ""; }; + 21C0B1DEE0F0BFD3F3651F79 /* Pods-stable.debug-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.debug-nightly.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.debug-nightly.xcconfig"; sourceTree = ""; }; + 261A31AC0DBA2D93BD1910D9 /* Pods-nightly.profile-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.profile-nightly.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.profile-nightly.xcconfig"; sourceTree = ""; }; + 285DE2278D380EE2A6647CA9 /* Pods-nightly.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.debug.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.debug.xcconfig"; sourceTree = ""; }; + 29304D1832AA30DE0C33E05C /* Pods-dev.profile-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.profile-stable.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.profile-stable.xcconfig"; sourceTree = ""; }; + 2DA87118BE2AF25875B7C376 /* Pods-stable.release-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.release-stable.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.release-stable.xcconfig"; sourceTree = ""; }; + 2F9AD76AF35FFC693C051CE1 /* Pods-dev.release-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.release-nightly.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.release-nightly.xcconfig"; sourceTree = ""; }; + 39E15EE1745C9266FDB59558 /* Pods-stable.debug-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.debug-dev.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.debug-dev.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3E262038FF3BDA3B8A7BDAC3 /* Pods-Runner.release-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-dev.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-dev.xcconfig"; sourceTree = ""; }; + 3F754C793C1BC0E8B8FFB5B7 /* Pods-stable.profile-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.profile-stable.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.profile-stable.xcconfig"; sourceTree = ""; }; + 4238A4985255EC9F93067739 /* Pods_dev.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_dev.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 46E04A5AA989356A32CD8E66 /* Pods-dev.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.profile.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.profile.xcconfig"; sourceTree = ""; }; + 48E7E801EAE1B520AA5F35DD /* Pods-dev.profile-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.profile-dev.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.profile-dev.xcconfig"; sourceTree = ""; }; + 4BDAF8FFADB62CA017755094 /* Pods-stable.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.profile.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.profile.xcconfig"; sourceTree = ""; }; + 5014E8BD9F7181E528538444 /* Pods-stable.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.release.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.release.xcconfig"; sourceTree = ""; }; + 53AD516AAEB9A1331C99CBAE /* Pods-stable.profile-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.profile-dev.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.profile-dev.xcconfig"; sourceTree = ""; }; + 5A8B64E98ADDA28FB63AA32C /* Pods-Runner.release-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-nightly.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-nightly.xcconfig"; sourceTree = ""; }; + 636F4A85470D9E3B4CC8AFB8 /* Pods-nightly.profile-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.profile-dev.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.profile-dev.xcconfig"; sourceTree = ""; }; 66F649AFA6E49EA44F469DA3 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 68BE49B58C0EBB578948D773 /* Pods-nightly.release-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.release-dev.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.release-dev.xcconfig"; sourceTree = ""; }; + 6AE8151F4499707FA23C8223 /* Pods-dev.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.debug.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 77EFEBB27B276DD5F6B01B4B /* Pods-Runner.debug-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-nightly.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-nightly.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 864AC9150518DFBA85A46A15 /* Pods-stable.release-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.release-dev.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.release-dev.xcconfig"; sourceTree = ""; }; + 869E7B97AE866F2BCA2E5A6A /* Pods-Runner.release-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release-stable.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release-stable.xcconfig"; sourceTree = ""; }; + 89CD409D60E1362C529707A4 /* Pods-nightly.release-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.release-nightly.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.release-nightly.xcconfig"; sourceTree = ""; }; + 8AD587044EF2C6A6FA3059DC /* Pods-stable.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.debug.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.debug.xcconfig"; sourceTree = ""; }; + 8B9DFB8E20C11066C3AB696A /* Pods-dev.debug-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.debug-nightly.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.debug-nightly.xcconfig"; sourceTree = ""; }; + 8CF39CF9464623571B63D15B /* Pods-nightly.release-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.release-stable.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.release-stable.xcconfig"; sourceTree = ""; }; + 9232DBE472C8CEA1101843D9 /* Pods-nightly.profile-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.profile-stable.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.profile-stable.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -45,7 +128,30 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9878519B106548FD75CA15C0 /* Pods-nightly.debug-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.debug-dev.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.debug-dev.xcconfig"; sourceTree = ""; }; + A59B7A01EEC476AF3141B518 /* Pods-Runner.debug-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-dev.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-dev.xcconfig"; sourceTree = ""; }; + B38E6C7315D66215AFD8B218 /* Pods-stable.profile-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.profile-nightly.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.profile-nightly.xcconfig"; sourceTree = ""; }; + B536BDA02B405DB1009B3CE4 /* stable.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = stable.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B536BDA12B405DB1009B3CE4 /* stable-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "stable-Info.plist"; path = "/Users/xiaobowen/Documents/GitHub/spotube/ios/stable-Info.plist"; sourceTree = ""; }; + B536BDBF2B405FDE009B3CE4 /* dev.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dev.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B536BDC02B405FDE009B3CE4 /* dev-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "dev-Info.plist"; path = "/Users/xiaobowen/Documents/GitHub/spotube/ios/dev-Info.plist"; sourceTree = ""; }; + B536BDE42B4060B3009B3CE4 /* nightly.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = nightly.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B536BDE52B4060B3009B3CE4 /* nightly-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "nightly-Info.plist"; path = "/Users/xiaobowen/Documents/GitHub/spotube/ios/nightly-Info.plist"; sourceTree = ""; }; + B5F91A319C771EEC978B238A /* Pods_stable.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_stable.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B95530D9046F7F9BA07D2ADD /* Pods-Runner.profile-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-stable.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-stable.xcconfig"; sourceTree = ""; }; + BAC36FC304DBD4E8A8C00694 /* Pods_nightly.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_nightly.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BDE1B62C8A5219CAA5D19583 /* Pods-nightly.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.release.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.release.xcconfig"; sourceTree = ""; }; + C3F494F4E243EAE21CEC5765 /* Pods-Runner.profile-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile-dev.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile-dev.xcconfig"; sourceTree = ""; }; + C63F01302EF00EAECE6BEA7C /* Pods-dev.release-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.release-dev.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.release-dev.xcconfig"; sourceTree = ""; }; + CA0F4EAB0789E68A7C771A07 /* Pods-nightly.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.profile.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.profile.xcconfig"; sourceTree = ""; }; CE8646F5A4BCC46B0416DC84 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + D32BAE0F55672DD7669755B8 /* Pods-Runner.debug-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug-stable.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug-stable.xcconfig"; sourceTree = ""; }; + D9A69004587D01A7C68666CF /* Pods-dev.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.release.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.release.xcconfig"; sourceTree = ""; }; + E0EAB4380EE7C7EA7A350B6F /* Pods-stable.release-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.release-nightly.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.release-nightly.xcconfig"; sourceTree = ""; }; + E81F11471FD7D807286E33D6 /* Pods-dev.debug-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.debug-dev.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.debug-dev.xcconfig"; sourceTree = ""; }; + EB7783C1029CEC13F4B05D36 /* Pods-nightly.debug-nightly.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-nightly.debug-nightly.xcconfig"; path = "Target Support Files/Pods-nightly/Pods-nightly.debug-nightly.xcconfig"; sourceTree = ""; }; + EBBED0A8DE0D0E230CD03613 /* Pods-dev.release-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-dev.release-stable.xcconfig"; path = "Target Support Files/Pods-dev/Pods-dev.release-stable.xcconfig"; sourceTree = ""; }; + F6F397A82E788E50B186ADC7 /* Pods-stable.debug-stable.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-stable.debug-stable.xcconfig"; path = "Target Support Files/Pods-stable/Pods-stable.debug-stable.xcconfig"; sourceTree = ""; }; F7E9EBDD27997A73A4D38EE1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -55,6 +161,31 @@ buildActionMask = 2147483647; files = ( 46249B26D47C5DB81A4F972E /* Pods_Runner.framework in Frameworks */, + 051977801F58E8DBB6712352 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B536BD922B405DB1009B3CE4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E86E0C42011EDB42C34AF9A /* Pods_stable.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDB02B405FDE009B3CE4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C36A05AD330BBFAED75A62D5 /* Pods_dev.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDD22B4060B3009B3CE4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 17438EB903776D8D0E926C9B /* Pods_nightly.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -65,6 +196,9 @@ isa = PBXGroup; children = ( F7E9EBDD27997A73A4D38EE1 /* Pods_Runner.framework */, + 4238A4985255EC9F93067739 /* Pods_dev.framework */, + BAC36FC304DBD4E8A8C00694 /* Pods_nightly.framework */, + B5F91A319C771EEC978B238A /* Pods_stable.framework */, ); name = Frameworks; sourceTree = ""; @@ -75,8 +209,52 @@ 66F649AFA6E49EA44F469DA3 /* Pods-Runner.debug.xcconfig */, CE8646F5A4BCC46B0416DC84 /* Pods-Runner.release.xcconfig */, 171073CFF94F5751BC2B78DD /* Pods-Runner.profile.xcconfig */, + D32BAE0F55672DD7669755B8 /* Pods-Runner.debug-stable.xcconfig */, + 869E7B97AE866F2BCA2E5A6A /* Pods-Runner.release-stable.xcconfig */, + B95530D9046F7F9BA07D2ADD /* Pods-Runner.profile-stable.xcconfig */, + A59B7A01EEC476AF3141B518 /* Pods-Runner.debug-dev.xcconfig */, + 3E262038FF3BDA3B8A7BDAC3 /* Pods-Runner.release-dev.xcconfig */, + C3F494F4E243EAE21CEC5765 /* Pods-Runner.profile-dev.xcconfig */, + 77EFEBB27B276DD5F6B01B4B /* Pods-Runner.debug-nightly.xcconfig */, + 5A8B64E98ADDA28FB63AA32C /* Pods-Runner.release-nightly.xcconfig */, + 04C104D3779B4D1635D939BF /* Pods-Runner.profile-nightly.xcconfig */, + 6AE8151F4499707FA23C8223 /* Pods-dev.debug.xcconfig */, + 8B9DFB8E20C11066C3AB696A /* Pods-dev.debug-nightly.xcconfig */, + E81F11471FD7D807286E33D6 /* Pods-dev.debug-dev.xcconfig */, + 126B91CED32FAD3C40A67A23 /* Pods-dev.debug-stable.xcconfig */, + D9A69004587D01A7C68666CF /* Pods-dev.release.xcconfig */, + 2F9AD76AF35FFC693C051CE1 /* Pods-dev.release-nightly.xcconfig */, + C63F01302EF00EAECE6BEA7C /* Pods-dev.release-dev.xcconfig */, + EBBED0A8DE0D0E230CD03613 /* Pods-dev.release-stable.xcconfig */, + 46E04A5AA989356A32CD8E66 /* Pods-dev.profile.xcconfig */, + 1C9810F8B3FD927ED8C94791 /* Pods-dev.profile-nightly.xcconfig */, + 48E7E801EAE1B520AA5F35DD /* Pods-dev.profile-dev.xcconfig */, + 29304D1832AA30DE0C33E05C /* Pods-dev.profile-stable.xcconfig */, + 285DE2278D380EE2A6647CA9 /* Pods-nightly.debug.xcconfig */, + EB7783C1029CEC13F4B05D36 /* Pods-nightly.debug-nightly.xcconfig */, + 9878519B106548FD75CA15C0 /* Pods-nightly.debug-dev.xcconfig */, + 0F8FB58820FF492BD3CF9315 /* Pods-nightly.debug-stable.xcconfig */, + BDE1B62C8A5219CAA5D19583 /* Pods-nightly.release.xcconfig */, + 89CD409D60E1362C529707A4 /* Pods-nightly.release-nightly.xcconfig */, + 68BE49B58C0EBB578948D773 /* Pods-nightly.release-dev.xcconfig */, + 8CF39CF9464623571B63D15B /* Pods-nightly.release-stable.xcconfig */, + CA0F4EAB0789E68A7C771A07 /* Pods-nightly.profile.xcconfig */, + 261A31AC0DBA2D93BD1910D9 /* Pods-nightly.profile-nightly.xcconfig */, + 636F4A85470D9E3B4CC8AFB8 /* Pods-nightly.profile-dev.xcconfig */, + 9232DBE472C8CEA1101843D9 /* Pods-nightly.profile-stable.xcconfig */, + 8AD587044EF2C6A6FA3059DC /* Pods-stable.debug.xcconfig */, + 21C0B1DEE0F0BFD3F3651F79 /* Pods-stable.debug-nightly.xcconfig */, + 39E15EE1745C9266FDB59558 /* Pods-stable.debug-dev.xcconfig */, + F6F397A82E788E50B186ADC7 /* Pods-stable.debug-stable.xcconfig */, + 5014E8BD9F7181E528538444 /* Pods-stable.release.xcconfig */, + E0EAB4380EE7C7EA7A350B6F /* Pods-stable.release-nightly.xcconfig */, + 864AC9150518DFBA85A46A15 /* Pods-stable.release-dev.xcconfig */, + 2DA87118BE2AF25875B7C376 /* Pods-stable.release-stable.xcconfig */, + 4BDAF8FFADB62CA017755094 /* Pods-stable.profile.xcconfig */, + B38E6C7315D66215AFD8B218 /* Pods-stable.profile-nightly.xcconfig */, + 53AD516AAEB9A1331C99CBAE /* Pods-stable.profile-dev.xcconfig */, + 3F754C793C1BC0E8B8FFB5B7 /* Pods-stable.profile-stable.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -99,6 +277,9 @@ 97C146EF1CF9000F007C117D /* Products */, 67CBFE209DF24C94A9837AD5 /* Pods */, 0E0B839C4E103F896209E822 /* Frameworks */, + B536BDA12B405DB1009B3CE4 /* stable-Info.plist */, + B536BDC02B405FDE009B3CE4 /* dev-Info.plist */, + B536BDE52B4060B3009B3CE4 /* nightly-Info.plist */, ); sourceTree = ""; }; @@ -106,6 +287,9 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + B536BDA02B405DB1009B3CE4 /* stable.app */, + B536BDBF2B405FDE009B3CE4 /* dev.app */, + B536BDE42B4060B3009B3CE4 /* nightly.app */, ); name = Products; sourceTree = ""; @@ -150,13 +334,79 @@ productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + B536BD8C2B405DB1009B3CE4 /* stable */ = { + isa = PBXNativeTarget; + buildConfigurationList = B536BD9C2B405DB1009B3CE4 /* Build configuration list for PBXNativeTarget "stable" */; + buildPhases = ( + F0C8BA10A27CA77E18F842E7 /* [CP] Check Pods Manifest.lock */, + B536BD8E2B405DB1009B3CE4 /* Run Script */, + B536BD8F2B405DB1009B3CE4 /* Sources */, + B536BD922B405DB1009B3CE4 /* Frameworks */, + B536BD942B405DB1009B3CE4 /* Resources */, + B536BD992B405DB1009B3CE4 /* Embed Frameworks */, + B536BD9A2B405DB1009B3CE4 /* Thin Binary */, + A6D446F111DE4C4A202BE7F7 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = stable; + productName = Runner; + productReference = B536BDA02B405DB1009B3CE4 /* stable.app */; + productType = "com.apple.product-type.application"; + }; + B536BDAB2B405FDE009B3CE4 /* dev */ = { + isa = PBXNativeTarget; + buildConfigurationList = B536BDB82B405FDE009B3CE4 /* Build configuration list for PBXNativeTarget "dev" */; + buildPhases = ( + 6228176255365EAC646F2745 /* [CP] Check Pods Manifest.lock */, + B536BDAC2B405FDE009B3CE4 /* Run Script */, + B536BDAD2B405FDE009B3CE4 /* Sources */, + B536BDB02B405FDE009B3CE4 /* Frameworks */, + B536BDB12B405FDE009B3CE4 /* Resources */, + B536BDB62B405FDE009B3CE4 /* Embed Frameworks */, + B536BDB72B405FDE009B3CE4 /* Thin Binary */, + 244D41CE80E4BC0FFD63F8C6 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = dev; + productName = Runner; + productReference = B536BDBF2B405FDE009B3CE4 /* dev.app */; + productType = "com.apple.product-type.application"; + }; + B536BDCD2B4060B3009B3CE4 /* nightly */ = { + isa = PBXNativeTarget; + buildConfigurationList = B536BDDA2B4060B3009B3CE4 /* Build configuration list for PBXNativeTarget "nightly" */; + buildPhases = ( + 5CD4405E93760FBD048E36E2 /* [CP] Check Pods Manifest.lock */, + B536BDCE2B4060B3009B3CE4 /* Run Script */, + B536BDCF2B4060B3009B3CE4 /* Sources */, + B536BDD22B4060B3009B3CE4 /* Frameworks */, + B536BDD32B4060B3009B3CE4 /* Resources */, + B536BDD82B4060B3009B3CE4 /* Embed Frameworks */, + B536BDD92B4060B3009B3CE4 /* Thin Binary */, + D566C841A84D807A607F6DE5 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = nightly; + productName = Runner; + productReference = B536BDE42B4060B3009B3CE4 /* nightly.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -179,6 +429,9 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + B536BD8C2B405DB1009B3CE4 /* stable */, + B536BDAB2B405FDE009B3CE4 /* dev */, + B536BDCD2B4060B3009B3CE4 /* nightly */, ); }; /* End PBXProject section */ @@ -195,9 +448,59 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B536BD942B405DB1009B3CE4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B536BD952B405DB1009B3CE4 /* LaunchScreen.storyboard in Resources */, + B536BD962B405DB1009B3CE4 /* AppFrameworkInfo.plist in Resources */, + B536BD972B405DB1009B3CE4 /* Assets.xcassets in Resources */, + B536BD982B405DB1009B3CE4 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDB12B405FDE009B3CE4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B536BDB22B405FDE009B3CE4 /* LaunchScreen.storyboard in Resources */, + B536BDB32B405FDE009B3CE4 /* AppFrameworkInfo.plist in Resources */, + B536BDB42B405FDE009B3CE4 /* Assets.xcassets in Resources */, + B536BDB52B405FDE009B3CE4 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDD32B4060B3009B3CE4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B536BDD42B4060B3009B3CE4 /* LaunchScreen.storyboard in Resources */, + B536BDD52B4060B3009B3CE4 /* AppFrameworkInfo.plist in Resources */, + B536BDD62B4060B3009B3CE4 /* Assets.xcassets in Resources */, + B536BDD72B4060B3009B3CE4 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 244D41CE80E4BC0FFD63F8C6 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-dev/Pods-dev-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 2AF6C7D149EE8481703D5255 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -222,10 +525,12 @@ }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -234,6 +539,50 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 5CD4405E93760FBD048E36E2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-nightly-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6228176255365EAC646F2745 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-dev-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 6E9FEF583EA597C8B76255B2 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -253,6 +602,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -265,6 +615,155 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + A6D446F111DE4C4A202BE7F7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-stable/Pods-stable-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-stable/Pods-stable-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-stable/Pods-stable-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B536BD8E2B405DB1009B3CE4 /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + B536BD9A2B405DB1009B3CE4 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + B536BDAC2B405FDE009B3CE4 /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + B536BDB72B405FDE009B3CE4 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + B536BDCE2B4060B3009B3CE4 /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + B536BDD92B4060B3009B3CE4 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + D566C841A84D807A607F6DE5 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-nightly/Pods-nightly-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-nightly/Pods-nightly-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-nightly/Pods-nightly-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F0C8BA10A27CA77E18F842E7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-stable-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -277,6 +776,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + B536BD8F2B405DB1009B3CE4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B536BD902B405DB1009B3CE4 /* AppDelegate.swift in Sources */, + B536BD912B405DB1009B3CE4 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDAD2B405FDE009B3CE4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B536BDAE2B405FDE009B3CE4 /* AppDelegate.swift in Sources */, + B536BDAF2B405FDE009B3CE4 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B536BDCF2B4060B3009B3CE4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B536BDD02B4060B3009B3CE4 /* AppDelegate.swift in Sources */, + B536BDD12B4060B3009B3CE4 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ @@ -520,6 +1046,1437 @@ }; name = Release; }; + B536BD9D2B405DB1009B3CE4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + B536BD9E2B405DB1009B3CE4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + B536BD9F2B405DB1009B3CE4 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + B536BDA22B405E06009B3CE4 /* Debug-stable */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-stable"; + }; + B536BDA32B405E06009B3CE4 /* Debug-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-stable"; + }; + B536BDA42B405E06009B3CE4 /* Debug-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-stable"; + }; + B536BDA52B405E19009B3CE4 /* Release-stable */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-stable"; + }; + B536BDA62B405E19009B3CE4 /* Release-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-stable"; + }; + B536BDA72B405E19009B3CE4 /* Release-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-stable"; + }; + B536BDA82B405E1F009B3CE4 /* Profile-stable */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-stable"; + }; + B536BDA92B405E1F009B3CE4 /* Profile-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-stable"; + }; + B536BDAA2B405E1F009B3CE4 /* Profile-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-stable"; + }; + B536BDB92B405FDE009B3CE4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + B536BDBA2B405FDE009B3CE4 /* Debug-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-stable"; + }; + B536BDBB2B405FDE009B3CE4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + B536BDBC2B405FDE009B3CE4 /* Release-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-stable"; + }; + B536BDBD2B405FDE009B3CE4 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + B536BDBE2B405FDE009B3CE4 /* Profile-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-stable"; + }; + B536BDC12B406014009B3CE4 /* Debug-dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-dev"; + }; + B536BDC22B406014009B3CE4 /* Debug-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-dev"; + }; + B536BDC32B406014009B3CE4 /* Debug-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-dev"; + }; + B536BDC42B406014009B3CE4 /* Debug-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-dev"; + }; + B536BDC52B40601C009B3CE4 /* Release-dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-dev"; + }; + B536BDC62B40601C009B3CE4 /* Release-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-dev"; + }; + B536BDC72B40601C009B3CE4 /* Release-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-dev"; + }; + B536BDC82B40601C009B3CE4 /* Release-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-dev"; + }; + B536BDC92B406021009B3CE4 /* Profile-dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-dev"; + }; + B536BDCA2B406021009B3CE4 /* Profile-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-dev"; + }; + B536BDCB2B406021009B3CE4 /* Profile-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-dev"; + }; + B536BDCC2B406021009B3CE4 /* Profile-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-dev"; + }; + B536BDDB2B4060B3009B3CE4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + B536BDDC2B4060B3009B3CE4 /* Debug-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-dev"; + }; + B536BDDD2B4060B3009B3CE4 /* Debug-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-stable"; + }; + B536BDDE2B4060B3009B3CE4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + B536BDDF2B4060B3009B3CE4 /* Release-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-dev"; + }; + B536BDE02B4060B3009B3CE4 /* Release-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-stable"; + }; + B536BDE12B4060B3009B3CE4 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + B536BDE22B4060B3009B3CE4 /* Profile-dev */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-dev"; + }; + B536BDE32B4060B3009B3CE4 /* Profile-stable */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-stable"; + }; + B536BDE62B4060FE009B3CE4 /* Debug-nightly */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = "Debug-nightly"; + }; + B536BDE72B4060FE009B3CE4 /* Debug-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-nightly"; + }; + B536BDE82B4060FE009B3CE4 /* Debug-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-nightly"; + }; + B536BDE92B4060FE009B3CE4 /* Debug-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-nightly"; + }; + B536BDEA2B4060FE009B3CE4 /* Debug-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Debug-nightly"; + }; + B536BDEB2B406105009B3CE4 /* Release-nightly */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Release-nightly"; + }; + B536BDEC2B406105009B3CE4 /* Release-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-nightly"; + }; + B536BDED2B406105009B3CE4 /* Release-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-nightly"; + }; + B536BDEE2B406105009B3CE4 /* Release-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-nightly"; + }; + B536BDEF2B406105009B3CE4 /* Release-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Release-nightly"; + }; + B536BDF02B40610B009B3CE4 /* Profile-nightly */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = "Profile-nightly"; + }; + B536BDF12B40610B009B3CE4 /* Profile-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-nightly"; + }; + B536BDF22B40610B009B3CE4 /* Profile-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "stable-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.stable; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-nightly"; + }; + B536BDF32B40610B009B3CE4 /* Profile-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "dev-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.dev; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-nightly"; + }; + B536BDF42B40610B009B3CE4 /* Profile-nightly */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-nightly"; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = "nightly-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = oss.krtirtho.spotube.nightly; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = "Profile-nightly"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -527,8 +2484,17 @@ isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, + B536BDE62B4060FE009B3CE4 /* Debug-nightly */, + B536BDC12B406014009B3CE4 /* Debug-dev */, + B536BDA22B405E06009B3CE4 /* Debug-stable */, 97C147041CF9000F007C117D /* Release */, + B536BDEB2B406105009B3CE4 /* Release-nightly */, + B536BDC52B40601C009B3CE4 /* Release-dev */, + B536BDA52B405E19009B3CE4 /* Release-stable */, 249021D3217E4FDB00AE95B9 /* Profile */, + B536BDF02B40610B009B3CE4 /* Profile-nightly */, + B536BDC92B406021009B3CE4 /* Profile-dev */, + B536BDA82B405E1F009B3CE4 /* Profile-stable */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -537,8 +2503,74 @@ isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, + B536BDE72B4060FE009B3CE4 /* Debug-nightly */, + B536BDC22B406014009B3CE4 /* Debug-dev */, + B536BDA32B405E06009B3CE4 /* Debug-stable */, 97C147071CF9000F007C117D /* Release */, + B536BDEC2B406105009B3CE4 /* Release-nightly */, + B536BDC62B40601C009B3CE4 /* Release-dev */, + B536BDA62B405E19009B3CE4 /* Release-stable */, 249021D4217E4FDB00AE95B9 /* Profile */, + B536BDF12B40610B009B3CE4 /* Profile-nightly */, + B536BDCA2B406021009B3CE4 /* Profile-dev */, + B536BDA92B405E1F009B3CE4 /* Profile-stable */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B536BD9C2B405DB1009B3CE4 /* Build configuration list for PBXNativeTarget "stable" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B536BD9D2B405DB1009B3CE4 /* Debug */, + B536BDE82B4060FE009B3CE4 /* Debug-nightly */, + B536BDC32B406014009B3CE4 /* Debug-dev */, + B536BDA42B405E06009B3CE4 /* Debug-stable */, + B536BD9E2B405DB1009B3CE4 /* Release */, + B536BDED2B406105009B3CE4 /* Release-nightly */, + B536BDC72B40601C009B3CE4 /* Release-dev */, + B536BDA72B405E19009B3CE4 /* Release-stable */, + B536BD9F2B405DB1009B3CE4 /* Profile */, + B536BDF22B40610B009B3CE4 /* Profile-nightly */, + B536BDCB2B406021009B3CE4 /* Profile-dev */, + B536BDAA2B405E1F009B3CE4 /* Profile-stable */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B536BDB82B405FDE009B3CE4 /* Build configuration list for PBXNativeTarget "dev" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B536BDB92B405FDE009B3CE4 /* Debug */, + B536BDE92B4060FE009B3CE4 /* Debug-nightly */, + B536BDC42B406014009B3CE4 /* Debug-dev */, + B536BDBA2B405FDE009B3CE4 /* Debug-stable */, + B536BDBB2B405FDE009B3CE4 /* Release */, + B536BDEE2B406105009B3CE4 /* Release-nightly */, + B536BDC82B40601C009B3CE4 /* Release-dev */, + B536BDBC2B405FDE009B3CE4 /* Release-stable */, + B536BDBD2B405FDE009B3CE4 /* Profile */, + B536BDF32B40610B009B3CE4 /* Profile-nightly */, + B536BDCC2B406021009B3CE4 /* Profile-dev */, + B536BDBE2B405FDE009B3CE4 /* Profile-stable */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B536BDDA2B4060B3009B3CE4 /* Build configuration list for PBXNativeTarget "nightly" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B536BDDB2B4060B3009B3CE4 /* Debug */, + B536BDEA2B4060FE009B3CE4 /* Debug-nightly */, + B536BDDC2B4060B3009B3CE4 /* Debug-dev */, + B536BDDD2B4060B3009B3CE4 /* Debug-stable */, + B536BDDE2B4060B3009B3CE4 /* Release */, + B536BDEF2B406105009B3CE4 /* Release-nightly */, + B536BDDF2B4060B3009B3CE4 /* Release-dev */, + B536BDE02B4060B3009B3CE4 /* Release-stable */, + B536BDE12B4060B3009B3CE4 /* Profile */, + B536BDF42B40610B009B3CE4 /* Profile-nightly */, + B536BDE22B4060B3009B3CE4 /* Profile-dev */, + B536BDE32B4060B3009B3CE4 /* Profile-stable */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..a6b826db 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/nightly.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/nightly.xcscheme new file mode 100644 index 00000000..7ec18a73 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/nightly.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/stable.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/stable.xcscheme new file mode 100644 index 00000000..ddc19e2e --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/stable.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-1024x1024@1x.png new file mode 100644 index 00000000..dbc4596b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@1x.png new file mode 100644 index 00000000..4836771d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@2x.png new file mode 100644 index 00000000..90954ce9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@3x.png new file mode 100644 index 00000000..9c0ebd5f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@1x.png new file mode 100644 index 00000000..94cd79be Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@2x.png new file mode 100644 index 00000000..ff70cab7 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@3x.png new file mode 100644 index 00000000..6cdda1b6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@1x.png new file mode 100644 index 00000000..90954ce9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@2x.png new file mode 100644 index 00000000..5184f84f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@3x.png new file mode 100644 index 00000000..57e21a75 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-50x50@1x.png new file mode 100644 index 00000000..93e157b6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-50x50@2x.png new file mode 100644 index 00000000..d175beb2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-57x57@1x.png new file mode 100644 index 00000000..6d634c87 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-57x57@2x.png new file mode 100644 index 00000000..22da4950 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-60x60@2x.png new file mode 100644 index 00000000..57e21a75 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-60x60@3x.png new file mode 100644 index 00000000..3cfd01c2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-72x72@1x.png new file mode 100644 index 00000000..a826bb73 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-72x72@2x.png new file mode 100644 index 00000000..3a8a7832 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-76x76@1x.png new file mode 100644 index 00000000..f233322b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-76x76@2x.png new file mode 100644 index 00000000..2f5b082a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-83.5x83.5@2x.png new file mode 100644 index 00000000..e4ecc19a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/AppIcon-nightly-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/Contents.json new file mode 100644 index 00000000..e8947587 --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon-nightly.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"20x20","idiom":"iphone","filename":"AppIcon-nightly-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"AppIcon-nightly-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-nightly-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-nightly-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-nightly-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-nightly-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-nightly-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-nightly-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-nightly-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-nightly-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-nightly-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-nightly-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-nightly-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-nightly-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-nightly-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-nightly-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-nightly-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-nightly-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-nightly-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-nightly-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-nightly-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-nightly-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-nightly-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"AppIcon-nightly-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppIcon-nightly-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-1024x1024@1x.png new file mode 100644 index 00000000..aaf8f69b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png new file mode 100644 index 00000000..2ac9068e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png new file mode 100644 index 00000000..d0a01485 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png new file mode 100644 index 00000000..693f7baa Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png new file mode 100644 index 00000000..033019fc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png new file mode 100644 index 00000000..809668c3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png new file mode 100644 index 00000000..eaa1de13 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png new file mode 100644 index 00000000..d0a01485 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png new file mode 100644 index 00000000..ffb602b3 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png new file mode 100644 index 00000000..77d37d5d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-50x50@1x.png new file mode 100644 index 00000000..a26cd088 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-50x50@2x.png new file mode 100644 index 00000000..8d860f15 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-57x57@1x.png new file mode 100644 index 00000000..6a480baf Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-57x57@2x.png new file mode 100644 index 00000000..d8b55615 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png new file mode 100644 index 00000000..77d37d5d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png new file mode 100644 index 00000000..2b587235 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-72x72@1x.png new file mode 100644 index 00000000..efac11ba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-72x72@2x.png new file mode 100644 index 00000000..a73fe33c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png new file mode 100644 index 00000000..e8ac9032 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png new file mode 100644 index 00000000..e1859a0d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000..f863a923 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fab..05843b52 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"20x20","idiom":"iphone","filename":"AppIcon-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"AppIcon-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"AppIcon-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppIcon-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada47..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf03..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd96..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde1211..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd96..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b860..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b860..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d3..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f585..00000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d8b46b96..5ba9991f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Sptube + Spotube CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -60,5 +60,7 @@ This app require access to the device camera NSMicrophoneUsageDescription This app does not require access to the device microphone - + UIApplicationSupportsIndirectInputEvents + + diff --git a/ios/build/.last_build_id b/ios/build/.last_build_id new file mode 100644 index 00000000..ee73fd53 --- /dev/null +++ b/ios/build/.last_build_id @@ -0,0 +1 @@ +6f5ed64a4065df2d43bfb5b18863018c \ No newline at end of file diff --git a/ios/dev-Info.plist b/ios/dev-Info.plist new file mode 100644 index 00000000..022d5419 --- /dev/null +++ b/ios/dev-Info.plist @@ -0,0 +1,66 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Spotube Dev + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + spotube + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsForMedia + + + NSCameraUsageDescription + This app require access to the device camera + NSMicrophoneUsageDescription + This app does not require access to the device microphone + NSPhotoLibraryUsageDescription + This app require access to the photo library + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/ios/nightly-Info.plist b/ios/nightly-Info.plist new file mode 100644 index 00000000..5ba9991f --- /dev/null +++ b/ios/nightly-Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Spotube + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + spotube + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsForMedia + + + CADisableMinimumFrameDurationOnPhone + + UIStatusBarHidden + + NSPhotoLibraryUsageDescription + This app require access to the photo library + NSCameraUsageDescription + This app require access to the device camera + NSMicrophoneUsageDescription + This app does not require access to the device microphone + UIApplicationSupportsIndirectInputEvents + + + diff --git a/ios/stable-Info.plist b/ios/stable-Info.plist new file mode 100644 index 00000000..5ba9991f --- /dev/null +++ b/ios/stable-Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Spotube + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + spotube + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsForMedia + + + CADisableMinimumFrameDurationOnPhone + + UIStatusBarHidden + + NSPhotoLibraryUsageDescription + This app require access to the photo library + NSCameraUsageDescription + This app require access to the device camera + NSMicrophoneUsageDescription + This app does not require access to the device microphone + UIApplicationSupportsIndirectInputEvents + + + diff --git a/lib/collections/assets.gen.dart b/lib/collections/assets.gen.dart index ac39cf68..2587800e 100644 --- a/lib/collections/assets.gen.dart +++ b/lib/collections/assets.gen.dart @@ -34,6 +34,9 @@ class Assets { AssetGenImage('assets/bengali-patterns-bg.jpg'); static const AssetGenImage branding = AssetGenImage('assets/branding.png'); static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png'); + static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png'); + static const AssetGenImage likedTracks = + AssetGenImage('assets/liked-tracks.jpg'); static const AssetGenImage placeholder = AssetGenImage('assets/placeholder.png'); static const AssetGenImage spotubeHeroBanner = @@ -74,6 +77,8 @@ class Assets { bengaliPatternsBg, branding, emptyBox, + jiosaavn, + likedTracks, placeholder, spotubeHeroBanner, spotubeLogoForeground, diff --git a/lib/collections/fake.dart b/lib/collections/fake.dart new file mode 100644 index 00000000..8f5f9e8b --- /dev/null +++ b/lib/collections/fake.dart @@ -0,0 +1,199 @@ +import 'package:spotify/spotify.dart'; +import 'package:spotube/extensions/track.dart'; +import 'package:spotube/models/spotify_friends.dart'; + +abstract class FakeData { + static final Image image = Image() + ..height = 1 + ..width = 1 + ..url = "url"; + + static final Followers followers = Followers() + ..href = "text" + ..total = 1; + + static final Artist artist = Artist() + ..id = "1" + ..name = "Wow artist Good!" + ..images = [image] + ..popularity = 1 + ..type = "type" + ..uri = "uri" + ..externalUrls = externalUrls + ..genres = ["genre"] + ..href = "text" + ..followers = followers; + + static final externalIds = ExternalIds() + ..isrc = "text" + ..ean = "text" + ..upc = "text"; + + static final externalUrls = ExternalUrls()..spotify = "text"; + + static final Album album = Album() + ..id = "1" + ..genres = ["genre"] + ..label = "label" + ..popularity = 1 + ..albumType = AlbumType.album + ..artists = [artist] + ..availableMarkets = [Market.BD] + ..externalUrls = externalUrls + ..href = "text" + ..images = [image] + ..name = "Another good album" + ..releaseDate = "2021-01-01" + ..releaseDatePrecision = DatePrecision.day + ..tracks = [track] + ..type = "type" + ..uri = "uri" + ..externalIds = externalIds + ..copyrights = [ + Copyright() + ..type = CopyrightType.C + ..text = "text", + ]; + + static final ArtistSimple artistSimple = ArtistSimple() + ..id = "1" + ..name = "What an artist" + ..type = "type" + ..uri = "uri" + ..externalUrls = externalUrls; + + static final AlbumSimple albumSimple = AlbumSimple() + ..id = "1" + ..albumType = AlbumType.album + ..artists = [artistSimple] + ..availableMarkets = [Market.BD] + ..externalUrls = externalUrls + ..href = "text" + ..images = [image] + ..name = "A good album" + ..releaseDate = "2021-01-01" + ..releaseDatePrecision = DatePrecision.day + ..type = "type" + ..uri = "uri"; + + static final Track track = Track() + ..id = "1" + ..artists = [artist, artist, artist] + ..album = albumSimple + ..availableMarkets = [Market.BD] + ..discNumber = 1 + ..durationMs = 50000 + ..explicit = false + ..externalUrls = externalUrls + ..href = "text" + ..name = "A Track Name" + ..popularity = 1 + ..previewUrl = "url" + ..trackNumber = 1 + ..type = "type" + ..uri = "uri" + ..isPlayable = true + ..explicit = false + ..linkedFrom = trackLink; + + static final TrackLink trackLink = TrackLink() + ..id = "1" + ..type = "type" + ..uri = "uri" + ..externalUrls = {"spotify": "text"} + ..href = "text"; + + static final Paging paging = Paging() + ..href = "text" + ..itemsNative = [track.toJson()] + ..limit = 1 + ..next = "text" + ..offset = 1 + ..previous = "text" + ..total = 1; + + static final User user = User() + ..id = "1" + ..displayName = "Your Name" + ..birthdate = "2021-01-01" + ..country = Market.BD + ..email = "test@email.com" + ..followers = followers + ..href = "text" + ..images = [image] + ..type = "type" + ..uri = "uri"; + + static final TracksLink tracksLink = TracksLink() + ..href = "text" + ..total = 1; + + static final Playlist playlist = Playlist() + ..id = "1" + ..collaborative = false + ..description = "A very good playlist description" + ..externalUrls = externalUrls + ..followers = followers + ..href = "text" + ..images = [image] + ..name = "A good playlist" + ..owner = user + ..public = true + ..snapshotId = "text" + ..tracks = paging + ..tracksLink = tracksLink + ..type = "type" + ..uri = "uri"; + + static final PlaylistSimple playlistSimple = PlaylistSimple() + ..id = "1" + ..collaborative = false + ..externalUrls = externalUrls + ..href = "text" + ..images = [image] + ..name = "A good playlist" + ..owner = user + ..public = true + ..snapshotId = "text" + ..tracksLink = tracksLink + ..type = "type" + ..description = "A very good playlist description" + ..uri = "uri"; + + static final Category category = Category() + ..href = "text" + ..icons = [image] + ..id = "1" + ..name = "category"; + + static final friends = SpotifyFriends( + friends: [ + for (var i = 0; i < 3; i++) + SpotifyFriendActivity( + user: const SpotifyFriend( + name: "name", + imageUrl: "imageUrl", + uri: "uri", + ), + track: SpotifyActivityTrack( + name: "name", + artist: const SpotifyActivityArtist( + name: "name", + uri: "uri", + ), + album: const SpotifyActivityAlbum( + name: "name", + uri: "uri", + ), + context: SpotifyActivityContext( + name: "name", + index: i, + uri: "uri", + ), + imageUrl: "imageUrl", + uri: "uri", + ), + ), + ], + ); +} diff --git a/lib/collections/gradients.dart b/lib/collections/gradients.dart new file mode 100644 index 00000000..e861dde7 --- /dev/null +++ b/lib/collections/gradients.dart @@ -0,0 +1,232 @@ +import 'package:flutter/material.dart'; + +const gradients = [ + LinearGradient(colors: [ + Color.fromRGBO(123, 102, 255, 1), + Color.fromRGBO(95, 189, 255, 1), + Color.fromRGBO(150, 239, 255, 1), + Color.fromRGBO(197, 255, 248, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(245, 204, 160, 1), + Color.fromRGBO(228, 143, 69, 1), + Color.fromRGBO(153, 77, 28, 1), + Color.fromRGBO(107, 36, 12, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(243, 243, 243, 1), + Color.fromRGBO(197, 232, 152, 1), + Color.fromRGBO(41, 173, 178, 1), + Color.fromRGBO(7, 102, 173, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(240, 89, 65, 1), + Color.fromRGBO(190, 49, 68, 1), + Color.fromRGBO(135, 35, 65, 1), + Color.fromRGBO(34, 9, 44, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(119, 107, 93, 1), + Color.fromRGBO(176, 166, 149, 1), + Color.fromRGBO(235, 227, 213, 1), + Color.fromRGBO(243, 238, 234, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(208, 162, 247, 1), + Color.fromRGBO(220, 191, 255, 1), + Color.fromRGBO(229, 212, 255, 1), + Color.fromRGBO(241, 234, 255, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(221, 242, 253, 1), + Color.fromRGBO(155, 190, 200, 1), + Color.fromRGBO(66, 125, 157, 1), + Color.fromRGBO(22, 72, 99, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(119, 67, 219, 1), + Color.fromRGBO(195, 172, 208, 1), + Color.fromRGBO(247, 239, 229, 1), + Color.fromRGBO(255, 251, 245, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(194, 217, 255, 1), + Color.fromRGBO(142, 143, 250, 1), + Color.fromRGBO(119, 82, 254, 1), + Color.fromRGBO(25, 4, 130, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(104, 126, 255, 1), + Color.fromRGBO(128, 179, 255, 1), + Color.fromRGBO(152, 228, 255, 1), + Color.fromRGBO(182, 255, 250, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(176, 87, 141, 1), + Color.fromRGBO(217, 136, 185, 1), + Color.fromRGBO(250, 203, 234, 1), + Color.fromRGBO(255, 228, 214, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(190, 255, 247, 1), + Color.fromRGBO(166, 246, 255, 1), + Color.fromRGBO(158, 221, 255, 1), + Color.fromRGBO(100, 153, 233, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(245, 252, 205, 1), + Color.fromRGBO(120, 214, 198, 1), + Color.fromRGBO(65, 145, 151, 1), + Color.fromRGBO(18, 72, 107, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(229, 207, 247, 1), + Color.fromRGBO(157, 118, 193, 1), + Color.fromRGBO(113, 58, 190, 1), + Color.fromRGBO(91, 8, 136, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(249, 222, 201, 1), + Color.fromRGBO(247, 140, 162, 1), + Color.fromRGBO(216, 0, 50, 1), + Color.fromRGBO(61, 12, 17, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(242, 247, 161, 1), + Color.fromRGBO(53, 162, 159, 1), + Color.fromRGBO(8, 131, 149, 1), + Color.fromRGBO(7, 25, 82, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(243, 159, 90, 1), + Color.fromRGBO(174, 68, 90, 1), + Color.fromRGBO(102, 37, 73, 1), + Color.fromRGBO(69, 25, 82, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(255, 200, 200, 1), + Color.fromRGBO(255, 155, 130, 1), + Color.fromRGBO(255, 63, 164, 1), + Color.fromRGBO(87, 55, 93, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(238, 238, 238, 1), + Color.fromRGBO(100, 204, 197, 1), + Color.fromRGBO(23, 107, 135, 1), + Color.fromRGBO(5, 59, 80, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(198, 61, 47, 1), + Color.fromRGBO(226, 94, 62, 1), + Color.fromRGBO(255, 155, 80, 1), + Color.fromRGBO(255, 187, 92, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(236, 83, 176, 1), + Color.fromRGBO(157, 68, 192, 1), + Color.fromRGBO(77, 45, 183, 1), + Color.fromRGBO(14, 33, 160, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(242, 236, 190, 1), + Color.fromRGBO(226, 199, 153, 1), + Color.fromRGBO(192, 130, 97, 1), + Color.fromRGBO(154, 59, 59, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(255, 253, 140, 1), + Color.fromRGBO(151, 255, 244, 1), + Color.fromRGBO(112, 145, 245, 1), + Color.fromRGBO(121, 63, 223, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(67, 83, 52, 1), + Color.fromRGBO(158, 179, 132, 1), + Color.fromRGBO(206, 222, 189, 1), + Color.fromRGBO(250, 241, 228, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(250, 240, 230, 1), + Color.fromRGBO(185, 180, 199, 1), + Color.fromRGBO(92, 84, 112, 1), + Color.fromRGBO(53, 47, 68, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(255, 186, 134, 1), + Color.fromRGBO(246, 99, 92, 1), + Color.fromRGBO(194, 51, 115, 1), + Color.fromRGBO(121, 21, 91, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(213, 255, 208, 1), + Color.fromRGBO(64, 248, 255, 1), + Color.fromRGBO(39, 158, 255, 1), + Color.fromRGBO(12, 53, 106, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(131, 96, 150, 1), + Color.fromRGBO(237, 123, 123, 1), + Color.fromRGBO(240, 184, 110, 1), + Color.fromRGBO(235, 231, 108, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(63, 29, 56, 1), + Color.fromRGBO(77, 60, 119, 1), + Color.fromRGBO(162, 103, 138, 1), + Color.fromRGBO(225, 152, 152, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(254, 123, 229, 1), + Color.fromRGBO(151, 78, 195, 1), + Color.fromRGBO(80, 64, 153, 1), + Color.fromRGBO(49, 56, 102, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(248, 222, 34, 1), + Color.fromRGBO(249, 76, 16, 1), + Color.fromRGBO(199, 0, 57, 1), + Color.fromRGBO(144, 12, 63, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(101, 69, 31, 1), + Color.fromRGBO(118, 88, 39, 1), + Color.fromRGBO(200, 174, 125, 1), + Color.fromRGBO(234, 198, 150, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(255, 246, 224, 1), + Color.fromRGBO(216, 217, 218, 1), + Color.fromRGBO(97, 103, 122, 1), + Color.fromRGBO(39, 40, 41, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(145, 109, 179, 1), + Color.fromRGBO(228, 133, 134, 1), + Color.fromRGBO(252, 186, 173, 1), + Color.fromRGBO(253, 229, 236, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(124, 115, 192, 1), + Color.fromRGBO(148, 173, 215, 1), + Color.fromRGBO(172, 250, 223, 1), + Color.fromRGBO(232, 255, 206, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(174, 216, 204, 1), + Color.fromRGBO(205, 102, 136, 1), + Color.fromRGBO(122, 49, 111, 1), + Color.fromRGBO(70, 25, 89, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(237, 228, 255, 1), + Color.fromRGBO(215, 187, 245, 1), + Color.fromRGBO(160, 118, 249, 1), + Color.fromRGBO(101, 40, 247, 1) + ]), + LinearGradient(colors: [ + Color.fromRGBO(255, 236, 175, 1), + Color.fromRGBO(255, 176, 127, 1), + Color.fromRGBO(255, 82, 162, 1), + Color.fromRGBO(243, 21, 89, 1) + ]), +]; diff --git a/lib/collections/initializers.dart b/lib/collections/initializers.dart new file mode 100644 index 00000000..9627de1c --- /dev/null +++ b/lib/collections/initializers.dart @@ -0,0 +1,25 @@ +import 'dart:io'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:win32_registry/win32_registry.dart'; + +Future registerWindowsScheme(String scheme) async { + if (!DesktopTools.platform.isWindows) return; + String appPath = Platform.resolvedExecutable; + + String protocolRegKey = 'Software\\Classes\\$scheme'; + RegistryValue protocolRegValue = const RegistryValue( + 'URL Protocol', + RegistryValueType.string, + '', + ); + String protocolCmdRegKey = 'shell\\open\\command'; + RegistryValue protocolCmdRegValue = RegistryValue( + '', + RegistryValueType.string, + '"$appPath" "%1"', + ); + + final regKey = Registry.currentUser.createKey(protocolRegKey); + regKey.createValue(protocolRegValue); + regKey.createKey(protocolCmdRegKey).createValue(protocolCmdRegValue); +} diff --git a/lib/collections/language_codes.dart b/lib/collections/language_codes.dart index 0518363e..4554de63 100644 --- a/lib/collections/language_codes.dart +++ b/lib/collections/language_codes.dart @@ -164,10 +164,10 @@ abstract class LanguageLocals { // name: "Maldivian;", // nativeName: "ދިވެހި", // ), - // "nl": const ISOLanguageName( - // name: "Dutch", - // nativeName: "Vlaams", - // ), + "nl": const ISOLanguageName( + name: "Dutch", + nativeName: "Nederlands", + ), "en": const ISOLanguageName( name: "English", nativeName: "English", @@ -288,10 +288,10 @@ abstract class LanguageLocals { // name: "Icelandic", // nativeName: "Íslenska", // ), - // "it": const ISOLanguageName( - // name: "Italian", - // nativeName: "Italiano", - // ), + "it": const ISOLanguageName( + name: "Italian", + nativeName: "Italiano", + ), // "iu": const ISOLanguageName( // name: "Inuktitut", // nativeName: "ᐃᓄᒃᑎᑐᑦ", @@ -452,10 +452,10 @@ abstract class LanguageLocals { // name: "North Ndebele", // nativeName: "isiNdebele", // ), - // "ne": const ISOLanguageName( - // name: "Nepali", - // nativeName: "नेपाली", - // ), + "ne": const ISOLanguageName( + name: "Nepali", + nativeName: "नेपाली", + ), // "ng": const ISOLanguageName( // name: "Ndonga", // nativeName: "Owambo", diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 82597ddb..3e2c42e0 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -1,9 +1,11 @@ import 'package:catcher_2/catcher_2.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' hide Category; import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:spotify/spotify.dart' hide Search; import 'package:spotube/pages/album/album.dart'; +import 'package:spotube/pages/home/genres/genre_playlists.dart'; +import 'package:spotube/pages/home/genres/genres.dart'; import 'package:spotube/pages/home/home.dart'; import 'package:spotube/pages/lastfm_login/lastfm_login.dart'; import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart'; @@ -15,6 +17,7 @@ import 'package:spotube/pages/search/search.dart'; import 'package:spotube/pages/settings/blacklist.dart'; import 'package:spotube/pages/settings/about.dart'; import 'package:spotube/pages/settings/logs.dart'; +import 'package:spotube/pages/track/track.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/components/shared/spotube_page_route.dart'; import 'package:spotube/pages/artist/artist.dart'; @@ -38,6 +41,21 @@ final router = GoRouter( GoRoute( path: "/", pageBuilder: (context, state) => const SpotubePage(child: HomePage()), + routes: [ + GoRoute( + path: "genres", + pageBuilder: (context, state) => + const SpotubePage(child: GenrePage()), + ), + GoRoute( + path: "genre/:categoryId", + pageBuilder: (context, state) => SpotubePage( + child: GenrePlaylistsPage( + category: state.extra as Category, + ), + ), + ), + ], ), GoRoute( path: "/search", @@ -127,6 +145,15 @@ final router = GoRouter( ); }, ), + GoRoute( + path: "/track/:id", + pageBuilder: (context, state) { + final id = state.pathParameters["id"]!; + return SpotubePage( + child: TrackPage(trackId: id), + ); + }, + ), ], ), GoRoute( diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index d00775c7..65e6c1a0 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -41,6 +41,7 @@ abstract class SpotubeIcons { static const clock = FeatherIcons.clock; static const lyrics = Icons.lyrics_rounded; static const lyricsOff = Icons.lyrics_outlined; + static const noLyrics = Icons.music_off_outlined; static const logout = FeatherIcons.logOut; static const login = FeatherIcons.logIn; static const dashboard = FeatherIcons.grid; @@ -108,4 +109,6 @@ abstract class SpotubeIcons { static const noEye = FeatherIcons.eyeOff; static const normalize = FeatherIcons.barChart2; static const wikipedia = SimpleIcons.wikipedia; + static const discord = SimpleIcons.discord; + static const youtube = SimpleIcons.youtube; } diff --git a/lib/components/artist/artist_card.dart b/lib/components/artist/artist_card.dart index 434b90ad..3526e88f 100644 --- a/lib/components/artist/artist_card.dart +++ b/lib/components/artist/artist_card.dart @@ -1,6 +1,7 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; @@ -91,12 +92,14 @@ class ArtistCard extends HookConsumerWidget { decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(50)), - child: Text( - context.l10n.artist, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold, + child: Skeleton.ignore( + child: Text( + context.l10n.artist, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), ), ), diff --git a/lib/components/desktop_login/login_form.dart b/lib/components/desktop_login/login_form.dart index f2b183f4..5abb9524 100644 --- a/lib/components/desktop_login/login_form.dart +++ b/lib/components/desktop_login/login_form.dart @@ -17,7 +17,6 @@ class TokenLoginForm extends HookConsumerWidget { final authenticationNotifier = ref.watch(AuthenticationNotifier.provider.notifier); final directCodeController = useTextEditingController(); - final keyCodeController = useTextEditingController(); final mounted = useIsMounted(); final isLoading = useState(false); @@ -37,23 +36,13 @@ class TokenLoginForm extends HookConsumerWidget { keyboardType: TextInputType.visiblePassword, ), const SizedBox(height: 10), - TextField( - controller: keyCodeController, - decoration: InputDecoration( - hintText: context.l10n.spotify_cookie("\"sp_key (or sp_gaid)\""), - labelText: context.l10n.cookie_name_cookie("sp_key (or sp_gaid)"), - ), - keyboardType: TextInputType.visiblePassword, - ), - const SizedBox(height: 20), FilledButton( onPressed: isLoading.value ? null : () async { try { isLoading.value = true; - if (keyCodeController.text.isEmpty || - directCodeController.text.isEmpty) { + if (directCodeController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(context.l10n.fill_in_all_fields), @@ -63,7 +52,7 @@ class TokenLoginForm extends HookConsumerWidget { return; } final cookieHeader = - "sp_dc=${directCodeController.text.trim()}; sp_key=${keyCodeController.text.trim()}"; + "sp_dc=${directCodeController.text.trim()}"; authenticationNotifier.setCredentials( await AuthenticationCredentials.fromCookie( diff --git a/lib/components/genre/category_card.dart b/lib/components/genre/category_card.dart deleted file mode 100644 index 7f580157..00000000 --- a/lib/components/genre/category_card.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart' hide Page; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -import 'package:spotify/spotify.dart'; -import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; -import 'package:spotube/models/logger.dart'; -import 'package:spotube/services/queries/queries.dart'; - -class CategoryCard extends HookConsumerWidget { - final Category category; - CategoryCard( - this.category, { - Key? key, - }) : super(key: key); - - final logger = getLogger(CategoryCard); - - @override - Widget build(BuildContext context, ref) { - final playlistQuery = useQueries.category.playlistsOf( - ref, - category.id!, - ); - - final playlists = useMemoized( - () => playlistQuery.pages.expand( - (page) { - return page.items?.whereNotNull() ?? - const Iterable.empty(); - }, - ).toList(), - [playlistQuery.pages], - ); - - if (playlistQuery.hasErrors && - !playlistQuery.hasPageData && - !playlistQuery.isLoadingNextPage) { - return const SizedBox.shrink(); - } - - return HorizontalPlaybuttonCardView( - title: Text(category.name!), - isLoadingNextPage: playlistQuery.isLoadingNextPage, - hasNextPage: playlistQuery.hasNextPage, - items: playlists, - onFetchMore: playlistQuery.fetchNext, - ); - } -} diff --git a/lib/components/home/sections/featured.dart b/lib/components/home/sections/featured.dart new file mode 100644 index 00000000..8a7c2c95 --- /dev/null +++ b/lib/components/home/sections/featured.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart' hide Page; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/services/queries/queries.dart'; + +class HomeFeaturedSection extends HookConsumerWidget { + const HomeFeaturedSection({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final featuredPlaylistsQuery = useQueries.playlist.featured(ref); + final playlists = useMemoized( + () => featuredPlaylistsQuery.pages + .whereType>() + .expand((page) => page.items ?? const []), + [featuredPlaylistsQuery.pages], + ); + final isLoadingFeaturedPlaylists = !featuredPlaylistsQuery.hasPageData && + !featuredPlaylistsQuery.isLoadingNextPage; + + return Skeletonizer( + enabled: isLoadingFeaturedPlaylists, + child: HorizontalPlaybuttonCardView( + items: playlists.toList(), + title: Text(context.l10n.featured), + isLoadingNextPage: featuredPlaylistsQuery.isLoadingNextPage, + hasNextPage: featuredPlaylistsQuery.hasNextPage, + onFetchMore: featuredPlaylistsQuery.fetchNext, + ), + ); + } +} diff --git a/lib/components/home/sections/friends.dart b/lib/components/home/sections/friends.dart new file mode 100644 index 00000000..ef24b8d5 --- /dev/null +++ b/lib/components/home/sections/friends.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/home/sections/friends/friend_item.dart'; +import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; +import 'package:spotube/models/spotify_friends.dart'; +import 'package:spotube/services/queries/queries.dart'; + +class HomePageFriendsSection extends HookConsumerWidget { + const HomePageFriendsSection({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final friendsQuery = useQueries.user.friendActivity(ref); + final friends = friendsQuery.data?.friends ?? FakeData.friends.friends; + + final groupCount = useBreakpointValue( + sm: 3, + xs: 2, + md: 4, + lg: 5, + xl: 6, + xxl: 7, + ); + + final friendGroup = friends.fold>>( + [], + (previousValue, element) { + if (previousValue.isEmpty) { + return [ + [element] + ]; + } + + final lastGroup = previousValue.last; + if (lastGroup.length < groupCount) { + return [ + ...previousValue.sublist(0, previousValue.length - 1), + [...lastGroup, element] + ]; + } + + return [ + ...previousValue, + [element] + ]; + }, + ); + + if (!friendsQuery.isLoading && + (!friendsQuery.hasData || friendsQuery.data!.friends.isEmpty)) { + return const SliverToBoxAdapter( + child: SizedBox.shrink(), + ); + } + + return Skeletonizer.sliver( + enabled: friendsQuery.isLoading, + child: SliverMainAxisGroup( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Friends', + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ), + SliverToBoxAdapter( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final group in friendGroup) + Row( + children: [ + for (final friend in group) + Padding( + padding: const EdgeInsets.all(8.0), + child: FriendItem(friend: friend), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/components/home/sections/friends/friend_item.dart b/lib/components/home/sections/friends/friend_item.dart new file mode 100644 index 00000000..fcdadab7 --- /dev/null +++ b/lib/components/home/sections/friends/friend_item.dart @@ -0,0 +1,136 @@ +import 'package:fl_query_hooks/fl_query_hooks.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/models/spotify_friends.dart'; +import 'package:spotube/provider/spotify_provider.dart'; + +class FriendItem extends HookConsumerWidget { + final SpotifyFriendActivity friend; + const FriendItem({ + Key? key, + required this.friend, + }) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final ThemeData( + textTheme: textTheme, + colorScheme: colorScheme, + ) = Theme.of(context); + + final queryClient = useQueryClient(); + final spotify = ref.watch(spotifyProvider); + + return Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: colorScheme.surfaceVariant.withOpacity(0.3), + borderRadius: BorderRadius.circular(15), + ), + constraints: const BoxConstraints( + minWidth: 300, + ), + height: 80, + child: Row( + children: [ + CircleAvatar( + backgroundImage: UniversalImage.imageProvider( + friend.user.imageUrl, + ), + ), + const Gap(8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + friend.user.name, + style: textTheme.bodyLarge, + ), + RichText( + text: TextSpan( + style: textTheme.bodySmall, + children: [ + TextSpan( + text: friend.track.name, + recognizer: TapGestureRecognizer() + ..onTap = () { + context.push("/track/${friend.track.id}"); + }, + ), + const TextSpan(text: " • "), + const WidgetSpan( + child: Icon( + SpotubeIcons.artist, + size: 12, + ), + ), + TextSpan( + text: " ${friend.track.artist.name}", + recognizer: TapGestureRecognizer() + ..onTap = () { + context.push( + "/artist/${friend.track.artist.id}", + ); + }, + ), + const TextSpan(text: "\n"), + TextSpan( + text: friend.track.context.name, + recognizer: TapGestureRecognizer() + ..onTap = () async { + context.push( + "/${friend.track.context.path}", + extra: !friend.track.context.path + .startsWith("album") + ? null + : await queryClient.fetchQuery( + "album/${friend.track.album.id}", + () => spotify.albums.get( + friend.track.album.id, + ), + ), + ); + }, + ), + const TextSpan(text: " • "), + const WidgetSpan( + child: Icon( + SpotubeIcons.album, + size: 12, + ), + ), + TextSpan( + text: " ${friend.track.album.name}", + recognizer: TapGestureRecognizer() + ..onTap = () async { + final album = + await queryClient.fetchQuery( + "album/${friend.track.album.id}", + () => spotify.albums.get( + friend.track.album.id, + ), + ); + if (context.mounted) { + context.push( + "/album/${friend.track.album.id}", + extra: album, + ); + } + }, + ), + ], + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/components/home/sections/genres.dart b/lib/components/home/sections/genres.dart new file mode 100644 index 00000000..41ba235c --- /dev/null +++ b/lib/components/home/sections/genres.dart @@ -0,0 +1,154 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/collections/gradients.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/services/queries/queries.dart'; + +class HomeGenresSection extends HookConsumerWidget { + const HomeGenresSection({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final ThemeData(:textTheme, :colorScheme) = Theme.of(context); + final mediaQuery = MediaQuery.of(context); + + final recommendationMarket = ref.watch( + userPreferencesProvider.select((s) => s.recommendationMarket), + ); + final categoriesQuery = + useQueries.category.listAll(ref, recommendationMarket); + + final categories = categoriesQuery.data + ?.where((c) => (c.icons?.length ?? 0) > 0) + .take(mediaQuery.mdAndDown ? 6 : 10) + .toList() ?? + []; + + return SliverMainAxisGroup( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + context.l10n.genres, + style: textTheme.headlineSmall, + ), + Directionality( + textDirection: TextDirection.rtl, + child: TextButton.icon( + onPressed: () { + context.push('/genres'); + }, + icon: const Icon(SpotubeIcons.angleRight), + label: Text( + "Browse All", + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.secondary, + ), + ), + ), + ), + ], + ), + ), + ), + const SliverGap(8), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: Skeletonizer.sliver( + enabled: categoriesQuery.isLoading, + child: SliverGrid.builder( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: mediaQuery.mdAndDown ? 200 : 250, + mainAxisExtent: 50, + crossAxisSpacing: 16, + mainAxisSpacing: 16, + ), + itemCount: categoriesQuery.isLoading + ? mediaQuery.mdAndDown + ? 6 + : 10 + : categories.length, + itemBuilder: (context, index) { + final category = + categories.elementAtOrNull(index) ?? FakeData.category; + + return HookBuilder(builder: (context) { + final (:gradient, :textColor) = useMemoized( + () { + final gradient = + gradients[Random().nextInt(gradients.length)]; + final text = gradient.colors + .take(2) + .any((c) => c.computeLuminance() > 0.5) + ? Colors.grey[900] + : Colors.white; + return ( + gradient: LinearGradient( + colors: gradient.colors + .map((c) => c.withOpacity(0.8)) + .toList(), + ), + textColor: text + ); + }, + [], + ); + + return InkWell( + onTap: () { + context.push('/genre/${category.id}', extra: category); + }, + borderRadius: BorderRadius.circular(8), + child: Ink( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + image: UniversalImage.imageProvider( + category.icons!.first.url!, + ), + fit: BoxFit.cover, + ), + ), + child: Ink( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: colorScheme.surfaceVariant, + gradient: categoriesQuery.isLoading ? null : gradient, + ), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + category.name!, + style: textTheme.titleMedium + ?.copyWith(color: textColor), + ), + ), + ), + ), + ); + }); + }, + ), + ), + ), + ], + ); + } +} diff --git a/lib/components/home/sections/made_for_user.dart b/lib/components/home/sections/made_for_user.dart new file mode 100644 index 00000000..a3f96899 --- /dev/null +++ b/lib/components/home/sections/made_for_user.dart @@ -0,0 +1,35 @@ +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; +import 'package:spotube/services/queries/queries.dart'; + +class HomeMadeForUserSection extends HookConsumerWidget { + const HomeMadeForUserSection({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final madeForUser = useQueries.views.get(ref, "made-for-x-hub"); + + return SliverList.builder( + itemCount: madeForUser.data?["content"]?["items"]?.length ?? 0, + itemBuilder: (context, index) { + final item = madeForUser.data?["content"]?["items"]?[index]; + final playlists = item["content"]?["items"] + ?.where((itemL2) => itemL2["type"] == "playlist") + .map((itemL2) => PlaylistSimple.fromJson(itemL2)) + .toList() + .cast() ?? + []; + if (playlists.isEmpty) return const SizedBox.shrink(); + return HorizontalPlaybuttonCardView( + items: playlists, + title: Text(item["name"] ?? ""), + hasNextPage: false, + isLoadingNextPage: false, + onFetchMore: () {}, + ); + }, + ); + } +} diff --git a/lib/components/home/sections/new_releases.dart b/lib/components/home/sections/new_releases.dart new file mode 100644 index 00000000..0f4a046a --- /dev/null +++ b/lib/components/home/sections/new_releases.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart' hide Page; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/authentication_provider.dart'; +import 'package:spotube/services/queries/queries.dart'; +import 'package:spotube/utils/type_conversion_utils.dart'; + +class HomeNewReleasesSection extends HookConsumerWidget { + const HomeNewReleasesSection({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final auth = ref.watch(AuthenticationNotifier.provider); + + final newReleases = useQueries.album.newReleases(ref); + final userArtistsQuery = useQueries.artist.followedByMeAll(ref); + final userArtists = + userArtistsQuery.data?.map((s) => s.id!).toList() ?? const []; + + final albums = useMemoized( + () { + final allReleases = newReleases.pages + .whereType>() + .expand((page) => page.items ?? const []) + .map((album) => TypeConversionUtils.simpleAlbum_X_Album(album)); + + final userArtistReleases = allReleases.where((album) { + return album.artists + ?.any((artist) => userArtists.contains(artist.id!)) == + true; + }).toList(); + + if (userArtistReleases.isEmpty) return allReleases.toList(); + return userArtistReleases; + }, + [newReleases.pages], + ); + + final hasNewReleases = newReleases.hasPageData && + userArtistsQuery.hasData && + !newReleases.isLoadingNextPage; + + if (auth == null || !hasNewReleases) return const SizedBox.shrink(); + + return HorizontalPlaybuttonCardView( + items: albums, + title: Text(context.l10n.new_releases), + isLoadingNextPage: newReleases.isLoadingNextPage, + hasNextPage: newReleases.hasNextPage, + onFetchMore: newReleases.fetchNext, + ); + } +} diff --git a/lib/components/library/user_albums.dart b/lib/components/library/user_albums.dart index ccde43f9..200d1c59 100644 --- a/lib/components/library/user_albums.dart +++ b/lib/components/library/user_albums.dart @@ -3,12 +3,14 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:collection/collection.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/album/album_card.dart'; +import 'package:spotube/components/shared/fallbacks/not_found.dart'; import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'; import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/shared/waypoint.dart'; import 'package:spotube/extensions/context.dart'; @@ -82,30 +84,39 @@ class UserAlbums extends HookConsumerWidget { child: SingleChildScrollView( padding: const EdgeInsets.all(8.0), controller: controller, - child: Wrap( - runSpacing: 20, - alignment: WrapAlignment.center, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - if (albums.isEmpty) - Container( - alignment: Alignment.topLeft, - padding: const EdgeInsets.all(16.0), - child: const ShimmerPlaybuttonCard(count: 4), - ), - for (final album in albums) - AlbumCard( - TypeConversionUtils.simpleAlbum_X_Album(album), - ), - if (albumsQuery.hasNextPage) - Waypoint( - controller: controller, - isGrid: true, - onTouchEdge: albumsQuery.fetchNext, - child: const ShimmerPlaybuttonCard(count: 1), - ) - ], + child: Skeletonizer( + enabled: albumsQuery.pages.isEmpty, + child: Center( + child: Wrap( + runSpacing: 20, + alignment: WrapAlignment.center, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (albumsQuery.pages.isEmpty) + ...List.generate( + 10, + (index) => AlbumCard(FakeData.album), + ) + else if (albums.isEmpty) + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [NotFound()], + ), + for (final album in albums) + AlbumCard( + TypeConversionUtils.simpleAlbum_X_Album(album), + ), + if (albums.isNotEmpty && albumsQuery.hasNextPage) + Waypoint( + controller: controller, + isGrid: true, + onTouchEdge: albumsQuery.fetchNext, + child: AlbumCard(FakeData.album), + ) + ], + ), + ), ), ), ), diff --git a/lib/components/library/user_artists.dart b/lib/components/library/user_artists.dart index 881451b0..36b8528e 100644 --- a/lib/components/library/user_artists.dart +++ b/lib/components/library/user_artists.dart @@ -3,10 +3,13 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:collection/collection.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/artist/artist_card.dart'; +import 'package:spotube/components/shared/fallbacks/not_found.dart'; import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication_provider.dart'; @@ -87,12 +90,29 @@ class UserArtists extends HookConsumerWidget { width: double.infinity, child: SafeArea( child: Center( - child: Wrap( - spacing: 15, - runSpacing: 5, - children: filteredArtists - .mapIndexed((index, artist) => ArtistCard(artist)) - .toList(), + child: Skeletonizer( + enabled: artistQuery.isLoading, + child: Wrap( + spacing: 15, + runSpacing: 5, + children: artistQuery.isLoading + ? List.generate( + 10, (index) => ArtistCard(FakeData.artist)) + : filteredArtists.isEmpty + ? [ + const Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + NotFound(), + ], + ) + ] + : filteredArtists + .mapIndexed((index, artist) => + ArtistCard(artist)) + .toList(), + ), ), ), ), diff --git a/lib/components/library/user_local_tracks.dart b/lib/components/library/user_local_tracks.dart index cc8b10cf..f4e782d9 100644 --- a/lib/components/library/user_local_tracks.dart +++ b/lib/components/library/user_local_tracks.dart @@ -11,12 +11,14 @@ import 'package:metadata_god/metadata_god.dart'; import 'package:mime/mime.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/expandable_search/expandable_search.dart'; +import 'package:spotube/components/shared/fallbacks/not_found.dart'; import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart'; import 'package:spotube/components/shared/sort_tracks_dropdown.dart'; import 'package:spotube/components/shared/track_tile/track_tile.dart'; import 'package:spotube/extensions/context.dart'; @@ -254,6 +256,15 @@ class UserLocalTracks extends HookConsumerWidget { .toList(); }, [searchController.text, sortedTracks]); + if (!trackSnapshot.isLoading && filteredTracks.isEmpty) { + return const Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [NotFound()], + ), + ); + } + return Expanded( child: RefreshIndicator( onRefresh: () async { @@ -261,32 +272,48 @@ class UserLocalTracks extends HookConsumerWidget { }, child: InterScrollbar( controller: controller, - child: ListView.builder( - controller: controller, - physics: const AlwaysScrollableScrollPhysics(), - itemCount: filteredTracks.length, - itemBuilder: (context, index) { - final track = filteredTracks[index]; - return TrackTile( - index: index, - track: track, - userPlaylist: false, - onTap: () async { - await playLocalTracks( - ref, - sortedTracks, - currentTrack: track, - ); - }, - ); - }, + child: Skeletonizer( + enabled: trackSnapshot.isLoading, + child: ListView.builder( + controller: controller, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: + trackSnapshot.isLoading ? 5 : filteredTracks.length, + itemBuilder: (context, index) { + if (trackSnapshot.isLoading) { + return TrackTile(track: FakeData.track, index: index); + } + + final track = filteredTracks[index]; + return TrackTile( + index: index, + track: track, + userPlaylist: false, + onTap: () async { + await playLocalTracks( + ref, + sortedTracks, + currentTrack: track, + ); + }, + ); + }, + ), ), ), ), ); }, - loading: () => - const Expanded(child: ShimmerTrackTileGroup(noSliver: true)), + loading: () => Expanded( + child: Skeletonizer( + enabled: true, + child: ListView.builder( + itemCount: 5, + itemBuilder: (context, index) => + TrackTile(track: FakeData.track, index: index), + ), + ), + ), error: (error, stackTrace) => Text(error.toString() + stackTrace.toString()), ) diff --git a/lib/components/library/user_playlists.dart b/lib/components/library/user_playlists.dart index f7736ca7..32e91ed6 100644 --- a/lib/components/library/user_playlists.dart +++ b/lib/components/library/user_playlists.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart' hide Image; -import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:collection/collection.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/playlist/playlist_create_dialog.dart'; import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'; import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/playlist/playlist_card.dart'; import 'package:spotube/components/shared/waypoint.dart'; @@ -37,21 +37,21 @@ class UserPlaylists extends HookConsumerWidget { ); final likedTracksPlaylist = useMemoized( - () => PlaylistSimple() - ..name = context.l10n.liked_tracks - ..description = context.l10n.liked_tracks_description - ..type = "playlist" - ..collaborative = false - ..public = false - ..id = "user-liked-tracks" - ..images = [ - Image() - ..height = 300 - ..width = 300 - ..url = - "https://t.scdn.co/images/3099b3803ad9496896c43f22fe9be8c4.png" - ], - [context.l10n]); + () => PlaylistSimple() + ..name = context.l10n.liked_tracks + ..description = context.l10n.liked_tracks_description + ..type = "playlist" + ..collaborative = false + ..public = false + ..id = "user-liked-tracks" + ..images = [ + Image() + ..height = 300 + ..width = 300 + ..url = "assets/liked-tracks.jpg" + ], + [context.l10n], + ); final playlists = useMemoized( () { @@ -123,7 +123,7 @@ class UserPlaylists extends HookConsumerWidget { ), SliverLayoutBuilder(builder: (context, constrains) { return SliverGrid.builder( - itemCount: playlists.length + 1, + itemCount: playlists.isEmpty ? 6 : playlists.length + 1, gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200, mainAxisExtent: constrains.smAndDown ? 225 : 250, @@ -131,7 +131,7 @@ class UserPlaylists extends HookConsumerWidget { mainAxisSpacing: 8, ), itemBuilder: (context, index) { - if (index == playlists.length) { + if (playlists.isNotEmpty && index == playlists.length) { if (!playlistsQuery.hasNextPage) { return const SizedBox.shrink(); } @@ -140,11 +140,17 @@ class UserPlaylists extends HookConsumerWidget { controller: controller, isGrid: true, onTouchEdge: playlistsQuery.fetchNext, - child: const ShimmerPlaybuttonCard(count: 1), + child: Skeletonizer( + enabled: true, + child: PlaylistCard(FakeData.playlistSimple), + ), ); } - return PlaylistCard(playlists[index]); + return PlaylistCard( + playlists.elementAtOrNull(index) ?? + FakeData.playlistSimple, + ); }, ); }) diff --git a/lib/components/player/player.dart b/lib/components/player/player.dart index 889b7c5c..33283c3e 100644 --- a/lib/components/player/player.dart +++ b/lib/components/player/player.dart @@ -28,9 +28,11 @@ import 'package:spotube/utils/type_conversion_utils.dart'; class PlayerView extends HookConsumerWidget { final PanelController panelController; + final ScrollController scrollController; const PlayerView({ Key? key, required this.panelController, + required this.scrollController, }) : super(key: key); @override @@ -72,10 +74,14 @@ class PlayerView extends HookConsumerWidget { useMemoized(() => GlobalKey(), []); useEffect(() { - WidgetsBinding.instance.renderView.automaticSystemUiAdjustment = false; + for (final renderView in WidgetsBinding.instance.renderViews) { + renderView.automaticSystemUiAdjustment = false; + } return () { - WidgetsBinding.instance.renderView.automaticSystemUiAdjustment = true; + for (final renderView in WidgetsBinding.instance.renderViews) { + renderView.automaticSystemUiAdjustment = true; + } }; }, [panelController.isPanelOpen]); @@ -88,10 +94,10 @@ class PlayerView extends HookConsumerWidget { final topPadding = MediaQueryData.fromView(View.of(context)).padding.top; - return WillPopScope( - onWillPop: () async { + return PopScope( + canPop: false, + onPopInvoked: (didPop) async { panelController.close(); - return false; }, child: IconTheme( data: theme.iconTheme.copyWith(color: bodyTextColor), @@ -119,40 +125,43 @@ class PlayerView extends HookConsumerWidget { preferredSize: Size.fromHeight( kToolbarHeight + topPadding, ), - child: Padding( - padding: EdgeInsets.only(top: topPadding), - child: PageWindowTitleBar( - backgroundColor: Colors.transparent, - foregroundColor: titleTextColor, - toolbarOpacity: 1, - leading: IconButton( - icon: const Icon(SpotubeIcons.angleDown, size: 18), - onPressed: panelController.close, + child: ForceDraggableWidget( + child: Padding( + padding: EdgeInsets.only(top: topPadding), + child: PageWindowTitleBar( + backgroundColor: Colors.transparent, + foregroundColor: titleTextColor, + toolbarOpacity: 1, + leading: IconButton( + icon: const Icon(SpotubeIcons.angleDown, size: 18), + onPressed: panelController.close, + ), + actions: [ + IconButton( + icon: const Icon(SpotubeIcons.info, size: 18), + tooltip: context.l10n.details, + style: IconButton.styleFrom( + foregroundColor: bodyTextColor), + onPressed: currentTrack == null + ? null + : () { + showDialog( + context: context, + builder: (context) { + return TrackDetailsDialog( + track: currentTrack, + ); + }); + }, + ) + ], ), - actions: [ - IconButton( - icon: const Icon(SpotubeIcons.info, size: 18), - tooltip: context.l10n.details, - style: - IconButton.styleFrom(foregroundColor: bodyTextColor), - onPressed: currentTrack == null - ? null - : () { - showDialog( - context: context, - builder: (context) { - return TrackDetailsDialog( - track: currentTrack, - ); - }); - }, - ) - ], ), ), ), extendBodyBehindAppBar: true, body: SingleChildScrollView( + controller: scrollController, child: Container( alignment: Alignment.center, width: double.infinity, @@ -163,27 +172,29 @@ class PlayerView extends HookConsumerWidget { padding: const EdgeInsets.all(8.0), child: Column( children: [ - Container( - margin: const EdgeInsets.all(8), - constraints: const BoxConstraints( - maxHeight: 300, maxWidth: 300), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 2, - blurRadius: 10, - offset: Offset(0, 0), + ForceDraggableWidget( + child: Container( + margin: const EdgeInsets.all(8), + constraints: const BoxConstraints( + maxHeight: 300, maxWidth: 300), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + spreadRadius: 2, + blurRadius: 10, + offset: Offset(0, 0), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: UniversalImage( + path: albumArt, + placeholder: Assets.albumPlaceholder.path, + fit: BoxFit.cover, ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: UniversalImage( - path: albumArt, - placeholder: Assets.albumPlaceholder.path, - fit: BoxFit.cover, ), ), ), diff --git a/lib/components/player/player_overlay.dart b/lib/components/player/player_overlay.dart index 4869a0fa..2d63811e 100644 --- a/lib/components/player/player_overlay.dart +++ b/lib/components/player/player_overlay.dart @@ -43,6 +43,7 @@ class PlayerOverlay extends HookConsumerWidget { final mediaQuery = MediaQuery.of(context); final panelController = useMemoized(() => PanelController(), []); + final scrollController = useScrollController(); useEffect(() { return () { @@ -174,6 +175,7 @@ class PlayerOverlay extends HookConsumerWidget { ), ), ), + scrollController: scrollController, panelBuilder: (position) { // this is the reason we're getting an update final navigationHeight = ref.watch(navigationPanelHeight); @@ -188,8 +190,11 @@ class PlayerOverlay extends HookConsumerWidget { decoration: navigationHeight == 0 ? const BoxDecoration(borderRadius: BorderRadius.zero) : const BoxDecoration(borderRadius: radius), - child: HorizontalScrollableWidget( - child: PlayerView(panelController: panelController), + child: IgnoreDraggableWidget( + child: PlayerView( + panelController: panelController, + scrollController: scrollController, + ), ), ), ); diff --git a/lib/components/player/player_track_details.dart b/lib/components/player/player_track_details.dart index d6f275fa..66cb9ef5 100644 --- a/lib/components/player/player_track_details.dart +++ b/lib/components/player/player_track_details.dart @@ -4,6 +4,7 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/components/shared/links/link_text.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -44,10 +45,12 @@ class PlayerTrackDetails extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), - Text( + LinkText( playback.activeTrack?.name ?? "", + "/track/${playback.activeTrack?.id}", + push: true, overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyMedium?.copyWith( + style: theme.textTheme.bodyMedium!.copyWith( color: color, ), ), @@ -66,8 +69,10 @@ class PlayerTrackDetails extends HookConsumerWidget { flex: 1, child: Column( children: [ - Text( + LinkText( playback.activeTrack?.name ?? "", + "/track/${playback.activeTrack?.id}", + push: true, overflow: TextOverflow.ellipsis, style: TextStyle(fontWeight: FontWeight.bold, color: color), ), diff --git a/lib/components/player/sibling_tracks_sheet.dart b/lib/components/player/sibling_tracks_sheet.dart index cf1429b9..181c363a 100644 --- a/lib/components/player/sibling_tracks_sheet.dart +++ b/lib/components/player/sibling_tracks_sheet.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart' hide Offset; +import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; @@ -19,10 +20,28 @@ import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; import 'package:spotube/services/sourced_track/models/source_info.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/jiosaavn.dart'; +import 'package:spotube/services/sourced_track/sources/piped.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/utils/service_utils.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; +final sourceInfoToIconMap = { + YoutubeSourceInfo: const Icon(SpotubeIcons.youtube, color: Color(0xFFFF0000)), + JioSaavnSourceInfo: Container( + height: 30, + width: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(90), + image: DecorationImage( + image: Assets.jiosaavn.provider(), + fit: BoxFit.cover, + ), + ), + ), + PipedSourceInfo: const Icon(SpotubeIcons.piped), +}; + class SiblingTracksSheet extends HookConsumerWidget { final bool floating; const SiblingTracksSheet({ @@ -64,17 +83,34 @@ class SiblingTracksSheet extends HookConsumerWidget { return []; } - final results = await youtubeClient.search.search(searchTerm.trim()); + final resultsYt = await youtubeClient.search.search(searchTerm.trim()); + final resultsJioSaavn = + await jiosaavnClient.search.songs(searchTerm.trim()); - return await Future.wait( - results.map(YoutubeVideoInfo.fromVideo).mapIndexed((i, video) async { + final searchResults = await Future.wait([ + ...resultsJioSaavn.results.mapIndexed((i, song) async { + final siblingType = JioSaavnSourcedTrack.toSiblingType(song); + return siblingType.info; + }), + ...resultsYt + .map(YoutubeVideoInfo.fromVideo) + .mapIndexed((i, video) async { final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video); return siblingType.info; }), - ); + ]); + final activeSourceInfo = + (playlist.activeTrack! as SourcedTrack).sourceInfo; + return searchResults + ..removeWhere((element) => element.id == activeSourceInfo.id) + ..insert( + 0, + activeSourceInfo, + ); }, [ searchTerm, searchMode.value, + playlist.activeTrack, ]); final siblings = useMemoized( @@ -104,6 +140,7 @@ class SiblingTracksSheet extends HookConsumerWidget { final itemBuilder = useCallback( (SourceInfo sourceInfo) { + final icon = sourceInfoToIconMap[sourceInfo.runtimeType]; return ListTile( title: Text(sourceInfo.title), leading: Padding( @@ -118,7 +155,12 @@ class SiblingTracksSheet extends HookConsumerWidget { borderRadius: BorderRadius.circular(5), ), trailing: Text(sourceInfo.duration.toHumanReadableString()), - subtitle: Text(sourceInfo.artist), + subtitle: Row( + children: [ + if (icon != null) icon, + Text(" • ${sourceInfo.artist}"), + ], + ), enabled: playlist.isFetching != true, selected: playlist.isFetching != true && sourceInfo.id == @@ -137,7 +179,7 @@ class SiblingTracksSheet extends HookConsumerWidget { [playlist.isFetching, playlist.activeTrack, siblings], ); - var mediaQuery = MediaQuery.of(context); + final mediaQuery = MediaQuery.of(context); return SafeArea( child: ClipRRect( borderRadius: borderRadius, diff --git a/lib/components/root/sidebar.dart b/lib/components/root/sidebar.dart index ac5233ed..9b3fd3ed 100644 --- a/lib/components/root/sidebar.dart +++ b/lib/components/root/sidebar.dart @@ -159,7 +159,7 @@ class Sidebar extends HookConsumerWidget { margin: EdgeInsets.only( bottom: 10, left: 0, - top: kIsMacOS ? 35 : 5, + top: kIsMacOS ? 0 : 5, ), padding: const EdgeInsets.symmetric(horizontal: 6), decoration: BoxDecoration( diff --git a/lib/components/shared/dialogs/playlist_add_track_dialog.dart b/lib/components/shared/dialogs/playlist_add_track_dialog.dart index aadcd9d6..1c8e5aaa 100644 --- a/lib/components/shared/dialogs/playlist_add_track_dialog.dart +++ b/lib/components/shared/dialogs/playlist_add_track_dialog.dart @@ -11,9 +11,12 @@ import 'package:spotube/services/queries/queries.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class PlaylistAddTrackDialog extends HookConsumerWidget { + /// The id of the playlist this dialog was opened from + final String? openFromPlaylist; final List tracks; const PlaylistAddTrackDialog({ required this.tracks, + required this.openFromPlaylist, Key? key, }) : super(key: key); @@ -30,11 +33,12 @@ class PlaylistAddTrackDialog extends HookConsumerWidget { ?.where( (playlist) => playlist.owner?.id != null && - playlist.owner!.id == me.data?.id, + playlist.owner!.id == me.data?.id && + playlist.id != openFromPlaylist, ) .toList() ?? [], - [userPlaylists.data, me.data?.id], + [userPlaylists.data, me.data?.id, openFromPlaylist], ); final playlistsCheck = useState({}); diff --git a/lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart b/lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart index dca77233..dc9d30da 100644 --- a/lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart +++ b/lib/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart @@ -2,11 +2,12 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/components/album/album_card.dart'; import 'package:spotube/components/artist/artist_card.dart'; import 'package:spotube/components/playlist/playlist_card.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'; import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @@ -54,37 +55,49 @@ class HorizontalPlaybuttonCardView extends HookWidget { ), SizedBox( height: height, - child: ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith( - dragDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.mouse, - }, - ), - child: InfiniteList( - scrollController: scrollController, - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(vertical: 8.0), - itemCount: items.length, - onFetchData: onFetchMore, - loadingBuilder: (context) => const ShimmerPlaybuttonCard(), - emptyBuilder: (context) => - const ShimmerPlaybuttonCard(count: 5), - isLoading: isLoadingNextPage, - hasReachedMax: !hasNextPage, - itemBuilder: (context, index) { - final item = items[index]; + child: NotificationListener( + // disable multiple scrollbar to use this + onNotification: (notification) => true, + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith( + dragDevices: PointerDeviceKind.values.toSet(), + ), + child: items.isEmpty + ? ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, index) { + return AlbumCard(FakeData.albumSimple); + }, + ) + : InfiniteList( + scrollController: scrollController, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(vertical: 8.0), + itemCount: items.length, + onFetchData: onFetchMore, + loadingBuilder: (context) => Skeletonizer( + enabled: true, + child: AlbumCard(FakeData.albumSimple), + ), + isLoading: isLoadingNextPage, + hasReachedMax: !hasNextPage, + itemBuilder: (context, index) { + final item = items[index]; - return switch (item.runtimeType) { - PlaylistSimple => PlaylistCard(item as PlaylistSimple), - Album => AlbumCard(item as Album), - Artist => Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: ArtistCard(item as Artist), - ), - _ => const SizedBox.shrink(), - }; - }), + return switch (item.runtimeType) { + PlaylistSimple => + PlaylistCard(item as PlaylistSimple), + Album => AlbumCard(item as Album), + Artist => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0), + child: ArtistCard(item as Artist), + ), + _ => const SizedBox.shrink(), + }; + }), + ), ), ), ], diff --git a/lib/components/shared/links/link_text.dart b/lib/components/shared/links/link_text.dart index 217b247d..d7b00b72 100644 --- a/lib/components/shared/links/link_text.dart +++ b/lib/components/shared/links/link_text.dart @@ -8,6 +8,7 @@ class LinkText extends StatelessWidget { final TextAlign? textAlign; final TextOverflow? overflow; final String route; + final int? maxLines; final T? extra; final bool push; @@ -19,6 +20,7 @@ class LinkText extends StatelessWidget { this.extra, this.overflow, this.style = const TextStyle(), + this.maxLines, this.push = false, }) : super(key: key); @@ -37,6 +39,7 @@ class LinkText extends StatelessWidget { overflow: overflow, style: style, textAlign: textAlign, + maxLines: maxLines, ); } } diff --git a/lib/components/shared/page_window_title_bar.dart b/lib/components/shared/page_window_title_bar.dart index 43435f7d..4f522e0c 100644 --- a/lib/components/shared/page_window_title_bar.dart +++ b/lib/components/shared/page_window_title_bar.dart @@ -2,24 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; import 'package:spotube/utils/platform.dart'; import 'package:titlebar_buttons/titlebar_buttons.dart'; import 'dart:math'; import 'package:flutter/foundation.dart' show kIsWeb; -import 'dart:io' show Platform, exit; +import 'dart:io' show Platform; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; -import 'package:local_notifier/local_notifier.dart'; - -final closeNotification = DesktopTools.createNotification( - title: 'Spotube', - message: 'Running in background. Minimized to System Tray', - actions: [ - LocalNotificationAction(text: 'Close The App'), - ], -)?..onClickAction = (value) { - exit(0); - }; class PageWindowTitleBar extends StatefulHookConsumerWidget implements PreferredSizeWidget { @@ -113,12 +101,7 @@ class WindowTitleBarButtons extends HookConsumerWidget { const type = ThemeType.auto; Future onClose() async { - if (preferences.closeBehavior == CloseBehavior.close) { - exit(0); - } else { - await DesktopTools.window.hide(); - await closeNotification?.show(); - } + await DesktopTools.window.close(); } useEffect(() { diff --git a/lib/components/shared/playbutton_card.dart b/lib/components/shared/playbutton_card.dart index d9c48640..a8a75d30 100644 --- a/lib/components/shared/playbutton_card.dart +++ b/lib/components/shared/playbutton_card.dart @@ -1,11 +1,13 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; +import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/hover_builder.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; import 'package:spotube/hooks/utils/use_brightness_value.dart'; @@ -48,6 +50,7 @@ class PlaybuttonCard extends HookWidget { Widget build(BuildContext context) { final textsKey = useMemoized(() => GlobalKey(), []); final theme = Theme.of(context); + final mediaQuery = MediaQuery.of(context); final radius = BorderRadius.circular(15); final double size = useBreakpointValue( @@ -58,8 +61,8 @@ class PlaybuttonCard extends HookWidget { ); final end = useBreakpointValue( - xs: 10, - sm: 10, + xs: 7, + sm: 7, others: 15, ); @@ -84,22 +87,28 @@ class PlaybuttonCard extends HookWidget { splashFactory: theme.splashFactory, child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Stack( clipBehavior: Clip.none, children: [ - Padding( + Container( + margin: const EdgeInsets.fromLTRB(8, 8, 8, 0), padding: const EdgeInsets.only( left: 8, right: 8, top: 8, ), - child: ClipRRect( + height: mediaQuery.smAndDown + ? 120 + : mediaQuery.mdAndDown + ? 130 + : 150, + decoration: BoxDecoration( borderRadius: radius, - child: UniversalImage( - path: imageUrl, - placeholder: Assets.albumPlaceholder.path, + image: DecorationImage( + image: UniversalImage.imageProvider(imageUrl), + fit: BoxFit.cover, ), ), ), @@ -146,31 +155,35 @@ class PlaybuttonCard extends HookWidget { mainAxisSize: MainAxisSize.min, children: [ if (!isPlaying) - IconButton( - style: IconButton.styleFrom( - backgroundColor: theme.colorScheme.background, - foregroundColor: theme.colorScheme.primary, - minimumSize: const Size.square(10), + Skeleton.keep( + child: IconButton( + style: IconButton.styleFrom( + backgroundColor: theme.colorScheme.background, + foregroundColor: theme.colorScheme.primary, + minimumSize: const Size.square(10), + ), + icon: const Icon(SpotubeIcons.queueAdd), + onPressed: isLoading ? null : onAddToQueuePressed, ), - icon: const Icon(SpotubeIcons.queueAdd), - onPressed: isLoading ? null : onAddToQueuePressed, ), - const SizedBox(height: 5), + const Gap(5), IconButton( style: IconButton.styleFrom( backgroundColor: theme.colorScheme.primaryContainer, foregroundColor: theme.colorScheme.primary, minimumSize: const Size.square(10), ), - icon: isLoading - ? SizedBox.fromSize( - size: const Size.square(15), - child: const CircularProgressIndicator( - strokeWidth: 2), - ) - : isPlaying - ? const Icon(SpotubeIcons.pause) - : const Icon(SpotubeIcons.play), + icon: Skeleton.keep( + child: isLoading + ? SizedBox.fromSize( + size: const Size.square(15), + child: const CircularProgressIndicator( + strokeWidth: 2), + ) + : isPlaying + ? const Icon(SpotubeIcons.pause) + : const Icon(SpotubeIcons.play), + ), onPressed: isLoading ? null : onPlaybuttonPressed, ), ], diff --git a/lib/components/shared/shimmers/shimmer_artist_profile.dart b/lib/components/shared/shimmers/shimmer_artist_profile.dart deleted file mode 100644 index 75e50cd0..00000000 --- a/lib/components/shared/shimmers/shimmer_artist_profile.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:skeleton_text/skeleton_text.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart'; -import 'package:spotube/extensions/theme.dart'; -import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; - -class ShimmerArtistProfile extends HookWidget { - const ShimmerArtistProfile({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - final shimmerTheme = ShimmerColorTheme( - shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200], - shimmerColor: isDark ? Colors.grey[800] : Colors.grey[300], - ); - final shimmerColor = shimmerTheme.shimmerColor ?? Colors.white; - final shimmerBackgroundColor = - shimmerTheme.shimmerBackgroundColor ?? Colors.grey; - - final avatarWidth = useBreakpointValue( - xs: MediaQuery.of(context).size.width * 0.80, - sm: MediaQuery.of(context).size.width * 0.80, - md: MediaQuery.of(context).size.width * 0.50, - lg: MediaQuery.of(context).size.width * 0.30, - xl: MediaQuery.of(context).size.width * 0.30, - xxl: MediaQuery.of(context).size.width * 0.30, - ) ?? - 0; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(20), - child: SkeletonAnimation( - shimmerColor: shimmerColor, - borderRadius: BorderRadius.circular(avatarWidth), - shimmerDuration: 1000, - child: Container( - width: avatarWidth, - height: avatarWidth, - decoration: BoxDecoration( - color: shimmerBackgroundColor, - borderRadius: BorderRadius.circular(avatarWidth), - ), - ), - ), - ), - const SizedBox(width: 10), - const Flexible(child: ShimmerTrackTileGroup(noSliver: true)), - ], - ); - } -} diff --git a/lib/components/shared/shimmers/shimmer_categories.dart b/lib/components/shared/shimmers/shimmer_categories.dart deleted file mode 100644 index 9bc773da..00000000 --- a/lib/components/shared/shimmers/shimmer_categories.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart'; -import 'package:spotube/extensions/theme.dart'; -import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; - -class ShimmerCategories extends HookWidget { - const ShimmerCategories({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - final shimmerTheme = ShimmerColorTheme( - shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200], - ); - final shimmerBackgroundColor = - shimmerTheme.shimmerBackgroundColor ?? Colors.grey; - - final shimmerCount = useBreakpointValue( - xs: 2, - sm: 2, - md: 3, - lg: 3, - xl: 6, - xxl: 8, - ); - - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.only(left: 15), - height: 10, - width: 100, - decoration: BoxDecoration( - color: shimmerBackgroundColor, - borderRadius: BorderRadius.circular(10), - ), - ), - const SizedBox(height: 10), - Align( - alignment: Alignment.topLeft, - child: ShimmerPlaybuttonCard(count: shimmerCount), - ), - ], - ), - ); - } -} diff --git a/lib/components/shared/shimmers/shimmer_lyrics.dart b/lib/components/shared/shimmers/shimmer_lyrics.dart index b0fba340..b225c008 100644 --- a/lib/components/shared/shimmers/shimmer_lyrics.dart +++ b/lib/components/shared/shimmers/shimmer_lyrics.dart @@ -1,69 +1,38 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; -import 'package:skeleton_text/skeleton_text.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/theme.dart'; - -const widths = [20, 56, 89, 60, 25, 69]; +import 'package:skeletonizer/skeletonizer.dart'; class ShimmerLyrics extends HookWidget { const ShimmerLyrics({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - final shimmerTheme = ShimmerColorTheme( - shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200], - shimmerColor: isDark ? Colors.grey[800] : Colors.grey[300], - ); - final shimmerColor = shimmerTheme.shimmerColor ?? Colors.white; - final shimmerBackgroundColor = - shimmerTheme.shimmerBackgroundColor ?? Colors.grey; - - final mediaQuery = MediaQuery.of(context); - - return ListView.builder( - itemCount: 20, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - final widthsCp = [...widths]; - if (mediaQuery.isMd) { - widthsCp.removeLast(); - } - if (mediaQuery.smAndDown) { - widthsCp.removeLast(); - widthsCp.removeLast(); - } - widthsCp.shuffle(); - return Container( - margin: const EdgeInsets.symmetric(vertical: 5), - child: Row( + return Skeletonizer( + enabled: true, + child: ListView.builder( + itemCount: 30, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + final texts = [ + "Lorem ipsum", + "consectetur.", + "Sed", + "Sed non risus", + ]..shuffle(); + return Row( mainAxisAlignment: MainAxisAlignment.center, - children: widthsCp.map( - (width) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: SkeletonAnimation( - shimmerColor: shimmerColor, - shimmerDuration: 1000, - child: Container( - height: 10, - width: width.toDouble(), - decoration: BoxDecoration( - color: shimmerBackgroundColor, - borderRadius: BorderRadius.circular(10), - ), - margin: const EdgeInsets.only(top: 10), - ), - ), - ); - }, - ).toList(), - ), - ); - }, + children: [ + for (final text in texts) ...[ + Text(text), + if (text != texts.last) const Gap(10), + ], + ], + ); + }, + ), ); } } diff --git a/lib/components/shared/shimmers/shimmer_playbutton_card.dart b/lib/components/shared/shimmers/shimmer_playbutton_card.dart deleted file mode 100644 index 2259c9b0..00000000 --- a/lib/components/shared/shimmers/shimmer_playbutton_card.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:spotube/hooks/utils/use_breakpoint_value.dart'; - -class ShimmerPlaybuttonCardPainter extends CustomPainter { - final Color background; - final Color foreground; - ShimmerPlaybuttonCardPainter({ - required this.background, - required this.foreground, - }); - - @override - void paint(Canvas canvas, Size size) { - const radius = Radius.circular(15); - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(0, 0, size.width, size.height), - radius, - ), - Paint()..color = background, - ); - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(8, 8, size.width - 16, size.height - 90), - radius, - ), - Paint()..color = foreground, - ); - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(12, size.height - 67, size.width / 2, 10), - radius, - ), - Paint()..color = foreground, - ); - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(12, size.height - 45, size.width - 24, 8), - radius, - ), - Paint()..color = foreground, - ); - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(12, size.height - 30, size.width * .4, 8), - radius, - ), - Paint()..color = foreground, - ); - - canvas.drawCircle( - Offset(size.width * .85, size.height * .50), - 17, - Paint()..color = background, - ); - - canvas.drawCircle( - Offset(size.width * .85, size.height * .67), - 17, - Paint()..color = background, - ); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; - } -} - -class ShimmerPlaybuttonCard extends HookWidget { - final int count; - - const ShimmerPlaybuttonCard({ - Key? key, - this.count = 1, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final Size size = useBreakpointValue( - xs: const Size(130, 200), - sm: const Size(130, 200), - md: const Size(150, 220), - others: const Size(170, 240), - ); - - final isDark = theme.brightness == Brightness.dark; - final bgColor = theme.colorScheme.surfaceVariant.withOpacity(.2); - final fgColor = Color.lerp( - theme.colorScheme.surfaceVariant, - isDark ? Colors.black : Colors.white, - .4, - ); - - return Wrap( - spacing: 20, - runSpacing: 20, - children: [ - for (var i = 0; i < count; i++) ...[ - CustomPaint( - size: size, - painter: ShimmerPlaybuttonCardPainter( - background: bgColor, - foreground: fgColor!, - ), - ), - ] - ], - ); - } -} diff --git a/lib/components/shared/shimmers/shimmer_track_tile.dart b/lib/components/shared/shimmers/shimmer_track_tile.dart deleted file mode 100644 index dcb634ed..00000000 --- a/lib/components/shared/shimmers/shimmer_track_tile.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:spotube/extensions/theme.dart'; - -class ShimmerTrackTilePainter extends CustomPainter { - final Color background; - final Color foreground; - ShimmerTrackTilePainter({ - required this.background, - required this.foreground, - }); - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = background - ..style = PaintingStyle.fill; - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(0, 0, size.width, size.height), - const Radius.circular(5), - ), - paint, - ); - - canvas.drawRRect( - RRect.fromRectAndRadius( - Rect.fromLTWH(0, 0, size.height, size.height), - const Radius.circular(5), - ), - Paint()..color = foreground, - ); - - canvas.drawRRect( - RRect.fromRectAndRadius( - const Rect.fromLTWH(70, 10, 100, 10), - const Radius.circular(5), - ), - Paint()..color = foreground, - ); - - // draw Icons.play - const icon = Icons.play_arrow_outlined; - TextPainter textPainter = TextPainter(textDirection: TextDirection.rtl); - textPainter.text = TextSpan( - text: String.fromCharCode(icon.codePoint), - style: TextStyle( - fontSize: 40.0, - fontFamily: icon.fontFamily, - color: background, - ), - ); - textPainter.layout(); - textPainter.paint(canvas, const Offset(10, 10)); - - canvas.drawRRect( - RRect.fromRectAndRadius( - const Rect.fromLTWH(70, 30, 170, 7), - const Radius.circular(5), - ), - Paint()..color = foreground, - ); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - return false; - } -} - -class ShimmerTrackTile extends StatelessWidget { - const ShimmerTrackTile({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - final shimmerTheme = ShimmerColorTheme( - shimmerBackgroundColor: isDark ? Colors.grey[700] : Colors.grey[200], - shimmerColor: isDark ? Colors.grey[800] : Colors.grey[300], - ); - - return Padding( - padding: const EdgeInsets.only(bottom: 8.0, left: 8, right: 8), - child: CustomPaint( - size: const Size(double.infinity, 60), - painter: ShimmerTrackTilePainter( - background: shimmerTheme.shimmerBackgroundColor ?? - theme.scaffoldBackgroundColor, - foreground: shimmerTheme.shimmerColor ?? theme.cardColor, - ), - ), - ); - } -} - -class ShimmerTrackTileGroup extends StatelessWidget { - final bool noSliver; - final int count; - const ShimmerTrackTileGroup({ - super.key, - this.noSliver = false, - this.count = 5, - }); - - @override - Widget build(BuildContext context) { - if (noSliver) { - return ListView.builder( - itemCount: 5, - itemBuilder: (context, index) => const ShimmerTrackTile(), - ); - } - - return SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) => const ShimmerTrackTile(), - childCount: count, - ), - ); - } -} diff --git a/lib/components/shared/track_tile/track_options.dart b/lib/components/shared/track_tile/track_options.dart index b0633d34..724bc029 100644 --- a/lib/components/shared/track_tile/track_options.dart +++ b/lib/components/shared/track_tile/track_options.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; @@ -12,6 +13,7 @@ import 'package:spotube/components/shared/dialogs/playlist_add_track_dialog.dart import 'package:spotube/components/shared/dialogs/track_details_dialog.dart'; import 'package:spotube/components/shared/heart_button.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/authentication_provider.dart'; @@ -22,6 +24,7 @@ import 'package:spotube/services/mutations/mutations.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; enum TrackOptionValue { + album, share, addToPlaylist, addToQueue, @@ -40,12 +43,14 @@ class TrackOptions extends HookConsumerWidget { final bool userPlaylist; final String? playlistId; final ObjectRef?>? showMenuCbRef; + final Widget? icon; const TrackOptions({ Key? key, required this.track, this.showMenuCbRef, this.userPlaylist = false, this.playlistId, + this.icon, }) : super(key: key); void actionShare(BuildContext context, Track track) { @@ -64,20 +69,27 @@ class TrackOptions extends HookConsumerWidget { }); } - void actionAddToPlaylist(BuildContext context, Track track) { + void actionAddToPlaylist( + BuildContext context, + Track track, + ) { showDialog( context: context, builder: (context) => PlaylistAddTrackDialog( tracks: [track], + openFromPlaylist: playlistId, ), ); } @override Widget build(BuildContext context, ref) { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final mediaQuery = MediaQuery.of(context); + final router = GoRouter.of(context); + final playlist = ref.watch(ProxyPlaylistNotifier.provider); final playback = ref.watch(ProxyPlaylistNotifier.notifier); - final scaffoldMessenger = ScaffoldMessenger.of(context); final auth = ref.watch(AuthenticationNotifier.provider); ref.watch(downloadManagerProvider); final downloadManager = ref.watch(downloadManagerProvider.notifier); @@ -118,6 +130,12 @@ class TrackOptions extends HookConsumerWidget { final adaptivePopSheetList = AdaptivePopSheetList( onSelected: (value) async { switch (value) { + case TrackOptionValue.album: + await router.push( + '/album/${track.album!.id}', + extra: track.album!, + ); + break; case TrackOptionValue.delete: await File((track as LocalTrack).path).delete(); ref.refresh(localTracksProvider); @@ -191,7 +209,7 @@ class TrackOptions extends HookConsumerWidget { break; } }, - icon: const Icon(SpotubeIcons.moreHorizontal), + icon: icon ?? const Icon(SpotubeIcons.moreHorizontal), headings: [ ListTile( dense: true, @@ -229,6 +247,13 @@ class TrackOptions extends HookConsumerWidget { ) ], _ => [ + if (mediaQuery.smAndDown) + PopSheetEntry( + value: TrackOptionValue.album, + leading: const Icon(SpotubeIcons.album), + title: Text(context.l10n.go_to_album), + subtitle: Text(track.album!.name!), + ), if (!playlist.containsTrack(track)) ...[ PopSheetEntry( value: TrackOptionValue.addToQueue, diff --git a/lib/components/shared/track_tile/track_tile.dart b/lib/components/shared/track_tile/track_tile.dart index 6d4e236a..c3b03f3c 100644 --- a/lib/components/shared/track_tile/track_tile.dart +++ b/lib/components/shared/track_tile/track_tile.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/hover_builder.dart'; @@ -158,26 +159,28 @@ class TrackTile extends HookConsumerWidget { child: IconTheme( data: theme.iconTheme .copyWith(size: 26, color: Colors.white), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: (isPlaying && playlist.isFetching) || - isLoading.value - ? const SizedBox( - width: 26, - height: 26, - child: CircularProgressIndicator( - strokeWidth: 1.5, - color: Colors.white, - ), - ) - : isPlaying - ? Icon( - SpotubeIcons.pause, - color: theme.colorScheme.primary, - ) - : !isHovering - ? const SizedBox.shrink() - : const Icon(SpotubeIcons.play), + child: Skeleton.ignore( + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: (isPlaying && playlist.isFetching) || + isLoading.value + ? const SizedBox( + width: 26, + height: 26, + child: CircularProgressIndicator( + strokeWidth: 1.5, + color: Colors.white, + ), + ) + : isPlaying + ? Icon( + SpotubeIcons.pause, + color: theme.colorScheme.primary, + ) + : !isHovering + ? const SizedBox.shrink() + : const Icon(SpotubeIcons.play), + ), ), ), ), @@ -190,8 +193,10 @@ class TrackTile extends HookConsumerWidget { children: [ Expanded( flex: 6, - child: Text( + child: LinkText( track.name!, + "/track/${track.id}", + push: true, maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/lib/components/shared/tracks_view/sections/body/track_view_body.dart b/lib/components/shared/tracks_view/sections/body/track_view_body.dart index 486e4405..20caf4f1 100644 --- a/lib/components/shared/tracks_view/sections/body/track_view_body.dart +++ b/lib/components/shared/tracks_view/sections/body/track_view_body.dart @@ -4,9 +4,11 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/components/shared/expandable_search/expandable_search.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart'; +import 'package:spotube/components/shared/fallbacks/not_found.dart'; import 'package:spotube/components/shared/track_tile/track_tile.dart'; import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body_headers.dart'; import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_playlist.dart'; @@ -24,7 +26,6 @@ class TrackViewBodySection extends HookConsumerWidget { final playlist = ref.watch(ProxyPlaylistNotifier.provider); final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); final props = InheritedTrackView.of(context); - final trackViewState = ref.watch(trackViewProvider(props.tracks)); final searchController = useTextEditingController(); @@ -35,12 +36,17 @@ class TrackViewBodySection extends HookConsumerWidget { final isFiltering = useState(false); + final uniqTracks = useMemoized(() { + final trackIds = props.tracks.map((e) => e.id).toSet(); + return props.tracks.where((e) => trackIds.remove(e.id)).toList(); + }, [props.tracks]); + final tracks = useMemoized(() { List filteredTracks; if (searchQuery.isEmpty) { - filteredTracks = props.tracks; + filteredTracks = uniqTracks; } else { - filteredTracks = props.tracks + filteredTracks = uniqTracks .map((e) => (weightedRatio(e.name!, searchQuery), e)) .sorted((a, b) => b.$1.compareTo(a.$1)) .where((e) => e.$1 > 50) @@ -48,7 +54,7 @@ class TrackViewBodySection extends HookConsumerWidget { .toList(); } return ServiceUtils.sortTracks(filteredTracks, trackViewState.sortBy); - }, [trackViewState.sortBy, searchQuery, props.tracks]); + }, [trackViewState.sortBy, searchQuery, uniqTracks]); final isUserPlaylist = useIsUserPlaylist(ref, props.collectionId); @@ -80,7 +86,22 @@ class TrackViewBodySection extends HookConsumerWidget { onFetchData: props.pagination.onFetchMore, isLoading: props.pagination.isLoading, hasReachedMax: !props.pagination.hasNextPage, - loadingBuilder: (context) => const ShimmerTrackTile(), + loadingBuilder: (context) => Skeletonizer( + enabled: true, + child: TrackTile( + track: FakeData.track, + index: 0, + ), + ), + emptyBuilder: (context) => Skeletonizer( + enabled: true, + child: Column( + children: List.generate( + 10, + (index) => TrackTile(track: FakeData.track, index: index), + ), + ), + ), itemBuilder: (context, index) { final track = tracks[index]; return TrackTile( @@ -106,8 +127,9 @@ class TrackViewBodySection extends HookConsumerWidget { if (isActive || playlist.tracks.contains(track)) { await playlistNotifier.jumpToTrack(track); } else { + final tracks = await props.pagination.onFetchAll(); await playlistNotifier.load( - props.tracks, + tracks, initialIndex: index, autoPlay: true, ); diff --git a/lib/components/shared/tracks_view/sections/body/track_view_body_headers.dart b/lib/components/shared/tracks_view/sections/body/track_view_body_headers.dart index 57d8b296..7e4522a0 100644 --- a/lib/components/shared/tracks_view/sections/body/track_view_body_headers.dart +++ b/lib/components/shared/tracks_view/sections/body/track_view_body_headers.dart @@ -38,20 +38,16 @@ class TrackViewBodyHeaders extends HookConsumerWidget { ), ); }, - child: trackViewState.isSelecting - ? Checkbox( - value: trackViewState.hasSelectedAll, - onChanged: (checked) { - if (checked == true) { - trackViewState.selectAll(); - } else { - trackViewState.deselectAll(); - } - }, - ) - : constrains.mdAndUp - ? const SizedBox(width: 32) - : const SizedBox(width: 16), + child: Checkbox( + value: trackViewState.hasSelectedAll, + onChanged: (checked) { + if (checked == true) { + trackViewState.selectAll(); + } else { + trackViewState.deselectAll(); + } + }, + ), ), Expanded( flex: 7, diff --git a/lib/components/shared/tracks_view/sections/body/track_view_options.dart b/lib/components/shared/tracks_view/sections/body/track_view_options.dart index 4fcd0a59..583c9107 100644 --- a/lib/components/shared/tracks_view/sections/body/track_view_options.dart +++ b/lib/components/shared/tracks_view/sections/body/track_view_options.dart @@ -11,7 +11,6 @@ import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; -import 'package:spotube/services/queries/queries.dart'; class TrackViewBodyOptions extends HookConsumerWidget { const TrackViewBodyOptions({Key? key}) : super(key: key); @@ -30,11 +29,6 @@ class TrackViewBodyOptions extends HookConsumerWidget { final trackViewState = ref.watch(trackViewProvider(props.tracks)); final selectedTracks = trackViewState.selectedTracks; - final userPlaylists = useQueries.playlist.ofMineAll(ref); - - final isUserPlaylist = - userPlaylists.data?.any((e) => e.id == props.collectionId) ?? false; - return AdaptivePopSheetList( tooltip: context.l10n.more_actions, headings: [ @@ -66,6 +60,7 @@ class TrackViewBodyOptions extends HookConsumerWidget { context: context, builder: (context) { return PlaylistAddTrackDialog( + openFromPlaylist: props.collectionId, tracks: selectedTracks.toList(), ); }, @@ -100,15 +95,14 @@ class TrackViewBodyOptions extends HookConsumerWidget { context.l10n.download_count(selectedTracks.length), ), ), - if (!isUserPlaylist) - PopSheetEntry( - value: "add-to-playlist", - leading: const Icon(SpotubeIcons.playlistAdd), - enabled: selectedTracks.isNotEmpty, - title: Text( - context.l10n.add_count_to_playlist(selectedTracks.length), - ), + PopSheetEntry( + value: "add-to-playlist", + leading: const Icon(SpotubeIcons.playlistAdd), + enabled: selectedTracks.isNotEmpty, + title: Text( + context.l10n.add_count_to_playlist(selectedTracks.length), ), + ), PopSheetEntry( enabled: selectedTracks.isNotEmpty, value: "add-to-queue", diff --git a/lib/components/shared/tracks_view/sections/header/flexible_header.dart b/lib/components/shared/tracks_view/sections/header/flexible_header.dart index 7c469654..19241dc6 100644 --- a/lib/components/shared/tracks_view/sections/header/flexible_header.dart +++ b/lib/components/shared/tracks_view/sections/header/flexible_header.dart @@ -1,6 +1,5 @@ import 'dart:ui'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -62,7 +61,7 @@ class TrackViewFlexHeader extends HookConsumerWidget { clipBehavior: Clip.hardEdge, decoration: BoxDecoration( image: DecorationImage( - image: CachedNetworkImageProvider(props.image), + image: UniversalImage.imageProvider(props.image), fit: BoxFit.cover, ), ), @@ -88,50 +87,68 @@ class TrackViewFlexHeader extends HookConsumerWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Flex( - direction: mediaQuery.mdAndDown - ? Axis.vertical - : Axis.horizontal, - mainAxisSize: MainAxisSize.min, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10), - child: UniversalImage( - path: props.image, - width: 200, - height: 200, - placeholder: Assets.albumPlaceholder.path, + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: mediaQuery.mdAndDown + ? mediaQuery.size.width + : 800, + ), + child: Flex( + direction: mediaQuery.mdAndDown + ? Axis.vertical + : Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: UniversalImage( + path: props.image, + width: 200, + height: 200, + placeholder: Assets.albumPlaceholder.path, + ), ), - ), - const Gap(20), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: mediaQuery.mdAndDown - ? CrossAxisAlignment.center - : CrossAxisAlignment.start, - children: [ - Text(props.title, style: headingStyle), - const SizedBox(height: 10), - if (description != null && - description.isNotEmpty) - Text( - description, - style: defaultTextStyle.style.copyWith( - color: palette.bodyTextColor, + const Gap(20), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: mediaQuery.mdAndDown + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, + children: [ + Text( + props.title, + style: headingStyle, + textAlign: mediaQuery.mdAndDown + ? TextAlign.center + : TextAlign.start, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - textAlign: mediaQuery.mdAndDown - ? TextAlign.center - : TextAlign.start, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const Gap(10), - const TrackViewHeaderActions(), - const Gap(10), - TrackViewHeaderButtons(color: palette), - ], - ), - ], + const SizedBox(height: 10), + if (description != null && + description.isNotEmpty) + Text( + description, + style: + defaultTextStyle.style.copyWith( + color: palette.bodyTextColor, + ), + textAlign: mediaQuery.mdAndDown + ? TextAlign.center + : TextAlign.start, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const Gap(10), + const TrackViewHeaderActions(), + const Gap(10), + TrackViewHeaderButtons(color: palette), + ], + ), + ), + ], + ), ), ], ), diff --git a/lib/components/shared/tracks_view/track_view.dart b/lib/components/shared/tracks_view/track_view.dart index a65bcff1..a1a2d48b 100644 --- a/lib/components/shared/tracks_view/track_view.dart +++ b/lib/components/shared/tracks_view/track_view.dart @@ -3,7 +3,6 @@ import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:sliver_tools/sliver_tools.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart'; import 'package:spotube/components/shared/tracks_view/sections/header/flexible_header.dart'; import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; @@ -28,16 +27,17 @@ class TrackView extends HookConsumerWidget { ) : null, extendBodyBehindAppBar: true, - body: CustomScrollView( - slivers: [ - const TrackViewFlexHeader(), - SliverAnimatedSwitcher( - duration: const Duration(milliseconds: 500), - child: props.tracks.isEmpty - ? const ShimmerTrackTileGroup() - : const TrackViewBodySection(), - ), - ], + body: RefreshIndicator( + onRefresh: props.pagination.onRefresh, + child: const CustomScrollView( + slivers: [ + TrackViewFlexHeader(), + SliverAnimatedSwitcher( + duration: Duration(milliseconds: 500), + child: TrackViewBodySection(), + ), + ], + ), ), ); } diff --git a/lib/components/shared/tracks_view/track_view_props.dart b/lib/components/shared/tracks_view/track_view_props.dart index 59c05db2..1c6c7647 100644 --- a/lib/components/shared/tracks_view/track_view_props.dart +++ b/lib/components/shared/tracks_view/track_view_props.dart @@ -6,6 +6,7 @@ class PaginationProps { final bool hasNextPage; final bool isLoading; final VoidCallback onFetchMore; + final Future Function() onRefresh; final Future> Function() onFetchAll; const PaginationProps({ @@ -13,6 +14,7 @@ class PaginationProps { required this.isLoading, required this.onFetchMore, required this.onFetchAll, + required this.onRefresh, }); factory PaginationProps.fromQuery( @@ -24,6 +26,7 @@ class PaginationProps { isLoading: query.isLoadingNextPage, onFetchMore: query.fetchNext, onFetchAll: onFetchAll, + onRefresh: query.refreshAll, ); } @@ -33,7 +36,8 @@ class PaginationProps { other.hasNextPage == hasNextPage && other.isLoading == isLoading && other.onFetchMore == onFetchMore && - other.onFetchAll == onFetchAll; + other.onFetchAll == onFetchAll && + other.onRefresh == onRefresh; } @override @@ -42,7 +46,8 @@ class PaginationProps { hasNextPage.hashCode ^ isLoading.hashCode ^ onFetchMore.hashCode ^ - onFetchAll.hashCode; + onFetchAll.hashCode ^ + onRefresh.hashCode; } class InheritedTrackView extends InheritedWidget { diff --git a/lib/extensions/infinite_query.dart b/lib/extensions/infinite_query.dart index 90dcf73e..2181ab3c 100644 --- a/lib/extensions/infinite_query.dart +++ b/lib/extensions/infinite_query.dart @@ -9,17 +9,21 @@ extension FetchAllTracks on InfiniteQuery, dynamic, int> { return pages.expand((page) => page).toList(); } final tracks = await getAllTracks(); - final pagedTracks = tracks.fold( - >{}, - (acc, element) { - final index = acc.length; - final groupIndex = index ~/ 20; - final group = acc[groupIndex] ?? []; - group.add(element); - acc[groupIndex] = group; - return acc; - }, - ); + + final numOfPages = (tracks.length / 20).round(); + + final Map> pagedTracks = {}; + + for (var i = 0; i < numOfPages; i++) { + if (i == numOfPages - 1) { + final pageTracks = tracks.sublist(i * 20); + pagedTracks[i] = pageTracks; + break; + } + + final pageTracks = tracks.sublist(i * 20, (i + 1) * 20); + pagedTracks[i] = pageTracks; + } for (final group in pagedTracks.entries) { setPageData(group.key, group.value); diff --git a/lib/hooks/configurators/use_close_behavior.dart b/lib/hooks/configurators/use_close_behavior.dart new file mode 100644 index 00000000..05c03fff --- /dev/null +++ b/lib/hooks/configurators/use_close_behavior.dart @@ -0,0 +1,32 @@ +import 'dart:io'; + +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/hooks/configurators/use_window_listener.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; +import 'package:local_notifier/local_notifier.dart'; + +final closeNotification = DesktopTools.createNotification( + title: 'Spotube', + message: 'Running in background. Minimized to System Tray', + actions: [ + LocalNotificationAction(text: 'Close The App'), + ], +)?..onClickAction = (value) { + exit(0); + }; + +void useCloseBehavior(WidgetRef ref) { + useWindowListener( + onWindowClose: () async { + final preferences = ref.read(userPreferencesProvider); + if (preferences.closeBehavior == CloseBehavior.minimizeToTray) { + await DesktopTools.window.hide(); + closeNotification?.show(); + } else { + exit(0); + } + }, + ); +} diff --git a/lib/hooks/configurators/use_deep_linking.dart b/lib/hooks/configurators/use_deep_linking.dart new file mode 100644 index 00000000..3b7ec3f3 --- /dev/null +++ b/lib/hooks/configurators/use_deep_linking.dart @@ -0,0 +1,110 @@ +import 'dart:async'; + +import 'package:app_links/app_links.dart'; +import 'package:fl_query_hooks/fl_query_hooks.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/provider/spotify_provider.dart'; +import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; +import 'package:flutter_sharing_intent/model/sharing_file.dart'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; + +final appLinks = AppLinks(); +final linkStream = appLinks.allStringLinkStream.asBroadcastStream(); + +void useDeepLinking(WidgetRef ref) { + // single instance no worries + final spotify = ref.watch(spotifyProvider); + final queryClient = useQueryClient(); + + useEffect(() { + void uriListener(List files) async { + for (final file in files) { + if (file.type != SharedMediaType.URL) continue; + final url = Uri.parse(file.value!); + if (url.pathSegments.length != 2) continue; + + switch (url.pathSegments.first) { + case "album": + router.push( + "/album/${url.pathSegments.last}", + extra: await queryClient.fetchQuery( + "album/${url.pathSegments.last}", + () => spotify.albums.get(url.pathSegments.last), + ), + ); + break; + case "artist": + router.push("/artist/${url.pathSegments.last}"); + break; + case "playlist": + router.push( + "/playlist/${url.pathSegments.last}", + extra: await queryClient.fetchQuery( + "playlist/${url.pathSegments.last}", + () => spotify.playlists.get(url.pathSegments.last), + ), + ); + break; + case "track": + router.push( + "/track/${url.pathSegments.last}", + ); + break; + default: + break; + } + } + } + + StreamSubscription? mediaStream; + + if (DesktopTools.platform.isMobile) { + FlutterSharingIntent.instance.getInitialSharing().then(uriListener); + + mediaStream = + FlutterSharingIntent.instance.getMediaStream().listen(uriListener); + } + + final subscription = linkStream.listen((uri) async { + final startSegment = uri.split(":").take(2).join(":"); + final endSegment = uri.split(":").last; + + switch (startSegment) { + case "spotify:album": + await router.push( + "/album/$endSegment", + extra: await queryClient.fetchQuery( + "album/$endSegment", + () => spotify.albums.get(endSegment), + ), + ); + break; + case "spotify:artist": + await router.push("/artist/$endSegment"); + break; + case "spotify:track": + await router.push("/track/$endSegment"); + break; + case "spotify:playlist": + await router.push( + "/playlist/$endSegment", + extra: await queryClient.fetchQuery( + "playlist/$endSegment", + () => spotify.playlists.get(endSegment), + ), + ); + break; + default: + break; + } + }); + + return () { + mediaStream?.cancel(); + subscription.cancel(); + }; + }, [spotify, queryClient]); +} diff --git a/lib/hooks/configurators/use_init_sys_tray.dart b/lib/hooks/configurators/use_init_sys_tray.dart index db4964ce..8080bea6 100644 --- a/lib/hooks/configurators/use_init_sys_tray.dart +++ b/lib/hooks/configurators/use_init_sys_tray.dart @@ -25,7 +25,7 @@ void useInitSysTray(WidgetRef ref) { } final enabled = !playlist.isFetching; systemTray.value = await DesktopTools.createSystemTrayMenu( - title: DesktopTools.platform.isLinux ? "" : "Spotube", + title: DesktopTools.platform.isWindows ? "Spotube" : "", iconPath: "assets/spotube-logo.png", windowsIconPath: "assets/spotube-logo.ico", items: [ diff --git a/lib/hooks/configurators/use_window_listener.dart b/lib/hooks/configurators/use_window_listener.dart new file mode 100644 index 00000000..b91ad413 --- /dev/null +++ b/lib/hooks/configurators/use_window_listener.dart @@ -0,0 +1,197 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +class CallbackWindowListener implements WindowListener { + final VoidCallback? _onWindowClose; + final VoidCallback? _onWindowFocus; + final VoidCallback? _onWindowBlur; + final VoidCallback? _onWindowMaximize; + final VoidCallback? _onWindowUnmaximize; + final VoidCallback? _onWindowMinimize; + final VoidCallback? _onWindowRestore; + final VoidCallback? _onWindowResize; + final VoidCallback? _onWindowResized; + final VoidCallback? _onWindowMove; + final VoidCallback? _onWindowMoved; + final VoidCallback? _onWindowEnterFullScreen; + final VoidCallback? _onWindowLeaveFullScreen; + final VoidCallback? _onWindowDocked; + final VoidCallback? _onWindowUndocked; + final VoidCallback? _onWindowEvent; + + const CallbackWindowListener({ + VoidCallback? onWindowClose, + VoidCallback? onWindowFocus, + VoidCallback? onWindowBlur, + VoidCallback? onWindowMaximize, + VoidCallback? onWindowUnmaximize, + VoidCallback? onWindowMinimize, + VoidCallback? onWindowRestore, + VoidCallback? onWindowResize, + VoidCallback? onWindowResized, + VoidCallback? onWindowMove, + VoidCallback? onWindowMoved, + VoidCallback? onWindowEnterFullScreen, + VoidCallback? onWindowLeaveFullScreen, + VoidCallback? onWindowDocked, + VoidCallback? onWindowUndocked, + VoidCallback? onWindowEvent, + }) : _onWindowClose = onWindowClose, + _onWindowFocus = onWindowFocus, + _onWindowBlur = onWindowBlur, + _onWindowMaximize = onWindowMaximize, + _onWindowUnmaximize = onWindowUnmaximize, + _onWindowMinimize = onWindowMinimize, + _onWindowRestore = onWindowRestore, + _onWindowResize = onWindowResize, + _onWindowResized = onWindowResized, + _onWindowMove = onWindowMove, + _onWindowMoved = onWindowMoved, + _onWindowEnterFullScreen = onWindowEnterFullScreen, + _onWindowLeaveFullScreen = onWindowLeaveFullScreen, + _onWindowDocked = onWindowDocked, + _onWindowUndocked = onWindowUndocked, + _onWindowEvent = onWindowEvent; + + @override + void onWindowBlur() { + return _onWindowBlur?.call(); + } + + @override + void onWindowClose() { + return _onWindowClose?.call(); + } + + @override + void onWindowDocked() { + return _onWindowDocked?.call(); + } + + @override + void onWindowEnterFullScreen() { + return _onWindowEnterFullScreen?.call(); + } + + @override + void onWindowEvent(String eventName) { + return _onWindowEvent?.call(); + } + + @override + void onWindowFocus() { + return _onWindowFocus?.call(); + } + + @override + void onWindowLeaveFullScreen() { + return _onWindowLeaveFullScreen?.call(); + } + + @override + void onWindowMaximize() { + return _onWindowMaximize?.call(); + } + + @override + void onWindowMinimize() { + return _onWindowMinimize?.call(); + } + + @override + void onWindowMove() { + return _onWindowMove?.call(); + } + + @override + void onWindowMoved() { + return _onWindowMoved?.call(); + } + + @override + void onWindowResize() { + return _onWindowResize?.call(); + } + + @override + void onWindowResized() { + return _onWindowResized?.call(); + } + + @override + void onWindowRestore() { + return _onWindowRestore?.call(); + } + + @override + void onWindowUndocked() { + return _onWindowUndocked?.call(); + } + + @override + void onWindowUnmaximize() { + return _onWindowUnmaximize?.call(); + } +} + +void useWindowListener({ + VoidCallback? onWindowClose, + VoidCallback? onWindowFocus, + VoidCallback? onWindowBlur, + VoidCallback? onWindowMaximize, + VoidCallback? onWindowUnmaximize, + VoidCallback? onWindowMinimize, + VoidCallback? onWindowRestore, + VoidCallback? onWindowResize, + VoidCallback? onWindowResized, + VoidCallback? onWindowMove, + VoidCallback? onWindowMoved, + VoidCallback? onWindowEnterFullScreen, + VoidCallback? onWindowLeaveFullScreen, + VoidCallback? onWindowDocked, + VoidCallback? onWindowUndocked, + VoidCallback? onWindowEvent, +}) { + useEffect(() { + final listener = CallbackWindowListener( + onWindowClose: onWindowClose, + onWindowFocus: onWindowFocus, + onWindowBlur: onWindowBlur, + onWindowMaximize: onWindowMaximize, + onWindowUnmaximize: onWindowUnmaximize, + onWindowMinimize: onWindowMinimize, + onWindowRestore: onWindowRestore, + onWindowResize: onWindowResize, + onWindowResized: onWindowResized, + onWindowMove: onWindowMove, + onWindowMoved: onWindowMoved, + onWindowEnterFullScreen: onWindowEnterFullScreen, + onWindowLeaveFullScreen: onWindowLeaveFullScreen, + onWindowDocked: onWindowDocked, + onWindowUndocked: onWindowUndocked, + onWindowEvent: onWindowEvent, + ); + DesktopTools.window.addListener(listener); + return () { + DesktopTools.window.removeListener(listener); + }; + }, [ + onWindowClose, + onWindowFocus, + onWindowBlur, + onWindowMaximize, + onWindowUnmaximize, + onWindowMinimize, + onWindowRestore, + onWindowResize, + onWindowResized, + onWindowMove, + onWindowMoved, + onWindowEnterFullScreen, + onWindowLeaveFullScreen, + onWindowDocked, + onWindowUndocked, + onWindowEvent, + ]); +} diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index f587710c..20b6a47c 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -177,11 +177,9 @@ "step_2": "الخطوة 2", "step_2_steps": "1. بمجرد تسجيل الدخول، اضغط على F12 أو انقر بزر الماوس الأيمن > فحص لفتح أدوات تطوير المتصفح.\n2. ثم انتقل إلى علامة التبويب \"التطبيقات\" (Chrome وEdge وBrave وما إلى ذلك.) أو علامة التبويب \"التخزين\" (Firefox وPalemoon وما إلى ذلك..)\n3. انتقل إلى قسم \"ملفات تعريف الارتباط\" ثم القسم الفرعي \"https://accounts.spotify.com\"", "step_3": "الخطوة 3", - "step_3_steps": "انسخ قيم \"sp_dc\" و \"sp_key\" (أو sp_gaid) الكويز", "success_emoji": "نجاح 🥳", "success_message": "لقد قمت الآن بتسجيل الدخول بنجاح باستخدام حساب Spotify الخاص بك. عمل جيد يا صديقي!", "step_4": "الخطوة 4", - "step_4_steps": "قم بلصق قيم \"sp_dc\" و \"sp_key\" (أو sp_gaid) المنسوخة في الحقول المعنية", "something_went_wrong": "هناك خطأ ما", "piped_instance": "مثيل خادم Piped", "piped_description": "مثيل خادم Piped الذي سيتم استخدامه لمطابقة المقطوعة", @@ -251,7 +249,7 @@ "developers": "المطورون", "not_logged_in": "لم تقم بتسجيل الدخول", "search_mode": "وضع البحث", - "youtube_api_type": "نوع الـAPI", + "audio_source": "مصدر الصوت", "ok": "حسسناً", "failed_to_encrypt": "فشل في التشفير", "encryption_failed_warning": "يستخدم Spotube التشفير لتخزين بياناتك بشكل آمن. لكنها فشلت في القيام بذلك. لذلك سيعود الأمر إلى التخزين غير الآمن\nإذا كنت تستخدم Linux، فيرجى التأكد من تثبيت أي خدمة سرية (gnome-keyring، kde-wallet، keepassxc، إلخ)", @@ -279,5 +277,10 @@ "password": "كلمة المرور", "login": "تسجيل الدخول", "login_with_your_lastfm": "تسجيل الدخول باستخدام حساب Last.fm الخاص بك", - "scrobble_to_lastfm": "تسجيل الاستماع على Last.fm" + "scrobble_to_lastfm": "تسجيل الاستماع على Last.fm", + "go_to_album": "الانتقال إلى الألبوم", + "discord_rich_presence": "وجود ديسكورد الغني", + "browse_all": "تصفح الكل", + "genres": "الأنواع الموسيقية", + "explore_genres": "استكشاف الأنواع" } \ No newline at end of file diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index 02402179..74dc200d 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -175,11 +175,9 @@ "step_2": "ধাপ 2", "step_2_steps": "১. একবার আপনি লগ ইন করলে, ব্রাউজার ডেভটুল খুলতে F12 বা মাউসের রাইট ক্লিক > \"Inspect to open Browser DevTools\" টিপুন।\n২. তারপর \"Application\" ট্যাবে যান (Chrome, Edge, Brave etc..) অথবা \"Storage\" Tab (Firefox, Palemoon etc..)\n৩. \"Cookies \" বিভাগে যান তারপর \"https://accounts.spotify.com\" উপবিভাগে যান", "step_3": "ধাপ 3", - "step_3_steps": "\"sp_dc\" এবং \"sp_key\" (অথবা sp_gaid) কুকিজের মান কপি করুন", "success_emoji": "আমরা সফল🥳", "success_message": "এখন আপনি সফলভাবে আপনার Spotify অ্যাকাউন্ট দিয়ে লগ ইন করেছেন। সাধুভাত আপনাকে", "step_4": "ধাপ 4", - "step_4_steps": "কপি করা \"sp_dc\" এবং \"sp_key\" (অথবা sp_gaid) এর মান সংশ্লিষ্ট ফিল্ডে পেস্ট করুন", "something_went_wrong": "কিছু ভুল হয়েছে", "piped_instance": "Piped সার্ভার এড্রেস", "piped_description": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার", @@ -249,7 +247,7 @@ "developers": "ডেভেলপার", "not_logged_in": "আপনি লগইন করা নেই", "search_mode": "অনুসন্ধান মোড", - "youtube_api_type": "API প্রকার", + "audio_source": "অডিও উৎস", "ok": "ঠিক আছে", "failed_to_encrypt": "এনক্রিপ্ট করা ব্যর্থ হয়েছে", "encryption_failed_warning": "Spotube আপনার তথ্যগুলি নিরাপদভাবে স্টোর করতে এনক্রিপশন ব্যবহার করে। কিন্তু এটি ব্যর্থ হয়েছে। তাই এটি অনিরাপদ স্টোরে ফলফল হবে\nযদি আপনি Linux ব্যবহার করেন, তবে দয়া করে নিশ্চিত হউন যে আপনার কোনও সিক্রেট-সার্ভিস gnome-keyring, kde-wallet, keepassxc ইত্যাদি ইনস্টল করা আছে", @@ -279,5 +277,10 @@ "password": "পাসওয়ার্ড", "login": "লগইন", "login_with_your_lastfm": "আপনার Last.fm অ্যাকাউন্ট দিয়ে লগইন করুন", - "scrobble_to_lastfm": "Last.fm এ স্ক্রবল করুন" + "scrobble_to_lastfm": "Last.fm এ স্ক্রবল করুন", + "go_to_album": "الانتقال إلى الألبوم", + "discord_rich_presence": "وجود ديسكورد الغني", + "browse_all": "تصفح الكل", + "genres": "الأنواع الموسيقية", + "explore_genres": "استكشاف الأنواع" } \ No newline at end of file diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb index 81a11082..2c952457 100644 --- a/lib/l10n/app_ca.arb +++ b/lib/l10n/app_ca.arb @@ -175,11 +175,9 @@ "step_2": "Pas 2", "step_2_steps": "1. Una vegada que hagi iniciat sessió, premi F12 o faci clic dret amb el ratolí > Inspeccionar per obrir les eines de desenvolulpador del navegador.\n2. Després vagi a la pestanya \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Vagi a la secció \"Cookies\" i després a la subsecció \"https://accounts.spotify.com\"", "step_3": "Pas 3", - "step_3_steps": "Copiï els valors de les Cookies \"sp_dc\" i \"sp_key\" (o sp_gaid)", "success_emoji": "Èxit! 🥳", "success_message": "Ara has iniciat sessió amb èxit al teu compte de Spotify. Bona feina!", "step_4": "Pas 4", - "step_4_steps": "Enganxi els valors coppiats de \"sp_dc\" i \"sp_key\" (o sp_gaid) en els camps respectius", "something_went_wrong": "Quelcom ha sortit malament", "piped_instance": "Instància del servidor Piped", "piped_description": "La instància del servidor Piped a utilitzar per la coincidència de cançons", @@ -249,7 +247,7 @@ "developers": "Desenvolupadors", "not_logged_in": "No ha iniciat sesió", "search_mode": "Mode de cerca", - "youtube_api_type": "Tipus d'API de YouTube", + "audio_source": "Font d'àudio", "ok": "OK", "failed_to_encrypt": "Error al xifrar", "encryption_failed_warning": "Spotube utilitza el xifrado per emmagatzemar les seves dades de forma segura. Però ha fallat. Per tant, tornarà a un emmagatzament no segur\nSi estè utilizant Linux, asseguri's de tenir instal·lats els serveis secrets com gnome-keyring, kde-wallet i keepassxc", @@ -279,5 +277,10 @@ "password": "Contrasenya", "login": "Inicia la sessió", "login_with_your_lastfm": "Inicia la sessió amb el teu compte de Last.fm", - "scrobble_to_lastfm": "Scrobble a Last.fm" + "scrobble_to_lastfm": "Scrobble a Last.fm", + "go_to_album": "Anar a l'àlbum", + "discord_rich_presence": "Presència rica de Discord", + "browse_all": "Navega per tot", + "genres": "Gèneres", + "explore_genres": "Explora els gèneres" } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 339a8d65..59f832ea 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -175,11 +175,9 @@ "step_2": "Schritt 2", "step_2_steps": "1. Wenn du angemeldet bist, drücke F12 oder klicke mit der rechten Maustaste > Inspektion, um die Browser-Entwicklertools zu öffnen.\n2. Gehe dann zum \"Anwendungs\"-Tab (Chrome, Edge, Brave usw.) oder zum \"Storage\"-Tab (Firefox, Palemoon usw.)\n3. Gehe zum Abschnitt \"Cookies\" und dann zum Unterabschnitt \"https://accounts.spotify.com\"", "step_3": "Schritt 3", - "step_3_steps": "Kopiere die Werte der Cookies \"sp_dc\" und \"sp_key\" (oder sp_gaid)", "success_emoji": "Erfolg🥳", "success_message": "Jetzt bist du erfolgreich mit deinem Spotify-Konto angemeldet. Gut gemacht, Kumpel!", "step_4": "Schritt 4", - "step_4_steps": "Füge die kopierten Werte von \"sp_dc\" und \"sp_key\" (oder sp_gaid) in die entsprechenden Felder ein", "something_went_wrong": "Etwas ist schiefgelaufen", "piped_instance": "Piped-Serverinstanz", "piped_description": "Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll", @@ -249,7 +247,7 @@ "developers": "Entwickler", "not_logged_in": "Sie sind nicht angemeldet", "search_mode": "Suchmodus", - "youtube_api_type": "API-Typ", + "audio_source": "Audioquelle", "ok": "OK", "failed_to_encrypt": "Verschlüsselung fehlgeschlagen", "encryption_failed_warning": "Spotube verwendet Verschlüsselung, um Ihre Daten sicher zu speichern. Dies ist jedoch fehlgeschlagen. Daher wird es auf unsichere Speicherung zurückgreifen\nWenn Sie Linux verwenden, stellen Sie bitte sicher, dass Sie Secret-Services wie gnome-keyring, kde-wallet und keepassxc installiert haben", @@ -279,5 +277,10 @@ "password": "Passwort", "login": "Anmelden", "login_with_your_lastfm": "Mit Ihrem Last.fm-Konto anmelden", - "scrobble_to_lastfm": "Auf Last.fm scrobbeln" + "scrobble_to_lastfm": "Auf Last.fm scrobbeln", + "go_to_album": "Zum Album gehen", + "discord_rich_presence": "Discord Rich Presence", + "browse_all": "Alles durchsuchen", + "genres": "Genres", + "explore_genres": "Genres erkunden" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 730f51ea..07df5f06 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -177,11 +177,11 @@ "step_2": "Step 2", "step_2_steps": "1. Once you're logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection", "step_3": "Step 3", - "step_3_steps": "Copy the values of \"sp_dc\" and \"sp_key\" (or sp_gaid) Cookies", + "step_3_steps": "Copy the value of \"sp_dc\" Cookie", "success_emoji": "Success🥳", - "success_message": "Now you're successfully Logged In with your Spotify account. Good Job, mate!", + "success_message": "Now you've successfully Logged in with your Spotify account. Good Job, mate!", "step_4": "Step 4", - "step_4_steps": "Paste the copied \"sp_dc\" and \"sp_key\" (or sp_gaid) values in the respective fields", + "step_4_steps": "Paste the copied \"sp_dc\" value", "something_went_wrong": "Something went wrong", "piped_instance": "Piped Server Instance", "piped_description": "The Piped server instance to use for track matching", @@ -251,7 +251,7 @@ "developers": "Developers", "not_logged_in": "You're not logged in", "search_mode": "Search Mode", - "youtube_api_type": "API Type", + "audio_source": "Audio Source", "ok": "Ok", "failed_to_encrypt": "Failed to encrypt", "encryption_failed_warning": "Spotube uses encryption to securely store your data. But failed to do so. So it'll fallback to insecure storage\nIf you're using linux, please make sure you've any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed", @@ -279,5 +279,12 @@ "password": "Password", "login": "Login", "login_with_your_lastfm": "Login with your Last.fm account", - "scrobble_to_lastfm": "Scrobble to Last.fm" + "scrobble_to_lastfm": "Scrobble to Last.fm", + "go_to_album": "Go to Album", + "discord_rich_presence": "Discord Rich Presence", + "browse_all": "Browse All", + "genres": "Genres", + "explore_genres": "Explore Genres", + "friends": "Friends", + "no_lyrics_available": "Sorry, unable find lyrics for this track" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index f617705e..e04b4798 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -175,11 +175,9 @@ "step_2": "Paso 2", "step_2_steps": "1. Una vez que hayas iniciado sesión, presiona F12 o haz clic derecho con el ratón > Inspeccionar para abrir las herramientas de desarrollo del navegador.\n2. Luego ve a la pestaña \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Ve a la sección \"Cookies\" y luego la subsección \"https://accounts.spotify.com\"", "step_3": "Paso 3", - "step_3_steps": "Copia los valores de las Cookies \"sp_dc\" y \"sp_key\" (o sp_gaid)", "success_emoji": "¡Éxito! 🥳", "success_message": "Ahora has iniciado sesión con éxito en tu cuenta de Spotify. ¡Buen trabajo!", "step_4": "Paso 4", - "step_4_steps": "Pega los valores copiados de \"sp_dc\" y \"sp_key\" (o sp_gaid) en los campos respectivos", "something_went_wrong": "Algo salió mal", "piped_instance": "Instancia del servidor Piped", "piped_description": "La instancia del servidor Piped a utilizar para la coincidencia de pistas", @@ -249,7 +247,7 @@ "developers": "Desarrolladores", "not_logged_in": "No has iniciado sesión", "search_mode": "Modo de búsqueda", - "youtube_api_type": "Tipo de API de YouTube", + "audio_source": "Fuente de audio", "ok": "OK", "failed_to_encrypt": "Error al cifrar", "encryption_failed_warning": "Spotube utiliza el cifrado para almacenar sus datos de forma segura. Pero ha fallado. Por lo tanto, volverá a un almacenamiento no seguro\nSi está utilizando Linux, asegúrese de tener instalados servicios secretos como gnome-keyring, kde-wallet y keepassxc", @@ -279,5 +277,10 @@ "password": "Contraseña", "login": "Iniciar sesión", "login_with_your_lastfm": "Iniciar sesión con tu cuenta de Last.fm", - "scrobble_to_lastfm": "Scrobble a Last.fm" + "scrobble_to_lastfm": "Scrobble a Last.fm", + "go_to_album": "Ir al álbum", + "discord_rich_presence": "Presencia rica en Discord", + "browse_all": "Explorar todo", + "genres": "Géneros", + "explore_genres": "Explorar géneros" } \ No newline at end of file diff --git a/lib/l10n/app_fa.arb b/lib/l10n/app_fa.arb index 5454b13b..c9586cde 100644 --- a/lib/l10n/app_fa.arb +++ b/lib/l10n/app_fa.arb @@ -177,11 +177,9 @@ "step_2": "گام 2", "step_2_steps": "1. پس از ورود به سیستم، F12 یا کلیک راست ماوس > Inspect را فشار دهید تا ابزارهای توسعه مرورگر باز شود..\n2. سپس به تب \"Application\" (Chrome, Edge, Brave etc..) یا \"Storage\" Tab (Firefox, Palemoon etc..)\n3. به قسمت \"Cookies\" و به پخش \"https://accounts.spotify.com\" بروید", "step_3": "گام 3", - "step_3_steps": "کپی کردن مقادیر \"sp_dc\" و \"sp_key\" (یا sp_gaid) کوکی", "success_emoji": "موفقیت🥳", "success_message": "اکنون با موفقیت با حساب اسپوتیفای خود وارد شده اید", "step_4": "مرحله 4", - "step_4_steps": "مقدار کپی شده را \"sp_dc\" and \"sp_key\" (یا sp_gaid) در فیلد مربوط پر کنید", "something_went_wrong": "اشتباهی رخ داده", "piped_instance": "مشکل در ارتباط با سرور", "piped_description": "مشکل در ارتباط با سرور در دریافت آهنگ ها", @@ -251,7 +249,7 @@ "developers": "توسعه دهنده ها", "not_logged_in": "شما وارد نشده اید ", "search_mode": "حالت جستجو", - "youtube_api_type": "API نوع", + "audio_source": "منبع صدا", "ok": "باشد", "failed_to_encrypt": "رمز گذاری نشده", "encryption_failed_warning": "Spotube از رمزگذاری برای ذخیره ایمن داده های شما استفاده می کند. اما موفق به انجام این کار نشد. بنابراین به فضای ذخیره‌سازی ناامن تبدیل می‌شود\nاگر از لینوکس استفاده می‌کنید، لطفاً مطمئن شوید که سرویس مخفی (gnome-keyring، kde-wallet، keepassxc و غیره) را نصب کرده‌اید.", @@ -279,5 +277,10 @@ "password": "رمز عبور", "login": "ورود", "login_with_your_lastfm": "ورود با حساب کاربری Last.fm خود", - "scrobble_to_lastfm": "Scrobble به Last.fm" + "scrobble_to_lastfm": "Scrobble به Last.fm", + "go_to_album": "رفتن به آلبوم", + "discord_rich_presence": "حضور غنی دیسکورد", + "browse_all": "مرور همه", + "genres": "ژانرها", + "explore_genres": "استکشاف ژانرها" } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fbe5c335..0c3eb653 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -175,11 +175,9 @@ "step_2": "Étape 2", "step_2_steps": "1. Une fois connecté, appuyez sur F12 ou clic droit de la souris > Inspecter pour ouvrir les outils de développement du navigateur.\n2. Ensuite, allez dans l'onglet \"Application\" (Chrome, Edge, Brave, etc.) ou l'onglet \"Stockage\" (Firefox, Palemoon, etc.)\n3. Allez dans la section \"Cookies\", puis dans la sous-section \"https://accounts.spotify.com\"", "step_3": "Étape 3", - "step_3_steps": "Copiez les valeurs des cookies \"sp_dc\" et \"sp_key\" (ou sp_gaid)", "success_emoji": "Succès🥳", "success_message": "Vous êtes maintenant connecté avec succès à votre compte Spotify. Bon travail, mon ami!", "step_4": "Étape 4", - "step_4_steps": "Collez les valeurs copiées de \"sp_dc\" et \"sp_key\" (ou sp_gaid) dans les champs respectifs", "something_went_wrong": "Quelque chose s'est mal passé", "piped_instance": "Instance pipée", "piped_description": "L'instance de serveur Piped à utiliser pour la correspondance des pistes", @@ -249,7 +247,7 @@ "developers": "Développeurs", "not_logged_in": "Vous n'êtes pas connecté(e)", "search_mode": "Mode de recherche", - "youtube_api_type": "Type d'API", + "audio_source": "Source audio", "ok": "OK", "failed_to_encrypt": "Échec de la cryptage", "encryption_failed_warning": "Spotube utilise le cryptage pour stocker vos données en toute sécurité. Mais cela a échoué. Il basculera donc vers un stockage non sécurisé\nSi vous utilisez Linux, assurez-vous d'avoir installé des services secrets tels que gnome-keyring, kde-wallet et keepassxc", @@ -279,5 +277,10 @@ "password": "Mot de passe", "login": "Se connecter", "login_with_your_lastfm": "Se connecter avec votre compte Last.fm", - "scrobble_to_lastfm": "Scrobble à Last.fm" + "scrobble_to_lastfm": "Scrobble à Last.fm", + "go_to_album": "Aller à l'album", + "discord_rich_presence": "Présence riche de Discord", + "browse_all": "Parcourir tout", + "genres": "Genres", + "explore_genres": "Explorer les genres" } \ No newline at end of file diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index d33f41dc..dd27dabf 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -175,11 +175,9 @@ "step_2": "2 चरण", "step_2_steps": "1. जब आप लॉगिन हो जाएँ, तो F12 दबाएं या माउस राइट क्लिक> निरीक्षण करें ताकि ब्राउज़र डेवटूल्स खुलें।\n2. फिर ब्राउज़र के \"एप्लिकेशन\" टैब (Chrome, Edge, Brave आदि) या \"स्टोरेज\" टैब (Firefox, Palemoon आदि) में जाएं\n3. \"कुकीज़\" अनुभाग में जाएं फिर \"https: //accounts.spotify.com\" उप-अनुभाग में जाएं", "step_3": "स्टेप 3", - "step_3_steps": "\"sp_dc\" और \"sp_key\" (या sp_gaid) कुकीज़ के मान कॉपी करें", "success_emoji": "सफलता🥳", "success_message": "अब आप अपने स्पॉटिफाई अकाउंट से सफलतापूर्वक लॉगइन हो गए हैं। अच्छा काम किया!", "step_4": "स्टेप 4", - "step_4_steps": "कॉपी की गई \"sp_dc\" और \"sp_key\" (या sp_gaid) मानों को संबंधित फील्ड में पेस्ट करें", "something_went_wrong": "कुछ गलत हो गया", "piped_instance": "पाइप्ड सर्वर", "piped_description": "पाइप किए गए सर्वर", @@ -249,7 +247,7 @@ "developers": "डेवलपर्स", "not_logged_in": "आप लॉग इन नहीं हैं", "search_mode": "खोज मोड", - "youtube_api_type": "API प्रकार", + "audio_source": "ऑडियो स्रोत", "ok": "ठीक है", "failed_to_encrypt": "एन्क्रिप्ट करने में विफल रहा", "encryption_failed_warning": "Spotube आपके डेटा को सुरक्षित रूप से स्टोर करने के लिए एन्क्रिप्शन का उपयोग करता है। लेकिन इसमें विफल रहा। इसलिए, यह असुरक्षित स्टोरेज पर फॉलबैक करेगा\nयदि आप Linux का उपयोग कर रहे हैं, तो कृपया सुनिश्चित करें कि आपके पास gnome-keyring, kde-wallet, keepassxc आदि जैसी कोई सीक्रेट-सर्विस इंस्टॉल की गई है", @@ -279,5 +277,10 @@ "password": "पासवर्ड", "login": "लॉग इन करें", "login_with_your_lastfm": "अपने Last.fm अकाउंट से लॉगिन करें", - "scrobble_to_lastfm": "Last.fm पर स्क्रॉबल करें" + "scrobble_to_lastfm": "Last.fm पर स्क्रॉबल करें", + "go_to_album": "एल्बम पर जाएं", + "discord_rich_presence": "डिस्कॉर्ड रिच प्रेजेंस", + "browse_all": "सभी को ब्राउज़ करें", + "genres": "शैलियाँ", + "explore_genres": "शैलियों का अन्वेषण करें" } \ No newline at end of file diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb new file mode 100644 index 00000000..3680933a --- /dev/null +++ b/lib/l10n/app_it.arb @@ -0,0 +1,287 @@ +{ + "guest": "Ospite", + "browse": "Sfoglia", + "search": "Cerca", + "library": "Libreria", + "lyrics": "Testi", + "settings": "Impostazioni", + "genre_categories_filter": "Filtra categorie e generi...", + "genre": "Genere", + "personalized": "Personalizzato", + "featured": "In evidenza", + "new_releases": "Novità", + "songs": "Canzoni", + "playing_track": "Riproduzione {track}", + "queue_clear_alert": "Questo cancellerà la coda corrente. {track_length} tracce saranno rimosse\nVuoi continuare?", + "load_more": "Carica altro", + "playlists": "Playlist", + "artists": "Artisti", + "albums": "Album", + "tracks": "Tracce", + "downloads": "Downloads", + "filter_playlists": "Filtra le tue playlist...", + "liked_tracks": "Tracce piaciute", + "liked_tracks_description": "Tutte le tracce piaciute", + "create_playlist": "Crea Playlist", + "create_a_playlist": "Crea una playlist", + "update_playlist": "Aggiorna playlist", + "create": "Crea", + "cancel": "Annulla", + "update": "Aggiorna", + "playlist_name": "Nome Playlist", + "name_of_playlist": "Nome della playlist", + "description": "Descrizione", + "public": "Pubblico", + "collaborative": "Collaborativo", + "search_local_tracks": "Cerca tracce locali...", + "play": "Riproduci", + "delete": "Cancella", + "none": "Nessuno", + "sort_a_z": "Ordina dalla A-Z", + "sort_z_a": "Ordina dalla Z-A", + "sort_artist": "Ordina per Artista", + "sort_album": "Ordina per Album", + "sort_tracks": "Ordina tracce", + "currently_downloading": "Attualmente in Download ({tracks_length})", + "cancel_all": "Annulla Tutto", + "filter_artist": "Filtra artisti...", + "followers": "{followers} Seguaci", + "add_artist_to_blacklist": "Aggiungi artista alla lista nera", + "top_tracks": "Tracce Top", + "fans_also_like": "Ai fan piace anche", + "loading": "Caricamento...", + "artist": "Artista", + "blacklisted": "In lista nera", + "following": "Seguendo", + "follow": "Segui", + "artist_url_copied": "URL artista copiato negli appunti", + "added_to_queue": "Aggiunto {tracks} tracce alla coda", + "filter_albums": "Filtra album...", + "synced": "Sincronizzato", + "plain": "Semplice", + "shuffle": "Casuale", + "search_tracks": "Cerca tracce...", + "released": "Rilasciato", + "error": "Errore {error}", + "title": "Titolo", + "time": "Durata", + "more_actions": "Più azioni", + "download_count": "Scaricato ({count})", + "add_count_to_playlist": "Aggiungi ({count}) alla playlist", + "add_count_to_queue": "Aggiungi ({count}) alla Coda", + "play_count_next": "Riproduci ({count}) prossime", + "album": "Album", + "copied_to_clipboard": "Copiato {data} negli appunti", + "add_to_following_playlists": "Aggiungi {track} nelle seguenti Playlist", + "add": "Aggiungi", + "added_track_to_queue": "Aggiunto {track} alla coda", + "add_to_queue": "Aggiungi alla coda", + "track_will_play_next": "in seguito sarà riprodotta {track}", + "play_next": "Riproduci prossimo", + "removed_track_from_queue": "Rimosso {track} dalla coda", + "remove_from_queue": "Rimuovi dalla coda", + "remove_from_favorites": "Rimuovi dai preferiti", + "save_as_favorite": "Salva come preferito", + "add_to_playlist": "Aggiungi alla playlist", + "remove_from_playlist": "Rimuovi dalla playlist", + "add_to_blacklist": "Aggiungi alla blacklist", + "remove_from_blacklist": "Rimuovi dalla blacklist", + "share": "Condividi", + "mini_player": "Mini Riproduttore", + "slide_to_seek": "Scorri per cercare avanti o indietro", + "shuffle_playlist": "Playlist casuale", + "unshuffle_playlist": "Ordina playlist", + "previous_track": "Traccia precedente", + "next_track": "Traccia successiva", + "pause_playback": "Pausa Playback", + "resume_playback": "Riprendi Playback", + "loop_track": "Cicla traccia", + "repeat_playlist": "Ripeti playlist", + "queue": "Coda", + "alternative_track_sources": "Sorgenti traccia alternative", + "download_track": "Scarica traccia", + "tracks_in_queue": "{tracks} tracce in coda", + "clear_all": "Cancella tutto", + "show_hide_ui_on_hover": "Mostra/Nascondi UI al passaggio", + "always_on_top": "Sempre in cima", + "exit_mini_player": "Esci da Mini player", + "download_location": "Cartella di scarico", + "account": "Account", + "login_with_spotify": "Login con il tuo account Spotify", + "connect_with_spotify": "Connetti con Spotify", + "logout": "Esci", + "logout_of_this_account": "Esci da questo account", + "language_region": "Lingua & Regione", + "language": "Lingua", + "system_default": "Default sistema", + "market_place_region": "Regione del mercato", + "recommendation_country": "Paese Raccomandato", + "appearance": "Aspetto", + "layout_mode": "Modalità Layout", + "override_layout_settings": "Sovrascrivi le impostazioni del layout responsivo", + "adaptive": "Adattiva", + "compact": "Compatta", + "extended": "Estesa", + "theme": "Tema", + "dark": "Scuro", + "light": "Chiaro", + "system": "Sistema", + "accent_color": "Colore accento", + "sync_album_color": "Syncronizza colore album", + "sync_album_color_description": "Usa il colore dominante della copertina dell'album come colore accento", + "playback": "Riproduzione", + "audio_quality": "Qualità Audio", + "high": "Alta", + "low": "Bassa", + "pre_download_play": "Pre-scarica e riproduci", + "pre_download_play_description": "Anzi che effettuare lo stream dell'audio, scarica invece i byte e li riproduce (raccomandato per gli utenti con banda più alta)", + "skip_non_music": "Salta i segmenti non di musica (SponsorBlock)", + "blacklist_description": "Tracce e artisti in blacklist", + "wait_for_download_to_finish": "Prego attendere che lo scaricamento corrente finisca", + "desktop": "Desktop", + "close_behavior": "Comportamento Chiusura", + "close": "Chiudi", + "minimize_to_tray": "Minimizza in tray", + "show_tray_icon": "Mostra icona in tray di sistema", + "about": "A proposito di", + "u_love_spotube": "Sappiamo che ami Spotube", + "check_for_updates": "Controlla aggiornamenti", + "about_spotube": "A proposito di Spotube", + "blacklist": "Blacklist", + "please_sponsor": "Per favore sponsorizza/dona", + "spotube_description": "Spotube, un client spotify gratis per tutti, multipiattaforma e leggero", + "version": "Versione", + "build_number": "Numero Build", + "founder": "Fondatore", + "repository": "Repository", + "bug_issues": "Bug+Problemi", + "made_with": "Fatto con ❤️ in Bangladesh🇧🇩", + "kingkor_roy_tirtho": "Kingkor Roy Tirtho", + "copyright": "© 2021-{current_year} Kingkor Roy Tirtho", + "license": "Licenza", + "add_spotify_credentials": "Aggiungi le tue credenziali spotify per iniziare", + "credentials_will_not_be_shared_disclaimer": "Non ti preoccupare, le tue credenziali non saranno inviate o condivise con nessuno", + "know_how_to_login": "Non sai come farlo?", + "follow_step_by_step_guide": "Segui la guida passo-passo", + "spotify_cookie": "Cookie Spotify {name}", + "cookie_name_cookie": "Cookie {name}", + "fill_in_all_fields": "Inserire tutti i campi", + "submit": "Invia", + "exit": "Esci", + "previous": "Precedente", + "next": "Prossimo", + "done": "Finito", + "step_1": "Passo 1", + "first_go_to": "Prim, vai a", + "login_if_not_logged_in": "ed effettua il login o iscrizione se non sei già acceduto", + "step_2": "Passo 2", + "step_2_steps": "1. Quando sei acceduto premi F12 o premi il tasto destro del Mouse > Ispeziona per aprire gli strumenti di sviluppo del browser.\n2. Vai quindi nel tab \"Applicazione\" (Chrome, Edge, Brave etc..) o tab \"Archiviazione\" (Firefox, Palemoon etc..)\n3. Vai nella sezione \"Cookies\" quindi nella sezione \"https://accounts.spotify.com\"", + "step_3": "Passo 3", + "success_emoji": "Successo🥳", + "success_message": "Ora hai correttamente effettuato il login al tuo account Spotify. Bel lavoro, amico!", + "step_4": "Passo 4", + "something_went_wrong": "Qualcosa è andato storto", + "piped_instance": "Istanza Server Piped", + "piped_description": "L'istanza server Piped da usare per il match della tracccia", + "piped_warning": "Alcune di queste non funzioneranno benen. Usa quindi a tuo rischio", + "generate_playlist": "Genera Playlist", + "track_exists": "La traccia {track} esiste già", + "replace_downloaded_tracks": "Sostituisci tutte le tracce scaricate", + "skip_download_tracks": "Salta lo scaricamento di tutte le tracce scaricate", + "do_you_want_to_replace": "Vuoi sovrascrivere la traccia esistente??", + "replace": "Sovrascrivi", + "skip": "Salta", + "select_up_to_count_type": "Seleziona fino a {count} {type}", + "select_genres": "Seleziona Generi", + "add_genres": "Aggiungi Generi", + "country": "Paese", + "number_of_tracks_generate": "Nnumero di tracce da generare", + "acousticness": "Acustica", + "danceability": "Ballabilità", + "energy": "Energia", + "instrumentalness": "Strumentalità", + "liveness": "Vitalità", + "loudness": "Sonorità", + "speechiness": "Loquacità", + "valence": "Valenza", + "popularity": "Popolarità", + "key": "Chiave", + "duration": "Durata (s)", + "tempo": "Tempo (BPM)", + "mode": "Modo", + "time_signature": "Indicazione di tempo", + "short": "Corta", + "medium": "Media", + "long": "Lunga", + "min": "Min", + "max": "Max", + "target": "Obiettivo", + "moderate": "Moderato", + "deselect_all": "Deseleziona Tutto", + "select_all": "Seleziona Tutto", + "are_you_sure": "Sei certo?", + "generating_playlist": "Generazione delle tue playlist custom...", + "selected_count_tracks": "{count} tracce selezionate", + "download_warning": "Se scarichi tutte le Tracce in massa stai chiaramente piratando Musica e causando un danno alla società creativa della Musica. Spero che tu sia cosciente di questo. Cerca di rispettare e supportare sempre il duro lavoro degli Artisti", + "download_ip_ban_warning": "A proposito, il tuo IP può essere bloccato da YouTube per il numero di richieste di download eccessive rispetto la norma. Il blocco IP significa che non puoi usare YoutTube (anche hai effettuato l'accesso) per almeno 2-3 mesi dal dispositivo con questo IP. Spotube non ha responsabilità se questo dovesse accadere", + "by_clicking_accept_terms": "Cliccando su 'accetta' concordi con i seguenti termini:", + "download_agreement_1": "So che sto piratando Musica. Sono cattivo", + "download_agreement_2": "Supporterò l'Artista come potrò e sto facendo questo solo perchè non ho denaro per acquistare il suo prodotto dell'ingegno", + "download_agreement_3": "Sono completamente cosciente che il mio IP può essere bloccato da YouTube & non riterrò responsabili Spotube o i suoi autori/contributori per ogni inconveniente causato dalla mia azione corrente", + "decline": "Declino", + "accept": "Accetto", + "details": "Dettagli", + "youtube": "YouTube", + "channel": "Canale", + "likes": "Mi Piace", + "dislikes": "Non Mi Piace", + "views": "Viste", + "streamUrl": "URL dello streaming", + "stop": "Stop", + "sort_newest": "Ordina per nuovi aggiunti", + "sort_oldest": "Ordina per aggiunta più vecchia", + "sleep_timer": "Timer Dormire", + "mins": "{minutes} Minuti", + "hours": "{hours} Ore", + "hour": "{hours} Ora", + "custom_hours": "Orari Personalizzati", + "logs": "Log", + "developers": "Sviluppatori", + "not_logged_in": "Non hai effettuato l'accesso", + "search_mode": "Modalità Ricerca", + "youtube_api_type": "Tipo API", + "ok": "Ok", + "failed_to_encrypt": "Criptazione fallita", + "encryption_failed_warning": "Spotube usa la criptazione per memorizzare in modo sicuro i dati. Ma ha fallito a farlo. Passerà quindi in ripiego alla memorizzazione non siscura\nSe stai usando Linux assicurati di avere un servizio di segretezza installato (gnome-keyring, kde-wallet, keepassxc etc)", + "querying_info": "Richiesta informazioni...", + "piped_api_down": "Le Piped API non funzionano", + "piped_down_error_instructions": "L'istanza di Piped {pipedInstance} è correntemente offline\n\nCambia istanza o cambia 'Tipo API' alle API ufficiali YouTube\n\nAssicurati di riavviare l'app dopo il cambio", + "you_are_offline": "Sei correntemente offline", + "connection_restored": "Connessione ad internet ripristinata", + "use_system_title_bar": "Usa la barra del titolo di sistema", + "crunching_results": "Elaborazione risultati...", + "search_to_get_results": "Cerca per ottenere risultati", + "use_amoled_mode": "Usa modalità AMOLED", + "pitch_dark_theme": "Tema nero profondo", + "normalize_audio": "Normalizza audio", + "change_cover": "Cambia copertina", + "add_cover": "Aggiungi copertina", + "restore_defaults": "Ripristina default", + "download_music_codec": "Codec musicale scaricamento", + "streaming_music_codec": "Codec musicale streaming", + "login_with_lastfm": "Accesso a Last.fm", + "connect": "Connetti", + "disconnect_lastfm": "Disconnetti Last.fm", + "disconnect": "Disconnetti", + "username": "Nome utente", + "password": "Password", + "login": "Accesso", + "login_with_your_lastfm": "Accedi con il tuo account Last.fm", + "scrobble_to_lastfm": "Invia a Last.fm", + "audio_source": "Fonte audio", + "go_to_album": "Vai all'album", + "discord_rich_presence": "Presenza ricca di Discord", + "browse_all": "Esplora tutto", + "genres": "Generi", + "explore_genres": "Esplora generi" +} \ No newline at end of file diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 50c9369f..39e0dad8 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -175,11 +175,9 @@ "step_2": "ステップ 2", "step_2_steps": "1. ログインしたら、F12を押すか、マウス右クリック > 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます", "step_3": "ステップ 3", - "step_3_steps": "sp_dc と sp_key (または or sp_gaid) の値 (Value) をコピーします", "success_emoji": "成功🥳", "success_message": "アカウントへのログインに成功しました。よくできました!", "step_4": "ステップ 4", - "step_4_steps": "コピーした sp_dc と sp_key (または or sp_gaid) の値をそれぞれの入力欄に貼り付けます", "something_went_wrong": "何か誤りがあります", "piped_instance": "Piped サーバーのインスタンス", "piped_description": "曲の一致に使う Piped サーバーのインスタンス", @@ -249,7 +247,7 @@ "developers": "開発", "not_logged_in": "ログインしていません", "search_mode": "検索モード", - "youtube_api_type": "APIの種類", + "audio_source": "音声ソース", "ok": "分かりました", "failed_to_encrypt": "暗号化に失敗しました", "encryption_failed_warning": "Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください", @@ -279,5 +277,10 @@ "password": "パスワード", "login": "ログインする", "login_with_your_lastfm": "あなたのLast.fmアカウントでログインする", - "scrobble_to_lastfm": "Last.fmにスクロブルする" + "scrobble_to_lastfm": "Last.fmにスクロブルする", + "go_to_album": "アルバムに移動", + "discord_rich_presence": "ディスコードリッチプレゼンス", + "browse_all": "すべてを閲覧", + "genres": "ジャンル", + "explore_genres": "ジャンルを探索" } \ No newline at end of file diff --git a/lib/l10n/app_ne.arb b/lib/l10n/app_ne.arb new file mode 100644 index 00000000..9fca9ea4 --- /dev/null +++ b/lib/l10n/app_ne.arb @@ -0,0 +1,288 @@ +{ + "guest": "अतिथि", + "browse": "ब्राउज़ गर्नुहोस्", + "search": "खोजी गर्नुहोस्", + "library": "पुस्तकालय", + "lyrics": "गीतको शब्द", + "settings": "सेटिङ", + "genre_categories_filter": "शैली वा शैलीहरू फिल्टर गर्नुहोस्...", + "genre": "शैली", + "personalized": "व्यक्तिगत", + "featured": "विशेष", + "new_releases": "नयाँ रिलिज", + "songs": "गीतहरू", + "playing_track": "{track} बज्यो", + "queue_clear_alert": "यो हालको कतारलाई हटाउँछ। {track_length} ट्र्याकहरू हटाईन्छ\nके तपाईं जारी राख्न चाहनुहुन्छ?", + "load_more": "थप लोड गर्नुहोस्", + "playlists": "प्लेलिस्टहरू", + "artists": "कलाकारहरू", + "albums": "आल्बमहरू", + "tracks": "ट्र्याकहरू", + "downloads": "डाउनलोडहरू", + "filter_playlists": "तपाईंको प्लेलिस्टहरू फिल्टर गर्नुहोस्...", + "liked_tracks": "मन परेका ट्र्याकहरू", + "liked_tracks_description": "तपाईंको मन परेका सबै ट्र्याकहरू", + "create_playlist": "प्लेलिस्ट बनाउनुहोस्", + "create_a_playlist": "प्लेलिस्ट बनाउनुहोस्", + "update_playlist": "प्लेलिस्ट अपडेट गर्नुहोस्", + "create": "बनाउनुहोस्", + "cancel": "रद्द गर्नुहोस्", + "update": "अपडेट गर्नुहोस्", + "playlist_name": "प्लेलिस्टको नाम", + "name_of_playlist": "प्लेलिस्टको नाम", + "description": "विवरण", + "public": "सार्वजनिक", + "collaborative": "सहकारी", + "search_local_tracks": "स्थानीय ट्र्याकहरू खोजी गर्नुहोस्...", + "play": "बजाउनुहोस्", + "delete": "मेटाउनुहोस्", + "none": "कुनै पनि होइन", + "sort_a_z": "A-Zमा क्रमबद्ध गर्नुहोस्", + "sort_z_a": "Z-Aमा क्रमबद्ध गर्नुहोस्", + "sort_artist": "कलाकारबाट क्रमबद्ध गर्नुहोस्", + "sort_album": "आल्बमबाट क्रमबद्ध गर्नुहोस्", + "sort_tracks": "ट्र्याकहरूलाई क्रमबद्ध गर्नुहोस्", + "currently_downloading": "हाल डाउनलोड गर्दैछ ({tracks_length})", + "cancel_all": "सब रद्द गर्नुहोस्", + "filter_artist": "कलाकारहरूलाई फिल्टर गर्नुहोस्...", + "followers": "{followers} अनुयायीहरू", + "add_artist_to_blacklist": "कलाकारलाई कालोसूचीमा थप्नुहोस्", + "top_tracks": "शीर्ष ट्र्याकहरू", + "fans_also_like": "अनुयायीहरू पनि लाइक गर्छन्", + "loading": "लोड हुँदैछ...", + "artist": "कलाकार", + "blacklisted": "कालोसूचीमा", + "following": "फल्लो गर्दै", + "follow": "फल्लो गर्नुहोस्", + "artist_url_copied": "कलाकार URL क्लिपबोर्डमा प्रतिलिपि गरिएको छ", + "added_to_queue": "{tracks} ट्र्याकहरूलाई कतारमा थपिएको छ", + "filter_albums": "आल्बमहरूलाई फिल्टर गर्नुहोस्...", + "synced": "सिङ्क गरिएको", + "plain": "साधा", + "shuffle": "शफल", + "search_tracks": "ट्र्याकहरू खोजी गर्नुहोस्...", + "released": "रिलिज गरिएको", + "error": "त्रुटि {error}", + "title": "शीर्षक", + "time": "समय", + "more_actions": "थप कार्यहरू", + "download_count": "डाउनलोड ({count})", + "add_count_to_playlist": "प्लेलिस्टमा थप्नुहोस् ({count})", + "add_count_to_queue": "कतारमा थप्नुहोस् ({count})", + "play_count_next": "प्लेगरी गर्नुहोस् ({count})", + "album": "आल्बम", + "copied_to_clipboard": "{data} क्लिपबोर्डमा प्रतिलिपि गरिएको छ", + "add_to_following_playlists": "{track} लाई तलका प्लेलिस्टमा थप्नुहोस्", + "add": "थप्नुहोस्", + "added_track_to_queue": "{track} लाई कतारमा थपिएको छ", + "add_to_queue": "कतारमा थप्नुहोस्", + "track_will_play_next": "{track} अरूलाई पहिलोमा बज्नेछ", + "play_next": "पछिबजाउनुहोस्", + "removed_track_from_queue": "{track} लाई कतारबाट हटाइएको छ", + "remove_from_queue": "कतारबाट हटाउनुहोस्", + "remove_from_favorites": "पसन्दीदामा बाट हटाउनुहोस्", + "save_as_favorite": "पसन्दीदा बनाउनुहोस्", + "add_to_playlist": "प्लेलिस्टमा थप्नुहोस्", + "remove_from_playlist": "प्लेलिस्टबाट हटाउनुहोस्", + "add_to_blacklist": "कालोसूचीमा थप्नुहोस्", + "remove_from_blacklist": "कालोसूचीबाट हटाउनुहोस्", + "share": "साझा गर्नुहोस्", + "mini_player": "मिनि प्लेयर", + "slide_to_seek": "अगाडि वा पछाडि खोजी गर्नका लागि स्लाइड गर्नुहोस्", + "shuffle_playlist": "प्लेलिस्ट शफल गर्नुहोस्", + "unshuffle_playlist": "प्लेलिस्ट शफल नगर्नुहोस्", + "previous_track": "पूर्व ट्र्याक", + "next_track": "अरू ट्र्याक", + "pause_playback": "प्लेब्याक रोक्नुहोस्", + "resume_playback": "प्लेब्याक पुनः सुरु गर्नुहोस्", + "loop_track": "ट्र्याकलाई दोहोरोपट्टी बजाउनुहोस्", + "repeat_playlist": "प्लेलिस्ट पुनः बजाउनुहोस्", + "queue": "कतार", + "alternative_track_sources": "वैकल्पिक ट्र्याक स्रोतहरू", + "download_track": "ट्र्याक डाउनलोड गर्नुहोस्", + "tracks_in_queue": "कतारमा {tracks} ट्र्याकहरू", + "clear_all": "सब मेटाउनुहोस्", + "show_hide_ui_on_hover": "हवर गरेपछि UI देखाउनुहोस्/लुकाउनुहोस्", + "always_on_top": "सधैं टपमा राख्नुहोस्", + "exit_mini_player": "मिनि प्लेयर बाट बाहिर निस्कनुहोस्", + "download_location": "डाउनलोड स्थान", + "account": "खाता", + "login_with_spotify": "तपाईंको Spotify खातासँग लगइन गर्नुहोस्", + "connect_with_spotify": "Spotify सँग जडान गर्नुहोस्", + "logout": "बाहिर निस्कनुहोस्", + "logout_of_this_account": "यो खाताबाट बाहिर निस्कनुहोस्", + "language_region": "भाषा र क्षेत्र", + "language": "भाषा", + "system_default": "सिस्टम पूर्वनिर्धारित", + "market_place_region": "बजार स्थान", + "recommendation_country":"सिफारिस गरिएको देश", + "appearance": "दृष्टिकोण", + "layout_mode": "लेआउट मोड", + "override_layout_settings": "अनुकूलित प्रतिकृयात्मक लेआउट मोड सेटिङ्गहरू", + "adaptive": "अनुकूलित", + "compact": "संकुचित", + "extended": "बढाइएको", + "theme": "थिम", + "dark": "गाढा", + "light": "प्रकाश", + "system": "सिस्टम", + "accent_color": "एक्सेन्ट रङ्ग", + "sync_album_color": "एल्बम रङ्ग सिङ्क गर्नुहोस्", + "sync_album_color_description": "एल्बम कला को प्रमुख रङ्गलाई एक्सेन्ट रङ्गको रूपमा प्रयोग गर्दछ", + "playback": "प्लेब्याक", + "audio_quality": "आडियो गुणस्तर", + "high": "उच्च", + "low": "न्यून", + "pre_download_play": "पूर्व-डाउनलोड र प्ले गर्नुहोस्", + "pre_download_play_description": "आडियो स्ट्रिम गर्नु नगरी बाइटहरू डाउनलोड गरी बजाउँछ (उच्च ब्यान्डविथ उपयोगकर्ताहरूको लागि सिफारिस गरिएको)", + "skip_non_music": "गीतहरू बाहेक कुनै अनुष्ठान छोड्नुहोस् (स्पन्सरब्लक)", + "blacklist_description": "कालोसूची गीत र कलाकारहरू", + "wait_for_download_to_finish": "कृपया हालको डाउनलोड समाप्त हुन लागि पर्खनुहोस्", + "desktop": "डेस्कटप", + "close_behavior": "बन्द व्यवहार", + "close": "बन्द गर्नुहोस्", + "minimize_to_tray": "ट्रेमा कम गर्नुहोस्", + "show_tray_icon": "सिस्टम ट्रे आइकन देखाउनुहोस्", + "about": "बारेमा", + "u_love_spotube": "हामीले थाहा पारेका छौं तपाईंलाई Spotube मन पर्छ", + "check_for_updates": "अपडेटहरूको लागि जाँच गर्नुहोस्", + "about_spotube": "Spotube को बारेमा", + "blacklist": "कालोसूची", + "please_sponsor": "कृपया स्पन्सर/डोनेट गर्नुहोस्", + "spotube_description": "Spotube, एक हल्का, समृद्ध, स्वतन्त्र Spotify क्लाइयन", + "version": "संस्करण", + "build_number": "निर्माण नम्बर", + "founder": "संस्थापक", + "repository": "पुनरावलोकन स्थल", + "bug_issues": "त्रुटि + समस्याहरू", + "made_with": "❤️ 2021-2024 बाट बनाइएको", + "kingkor_roy_tirtho": "किङ्कोर राय तिर्थो", + "copyright": "© 2021-{current_year} किङ्कोर राय तिर्थो", + "license": "लाइसेन्स", + "add_spotify_credentials": "सुरु हुनका लागि तपाईंको स्पटिफाई क्रेडेन्शियल थप्नुहोस्", + "credentials_will_not_be_shared_disclaimer": "चिन्ता नगर्नुहोस्, तपाईंको कुनै पनि क्रेडेन्शियलहरूले कसैले संग्रह वा साझा गर्नेछैन", + "know_how_to_login": "कसरी लगिन गर्ने भन्ने थाहा छैन?", + "follow_step_by_step_guide": "चरणबद्ध मार्गदर्शनमा साथी बनाउनुहोस्", + "spotify_cookie": "Spotify {name} कुकी", + "cookie_name_cookie": "{name} कुकी", + "fill_in_all_fields": "कृपया सबै क्षेत्रहरू भर्नुहोस्", + "submit": "पेश गर्नुहोस्", + "exit": "बाहिर निस्कनुहोस्", + "previous": "पूर्ववत", + "next": "अरू", + "done": "गरिएको", + "step_1": "कदम 1", + "first_go_to": "पहिलो, जानुहोस्", + "login_if_not_logged_in": "र लगइन/साइनअप गर्नुहोस् जुन तपाईंले लगइन गरेनन्", + "step_2": "कदम 2", + "step_2_steps": "1. एकबार तपाईं लगइन गरे पछि, F12 थिच्नुहोस् वा माउस राइट क्लिक गर्नुहोस् > इन्स्पेक्ट गर्नुहोस् भने ब्राउजर डेभटुलहरू खुलाउनका लागि।\n2. तपाईंको \"एप्लिकेसन\" ट्याबमा जानुहोस् (Chrome, Edge, Brave इत्यादि) वा \"स्टोरेज\" ट्याबमा जानुहोस् (Firefox, Palemoon इत्यादि)\n3. तपाईंको इन्सेक्ट गरेको ब्राउजर डेभटुलहरूमा \"कुकीहरू\" खण्डमा जानुहोस् अनि \"https://accounts.spotify.com\" उपकोणमा जानुहोस्", + "step_3": "कदम 3", + "step_3_steps": "\"sp_dc\" र \"sp_key\" (वा sp_gaid) कुकीहरूको मानहरू प्रतिलिपि गर्नुहोस्", + "success_emoji": "सफलता 🥳", + "success_message": "हाम्रो सानो भाइ, अब तपाईं सफलतापूर्वक आफ्नो Spotify खातामा लगइन गरेका छौं। राम्रो काम गरेको!", + "step_4": "कदम 4", + "step_4_steps": "प्रतिलिपि गरेको \"sp_dc\" र \"sp_key\" (वा sp_gaid) मानहरूलाई आफ्नो ठाउँमा पेस्ट गर्नुहोस्", + "something_went_wrong": "केहि गल्ति भएको छ", + "piped_instance": "पाइपड सर्भर इन्स्ट्यान्स", + "piped_description": "गीत मिलाउको लागि प्रयोग गर्ने पाइपड सर्भर इन्स्ट्यान्स", + "piped_warning": "तिनीहरूमध्ये केहि ठिक गर्न सक्छ। यसलाई आफ्नो जोखिममा प्रयोग गर्नुहोस्", + "generate_playlist": "प्लेलिस्ट बनाउनुहोस्", + "track_exists": "ट्र्याक {track} पहिले नै छ", + "replace_downloaded_tracks": "सबै डाउनलोड गरिएका ट्र्याकहरूलाई परिवर्तन गर्नुहोस्", + "skip_download_tracks": "सबै डाउनलोड गरिएका ट्र्याकहरूलाई छोड्नुहोस्", + "do_you_want_to_replace": "के तपाईंले वर्तमान ट्र्याकलाई परिवर्तन गर्न चाहनुहुन्छ?", + "replace": "परिवर्तन गर्नुहोस्", + "skip": "छोड्नुहोस्", + "select_up_to_count_type": "{count} {type} सम्म चयन गर्नुहोस्", + "select_genres": "जनरहरू चयन गर्नुहोस्", + "add_genres": "जनरहरू थप्नुहोस्", + "country": "देश", + "number_of_tracks_generate": "बनाउनका लागि ट्र्याकहरूको संख्या", + "acousticness": "एकोस्टिकनेस", + "danceability": "नृत्यक्षमता", + "energy": "ऊर्जा", + "instrumentalness": "साजा रहेकोता", + "liveness": "प्राणिकता", + "loudness": "शोर", + "speechiness": "भाषण", + "valence": "मानसिक स्वभाव", + "popularity": "लोकप्रियता", + "key": "कुञ्जी", + "duration": "अवधि (सेकेण्ड)", + "tempo": "गति (बीपीएम)", + "mode": "मोड", + "time_signature": "समय हस्ताक्षर", + "short": "सानो", + "medium": "मध्यम", + "long": "लामो", + "min": "न्यून", + "max": "अधिक", + "target": "लक्ष्य", + "moderate": "मध्यस्थ", + "deselect_all": "सबै छान्नुहोस्", + "select_all": "सबै चयन गर्नुहोस्", + "are_you_sure": "के तपाईं सुनिश्चित हुनुहुन्छ?", + "generating_playlist": "तपाईंको विशेष प्लेलिस्ट बनाइएको छ...", + "selected_count_tracks": "{count} ट्र्याकहरू छन् चयन गरिएका", + "download_warning": "यदि तपाईं सबै ट्र्याकहरूलाई बल्कमा डाउनलोड गर्छनु हो भने तपाईं स्पष्ट रूपमा साङ्गीत चोरी गरिरहेका छन् र यो साङ्गीतको रचनात्मक समाजलाई क्षति पनि पुर्याउँछ। उमेराइएको छ कि तपाईं यसको बारेमा जागरूक छिनुहुन्छ। सधैं, कला गर्दै र कलाकारको कडा परम्परा समर्थन गर्दै आइन्छ।", + "download_ip_ban_warning": "बितिएका डाउनलोड अनुरोधहरूका कारण तपाईंको आइपीले YouTube मा ब्लक हुन सक्छ। आइपी ब्लक भनेको कम्तीमा 2-3 महिनासम्म तपाईं त्यस आइपी यन्त्रबाट YouTube प्रयोग गर्न सक्नुहुन्छ। र यदि यो हुँदैछ भने स्पट्यूबले यसलाई कसैले गरेको बारेमा कुनै दायित्व लिन्छैन।", + "by_clicking_accept_terms": "'स्वीकृत' गरेर तपाईं निम्नलिखित निर्वाचन गर्दैछिन्:", + "download_agreement_1": "म मन्ने छु कि म साङ्गीत चोरी गरिरहेको छु। म बुरो हुँ", + "download_agreement_2": "म कहिल्यै कहिल्यै तिनीहरूलाई समर्थन गर्नेछु र म यो तिनीहरूको कला किन्ने पैसा छैन भने मा मात्र यो गरेको छु", + "download_agreement_3": "म पूरा रूपमा जान्छु कि मेरो आइपी YouTube मा ब्लक हुन सक्छ र म मन्छेहरूले मेरो चासोबाट भएको कुनै दुर्घटनामा स्पट्यूब वा तिनीहरूको मालिकहरू/सहयोगीहरूलाई दायित्वी ठान्छुँभन्ने पूर्ण जानकारी छैन", + "decline": "अस्वीकृत", + "accept": "स्वीकृत", + "details": "विवरण", + "youtube": "YouTube", + "channel": "च्यानल", + "likes": "लाइकहरू", + "dislikes": "असुनुहरू", + "views": "हेरिएको", + "streamUrl": "स्ट्रिम यूआरएल", + "stop": "रोक्नुहोस्", + "sort_newest": "नयाँ थपिएकोमा क्रमबद्ध गर्नुहोस्", + "sort_oldest": "पुरानो थपिएकोमा क्रमबद्ध गर्नुहोस्", + "sleep_timer": "सुत्ने टाइमर", + "mins": "{minutes} मिनेटहरू", + "hours": "{hours} घण्टाहरू", + "hour": "{hours} घण्टा", + "custom_hours": "कस्टम घण्टाहरू", + "logs": "लगहरू", + "developers": "डेभेलपर्स", + "not_logged_in": "तपाईंले लगइन गरेका छैनौं", + "search_mode": "खोज मोड", + "audio_source": "अडियो स्रोत", + "ok": "ठिक छ", + "failed_to_encrypt": "एन्क्रिप्ट गर्न सकिएन", + "encryption_failed_warning": "स्पट्यूबले तपाईंको डेटा सुरक्षित रूपमा स्टोर गर्नका लागि एन्क्रिप्ट गर्न खोजेको छ। तर यसले गरेको छैन। यसले असुरक्षित स्टोरेजमा फल्लब्याक गर्दछ\nयदि तपाईंले लिनक्स प्रयोग गरिरहेका छन् भने कृपया सुनिश्चित गर्नुहोस् कि तपाईंले कुनै सीक्रेट-सर्भिस (गोनोम-किरिङ, केडीइ-वालेट, किपासेक्ससि इत्यादि) इन्स्टल गरेका छौं", + "querying_info": "जानकारी हेर्दै...", + "piped_api_down": "पाइपड एपीआई डाउन छ", + "piped_down_error_instructions": "पाइपड इन्स्ट्यान्स {pipedInstance} हाल डाउन छ\n\nजीसनै इन्स्ट्यान्स परिवर्तन गर्नुहोस् वा 'एपीआई प्रकार' लाइ YouTube आफिसियल एपीआईमा परिवर्तन गर्नुहोस्\n\nपरिवर्तनपछि एप्लिकेसन पुन: सुरु गर्नुहोस्", + "you_are_offline": "तपाईं वर्तमान अफलाइन हुनुहुन्छ", + "connection_restored": "तपाईंको इन्टरनेट कनेक्सन पुन: स्थापित भएको छ", + "use_system_title_bar": "सिस्टम शीर्षक पट्टी प्रयोग गर्नुहोस्", + "crunching_results": "परिणामहरू कपालबाट पीस्दै...", + "search_to_get_results": "परिणामहरू प्राप्त गर्नका लागि खोज्नुहोस्", + "use_amoled_mode": "कृष्ण ब्ल्याक गाढा थिम प्रयोग गर्नुहोस्", + "pitch_dark_theme": "एमोलेड मोड", + "normalize_audio": "अडियो सामान्य गर्नुहोस्", + "change_cover": "कवर परिवर्तन गर्नुहोस्", + "add_cover": "कवर थप्नुहोस्", + "restore_defaults": "पूर्वनिर्धारितहरू पुनः स्थापित गर्नुहोस्", + "download_music_codec": "साङ्गीत कोडेक डाउनलोड गर्नुहोस्", + "streaming_music_codec": "स्ट्रिमिङ साङ्गीत कोडेक", + "login_with_lastfm": "लास्ट.एफ.एम सँग लगइन गर्नुहोस्", + "connect": "जडान गर्नुहोस्", + "disconnect_lastfm": "लास्ट.एफ.एम डिसकनेक्ट गर्नुहोस्", + "disconnect": "डिसकनेक्ट", + "username": "प्रयोगकर्ता नाम", + "password": "पासवर्ड", + "login": "लगइन", + "login_with_your_lastfm": "तपाईंको लास्ट.एफ.एम खातामा लगइन गर्नुहोस्", + "scrobble_to_lastfm": "लास्ट.एफ.एम मा स्क्रबल गर्नुहोस्", + "go_to_album": "आल्बममा जानुहोस्", + "discord_rich_presence": "डिस्कर्ड धनी उपस्थिति", + "browse_all": "सबै हेर्नुहोस्", + "genres": "शैलीहरू", + "explore_genres": "शैलीहरू अन्वेषण गर्नुहोस्" +} \ No newline at end of file diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb new file mode 100644 index 00000000..23eee51b --- /dev/null +++ b/lib/l10n/app_nl.arb @@ -0,0 +1,287 @@ +{ + "guest": "Gast", + "browse": "Bladeren", + "search": "Zoek op", + "library": "Bibliotheek", + "lyrics": "Liedteksten", + "settings": "Instellingen", + "genre_categories_filter": "Categorieën of genres filteren...", + "genre": "Genre", + "personalized": "Gepersonaliseerd", + "featured": "Aanbevolen", + "new_releases": "Nieuwe uitgaves", + "songs": "Liedjes", + "playing_track": "{track} afspelen", + "queue_clear_alert": "Dit zal de huidige wachtrij wissen. {track_length} tracks worden verwijderd\nWilt u doorgaan?", + "load_more": "Meer laden", + "playlists": "Afspeellijsten", + "artists": "Kunstenaars", + "albums": "Albums", + "tracks": "Nummers", + "downloads": "Downloads", + "filter_playlists": "Filter uw afspeellijsten...", + "liked_tracks": "Geliefde tracks", + "liked_tracks_description": "Al je favoriete nummers", + "create_playlist": "Afspeellijst maken", + "create_a_playlist": "Een afspeellijst maken", + "update_playlist": "Afspeellijst bijwerken", + "create": "Maak", + "cancel": "Annuleren", + "update": "Bijwerken", + "playlist_name": "Afspeellijstnaam", + "name_of_playlist": "Naam van de afspeellijst", + "description": "Beschrijving", + "public": "Openbaar", + "collaborative": "Samenwerkend", + "search_local_tracks": "Lokale nummers zoeken...", + "play": "Speel", + "delete": "Wissen", + "none": "Geen", + "sort_a_z": "Sorteren op A-Z", + "sort_z_a": "Sorteren op Z-A", + "sort_artist": "Sorteren op kunstenaar", + "sort_album": "Sorteren op album", + "sort_tracks": "Nummers sorteren", + "currently_downloading": "Momenteel aan het downloaden ({tracks_length})", + "cancel_all": "Alle annuleren", + "filter_artist": "Kunstenaars filteren...", + "followers": "{followers} volgers", + "add_artist_to_blacklist": "Kunstenaar toevoegen aan zwarte lijst", + "top_tracks": "Topsporen", + "fans_also_like": "Liefhebbers willen ook", + "loading": "Aan het laden...", + "artist": "Kunstenaar", + "blacklisted": "Op de zwarte lijst", + "following": "Op volg", + "follow": "Volgen", + "artist_url_copied": "URL artiest gekopieerd naar klembord", + "added_to_queue": "{tracks} tracks toegevoegd aan wachtrij", + "filter_albums": "Albums filteren...", + "synced": "Gesynchroniseerd", + "plain": "Eenvoudig", + "shuffle": "Schuifelen", + "search_tracks": "Zoek nummers...", + "released": "Vrijgegeven", + "error": "Fout {error}", + "title": "Titel", + "time": "Tijd", + "more_actions": "Meer acties", + "download_count": "({count}) downloads", + "add_count_to_playlist": "Voeg ({count}) toe aan afspeellijst", + "add_count_to_queue": "Voeg ({count}) toe aan wachtrij", + "play_count_next": "Speel ({count}) volgende", + "album": "Album", + "copied_to_clipboard": "{data} naar klembord gekopieerd", + "add_to_following_playlists": "Voeg {track} toe aan volgende afspeellijsten", + "add": "Toevoegen", + "added_track_to_queue": "{track} toegevoegd aan wachtrij", + "add_to_queue": "Toevoegen aan wachtrij", + "track_will_play_next": "{track} zal hierna spelen", + "play_next": "Volgende afspelen", + "removed_track_from_queue": "{track} uit wachtrij verwijderd", + "remove_from_queue": "Verwijderen uit wachtrij", + "remove_from_favorites": "Verwijderen uit favorieten", + "save_as_favorite": "Opslaan als favoriet", + "add_to_playlist": "Toevoegen aan afspeellijst", + "remove_from_playlist": "Verwijderen uit afspeellijst", + "add_to_blacklist": "Toevoegen aan zwarte lijst", + "remove_from_blacklist": "Verwijderen uit zwarte lijst", + "share": "Delen", + "mini_player": "Minispeler", + "slide_to_seek": "Schuif om vooruit of achteruit te zoeken", + "shuffle_playlist": "Afspeellijst schuifelen", + "unshuffle_playlist": "Afspeellijst onschuifelen", + "previous_track": "Vorige nummer", + "next_track": "Volgende nummer", + "pause_playback": "Weergave pauzeren", + "resume_playback": "Weergave hervatten", + "loop_track": "Nummer loopen", + "repeat_playlist": "Afspeellijst herhalen", + "queue": "Wachtrij", + "alternative_track_sources": "Alternatieve nummerbronnen", + "download_track": "Nummer downloaden", + "tracks_in_queue": "{tracks} tracks in wachtrij", + "clear_all": "Wis alles", + "show_hide_ui_on_hover": "UI tonen/verbergen bij zweven", + "always_on_top": "Altijd bovenaan", + "exit_mini_player": "Minispeler afsluiten", + "download_location": "Downloadlocatie", + "account": "Account", + "login_with_spotify": "Inloggen met je Spotify-account", + "connect_with_spotify": "Verbinden met Spotify", + "logout": "Afmelden", + "logout_of_this_account": "Afmelden van dit account", + "language_region": "Taal & Regio", + "language": "Taal", + "system_default": "Systeemstandaard", + "market_place_region": "Marktplaats-regio", + "recommendation_country": "Aanbeveling Land", + "appearance": "Uiterlijk", + "layout_mode": "Opmaakmodus", + "override_layout_settings": "Instellingen voor responsieve opmaakmodus opheffen", + "adaptive": "Aanpassingsgericht", + "compact": "Compact", + "extended": "Uitgebreide", + "theme": "Thema", + "dark": "Donker", + "light": "Licht", + "system": "Systeem", + "accent_color": "Accentkleur", + "sync_album_color": "Albumkleur synchroniseren", + "sync_album_color_description": "Gebruikt de overheersende kleur van het albumartikel als accentkleur", + "playback": "Weergave", + "audio_quality": "Audiokwaliteit", + "high": "Hoog", + "low": "Laag", + "pre_download_play": "Vooraf downloaden en spelen", + "pre_download_play_description": "In plaats van audio te streamen, kun je bytes downloaden en afspelen (aanbevolen voor gebruikers met een hogere bandbreedte)", + "skip_non_music": "Niet-muzieksegmenten overslaan (SponsorBlock)", + "blacklist_description": "Nummers en artiesten op de zwarte lijst", + "wait_for_download_to_finish": "Wacht tot de huidige download is voltooid", + "desktop": "Bureaublad", + "close_behavior": "Sluitgedrag", + "close": "Sluit af", + "minimize_to_tray": "Minimaliseren naar lade", + "show_tray_icon": "Systeemvakpictogram tonen", + "about": "Over", + "u_love_spotube": "We weten dat jullie van Spotube houden", + "check_for_updates": "Controleren op updates", + "about_spotube": "Over Spotube", + "blacklist": "Zwarte lijst", + "please_sponsor": "Sponsor/Doneer a.u.b.", + "spotube_description": "Spotube, een lichtgewicht, cross-platform, vrij-voor-alles Spotify-client", + "version": "Versie", + "build_number": "Beeldnummer", + "founder": "Stichter", + "repository": "Opslagplaats", + "bug_issues": "Bug+problemen", + "made_with": "Gemaakt met ❤️ in Bangladesh🇧🇩", + "kingkor_roy_tirtho": "Kingkor Roy Tirtho", + "copyright": "© 2021-{current_year} Kingkor Roy Tirtho", + "license": "Licentie", + "add_spotify_credentials": "Voeg je spotify-referenties toe om te beginnen", + "credentials_will_not_be_shared_disclaimer": "Maakt u geen zorgen, uw gegevens worden niet verzameld of gedeeld met anderen.", + "know_how_to_login": "Weet u niet hoe u dit moet doen?", + "follow_step_by_step_guide": "Volg de stap voor stap gids", + "spotify_cookie": "Spotify {name} Cookie", + "cookie_name_cookie": "{name} Cookie", + "fill_in_all_fields": "Vul alle velden in a.u.b.", + "submit": "Verzenden", + "exit": "Ga weg", + "previous": "Vorige", + "next": "Volgende", + "done": "Klaar", + "step_1": "Stap 1", + "first_go_to": "Ga eerst naar", + "login_if_not_logged_in": "en Inloggen/Aanmelden als u niet bent ingelogd", + "step_2": "Stap 2", + "step_2_steps": "1. Zodra je bent aangemeld, druk je op F12 of klik je met de rechtermuisknop > Inspect om de Browser devtools te openen.\n2. Ga vervolgens naar het tabblad \"Toepassing\" (Chrome, Edge, Brave enz..) of naar het tabblad \"Opslag\" (Firefox, Palemoon enz..).\n3. Ga naar de sectie \"Cookies\" en vervolgens naar de subsectie \"https://accounts.spotify.com\".", + "step_3": "Stap 3", + "success_emoji": "Succes🥳", + "success_message": "Je bent nu succesvol ingelogd met je Spotify account. Goed gedaan, maat!", + "step_4": "Stap 4", + "something_went_wrong": "Er ging iets mis", + "piped_instance": "Piped-serverinstantie", + "piped_description": "De Piped-serverinstantie die moet worden gebruikt voor het matchen van sporen", + "piped_warning": "Sommige werken misschien niet goed. Dus gebruik ze op eigen risico", + "generate_playlist": "Afspeellijst genereren", + "track_exists": "Nummer {track} bestaat al", + "replace_downloaded_tracks": "Alle gedownloade nummers vervangen", + "skip_download_tracks": "Downloaden van alle gedownloade nummers overslaan", + "do_you_want_to_replace": "Wil je de bestaande nummer vervangen?", + "replace": "Vervangen", + "skip": "Overslaan", + "select_up_to_count_type": "Selecteer tot {count} {type}", + "select_genres": "Genres selecteren", + "add_genres": "Genres toevoegen", + "country": "Land", + "number_of_tracks_generate": "Aantal nummers om te genereren", + "acousticness": "Akoesticiteit", + "danceability": "Dansbaarheid", + "energy": "Energie", + "instrumentalness": "Instrumentaliteit", + "liveness": "Levendigheid", + "loudness": "Luidheid", + "speechiness": "Sprakeligheid", + "valence": "Valentie", + "popularity": "Populariteit", + "key": "Sleutel", + "duration": "Tijdsduur (s)", + "tempo": "Tempo (SPM)", + "mode": "Modus", + "time_signature": "Tijdsnotatie", + "short": "Kort", + "medium": "Middel", + "long": "Lang", + "min": "Min", + "max": "Max", + "target": "Doel", + "moderate": "Matig", + "deselect_all": "Alles deselecteren", + "select_all": "Alles selecteren", + "are_you_sure": "Weet je het zeker?", + "generating_playlist": "Je aangepaste afspeellijst genereren...", + "selected_count_tracks": "{count} nummers geselecteerd", + "download_warning": "Als je alle Tracks in bulk downloadt, ben je duidelijk bezig met muziekpiraterij en breng je schade toe aan de creatieve muziekmaatschappij. Ik hoop dat je je hiervan bewust bent. Probeer altijd het harde werk van artiesten te respecteren en te steunen.", + "download_ip_ban_warning": "BTW, je IP-adres kan worden geblokkeerd op YouTube als gevolg van buitensporige downloadverzoeken dan normaal. IP blokkering betekent dat je YouTube niet kunt gebruiken (zelfs als je ingelogd bent) voor tenminste 2-3 maanden vanaf dat IP apparaat. Spotube is niet verantwoordelijk als dit ooit gebeurt.", + "by_clicking_accept_terms": "Door op 'accepteren' te klikken ga je akkoord met de volgende voorwaarden:", + "download_agreement_1": "Ik weet dat ik muziek illegaal verveel. Ik ben en crimineel.", + "download_agreement_2": "Ik steun de kunstenaar waar ik kan en ik doe dit alleen omdat ik geen geld heb om hun kunst te kopen.", + "download_agreement_3": "Ik ben me er volledig van bewust dat mijn IP geblokkeerd kan worden op YouTube & ik houd Spotube of zijn eigenaars/contributeurs niet verantwoordelijk voor ongelukken die veroorzaakt worden door mijn huidige actie.", + "decline": "Weigeren", + "accept": "Accepteren", + "details": "Bijzonderheden", + "youtube": "YouTube", + "channel": "Kanaal", + "likes": "Liefs", + "dislikes": "Hekels", + "views": "Weergaven", + "streamUrl": "Stream-URL", + "stop": "Stoppen", + "sort_newest": "Sorteren op nieuwste toegevoegd", + "sort_oldest": "Sorteren op oudste toegevoegd", + "sleep_timer": "Slaaptimer", + "mins": "{minutes} minuten", + "hours": "{hours} uren", + "hour": "{hours} uur", + "custom_hours": "Aangepaste uren", + "logs": "Logboeken", + "developers": "Ontwikkelaars", + "not_logged_in": "U bent niet aangemeld", + "search_mode": "Zoekmodus", + "youtube_api_type": "API-type", + "ok": "Oké", + "failed_to_encrypt": "Versleuteling mislukt", + "encryption_failed_warning": "Spotube gebruikt encryptie om je gegevens veilig op te slaan. Maar dat is niet gelukt. Dus zal het terugvallen op onveilige opslag.\nAls je linux gebruikt, zorg er dan voor dat je een geheim-dienst (gnome-keyring, kde-wallet, keepassxc etc) hebt geïnstalleerd.", + "querying_info": "Info opvragen...", + "piped_api_down": "Piped API is uit", + "piped_down_error_instructions": "De Piped-instantie {pipedInstance} is momenteel uitgevallen\n\nVerander de instantie of verander het 'API-type' naar de officiële YouTube API.\n\nZorg ervoor dat u de app herstart na de wijziging", + "you_are_offline": "U bent momenteel offline", + "connection_restored": "Uw internetverbinding is hersteld", + "use_system_title_bar": "Systeemtitelbalk gebruiken", + "crunching_results": "Resultaten kraken...", + "search_to_get_results": "Zoek om resultaten te krijgen", + "use_amoled_mode": "Pikzwart donkerthema", + "pitch_dark_theme": "AMOLED-modus", + "normalize_audio": "Audio normaliseren", + "change_cover": "Dekking wijzigen", + "add_cover": "Dekking toevoegen", + "restore_defaults": "Standaardwaarden herstellen", + "download_music_codec": "Muziek-codec downloaden", + "streaming_music_codec": "Muziek-codec streamen", + "login_with_lastfm": "Aanmelden met Last.fm", + "connect": "Verbinden", + "disconnect_lastfm": "Last.fm verbreken", + "disconnect": "Ontkoppelen", + "username": "Gebruikersnaam", + "password": "Wachtwoord", + "login": "Inloggen", + "login_with_your_lastfm": "Inloggen met uw Last.fm account", + "scrobble_to_lastfm": "Scrobbel naar Last.fm", + "audio_source": "Audiobron", + "go_to_album": "Ga naar album", + "discord_rich_presence": "Discord Rich Presence", + "browse_all": "Alles bekijken", + "genres": "Genres", + "explore_genres": "Verken genres" +} \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 1a946615..4ae48338 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -175,11 +175,9 @@ "step_2": "Krok 2", "step_2_steps": "1. Jeśli jesteś zalogowany, naciśnij klawisz F12 lub Kliknij prawym przyciskiem myszy > Zbadaj, aby odtworzyć narzędzia developerskie.\n2. Następnie przejdź do zakładki \"Application\" (Chrome, Edge, Brave etc..) lub zakładki \"Storage\" (Firefox, Palemoon etc..)\n3. Przejdź do sekcji \"Cookies\" a następnie do pod-sekcji \"https://accounts.spotify.com\"", "step_3": "Krok 3", - "step_3_steps": "Skopiuj wartości \"sp_dc\" i \"sp_key\" (lub sp_gaid) Ciasteczek", "success_emoji": "Sukces!🥳", "success_message": "Udało ci się zalogować! Dobra robota, stary!", "step_4": "Krok 4", - "step_4_steps": "Wklej wartości \"sp_dc\" i \"sp_key\" (lub sp_gaid) do odpowiednich pul.", "something_went_wrong": "Coś poszło nie tak 🙁", "piped_instance": "Instancja serwera Piped", "piped_description": "Instancja serwera Piped używana jest do dopasowania utworów.", @@ -249,7 +247,7 @@ "developers": "Developerzy", "not_logged_in": "Nie jesteś zalogowany", "search_mode": "Tryb szukania", - "youtube_api_type": "Typ API", + "audio_source": "Źródło dźwięku", "ok": "Ok", "failed_to_encrypt": "Nie można zaszyfrować :(", "encryption_failed_warning": "Spotube używa szyfrowania do bezpiecznego przechowywania danych. Ale nie udało się tego zrobić. Więc powróci do niezabezpieczonego przechowywania\nJeśli używasz Linuksa, upewnij się, że masz zainstalowane jakieś usługi do szyfrowania (gnome-keyring, kde-wallet, keepassxc itp.)", @@ -279,5 +277,10 @@ "password": "Hasło", "login": "Zaloguj", "login_with_your_lastfm": "Zaloguj się na swoje konto Last.fm", - "scrobble_to_lastfm": "Scrobbluj do Last.fm" + "scrobble_to_lastfm": "Scrobbluj do Last.fm", + "go_to_album": "Przejdź do albumu", + "discord_rich_presence": "Obecność na Discordzie", + "browse_all": "Przeglądaj wszystko", + "genres": "Gatunki muzyczne", + "explore_genres": "Eksploruj gatunki" } \ No newline at end of file diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 97df3db3..5ea4cca0 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -175,11 +175,9 @@ "step_2": "Passo 2", "step_2_steps": "1. Uma vez logado, pressione F12 ou clique com o botão direito do mouse > Inspecionar para abrir as ferramentas de desenvolvimento do navegador.\n2. Em seguida, vá para a guia \"Aplicativo\" (Chrome, Edge, Brave, etc.) ou \"Armazenamento\" (Firefox, Palemoon, etc.)\n3. Acesse a seção \"Cookies\" e depois a subseção \"https://accounts.spotify.com\"", "step_3": "Passo 3", - "step_3_steps": "Copie os valores dos Cookies \"sp_dc\" e \"sp_key\" (ou sp_gaid)", "success_emoji": "Sucesso🥳", "success_message": "Agora você está logado com sucesso em sua conta do Spotify. Bom trabalho!", "step_4": "Passo 4", - "step_4_steps": "Cole os valores copiados \"sp_dc\" e \"sp_key\" (ou sp_gaid) nos campos correspondentes", "something_went_wrong": "Algo deu errado", "piped_instance": "Instância do Servidor Piped", "piped_description": "A instância do servidor Piped a ser usada para correspondência de faixas", @@ -249,7 +247,7 @@ "developers": "Desenvolvedores", "not_logged_in": "Você não está logado", "search_mode": "Modo de Busca", - "youtube_api_type": "Tipo de API", + "audio_source": "Fonte de Áudio", "ok": "Ok", "failed_to_encrypt": "Falha ao criptografar", "encryption_failed_warning": "O Spotube usa criptografia para armazenar seus dados com segurança, mas falhou em fazê-lo. Portanto, ele voltará para o armazenamento não seguro.\nSe você estiver usando o Linux, certifique-se de ter algum serviço secreto (gnome-keyring, kde-wallet, keepassxc, etc.) instalado", @@ -279,5 +277,10 @@ "password": "Palavra-passe", "login": "Iniciar sessão", "login_with_your_lastfm": "Inicie sessão na sua conta Last.fm", - "scrobble_to_lastfm": "Scrobble para o Last.fm" + "scrobble_to_lastfm": "Scrobble para o Last.fm", + "go_to_album": "Ir para o álbum", + "discord_rich_presence": "Presença rica no Discord", + "browse_all": "Navegar por tudo", + "genres": "Gêneros", + "explore_genres": "Explorar gêneros" } \ No newline at end of file diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 098e73c7..24120d62 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -175,11 +175,9 @@ "step_2": "Шаг 2", "step_2_steps": "1. После входа в систему нажмите F12 или щелкните правой кнопкой мыши > «Проверить», чтобы открыть инструменты разработчика браузера.\n2. Затем перейдите на вкладку \"Application\" (Chrome, Edge, Brave и т.д..) or \"Storage\" (Firefox, Palemoon и т.д..)\n3. Перейдите в раздел \"Cookies\", а затем в подраздел \"https://accounts.spotify.com\"", "step_3": "Шаг 3", - "step_3_steps": "Скопируйте значения \"sp_dc\" и \"sp_key\" (или sp_gaid) Cookies", "success_emoji": "Успешно 🥳", "success_message": "Теперь вы успешно вошли в свою учетную запись Spotify. Отличная работа, приятель!", "step_4": "Шаг 4", - "step_4_steps": "Вставьте скопированные \"sp_dc\" и \"sp_key\" (или sp_gaid) значения в соответствующие поля", "something_went_wrong": "Что-то пошло не так", "piped_instance": "Экземпляр сервера Piped", "piped_description": "Серверный экземпляр Piped для сопоставления треков", @@ -249,7 +247,7 @@ "developers": "Разработчики", "not_logged_in": "Вы не выполнили вход", "search_mode": "Режим поиска", - "youtube_api_type": "Тип API", + "audio_source": "Источник аудио", "ok": "Ок", "failed_to_encrypt": "Не удалось зашифровать", "encryption_failed_warning": "Spotube использует шифрование для безопасного хранения ваших данных. Однако в этом случае произошла ошибка. Поэтому будет использовано небезопасное хранилище.\nЕсли вы используете Linux, убедитесь, что у вас установлен какой-либо инструмент для работы с секретами (gnome-keyring, kde-wallet, keepassxc и т.д.)", @@ -279,5 +277,10 @@ "password": "Пароль", "login": "Войти", "login_with_your_lastfm": "Войти в свою учетную запись Last.fm", - "scrobble_to_lastfm": "Скробблинг на Last.fm" + "scrobble_to_lastfm": "Скробблинг на Last.fm", + "go_to_album": "Перейти к альбому", + "discord_rich_presence": "Богатое присутствие в Discord", + "browse_all": "Просмотреть все", + "genres": "Жанры", + "explore_genres": "Исследовать жанры" } \ No newline at end of file diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 1d72ec85..e6b0ce34 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -177,11 +177,9 @@ "step_2": "2. Adım", "step_2_steps": "1. Giriş yaptıktan sonra, Tarayıcı devtools.\n2'yi açmak için F12'ye basın veya Fare Sağ Tıklaması > İncele'ye basın. Ardından \"Uygulama\" Sekmesine (Chrome, Edge, Brave vb.) veya \"Depolama\" Sekmesine (Firefox, Palemoon vb.) gidin\n3. \"Çerezler\" bölümüne ve ardından \"https://accounts.spotify.com\" alt bölümüne gidin", "step_3": "3. Adım", - "step_3_steps": "\"sp_dc\" ve \"sp_key\" (veya sp_gaid) Çerezlerinin değerlerini kopyalayın", "success_emoji": "Başarılı🥳", "success_message": "Şimdi Spotify hesabınızla başarılı bir şekilde oturum açtınız. İyi iş, dostum!", "step_4": "4. Adım", - "step_4_steps": "Kopyalanan \"sp_dc\" ve \"sp_key\" (veya sp_gaid) değerlerini ilgili alanlara yapıştırın", "something_went_wrong": "Bir şeyler ters gitti", "piped_instance": "Piped Sunucu Örneği", "piped_description": "Parça eşleştirme için kullanılacak Piped sunucu örneği", @@ -251,7 +249,7 @@ "developers": "Geliştiriciler", "not_logged_in": "Giriş yapmadınız", "search_mode": "Arama Modu", - "youtube_api_type": "API Türü", + "audio_source": "Ses Kaynağı", "ok": "Tamam", "failed_to_encrypt": "Şifreleme başarısız oldu", "encryption_failed_warning": "Spotube, verilerinizi güvenli bir şekilde depolamak için şifreleme kullanır. Ancak bunu başaramadı. Bu nedenle güvensiz bir depolamaya geri dönecektir. Linux kullanıyorsanız, lütfen gnome-keyring, kde-wallet, keepassxc vb. gibi bir güvenlik hizmetinizin kurulu olduğundan emin olun.", @@ -279,5 +277,10 @@ "password": "Şifre", "login": "Giriş Yap", "login_with_your_lastfm": "Last.fm hesabınız ile giriş yapın", - "scrobble_to_lastfm": "Last.fm için Scrobble" + "scrobble_to_lastfm": "Last.fm için Scrobble", + "go_to_album": "Albüme Git", + "discord_rich_presence": "Discord Zengin Varlık", + "browse_all": "Tümünü Gözat", + "genres": "Müzik Türleri", + "explore_genres": "Türleri Keşfet" } \ No newline at end of file diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index fa0877d1..a5199a04 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -177,11 +177,9 @@ "step_2": "Крок 2", "step_2_steps": "1. Після входу натисніть F12 або клацніть правою кнопкою миші > Інспектувати, щоб відкрити інструменти розробки браузера.\n2. Потім перейдіть на вкладку 'Програма' (Chrome, Edge, Brave тощо) або вкладку 'Сховище' (Firefox, Palemoon тощо).\n3. Перейдіть до розділу 'Кукі-файли', а потім до підрозділу 'https://accounts.spotify.com'", "step_3": "Крок 3", - "step_3_steps": "Скопіюйте значення кукі-файлів 'sp_dc' та 'sp_key' (або sp_gaid)", "success_emoji": "Успіх🥳", "success_message": "Тепер ви успішно ввійшли у свій обліковий запис Spotify. Гарна робота, друже!", "step_4": "Крок 4", - "step_4_steps": "Вставте скопійовані значення 'sp_dc' та 'sp_key' (або sp_gaid) у відповідні поля", "something_went_wrong": "Щось пішло не так", "piped_instance": "Примірник сервера Piped", "piped_description": "Примірник сервера Piped, який використовуватиметься для зіставлення треків", @@ -251,7 +249,7 @@ "developers": "Розробники", "not_logged_in": "Ви не ввійшли в обліковий запис", "search_mode": "Режим пошуку", - "youtube_api_type": "Тип API", + "audio_source": "Джерело аудіо", "ok": "Гаразд", "failed_to_encrypt": "Не вдалося зашифрувати", "encryption_failed_warning": "Spotube використовує шифрування для безпечного зберігання ваших даних. Але не вдалося цього зробити. Тому він перейде до небезпечного зберігання\nЯкщо ви використовуєте Linux, переконайтеся, що у вас встановлено будь-який секретний сервіс (gnome-keyring, kde-wallet, keepassxc тощо)", @@ -279,5 +277,10 @@ "password": "Пароль", "login": "Увійти", "login_with_your_lastfm": "Увійти в свій обліковий запис Last.fm", - "scrobble_to_lastfm": "Скробблінг на Last.fm" + "scrobble_to_lastfm": "Скробблінг на Last.fm", + "go_to_album": "Перейти до альбому", + "discord_rich_presence": "Багата присутність у Discord", + "browse_all": "Переглянути все", + "genres": "Жанри", + "explore_genres": "Досліджувати жанри" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9936c812..30f4a82c 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -175,11 +175,9 @@ "step_2": "步骤 2", "step_2_steps": "1. 一旦你已经完成登录, 按 F12 键或者鼠标右击网页空白区域 > 选择“检查”以打开浏览器开发者工具(DevTools)\n2. 然后选择 \"应用(Application)\" 标签页(Chrome, Edge, Brave 等基于 Chromium 的浏览器) 或 \"存储(Storage)\" 标签页 (Firefox, Palemoon 等基于 Firefox 的浏览器))\n3. 选择 \"Cookies\" 栏目然后选择 \"https://accounts.spotify.com\" 子栏目", "step_3": "步骤 3", - "step_3_steps": "复制名称为 \"sp_dc\" 和 \"sp_key\" (或 sp_gaid) 的值(Cookie Value)", "success_emoji": "成功🥳", "success_message": "你已经成功使用 Spotify 登录。干得漂亮!", "step_4": "步骤 4", - "step_4_steps": "将 \"sp_dc\" 与 \"sp_key\" (或 sp_gaid) 的值分别复制后粘贴到对应的区域", "something_went_wrong": "某些地方出现了问题", "piped_instance": "管道服务器实例", "piped_description": "管道服务器实例用于匹配歌曲", @@ -249,7 +247,7 @@ "developers": "开发者", "not_logged_in": "你尚未登录", "search_mode": "搜索模式", - "youtube_api_type": "API 类型", + "audio_source": "音频源", "ok": "确定", "failed_to_encrypt": "加密失败", "encryption_failed_warning": "Spotube使用加密来安全地存储您的数据。但是失败了。因此,它将回退到不安全的存储\n如果您使用Linux,请确保已安装gnome-keyring、kde-wallet和keepassxc等秘密服务", @@ -279,5 +277,10 @@ "password": "密码", "login": "登录", "login_with_your_lastfm": "使用您的 Last.fm 帐户登录", - "scrobble_to_lastfm": "在 Last.fm 上记录播放" + "scrobble_to_lastfm": "在 Last.fm 上记录播放", + "go_to_album": "前往专辑", + "discord_rich_presence": "Discord 丰富展现", + "browse_all": "浏览全部", + "genres": "音乐类型", + "explore_genres": "探索音乐类型" } \ No newline at end of file diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 61a6d097..335be545 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -6,7 +6,9 @@ /// iceyear@github => Simplified Chinese /// TexturedPolak@github => Polish /// yuri-val@github => Ukrainian +/// energywave@github, ncvescera@github, OpenCode@github => Italian /// mdksec@github => Turkish +/// SecularSteve@github => Dutch import 'package:flutter/material.dart'; class L10n { @@ -19,8 +21,11 @@ class L10n { const Locale('es', 'ES'), const Locale("fa", "IR"), const Locale('fr', 'FR'), + const Locale('ne', 'NP'), const Locale('hi', 'IN'), + const Locale('it', 'IT'), const Locale('ja', 'JP'), + const Locale('nl', 'NL'), const Locale('pl', 'PL'), const Locale('pt', 'PT'), const Locale('ru', 'RU'), diff --git a/lib/main.dart b/lib/main.dart index 7bb96543..b6afa85c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,8 +13,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:media_kit/media_kit.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotube/collections/initializers.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/intents.dart'; +import 'package:spotube/hooks/configurators/use_close_behavior.dart'; +import 'package:spotube/hooks/configurators/use_deep_linking.dart'; import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart'; import 'package:spotube/hooks/configurators/use_get_storage_perms.dart'; import 'package:spotube/l10n/l10n.dart'; @@ -40,6 +43,8 @@ Future main(List rawArgs) async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + await registerWindowsScheme("spotify"); + FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); MediaKit.ensureInitialized(); @@ -49,14 +54,9 @@ Future main(List rawArgs) async { await FlutterDisplayMode.setHighRefreshRate(); } - await DesktopTools.ensureInitialized( - DesktopWindowOptions( - hideTitleBar: true, - title: "Spotube", - backgroundColor: Colors.transparent, - minimumSize: const Size(300, 700), - ), - ); + if (DesktopTools.platform.isDesktop) { + await DesktopTools.window.setPreventClose(true); + } await SystemTheme.accentColor.load(); @@ -98,6 +98,15 @@ Future main(List rawArgs) async { path: hiveCacheDir, ); + await DesktopTools.ensureInitialized( + DesktopWindowOptions( + hideTitleBar: true, + title: "Spotube", + backgroundColor: Colors.transparent, + minimumSize: const Size(300, 700), + ), + ); + Catcher2( enableLogger: arguments["verbose"], debugConfig: Catcher2Options( @@ -176,7 +185,11 @@ class SpotubeState extends ConsumerState { final paletteColor = ref.watch(paletteProvider.select((s) => s?.dominantColor?.color)); + useDisableBatteryOptimizations(); useInitSysTray(ref); + useDeepLinking(ref); + useCloseBehavior(ref); + useGetStoragePermissions(ref); useEffect(() { FlutterNativeSplash.remove(); @@ -184,13 +197,9 @@ class SpotubeState extends ConsumerState { /// For enabling hot reload for audio player if (!kDebugMode) return; audioPlayer.dispose(); - // youtube.close(); }; }, []); - useDisableBatteryOptimizations(); - useGetStoragePermissions(ref); - final lightTheme = useMemoized( () => theme(paletteColor ?? accentMaterialColor, Brightness.light, false), [paletteColor, accentMaterialColor], @@ -219,7 +228,9 @@ class SpotubeState extends ConsumerState { builder: (context, child) { return DevicePreview.appBuilder( context, - DragToResizeArea(child: child!), + DesktopTools.platform.isDesktop + ? DragToResizeArea(child: child!) + : child, ); }, themeMode: themeMode, diff --git a/lib/models/spotify_friends.dart b/lib/models/spotify_friends.dart new file mode 100644 index 00000000..b386fb81 --- /dev/null +++ b/lib/models/spotify_friends.dart @@ -0,0 +1,111 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'spotify_friends.g.dart'; + +@JsonSerializable(createToJson: false) +class SpotifyFriend { + final String uri; + final String name; + final String imageUrl; + + const SpotifyFriend({ + required this.uri, + required this.name, + required this.imageUrl, + }); + + factory SpotifyFriend.fromJson(Map json) => + _$SpotifyFriendFromJson(json); + + String get id => uri.split(":").last; +} + +@JsonSerializable(createToJson: false) +class SpotifyActivityArtist { + final String uri; + final String name; + + const SpotifyActivityArtist({required this.uri, required this.name}); + + factory SpotifyActivityArtist.fromJson(Map json) => + _$SpotifyActivityArtistFromJson(json); + + String get id => uri.split(":").last; +} + +@JsonSerializable(createToJson: false) +class SpotifyActivityAlbum { + final String uri; + final String name; + + const SpotifyActivityAlbum({required this.uri, required this.name}); + + factory SpotifyActivityAlbum.fromJson(Map json) => + _$SpotifyActivityAlbumFromJson(json); + + String get id => uri.split(":").last; +} + +@JsonSerializable(createToJson: false) +class SpotifyActivityContext { + final String uri; + final String name; + final num index; + + const SpotifyActivityContext({ + required this.uri, + required this.name, + required this.index, + }); + + factory SpotifyActivityContext.fromJson(Map json) => + _$SpotifyActivityContextFromJson(json); + + String get id => uri.split(":").last; + String get path => uri.split(":").skip(1).join("/"); +} + +@JsonSerializable(createToJson: false) +class SpotifyActivityTrack { + final String uri; + final String name; + final String imageUrl; + final SpotifyActivityArtist artist; + final SpotifyActivityAlbum album; + final SpotifyActivityContext context; + + const SpotifyActivityTrack({ + required this.uri, + required this.name, + required this.imageUrl, + required this.artist, + required this.album, + required this.context, + }); + + factory SpotifyActivityTrack.fromJson(Map json) => + _$SpotifyActivityTrackFromJson(json); + + String get id => uri.split(":").last; +} + +@JsonSerializable(createToJson: false) +class SpotifyFriendActivity { + SpotifyFriend user; + SpotifyActivityTrack track; + + SpotifyFriendActivity({required this.user, required this.track}); + + factory SpotifyFriendActivity.fromJson(Map json) => + _$SpotifyFriendActivityFromJson(json); +} + +@JsonSerializable(createToJson: false) +class SpotifyFriends { + List friends; + + SpotifyFriends({required this.friends}); + + factory SpotifyFriends.fromJson(Map json) => + _$SpotifyFriendsFromJson(json); +} diff --git a/lib/models/spotify_friends.g.dart b/lib/models/spotify_friends.g.dart new file mode 100644 index 00000000..4a32dd09 --- /dev/null +++ b/lib/models/spotify_friends.g.dart @@ -0,0 +1,65 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'spotify_friends.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SpotifyFriend _$SpotifyFriendFromJson(Map json) => + SpotifyFriend( + uri: json['uri'] as String, + name: json['name'] as String, + imageUrl: json['imageUrl'] as String, + ); + +SpotifyActivityArtist _$SpotifyActivityArtistFromJson( + Map json) => + SpotifyActivityArtist( + uri: json['uri'] as String, + name: json['name'] as String, + ); + +SpotifyActivityAlbum _$SpotifyActivityAlbumFromJson( + Map json) => + SpotifyActivityAlbum( + uri: json['uri'] as String, + name: json['name'] as String, + ); + +SpotifyActivityContext _$SpotifyActivityContextFromJson( + Map json) => + SpotifyActivityContext( + uri: json['uri'] as String, + name: json['name'] as String, + index: json['index'] as num, + ); + +SpotifyActivityTrack _$SpotifyActivityTrackFromJson( + Map json) => + SpotifyActivityTrack( + uri: json['uri'] as String, + name: json['name'] as String, + imageUrl: json['imageUrl'] as String, + artist: SpotifyActivityArtist.fromJson( + json['artist'] as Map), + album: + SpotifyActivityAlbum.fromJson(json['album'] as Map), + context: SpotifyActivityContext.fromJson( + json['context'] as Map), + ); + +SpotifyFriendActivity _$SpotifyFriendActivityFromJson( + Map json) => + SpotifyFriendActivity( + user: SpotifyFriend.fromJson(json['user'] as Map), + track: + SpotifyActivityTrack.fromJson(json['track'] as Map), + ); + +SpotifyFriends _$SpotifyFriendsFromJson(Map json) => + SpotifyFriends( + friends: (json['friends'] as List) + .map((e) => SpotifyFriendActivity.fromJson(e as Map)) + .toList(), + ); diff --git a/lib/pages/artist/artist.dart b/lib/pages/artist/artist.dart index 693e825b..d511cb97 100644 --- a/lib/pages/artist/artist.dart +++ b/lib/pages/artist/artist.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; import 'package:spotube/components/artist/artist_album_list.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_artist_profile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/logger.dart'; import 'package:spotube/pages/artist/section/footer.dart'; @@ -35,45 +35,46 @@ class ArtistPage extends HookConsumerWidget { ), extendBodyBehindAppBar: true, body: Builder(builder: (context) { - if (artistQuery.isLoading || !artistQuery.hasData) { - const ShimmerArtistProfile(); - } else if (artistQuery.hasError) { + if (artistQuery.hasError && artistQuery.data == null) { return Center(child: Text(artistQuery.error.toString())); } - return CustomScrollView( - controller: scrollController, - slivers: [ - SliverToBoxAdapter( - child: SafeArea( - bottom: false, - child: ArtistPageHeader(artistId: artistId), - ), - ), - const SliverGap(50), - ArtistPageTopTracks(artistId: artistId), - const SliverGap(50), - SliverToBoxAdapter(child: ArtistAlbumList(artistId)), - const SliverGap(20), - SliverPadding( - padding: const EdgeInsets.all(8.0), - sliver: SliverToBoxAdapter( - child: Text( - context.l10n.fans_also_like, - style: theme.textTheme.headlineSmall, + return Skeletonizer( + enabled: artistQuery.isLoading, + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: SafeArea( + bottom: false, + child: ArtistPageHeader(artistId: artistId), ), ), - ), - SliverSafeArea( - sliver: ArtistPageRelatedArtists(artistId: artistId), - ), - if (artistQuery.data != null) - SliverSafeArea( - top: false, + const SliverGap(50), + ArtistPageTopTracks(artistId: artistId), + const SliverGap(50), + SliverToBoxAdapter(child: ArtistAlbumList(artistId)), + const SliverGap(20), + SliverPadding( + padding: const EdgeInsets.all(8.0), sliver: SliverToBoxAdapter( - child: ArtistPageFooter(artist: artistQuery.data!), + child: Text( + context.l10n.fans_also_like, + style: theme.textTheme.headlineSmall, + ), ), ), - ], + SliverSafeArea( + sliver: ArtistPageRelatedArtists(artistId: artistId), + ), + if (artistQuery.data != null) + SliverSafeArea( + top: false, + sliver: SliverToBoxAdapter( + child: ArtistPageFooter(artist: artistQuery.data!), + ), + ), + ], + ), ); }), ), diff --git a/lib/pages/artist/section/footer.dart b/lib/pages/artist/section/footer.dart index 3c0db8a5..b01ef705 100644 --- a/lib/pages/artist/section/footer.dart +++ b/lib/pages/artist/section/footer.dart @@ -38,9 +38,9 @@ class ArtistPageFooter extends HookConsumerWidget { BlendMode.darken, ), image: UniversalImage.imageProvider( - summary.data!.originalimage?.source_ ?? artistImage, - height: summary.data!.originalimage?.height.toDouble(), - width: summary.data!.originalimage?.width.toDouble(), + summary.data!.thumbnail?.source_ ?? artistImage, + height: summary.data!.thumbnail?.height.toDouble(), + width: summary.data!.thumbnail?.width.toDouble(), ), fit: BoxFit.cover, alignment: Alignment.center, diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 9fc9d78e..7cee7a01 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -4,7 +4,9 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; @@ -25,7 +27,7 @@ class ArtistPageHeader extends HookConsumerWidget { Widget build(BuildContext context, ref) { final queryClient = useQueryClient(); final artistQuery = useQueries.artist.get(ref, artistId); - final artist = artistQuery.data; + final artist = artistQuery.data ?? FakeData.artist; final scaffoldMessenger = ScaffoldMessenger.of(context); final mediaQuery = MediaQuery.of(context); @@ -41,10 +43,6 @@ class ArtistPageHeader extends HookConsumerWidget { xxl: textTheme.titleMedium, ); - if (artist == null) { - return const SizedBox.shrink(); - } - final spotify = ref.read(spotifyProvider); final auth = ref.watch(AuthenticationNotifier.provider); final blacklist = ref.watch(BlackListNotifier.provider); @@ -96,10 +94,12 @@ class ArtistPageHeader extends HookConsumerWidget { decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(50)), - child: Text( - artist.type!.toUpperCase(), - style: chipTextVariant.copyWith( - color: Colors.white, + child: Skeleton.keep( + child: Text( + artist.type!.toUpperCase(), + style: chipTextVariant.copyWith( + color: Colors.white, + ), ), ), ), @@ -138,113 +138,115 @@ class ArtistPageHeader extends HookConsumerWidget { ), ), const Gap(20), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (auth != null) - HookBuilder( - builder: (context) { - final isFollowingQuery = - useQueries.artist.doIFollow(ref, artistId); + Skeleton.keep( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (auth != null) + HookBuilder( + builder: (context) { + final isFollowingQuery = + useQueries.artist.doIFollow(ref, artistId); - final followUnfollow = useCallback(() async { - try { - isFollowingQuery.data! - ? await spotify.me.unfollow( - FollowingType.artist, - [artistId], - ) - : await spotify.me.follow( - FollowingType.artist, - [artistId], - ); - await isFollowingQuery.refresh(); + final followUnfollow = useCallback(() async { + try { + isFollowingQuery.data! + ? await spotify.me.unfollow( + FollowingType.artist, + [artistId], + ) + : await spotify.me.follow( + FollowingType.artist, + [artistId], + ); + await isFollowingQuery.refresh(); - queryClient.refreshInfiniteQueryAllPages( - "user-following-artists"); - } finally { - queryClient.refreshQuery( - "user-follows-artists-query/$artistId", + queryClient.refreshInfiniteQueryAllPages( + "user-following-artists"); + } finally { + queryClient.refreshQuery( + "user-follows-artists-query/$artistId", + ); + } + }, [isFollowingQuery]); + + if (isFollowingQuery.isLoading || + !isFollowingQuery.hasData) { + return const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(), ); } - }, [isFollowingQuery]); - if (isFollowingQuery.isLoading || - !isFollowingQuery.hasData) { - return const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator(), - ); - } + if (isFollowingQuery.data!) { + return OutlinedButton( + onPressed: followUnfollow, + child: Text(context.l10n.following), + ); + } - if (isFollowingQuery.data!) { - return OutlinedButton( + return FilledButton( onPressed: followUnfollow, - child: Text(context.l10n.following), + child: Text(context.l10n.follow), ); + }, + ), + const SizedBox(width: 5), + IconButton( + tooltip: context.l10n.add_artist_to_blacklist, + icon: Icon( + SpotubeIcons.userRemove, + color: + !isBlackListed ? Colors.red[400] : Colors.white, + ), + style: IconButton.styleFrom( + backgroundColor: + isBlackListed ? Colors.red[400] : null, + ), + onPressed: () async { + if (isBlackListed) { + ref + .read(BlackListNotifier.provider.notifier) + .remove( + BlacklistedElement.artist( + artist.id!, artist.name!), + ); + } else { + ref.read(BlackListNotifier.provider.notifier).add( + BlacklistedElement.artist( + artist.id!, artist.name!), + ); } - - return FilledButton( - onPressed: followUnfollow, - child: Text(context.l10n.follow), - ); }, ), - const SizedBox(width: 5), - IconButton( - tooltip: context.l10n.add_artist_to_blacklist, - icon: Icon( - SpotubeIcons.userRemove, - color: - !isBlackListed ? Colors.red[400] : Colors.white, - ), - style: IconButton.styleFrom( - backgroundColor: - isBlackListed ? Colors.red[400] : null, - ), - onPressed: () async { - if (isBlackListed) { - ref - .read(BlackListNotifier.provider.notifier) - .remove( - BlacklistedElement.artist( - artist.id!, artist.name!), - ); - } else { - ref.read(BlackListNotifier.provider.notifier).add( - BlacklistedElement.artist( - artist.id!, artist.name!), - ); - } - }, - ), - IconButton( - icon: const Icon(SpotubeIcons.share), - onPressed: () async { - if (artist.externalUrls?.spotify != null) { - await Clipboard.setData( - ClipboardData( - text: artist.externalUrls!.spotify!, + IconButton( + icon: const Icon(SpotubeIcons.share), + onPressed: () async { + if (artist.externalUrls?.spotify != null) { + await Clipboard.setData( + ClipboardData( + text: artist.externalUrls!.spotify!, + ), + ); + } + + if (!context.mounted) return; + + scaffoldMessenger.showSnackBar( + SnackBar( + width: 300, + behavior: SnackBarBehavior.floating, + content: Text( + context.l10n.artist_url_copied, + textAlign: TextAlign.center, + ), ), ); - } - - if (!context.mounted) return; - - scaffoldMessenger.showSnackBar( - SnackBar( - width: 300, - behavior: SnackBarBehavior.floating, - content: Text( - context.l10n.artist_url_copied, - textAlign: TextAlign.center, - ), - ), - ); - }, - ) - ], + }, + ) + ], + ), ) ], ), diff --git a/lib/pages/artist/section/top_tracks.dart b/lib/pages/artist/section/top_tracks.dart index 9e3e4054..771757b9 100644 --- a/lib/pages/artist/section/top_tracks.dart +++ b/lib/pages/artist/section/top_tracks.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/shared/track_tile/track_tile.dart'; import 'package:spotube/extensions/context.dart'; @@ -28,11 +30,7 @@ class ArtistPageTopTracks extends HookConsumerWidget { topTracksQuery.data ?? [], ); - if (topTracksQuery.isLoading || !topTracksQuery.hasData) { - return const SliverToBoxAdapter( - child: Center(child: CircularProgressIndicator()), - ); - } else if (topTracksQuery.hasError) { + if (topTracksQuery.hasError) { return SliverToBoxAdapter( child: Center( child: Text(topTracksQuery.error.toString()), @@ -40,7 +38,8 @@ class ArtistPageTopTracks extends HookConsumerWidget { ); } - final topTracks = topTracksQuery.data!; + final topTracks = + topTracksQuery.data ?? List.generate(10, (index) => FakeData.track); void playPlaylist(List tracks, {Track? currentTrack}) async { currentTrack ??= tracks.first; @@ -92,9 +91,11 @@ class ArtistPageTopTracks extends HookConsumerWidget { ), const SizedBox(width: 5), IconButton( - icon: Icon( - isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play, - color: Colors.white, + icon: Skeleton.keep( + child: Icon( + isPlaylistPlaying ? SpotubeIcons.stop : SpotubeIcons.play, + color: Colors.white, + ), ), style: IconButton.styleFrom( backgroundColor: theme.colorScheme.primary, diff --git a/lib/pages/home/genres.dart b/lib/pages/home/genres.dart deleted file mode 100644 index 54fb6786..00000000 --- a/lib/pages/home/genres.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:collection/collection.dart'; -import 'package:fuzzywuzzy/fuzzywuzzy.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/components/genre/category_card.dart'; -import 'package:spotube/components/shared/expandable_search/expandable_search.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_categories.dart'; -import 'package:spotube/components/shared/waypoint.dart'; - -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/services/queries/queries.dart'; -import 'package:very_good_infinite_list/very_good_infinite_list.dart'; - -class GenrePage extends HookConsumerWidget { - const GenrePage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context, ref) { - final scrollController = useScrollController(); - final recommendationMarket = ref.watch( - userPreferencesProvider.select((s) => s.recommendationMarket), - ); - final categoriesQuery = useQueries.category.list(ref, recommendationMarket); - final isFiltering = useState(false); - - final isMounted = useIsMounted(); - - final searchController = useTextEditingController(); - final searchFocus = useFocusNode(); - - useValueListenable(searchController); - - final categories = useMemoized( - () { - final categories = categoriesQuery.pages - .expand( - (page) => page.items ?? const Iterable.empty(), - ) - .toList(); - if (searchController.text.isEmpty) { - return categories; - } - return categories - .map((e) => ( - weightedRatio(e.name!, searchController.text), - e, - )) - .sorted((a, b) => b.$1.compareTo(a.$1)) - .where((e) => e.$1 > 50) - .map((e) => e.$2) - .toList(); - }, - [categoriesQuery.pages, searchController.text], - ); - - final list = RefreshIndicator( - onRefresh: () async { - await categoriesQuery.refreshAll(); - }, - child: Waypoint( - onTouchEdge: () async { - if (categoriesQuery.hasNextPage && isMounted()) { - await categoriesQuery.fetchNext(); - } - }, - controller: scrollController, - child: Column( - children: [ - ExpandableSearchField( - isFiltering: isFiltering.value, - onChangeFiltering: (value) => isFiltering.value = value, - searchController: searchController, - searchFocus: searchFocus, - ), - if (!categoriesQuery.hasPageData && - !categoriesQuery.isLoadingNextPage) - const ShimmerCategories() - else - Expanded( - child: InfiniteList( - scrollController: scrollController, - itemCount: categories.length, - onFetchData: categoriesQuery.fetchNext, - isLoading: categoriesQuery.isLoadingNextPage, - hasReachedMax: !categoriesQuery.hasNextPage, - loadingBuilder: (context) => const ShimmerCategories(), - itemBuilder: (context, index) { - return CategoryCard(categories[index]); - }, - ), - ), - ], - ), - ), - ); - - return Stack( - children: [ - Positioned.fill(child: list), - Positioned( - top: 0, - right: 10, - child: ExpandableSearchButton( - isFiltering: isFiltering.value, - searchFocus: searchFocus, - icon: const Icon(SpotubeIcons.search), - onPressed: (value) { - isFiltering.value = value; - if (isFiltering.value) { - scrollController.animateTo( - 0, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - } - }, - ), - ), - ], - ); - } -} diff --git a/lib/pages/home/genres/genre_playlists.dart b/lib/pages/home/genres/genre_playlists.dart new file mode 100644 index 00000000..78f32245 --- /dev/null +++ b/lib/pages/home/genres/genre_playlists.dart @@ -0,0 +1,174 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotify/spotify.dart' hide Offset; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/playlist/playlist_card.dart'; +import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/components/shared/page_window_title_bar.dart'; +import 'package:spotube/components/shared/waypoint.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/services/queries/queries.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; + +class GenrePlaylistsPage extends HookConsumerWidget { + final Category category; + const GenrePlaylistsPage({Key? key, required this.category}) + : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final playlistsQuery = useQueries.category.playlistsOf( + ref, + category.id!, + ); + + final playlists = useMemoized( + () => playlistsQuery.pages.expand( + (page) { + return page.items?.whereNotNull() ?? + const Iterable.empty(); + }, + ).toList(), + [playlistsQuery.pages], + ); + + final mediaQuery = MediaQuery.of(context); + + final scrollController = useScrollController(); + + return Scaffold( + appBar: DesktopTools.platform.isDesktop + ? const PageWindowTitleBar( + leading: BackButton(color: Colors.white), + backgroundColor: Colors.transparent, + foregroundColor: Colors.white, + ) + : null, + extendBodyBehindAppBar: true, + body: CustomScrollView( + controller: scrollController, + slivers: [ + SliverAppBar( + automaticallyImplyLeading: DesktopTools.platform.isMobile, + expandedHeight: mediaQuery.mdAndDown ? 200 : 150, + pinned: true, + floating: false, + title: const Text(""), + backgroundColor: Colors.brown.withOpacity(0.7), + flexibleSpace: FlexibleSpaceBar( + stretchModes: const [ + StretchMode.zoomBackground, + StretchMode.blurBackground, + ], + background: DecoratedBox( + decoration: BoxDecoration( + image: DecorationImage( + image: UniversalImage.imageProvider( + category.icons!.first.url!, + ), + fit: BoxFit.cover, + ), + ), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: const ColoredBox(color: Colors.transparent), + ), + ), + centerTitle: DesktopTools.platform.isDesktop, + title: Text( + category.name!, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.white, + letterSpacing: 3, + shadows: [ + const Shadow( + offset: Offset(-1.5, -1.5), + color: Colors.black54, + ), + const Shadow( + offset: Offset(1.5, -1.5), + color: Colors.black54, + ), + const Shadow( + offset: Offset(1.5, 1.5), + color: Colors.black54, + ), + const Shadow( + offset: Offset(-1.5, 1.5), + color: Colors.black54, + ), + ], + ), + ), + collapseMode: CollapseMode.parallax, + ), + ), + const SliverGap(20), + SliverSafeArea( + top: false, + sliver: SliverPadding( + padding: EdgeInsets.symmetric( + horizontal: mediaQuery.mdAndDown ? 12 : 24, + ), + sliver: playlists.isEmpty + ? Skeletonizer.sliver( + child: SliverToBoxAdapter( + child: Wrap( + spacing: 12, + runSpacing: 12, + children: List.generate( + 6, + (index) => PlaylistCard(FakeData.playlist), + ), + ), + ), + ) + : SliverGrid.builder( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 190, + mainAxisExtent: mediaQuery.mdAndDown ? 225 : 250, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + itemCount: playlists.length + 1, + itemBuilder: (context, index) { + final playlist = playlists.elementAtOrNull(index); + + if (playlist == null) { + if (!playlistsQuery.hasNextPage) { + return const SizedBox.shrink(); + } + return Skeletonizer( + enabled: true, + child: Waypoint( + controller: scrollController, + isGrid: true, + onTouchEdge: () async { + if (playlistsQuery.hasNextPage) { + await playlistsQuery.fetchNext(); + } + }, + child: PlaylistCard(FakeData.playlist), + ), + ); + } + + return Skeleton.keep( + child: PlaylistCard(playlist), + ); + }, + ), + ), + ), + const SliverGap(20), + ], + ), + ); + } +} diff --git a/lib/pages/home/genres/genres.dart b/lib/pages/home/genres/genres.dart new file mode 100644 index 00000000..dc165fe4 --- /dev/null +++ b/lib/pages/home/genres/genres.dart @@ -0,0 +1,98 @@ +import 'dart:math'; + +import 'package:auto_size_text/auto_size_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart' hide Offset; +import 'package:spotube/collections/gradients.dart'; +import 'package:spotube/components/shared/page_window_title_bar.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; + +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/services/queries/queries.dart'; + +class GenrePage extends HookConsumerWidget { + const GenrePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final ThemeData(:textTheme) = Theme.of(context); + final scrollController = useScrollController(); + final recommendationMarket = ref.watch( + userPreferencesProvider.select((s) => s.recommendationMarket), + ); + final categoriesQuery = + useQueries.category.listAll(ref, recommendationMarket); + + final categories = categoriesQuery.data ?? []; + + final mediaQuery = MediaQuery.of(context); + + return Scaffold( + appBar: PageWindowTitleBar( + title: Text(context.l10n.explore_genres), + automaticallyImplyLeading: true, + ), + body: SafeArea( + top: false, + child: GridView.builder( + padding: const EdgeInsets.all(12), + controller: scrollController, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + childAspectRatio: 9 / 18, + maxCrossAxisExtent: mediaQuery.smAndDown ? 200 : 300, + mainAxisExtent: 200, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + itemCount: categories.length, + itemBuilder: (context, index) { + final category = categories[index]; + final gradient = gradients[Random().nextInt(gradients.length)]; + return InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () { + context.push("/genre/${category.id}", extra: category); + }, + child: Ink( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: DecorationImage( + image: NetworkImage(category.icons!.first.url!), + fit: BoxFit.cover, + ), + gradient: gradient, + ), + child: Align( + alignment: Alignment.bottomCenter, + child: AutoSizeText( + category.name!, + style: textTheme.titleLarge?.copyWith( + color: Colors.white, + shadows: [ + // stroke shadow + const Shadow( + color: Colors.black, + offset: Offset(1, 1), + blurRadius: 2, + ), + ], + ), + maxLines: 1, + textAlign: TextAlign.center, + maxFontSize: textTheme.titleLarge!.fontSize!, + minFontSize: textTheme.titleMedium!.fontSize!, + ), + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 34f136b6..eb2ddb94 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -1,36 +1,41 @@ import 'package:flutter/material.dart'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/components/home/sections/featured.dart'; +import 'package:spotube/components/home/sections/friends.dart'; +import 'package:spotube/components/home/sections/genres.dart'; +import 'package:spotube/components/home/sections/made_for_user.dart'; +import 'package:spotube/components/home/sections/new_releases.dart'; import 'package:spotube/components/shared/page_window_title_bar.dart'; -import 'package:spotube/components/shared/themed_button_tab_bar.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/home/genres.dart'; -import 'package:spotube/pages/home/personalized.dart'; class HomePage extends HookConsumerWidget { const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context, ref) { - return DefaultTabController( - length: 2, - child: Scaffold( - appBar: PageWindowTitleBar( - centerTitle: true, - leadingWidth: double.infinity, - leading: ThemedButtonsTabBar( - tabs: [ - Tab(text: " ${context.l10n.personalized} "), - Tab(text: " ${context.l10n.genre} "), + final controller = useScrollController(); + + return SafeArea( + bottom: false, + child: Scaffold( + appBar: DesktopTools.platform.isMobile + ? null + : const PageWindowTitleBar(), + body: CustomScrollView( + controller: controller, + slivers: [ + const HomeGenresSection(), + SliverList.list( + children: const [ + HomeFeaturedSection(), + HomeNewReleasesSection(), + ], + ), + const HomePageFriendsSection(), + const SliverSafeArea(sliver: HomeMadeForUserSection()), ], ), - ), - body: const TabBarView( - children: [ - PersonalizedPage(), - GenrePage(), - ], - ), - ), - ); + )); } } diff --git a/lib/pages/home/personalized.dart b/lib/pages/home/personalized.dart deleted file mode 100644 index 7fbd27ae..00000000 --- a/lib/pages/home/personalized.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:flutter/material.dart' hide Page; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -import 'package:spotify/spotify.dart'; -import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; -import 'package:spotube/components/shared/shimmers/shimmer_categories.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/authentication_provider.dart'; -import 'package:spotube/services/queries/queries.dart'; -import 'package:spotube/utils/type_conversion_utils.dart'; - -class PersonalizedPage extends HookConsumerWidget { - const PersonalizedPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context, ref) { - final controller = useScrollController(); - final auth = ref.watch(AuthenticationNotifier.provider); - final featuredPlaylistsQuery = useQueries.playlist.featured(ref); - final playlists = useMemoized( - () => featuredPlaylistsQuery.pages - .whereType>() - .expand((page) => page.items ?? const []), - [featuredPlaylistsQuery.pages], - ); - - final madeForUser = useQueries.views.get(ref, "made-for-x-hub"); - - final newReleases = useQueries.album.newReleases(ref); - final userArtistsQuery = useQueries.artist.followedByMeAll(ref); - final userArtists = - userArtistsQuery.data?.map((s) => s.id!).toList() ?? const []; - - final albums = useMemoized( - () => newReleases.pages - .whereType>() - .expand((page) => page.items ?? const []) - .where((album) { - return album.artists - ?.any((artist) => userArtists.contains(artist.id!)) == - true; - }) - .map((album) => TypeConversionUtils.simpleAlbum_X_Album(album)) - .toList(), - [newReleases.pages], - ); - - return CustomScrollView( - controller: controller, - slivers: [ - SliverList.list( - children: [ - AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: !featuredPlaylistsQuery.hasPageData && - !featuredPlaylistsQuery.isLoadingNextPage - ? const ShimmerCategories() - : HorizontalPlaybuttonCardView( - items: playlists.toList(), - title: Text(context.l10n.featured), - isLoadingNextPage: - featuredPlaylistsQuery.isLoadingNextPage, - hasNextPage: featuredPlaylistsQuery.hasNextPage, - onFetchMore: featuredPlaylistsQuery.fetchNext, - ), - ), - if (auth != null) - AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: newReleases.hasPageData && - userArtistsQuery.hasData && - !newReleases.isLoadingNextPage - ? HorizontalPlaybuttonCardView( - items: albums, - title: Text(context.l10n.new_releases), - isLoadingNextPage: newReleases.isLoadingNextPage, - hasNextPage: newReleases.hasNextPage, - onFetchMore: newReleases.fetchNext, - ) - : const ShimmerCategories(), - ), - ], - ), - SliverSafeArea( - sliver: SliverList.builder( - itemCount: madeForUser.data?["content"]?["items"]?.length ?? 0, - itemBuilder: (context, index) { - final item = madeForUser.data?["content"]?["items"]?[index]; - final playlists = item["content"]?["items"] - ?.where((itemL2) => itemL2["type"] == "playlist") - .map((itemL2) => PlaylistSimple.fromJson(itemL2)) - .toList() - .cast() ?? - []; - if (playlists.isEmpty) return const SizedBox.shrink(); - return HorizontalPlaybuttonCardView( - items: playlists, - title: Text(item["name"] ?? ""), - hasNextPage: false, - isLoadingNextPage: false, - onFetchMore: () {}, - ); - }, - ), - ), - ], - ); - } -} diff --git a/lib/pages/library/playlist_generate/playlist_generate.dart b/lib/pages/library/playlist_generate/playlist_generate.dart index 4b8dddaf..802b28d3 100644 --- a/lib/pages/library/playlist_generate/playlist_generate.dart +++ b/lib/pages/library/playlist_generate/playlist_generate.dart @@ -242,267 +242,284 @@ class PlaylistGeneratorPage extends HookConsumerWidget { }, ); + final controller = useScrollController(); + return Scaffold( appBar: PageWindowTitleBar( leading: const BackButton(), title: Text(context.l10n.generate_playlist), centerTitle: true, ), - body: Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: Breakpoints.lg), - child: SliderTheme( - data: const SliderThemeData( - overlayShape: RoundSliderOverlayShape(), - ), - child: SafeArea( - child: LayoutBuilder(builder: (context, constrains) { - return ListView( - padding: const EdgeInsets.all(16), - children: [ - ValueListenableBuilder( - valueListenable: limit, - builder: (context, value, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.number_of_tracks_generate, - style: textTheme.titleMedium, - ), - Row( + body: Scrollbar( + controller: controller, + child: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: Breakpoints.lg), + child: SliderTheme( + data: const SliderThemeData( + overlayShape: RoundSliderOverlayShape(), + ), + child: SafeArea( + child: LayoutBuilder(builder: (context, constrains) { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(scrollbars: false), + child: ListView( + controller: controller, + padding: const EdgeInsets.all(16), + children: [ + ValueListenableBuilder( + valueListenable: limit, + builder: (context, value, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 40, - height: 40, - alignment: Alignment.center, - decoration: BoxDecoration( - color: theme.colorScheme.primary, - shape: BoxShape.circle, - ), - child: Text( - value.round().toString(), - style: textTheme.bodyLarge?.copyWith( - color: theme.colorScheme.primaryContainer, - ), - ), + Text( + context.l10n.number_of_tracks_generate, + style: textTheme.titleMedium, ), - Expanded( - child: Slider( - value: value.toDouble(), - min: 10, - max: 100, - divisions: 9, - label: value.round().toString(), - onChanged: (value) { - limit.value = value.round(); - }, - ), + Row( + children: [ + Container( + width: 40, + height: 40, + alignment: Alignment.center, + decoration: BoxDecoration( + color: theme.colorScheme.primary, + shape: BoxShape.circle, + ), + child: Text( + value.round().toString(), + style: textTheme.bodyLarge?.copyWith( + color: theme + .colorScheme.primaryContainer, + ), + ), + ), + Expanded( + child: Slider( + value: value.toDouble(), + min: 10, + max: 100, + divisions: 9, + label: value.round().toString(), + onChanged: (value) { + limit.value = value.round(); + }, + ), + ) + ], ) ], - ) - ], - ); - }, - ), - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: countrySelector, - ), - const SizedBox(width: 16), - Expanded( - child: genreSelector, - ), + ); + }, + ), + const SizedBox(height: 16), + if (constrains.mdAndUp) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: countrySelector, + ), + const SizedBox(width: 16), + Expanded( + child: genreSelector, + ), + ], + ) + else ...[ + countrySelector, + const SizedBox(height: 16), + genreSelector, ], - ) - else ...[ - countrySelector, - const SizedBox(height: 16), - genreSelector, - ], - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: artistAutoComplete, - ), - const SizedBox(width: 16), - Expanded( - child: tracksAutocomplete, - ), + const SizedBox(height: 16), + if (constrains.mdAndUp) + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: artistAutoComplete, + ), + const SizedBox(width: 16), + Expanded( + child: tracksAutocomplete, + ), + ], + ) + else ...[ + artistAutoComplete, + const SizedBox(height: 16), + tracksAutocomplete, ], - ) - else ...[ - artistAutoComplete, - const SizedBox(height: 16), - tracksAutocomplete, - ], - const SizedBox(height: 16), - RecommendationAttributeDials( - title: Text(context.l10n.acousticness), - values: acousticness.value, - onChanged: (value) { - acousticness.value = value; - }, + const SizedBox(height: 16), + RecommendationAttributeDials( + title: Text(context.l10n.acousticness), + values: acousticness.value, + onChanged: (value) { + acousticness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.danceability), + values: danceability.value, + onChanged: (value) { + danceability.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.energy), + values: energy.value, + onChanged: (value) { + energy.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.instrumentalness), + values: instrumentalness.value, + onChanged: (value) { + instrumentalness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.liveness), + values: liveness.value, + onChanged: (value) { + liveness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.loudness), + values: loudness.value, + onChanged: (value) { + loudness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.speechiness), + values: speechiness.value, + onChanged: (value) { + speechiness.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.valence), + values: valence.value, + onChanged: (value) { + valence.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.popularity), + values: popularity.value, + base: 100, + onChanged: (value) { + popularity.value = value; + }, + ), + RecommendationAttributeDials( + title: Text(context.l10n.key), + values: key.value, + base: 11, + onChanged: (value) { + key.value = value; + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.duration), + values: ( + max: durationMs.value.max / 1000, + target: durationMs.value.target / 1000, + min: durationMs.value.min / 1000, + ), + onChanged: (value) { + durationMs.value = ( + max: value.max * 1000, + target: value.target * 1000, + min: value.min * 1000, + ); + }, + presets: { + context.l10n.short: (min: 50, target: 90, max: 120), + context.l10n.medium: ( + min: 120, + target: 180, + max: 200 + ), + context.l10n.long: (min: 480, target: 560, max: 640) + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.tempo), + values: tempo.value, + onChanged: (value) { + tempo.value = value; + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.mode), + values: mode.value, + onChanged: (value) { + mode.value = value; + }, + ), + RecommendationAttributeFields( + title: Text(context.l10n.time_signature), + values: timeSignature.value, + onChanged: (value) { + timeSignature.value = value; + }, + ), + const SizedBox(height: 20), + FilledButton.icon( + icon: const Icon(SpotubeIcons.magic), + label: Text(context.l10n.generate_playlist), + onPressed: artists.value.isEmpty && + tracks.value.isEmpty && + genres.value.isEmpty + ? null + : () { + final PlaylistGenerateResultRouteState + routeState = ( + seeds: ( + artists: artists.value + .map((a) => a.id!) + .toList(), + tracks: tracks.value + .map((t) => t.id!) + .toList(), + genres: genres.value + ), + market: market.value, + limit: limit.value, + parameters: ( + acousticness: acousticness.value, + danceability: danceability.value, + energy: energy.value, + instrumentalness: instrumentalness.value, + liveness: liveness.value, + loudness: loudness.value, + speechiness: speechiness.value, + valence: valence.value, + popularity: popularity.value, + key: key.value, + duration_ms: durationMs.value, + tempo: tempo.value, + mode: mode.value, + time_signature: timeSignature.value, + ) + ); + GoRouter.of(context).push( + "/library/generate/result", + extra: routeState, + ); + }, + ), + ], ), - RecommendationAttributeDials( - title: Text(context.l10n.danceability), - values: danceability.value, - onChanged: (value) { - danceability.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.energy), - values: energy.value, - onChanged: (value) { - energy.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.instrumentalness), - values: instrumentalness.value, - onChanged: (value) { - instrumentalness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.liveness), - values: liveness.value, - onChanged: (value) { - liveness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.loudness), - values: loudness.value, - onChanged: (value) { - loudness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.speechiness), - values: speechiness.value, - onChanged: (value) { - speechiness.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.valence), - values: valence.value, - onChanged: (value) { - valence.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.popularity), - values: popularity.value, - base: 100, - onChanged: (value) { - popularity.value = value; - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.key), - values: key.value, - base: 11, - onChanged: (value) { - key.value = value; - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.duration), - values: ( - max: durationMs.value.max / 1000, - target: durationMs.value.target / 1000, - min: durationMs.value.min / 1000, - ), - onChanged: (value) { - durationMs.value = ( - max: value.max * 1000, - target: value.target * 1000, - min: value.min * 1000, - ); - }, - presets: { - context.l10n.short: (min: 50, target: 90, max: 120), - context.l10n.medium: (min: 120, target: 180, max: 200), - context.l10n.long: (min: 480, target: 560, max: 640) - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.tempo), - values: tempo.value, - onChanged: (value) { - tempo.value = value; - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.mode), - values: mode.value, - onChanged: (value) { - mode.value = value; - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.time_signature), - values: timeSignature.value, - onChanged: (value) { - timeSignature.value = value; - }, - ), - const SizedBox(height: 20), - FilledButton.icon( - icon: const Icon(SpotubeIcons.magic), - label: Text(context.l10n.generate_playlist), - onPressed: artists.value.isEmpty && - tracks.value.isEmpty && - genres.value.isEmpty - ? null - : () { - final PlaylistGenerateResultRouteState - routeState = ( - seeds: ( - artists: - artists.value.map((a) => a.id!).toList(), - tracks: - tracks.value.map((t) => t.id!).toList(), - genres: genres.value - ), - market: market.value, - limit: limit.value, - parameters: ( - acousticness: acousticness.value, - danceability: danceability.value, - energy: energy.value, - instrumentalness: instrumentalness.value, - liveness: liveness.value, - loudness: loudness.value, - speechiness: speechiness.value, - valence: valence.value, - popularity: popularity.value, - key: key.value, - duration_ms: durationMs.value, - tempo: tempo.value, - mode: mode.value, - time_signature: timeSignature.value, - ) - ); - GoRouter.of(context).push( - "/library/generate/result", - extra: routeState, - ); - }, - ), - ], - ); - }), + ); + }), + ), ), ), ), diff --git a/lib/pages/library/playlist_generate/playlist_generate_result.dart b/lib/pages/library/playlist_generate/playlist_generate_result.dart index 015685f1..f751b65b 100644 --- a/lib/pages/library/playlist_generate/playlist_generate_result.dart +++ b/lib/pages/library/playlist_generate/playlist_generate_result.dart @@ -163,6 +163,7 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { context: context, builder: (context) => PlaylistAddTrackDialog( + openFromPlaylist: null, tracks: selectedTracks.value .map( (e) => generatedPlaylist.data! diff --git a/lib/pages/lyrics/plain_lyrics.dart b/lib/pages/lyrics/plain_lyrics.dart index f6eaa5d5..bee5114d 100644 --- a/lib/pages/lyrics/plain_lyrics.dart +++ b/lib/pages/lyrics/plain_lyrics.dart @@ -1,12 +1,15 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/lyrics/zoom_controls.dart'; import 'package:spotube/components/shared/shimmers/shimmer_lyrics.dart'; import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; @@ -72,10 +75,22 @@ class PlainLyrics extends HookConsumerWidget { if (lyricsQuery.isLoading || lyricsQuery.isRefreshing) { return const ShimmerLyrics(); } else if (lyricsQuery.hasError) { - return Text( - "Sorry, no Lyrics were found for `${playlist.activeTrack?.name}` :'(\n${lyricsQuery.error.toString()}", - style: textTheme.bodyLarge?.copyWith( - color: palette.bodyTextColor, + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + context.l10n.no_lyrics_available, + style: textTheme.bodyLarge?.copyWith( + color: palette.bodyTextColor, + ), + textAlign: TextAlign.center, + ), + const Gap(26), + const Icon(SpotubeIcons.noLyrics, size: 60), + ], ), ); } diff --git a/lib/pages/lyrics/synced_lyrics.dart b/lib/pages/lyrics/synced_lyrics.dart index 36a9f316..ddef1c65 100644 --- a/lib/pages/lyrics/synced_lyrics.dart +++ b/lib/pages/lyrics/synced_lyrics.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:spotify/spotify.dart' hide Offset; @@ -7,6 +8,7 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/lyrics/zoom_controls.dart'; import 'package:spotube/components/shared/shimmers/shimmer_lyrics.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/components/lyrics/use_synced_lyrics.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; @@ -77,7 +79,7 @@ class SyncedLyrics extends HookConsumerWidget { : textTheme.headlineMedium?.copyWith(fontSize: 25)) ?.copyWith(color: palette.titleTextColor); - var bodyTextTheme = textTheme.bodyLarge?.copyWith( + final bodyTextTheme = textTheme.bodyLarge?.copyWith( color: palette.bodyTextColor, ); return Stack( @@ -184,14 +186,23 @@ class SyncedLyrics extends HookConsumerWidget { ), if (playlist.activeTrack != null && (timedLyricsQuery.isLoading || timedLyricsQuery.isRefreshing)) - const Expanded(child: ShimmerLyrics()) - else if (playlist.activeTrack != null && - (timedLyricsQuery.hasError)) - Text( - "Sorry, no Lyrics were found for `${playlist.activeTrack?.name}` :'(\n${timedLyricsQuery.error.toString()}", - style: bodyTextTheme, + const Expanded( + child: ShimmerLyrics(), ) - else if (isUnSyncLyric == true) + else if (playlist.activeTrack != null && + (timedLyricsQuery.hasError)) ...[ + Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(16), + child: Text( + context.l10n.no_lyrics_available, + style: bodyTextTheme, + textAlign: TextAlign.center, + ), + ), + const Gap(26), + const Icon(SpotubeIcons.noLyrics, size: 60), + ] else if (isUnSyncLyric == true) Expanded( child: Center( child: RichText( @@ -201,7 +212,7 @@ class SyncedLyrics extends HookConsumerWidget { children: [ const TextSpan( text: - "Synced lyrics is not available for this song. Please use the", + "Synced lyrics are not available for this song. Please use the", ), TextSpan( text: " Plain Lyrics ", diff --git a/lib/pages/mobile_login/mobile_login.dart b/lib/pages/mobile_login/mobile_login.dart index 7ab0ea2a..8b9bce4c 100644 --- a/lib/pages/mobile_login/mobile_login.dart +++ b/lib/pages/mobile_login/mobile_login.dart @@ -55,12 +55,7 @@ class WebViewLogin extends HookConsumerWidget { final cookies = await CookieManager.instance().getCookies(url: action); final cookieHeader = - cookies.fold("", (previousValue, element) { - if (element.name == "sp_dc" || element.name == "sp_key") { - return "$previousValue; ${element.name}=${element.value}"; - } - return previousValue; - }); + "sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}"; authenticationNotifier.setCredentials( await AuthenticationCredentials.fromCookie(cookieHeader), diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 1f252ed4..1fb2e1dc 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -4,7 +4,6 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/components/shared/tracks_view/track_view.dart'; import 'package:spotube/components/shared/tracks_view/track_view_props.dart'; import 'package:spotube/services/queries/queries.dart'; -import 'package:spotube/utils/type_conversion_utils.dart'; class LikedPlaylistPage extends HookConsumerWidget { final PlaylistSimple playlist; @@ -20,10 +19,7 @@ class LikedPlaylistPage extends HookConsumerWidget { return InheritedTrackView( collectionId: playlist.id!, - image: TypeConversionUtils.image_X_UrlString( - playlist.images, - placeholder: ImagePlaceholder.collection, - ), + image: "assets/liked-tracks.jpg", pagination: PaginationProps( hasNextPage: false, isLoading: false, @@ -31,6 +27,9 @@ class LikedPlaylistPage extends HookConsumerWidget { onFetchAll: () async { return tracks.toList(); }, + onRefresh: () async { + await likedTracks.refresh(); + }, ), title: playlist.name!, description: playlist.description, diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index ab39b225..29601a09 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -53,7 +53,7 @@ class PlaylistPage extends HookConsumerWidget { ), pagination: PaginationProps.fromQuery( tracksQuery, - onFetchAll: () async { + onFetchAll: () { return tracksQuery.fetchAllTracks( getAllTracks: () async { final res = await spotify.playlists diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index b19162fa..f4a78d4f 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -114,8 +114,9 @@ class SearchPage extends HookConsumerWidget { ), color: theme.scaffoldBackgroundColor, child: TextField( - autofocus: - queries.none((s) => s.hasPageData && !s.hasPageError), + autofocus: queries + .none((s) => s.hasPageData && !s.hasPageError) && + !kIsMobile, decoration: InputDecoration( prefixIcon: const Icon(SpotubeIcons.search), hintText: "${context.l10n.search}...", diff --git a/lib/pages/search/sections/artists.dart b/lib/pages/search/sections/artists.dart index b736bf13..fe4459d6 100644 --- a/lib/pages/search/sections/artists.dart +++ b/lib/pages/search/sections/artists.dart @@ -32,7 +32,7 @@ class SearchArtistsSection extends HookConsumerWidget { hasNextPage: query.hasNextPage, items: artists, onFetchMore: query.fetchNext, - title: Text(context.l10n.albums), + title: Text(context.l10n.artists), ); } } diff --git a/lib/pages/settings/sections/desktop.dart b/lib/pages/settings/sections/desktop.dart index 41d6d61e..ae721fc4 100644 --- a/lib/pages/settings/sections/desktop.dart +++ b/lib/pages/settings/sections/desktop.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/settings/section_card_with_heading.dart'; @@ -50,6 +51,13 @@ class SettingsDesktopSection extends HookConsumerWidget { value: preferences.systemTitleBar, onChanged: preferencesNotifier.setSystemTitleBar, ), + if (!DesktopTools.platform.isMacOS) + SwitchListTile( + secondary: const Icon(SpotubeIcons.discord), + title: Text(context.l10n.discord_rich_presence), + value: preferences.discordPresence, + onChanged: preferencesNotifier.setDiscordPresence, + ), ], ); } diff --git a/lib/pages/settings/sections/playback.dart b/lib/pages/settings/sections/playback.dart index a0316b33..d36e0713 100644 --- a/lib/pages/settings/sections/playback.dart +++ b/lib/pages/settings/sections/playback.dart @@ -51,7 +51,7 @@ class SettingsPlaybackSection extends HookConsumerWidget { ), AdaptiveSelectTile( secondary: const Icon(SpotubeIcons.api), - title: Text(context.l10n.youtube_api_type), + title: Text(context.l10n.audio_source), value: preferences.audioSource, options: AudioSource.values .map((e) => DropdownMenuItem( diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 842d5240..f773b809 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -30,12 +30,13 @@ class SettingsPage extends HookConsumerWidget { title: Text(context.l10n.settings), centerTitle: true, ), - body: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: Container( - constraints: const BoxConstraints(maxWidth: 1366), + body: Scrollbar( + controller: controller, + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 1366), + child: ScrollConfiguration( + behavior: const ScrollBehavior().copyWith(scrollbars: false), child: ListView( controller: controller, children: [ @@ -59,7 +60,7 @@ class SettingsPage extends HookConsumerWidget { ), ), ), - ], + ), ), ), ); diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart new file mode 100644 index 00000000..14052c10 --- /dev/null +++ b/lib/pages/track/track.dart @@ -0,0 +1,227 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/shared/heart_button.dart'; +import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/components/shared/links/link_text.dart'; +import 'package:spotube/components/shared/page_window_title_bar.dart'; +import 'package:spotube/components/shared/track_tile/track_options.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/queries/queries.dart'; +import 'package:spotube/utils/type_conversion_utils.dart'; +import 'package:spotube/extensions/constrains.dart'; + +class TrackPage extends HookConsumerWidget { + final String trackId; + const TrackPage({ + Key? key, + required this.trackId, + }) : super(key: key); + + @override + Widget build(BuildContext context, ref) { + final ThemeData(:textTheme, :colorScheme) = Theme.of(context); + final mediaQuery = MediaQuery.of(context); + + final playlist = ref.watch(ProxyPlaylistNotifier.provider); + final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier); + + final isActive = playlist.activeTrack?.id == trackId; + + final trackQuery = useQueries.tracks.track(ref, trackId); + + final track = trackQuery.data ?? FakeData.track; + + void onPlay() async { + if (isActive) { + audioPlayer.pause(); + } else { + await playlistNotifier.load([track], autoPlay: true); + } + } + + return Scaffold( + appBar: const PageWindowTitleBar( + automaticallyImplyLeading: true, + backgroundColor: Colors.transparent, + ), + extendBodyBehindAppBar: true, + body: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: UniversalImage.imageProvider( + TypeConversionUtils.image_X_UrlString( + track.album!.images, + placeholder: ImagePlaceholder.albumArt, + ), + ), + fit: BoxFit.cover, + colorFilter: ColorFilter.mode( + colorScheme.surface.withOpacity(0.5), + BlendMode.srcOver, + ), + alignment: Alignment.topCenter, + ), + ), + ), + ), + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: Skeletonizer( + enabled: trackQuery.isLoading, + child: Container( + alignment: Alignment.topCenter, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + colorScheme.surface, + Colors.transparent, + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: const [0.2, 1], + ), + ), + child: SafeArea( + child: Wrap( + spacing: 20, + runSpacing: 20, + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + runAlignment: WrapAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: UniversalImage( + path: TypeConversionUtils.image_X_UrlString( + track.album!.images, + placeholder: ImagePlaceholder.albumArt, + ), + height: 200, + width: 200, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: mediaQuery.smAndDown + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + track.name!, + style: textTheme.titleLarge, + ), + const Gap(10), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(SpotubeIcons.album), + const Gap(5), + Flexible( + child: LinkText( + track.album!.name!, + '/album/${track.album!.id}', + push: true, + extra: track.album, + ), + ), + ], + ), + const Gap(10), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(SpotubeIcons.artist), + const Gap(5), + TypeConversionUtils + .artists_X_ClickableArtists( + track.artists!, + ), + ], + ), + const Gap(10), + ConstrainedBox( + constraints: + const BoxConstraints(maxWidth: 350), + child: Row( + mainAxisSize: mediaQuery.smAndDown + ? MainAxisSize.max + : MainAxisSize.min, + children: [ + const Gap(5), + if (!isActive && + !playlist.tracks.contains(track)) + OutlinedButton.icon( + icon: const Icon(SpotubeIcons.queueAdd), + label: Text(context.l10n.queue), + onPressed: () { + playlistNotifier.addTrack(track); + }, + ), + const Gap(5), + if (!isActive && + !playlist.tracks.contains(track)) + IconButton.outlined( + icon: + const Icon(SpotubeIcons.lightning), + tooltip: context.l10n.play_next, + onPressed: () { + playlistNotifier + .addTracksAtFirst([track]); + }, + ), + const Gap(5), + IconButton.filled( + tooltip: isActive + ? context.l10n.pause_playback + : context.l10n.play, + icon: Icon( + isActive + ? SpotubeIcons.pause + : SpotubeIcons.play, + color: colorScheme.onPrimary, + ), + onPressed: onPlay, + ), + const Gap(5), + if (mediaQuery.smAndDown) + const Spacer() + else + const Gap(20), + TrackHeartButton(track: track), + TrackOptions( + track: track, + userPlaylist: false, + ), + const Gap(5), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/provider/discord_provider.dart b/lib/provider/discord_provider.dart new file mode 100644 index 00000000..3aa547a9 --- /dev/null +++ b/lib/provider/discord_provider.dart @@ -0,0 +1,70 @@ +import 'package:dart_discord_rpc/dart_discord_rpc.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/env.dart'; +import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/utils/type_conversion_utils.dart'; + +class Discord extends ChangeNotifier { + final DiscordRPC? discordRPC; + final bool isEnabled; + + Discord(this.isEnabled) + : discordRPC = (DesktopTools.platform.isWindows || + DesktopTools.platform.isLinux) && + isEnabled + ? DiscordRPC(applicationId: Env.discordAppId) + : null { + discordRPC?.start(autoRegister: true); + } + + void updatePresence(Track track) { + clear(); + final artistNames = + TypeConversionUtils.artists_X_String(track.artists ?? []); + discordRPC?.updatePresence( + DiscordPresence( + details: "Song: ${track.name} by $artistNames", + state: "Vibing in Music", + startTimeStamp: DateTime.now().millisecondsSinceEpoch, + largeImageKey: "spotube-logo-foreground", + largeImageText: "Spotube", + smallImageKey: "spotube-logo-foreground", + smallImageText: "Spotube", + ), + ); + } + + void clear() { + discordRPC?.clearPresence(); + } + + void shutdown() { + discordRPC?.shutDown(); + } + + @override + void dispose() { + clear(); + shutdown(); + super.dispose(); + } +} + +final discordProvider = ChangeNotifierProvider( + (ref) { + final isEnabled = + ref.watch(userPreferencesProvider.select((s) => s.discordPresence)); + final playback = ref.read(ProxyPlaylistNotifier.provider); + final discord = Discord(isEnabled); + + if (playback.activeTrack != null) { + discord.updatePresence(playback.activeTrack!); + } + + return discord; + }, +); diff --git a/lib/provider/download_manager_provider.dart b/lib/provider/download_manager_provider.dart index 691a1385..dc538938 100644 --- a/lib/provider/download_manager_provider.dart +++ b/lib/provider/download_manager_provider.dart @@ -25,7 +25,7 @@ class DownloadManagerProvider extends ChangeNotifier { final (:request, :status) = event; final track = $history.firstWhereOrNull( - (element) => element.url == request.url, + (element) => element.getUrlOfCodec(downloadCodec) == request.url, ); if (track == null) return; diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index 89bb8a6c..ca0fb308 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -24,10 +24,12 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart import 'package:spotube/provider/user_preferences/user_preferences_state.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_services/audio_services.dart'; -import 'package:spotube/services/discord/discord.dart'; +import 'package:spotube/provider/discord_provider.dart'; import 'package:spotube/services/sourced_track/exceptions.dart'; import 'package:spotube/services/sourced_track/models/source_info.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; +import 'package:spotube/services/sourced_track/sources/piped.dart'; +import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/utils/persisted_state_notifier.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; @@ -62,6 +64,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier ProxyPlaylist get playlist => state; BlackListNotifier get blacklist => ref.read(BlackListNotifier.provider.notifier); + Discord get discord => ref.read(discordProvider); static final provider = StateNotifierProvider( @@ -161,8 +164,8 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier return; } try { - final isNotYTMode = preferences.audioSource != AudioSource.youtube || - (preferences.audioSource == AudioSource.piped && + final isNotYTMode = state.activeTrack is! YoutubeSourcedTrack && + (state.activeTrack is PipedSourcedTrack && preferences.searchMode == SearchMode.youtubeMusic); if (isNotYTMode || !preferences.skipNonMusic) return; diff --git a/lib/provider/scrobbler_provider.dart b/lib/provider/scrobbler_provider.dart index 5ac3c5a1..bf234e62 100644 --- a/lib/provider/scrobbler_provider.dart +++ b/lib/provider/scrobbler_provider.dart @@ -43,7 +43,7 @@ class ScrobblerNotifier extends PersistedStateNotifier { _scrobbleController.stream.listen((track) async { try { await state?.scrobblenaut.track.scrobble( - artist: TypeConversionUtils.artists_X_String(track.artists!), + artist: track.artists!.first.name!, track: track.name!, album: track.album!.name!, chosenByUser: true, diff --git a/lib/provider/user_preferences/user_preferences_provider.dart b/lib/provider/user_preferences/user_preferences_provider.dart index 88a0df2e..46569269 100644 --- a/lib/provider/user_preferences/user_preferences_provider.dart +++ b/lib/provider/user_preferences/user_preferences_provider.dart @@ -110,6 +110,10 @@ class UserPreferencesNotifier extends PersistedStateNotifier { } } + void setDiscordPresence(bool discordPresence) { + state = state.copyWith(discordPresence: discordPresence); + } + void setAmoledDarkTheme(bool isAmoled) { state = state.copyWith(amoledDarkTheme: isAmoled); } diff --git a/lib/provider/user_preferences/user_preferences_state.dart b/lib/provider/user_preferences/user_preferences_state.dart index b3d7fe8a..4244ca67 100644 --- a/lib/provider/user_preferences/user_preferences_state.dart +++ b/lib/provider/user_preferences/user_preferences_state.dart @@ -198,6 +198,9 @@ final class UserPreferences { ) final SourceCodecs downloadMusicCodec; + @JsonKey(defaultValue: true) + final bool discordPresence; + UserPreferences({ required this.audioQuality, required this.albumColorSync, @@ -219,6 +222,7 @@ final class UserPreferences { required this.audioSource, required this.streamMusicCodec, required this.downloadMusicCodec, + required this.discordPresence, }); factory UserPreferences.withDefaults() { @@ -255,6 +259,7 @@ final class UserPreferences { SourceCodecs? downloadMusicCodec, SourceCodecs? streamMusicCodec, bool? systemTitleBar, + bool? discordPresence, }) { return UserPreferences( themeMode: themeMode ?? this.themeMode, @@ -277,6 +282,7 @@ final class UserPreferences { normalizeAudio: normalizeAudio ?? this.normalizeAudio, streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, systemTitleBar: systemTitleBar ?? this.systemTitleBar, + discordPresence: discordPresence ?? this.discordPresence, ); } } diff --git a/lib/provider/user_preferences/user_preferences_state.g.dart b/lib/provider/user_preferences/user_preferences_state.g.dart index 54cd3aa2..59043601 100644 --- a/lib/provider/user_preferences/user_preferences_state.g.dart +++ b/lib/provider/user_preferences/user_preferences_state.g.dart @@ -63,6 +63,7 @@ UserPreferences _$UserPreferencesFromJson(Map json) => _$SourceCodecsEnumMap, json['downloadMusicCodec'], unknownValue: SourceCodecs.m4a) ?? SourceCodecs.m4a, + discordPresence: json['discordPresence'] as bool? ?? true, ); Map _$UserPreferencesToJson(UserPreferences instance) => @@ -88,6 +89,7 @@ Map _$UserPreferencesToJson(UserPreferences instance) => 'audioSource': _$AudioSourceEnumMap[instance.audioSource]!, 'streamMusicCodec': _$SourceCodecsEnumMap[instance.streamMusicCodec]!, 'downloadMusicCodec': _$SourceCodecsEnumMap[instance.downloadMusicCodec]!, + 'discordPresence': instance.discordPresence, }; const _$SourceQualitiesEnumMap = { diff --git a/lib/services/custom_spotify_endpoints/spotify_endpoints.dart b/lib/services/custom_spotify_endpoints/spotify_endpoints.dart index 4a55130a..e27b701b 100644 --- a/lib/services/custom_spotify_endpoints/spotify_endpoints.dart +++ b/lib/services/custom_spotify_endpoints/spotify_endpoints.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:spotify/spotify.dart'; +import 'package:spotube/models/spotify_friends.dart'; class CustomSpotifyEndpoints { static const _baseUrl = 'https://api.spotify.com/v1'; @@ -162,4 +163,71 @@ class CustomSpotifyEndpoints { result["tracks"].map((track) => Track.fromJson(track)).toList(), ); } + + Future getFriendActivity() async { + final res = await _client.get( + Uri.parse("https://guc-spclient.spotify.com/presence-view/v1/buddylist"), + headers: { + "content-type": "application/json", + "authorization": "Bearer $accessToken", + "accept": "application/json", + }, + ); + return SpotifyFriends.fromJson(jsonDecode(res.body)); + } + + Future artist({required String id}) async { + final pathQuery = "$_baseUrl/artists/$id"; + + final res = await _client.get( + Uri.parse(pathQuery), + headers: { + "content-type": "application/json", + if (accessToken.isNotEmpty) "authorization": "Bearer $accessToken", + "accept": "application/json", + }, + ); + final data = jsonDecode(res.body); + + return Artist.fromJson(_purifyArtistResponse(data)); + } + + Future> relatedArtists({required String id}) async { + final pathQuery = "$_baseUrl/artists/$id/related-artists"; + + final res = await _client.get( + Uri.parse(pathQuery), + headers: { + "content-type": "application/json", + if (accessToken.isNotEmpty) "authorization": "Bearer $accessToken", + "accept": "application/json", + }, + ); + + final data = jsonDecode(res.body); + + return List.castFrom( + data["artists"] + .map((artist) => Artist.fromJson(_purifyArtistResponse(artist))) + .toList(), + ); + } + + Map _purifyArtistResponse(Map data) { + if (data["popularity"] != null) { + data["popularity"] = data["popularity"].toInt(); + } + if (data["followers"]?["total"] != null) { + data["followers"]["total"] = data["followers"]["total"].toInt(); + } + if (data["images"] != null) { + data["images"] = data["images"].map((e) { + e["height"] = e["height"].toInt(); + e["width"] = e["width"].toInt(); + return e; + }).toList(); + } + + return data; + } } diff --git a/lib/services/discord/discord.dart b/lib/services/discord/discord.dart deleted file mode 100644 index 2a40e388..00000000 --- a/lib/services/discord/discord.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:dart_discord_rpc/dart_discord_rpc.dart'; -import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/env.dart'; -import 'package:spotube/utils/type_conversion_utils.dart'; - -class Discord { - final DiscordRPC? discordRPC; - - Discord() - : discordRPC = - DesktopTools.platform.isWindows || DesktopTools.platform.isLinux - ? DiscordRPC(applicationId: Env.discordAppId) - : null { - discordRPC?.start(autoRegister: true); - } - - void updatePresence(Track track) { - clear(); - final artistNames = - TypeConversionUtils.artists_X_String(track.artists ?? []); - discordRPC?.updatePresence( - DiscordPresence( - details: "Song: ${track.name} by $artistNames", - state: "Vibing in Music", - startTimeStamp: DateTime.now().millisecondsSinceEpoch, - largeImageKey: "spotube-logo-foreground", - largeImageText: "Spotube", - smallImageKey: "spotube-logo-foreground", - smallImageText: "Spotube", - ), - ); - } - - void clear() { - discordRPC?.clearPresence(); - } - - void shutdown() { - discordRPC?.shutDown(); - } -} - -final discord = Discord(); diff --git a/lib/services/download_manager/chunked_download.dart b/lib/services/download_manager/chunked_download.dart index b2849a3c..9e5e0a98 100644 --- a/lib/services/download_manager/chunked_download.dart +++ b/lib/services/download_manager/chunked_download.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; import 'package:spotube/models/logger.dart'; final logger = getLogger("ChunkedDownload"); diff --git a/lib/services/download_manager/download_manager.dart b/lib/services/download_manager/download_manager.dart index 904f06cf..d7a42430 100644 --- a/lib/services/download_manager/download_manager.dart +++ b/lib/services/download_manager/download_manager.dart @@ -6,6 +6,8 @@ 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/models/logger.dart'; import 'package:spotube/services/download_manager/chunked_download.dart'; import 'package:spotube/services/download_manager/download_request.dart'; @@ -77,7 +79,18 @@ class DownloadManager { logger.d("[DownloadManager] $url"); final file = File(savePath.toString()); - partialFilePath = savePath + partialExtension; + + 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(); @@ -111,7 +124,9 @@ class DownloadManager { await ioSink.addStream(partialChunkFile.openRead()); await partialChunkFile.delete(); await ioSink.close(); - await partialFile.rename(savePath); + + await partialFile.copy(savePath); + await partialFile.delete(); setStatus(task, DownloadStatus.completed); } @@ -125,7 +140,8 @@ class DownloadManager { ); if (response.statusCode == HttpStatus.ok) { - await partialFile.rename(savePath); + await partialFile.copy(savePath); + await partialFile.delete(); setStatus(task, DownloadStatus.completed); } } diff --git a/lib/services/queries/artist.dart b/lib/services/queries/artist.dart index 1b939c82..5ccc4955 100644 --- a/lib/services/queries/artist.dart +++ b/lib/services/queries/artist.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart'; import 'package:spotube/hooks/spotify/use_spotify_query.dart'; +import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/wikipedia/wikipedia.dart'; import 'package:wikipedia_api/wikipedia_api.dart'; @@ -15,9 +16,10 @@ class ArtistQueries { WidgetRef ref, String artist, ) { + final customSpotify = ref.watch(customSpotifyEndpointProvider); return useSpotifyQuery( "artist-profile/$artist", - (spotify) => spotify.artists.get(artist), + (spotify) => customSpotify.artist(id: artist), ref: ref, ); } @@ -125,10 +127,11 @@ class ArtistQueries { WidgetRef ref, String artist, ) { + final customSpotify = ref.watch(customSpotifyEndpointProvider); return useSpotifyQuery, dynamic>( "artist-related-artist-query/$artist", (spotify) { - return spotify.artists.relatedArtists(artist); + return customSpotify.relatedArtists(id: artist); }, ref: ref, ); diff --git a/lib/services/queries/category.dart b/lib/services/queries/category.dart index 960b5702..d520b909 100644 --- a/lib/services/queries/category.dart +++ b/lib/services/queries/category.dart @@ -5,12 +5,37 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/spotify/use_spotify_infinite_query.dart'; +import 'package:spotube/hooks/spotify/use_spotify_query.dart'; import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; class CategoryQueries { const CategoryQueries(); + Query, dynamic> listAll( + WidgetRef ref, + Market recommendationMarket, + ) { + ref.watch(userPreferencesProvider.select((s) => s.locale)); + final locale = useContext().l10n.localeName; + final query = useSpotifyQuery, dynamic>( + "category-playlists", + (spotify) async { + final categories = await spotify.categories + .list( + country: recommendationMarket, + locale: locale, + ) + .all(); + + return categories.toList()..shuffle(); + }, + ref: ref, + ); + + return query; + } + InfiniteQuery, dynamic, int> list( WidgetRef ref, Market recommendationMarket, diff --git a/lib/services/queries/lyrics.dart b/lib/services/queries/lyrics.dart index faa5bdec..618f960f 100644 --- a/lib/services/queries/lyrics.dart +++ b/lib/services/queries/lyrics.dart @@ -63,8 +63,8 @@ class LyricsQueries { /// Special thanks to [raptag](https://github.com/raptag) for discovering this /// jem - Query spotifySynced(WidgetRef ref, Track? track) { - return useSpotifyQuery( + Query spotifySynced(WidgetRef ref, Track? track) { + return useSpotifyQuery( "spotify-synced-lyrics/${track?.id}}", (spotify) async { if (track == null) { diff --git a/lib/services/queries/queries.dart b/lib/services/queries/queries.dart index cc3ce132..30c23268 100644 --- a/lib/services/queries/queries.dart +++ b/lib/services/queries/queries.dart @@ -4,6 +4,7 @@ import 'package:spotube/services/queries/category.dart'; import 'package:spotube/services/queries/lyrics.dart'; import 'package:spotube/services/queries/playlist.dart'; import 'package:spotube/services/queries/search.dart'; +import 'package:spotube/services/queries/tracks.dart'; import 'package:spotube/services/queries/user.dart'; import 'package:spotube/services/queries/views.dart'; @@ -17,6 +18,7 @@ class Queries { final search = const SearchQueries(); final user = const UserQueries(); final views = const ViewsQueries(); + final tracks = const TracksQueries(); } const useQueries = Queries._(); diff --git a/lib/services/queries/tracks.dart b/lib/services/queries/tracks.dart new file mode 100644 index 00000000..52bab984 --- /dev/null +++ b/lib/services/queries/tracks.dart @@ -0,0 +1,16 @@ +import 'package:fl_query/fl_query.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/hooks/spotify/use_spotify_query.dart'; + +class TracksQueries { + const TracksQueries(); + + Query track(WidgetRef ref, String id) { + return useSpotifyQuery( + "track/$id", + (spotify) => spotify.tracks.get(id), + ref: ref, + ); + } +} diff --git a/lib/services/queries/user.dart b/lib/services/queries/user.dart index 40799c1e..82af600f 100644 --- a/lib/services/queries/user.dart +++ b/lib/services/queries/user.dart @@ -3,7 +3,9 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/hooks/spotify/use_spotify_query.dart'; +import 'package:spotube/models/spotify_friends.dart'; import 'package:spotube/provider/authentication_provider.dart'; +import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; class UserQueries { @@ -37,4 +39,15 @@ class UserQueries { ref: ref, ); } + + Query friendActivity(WidgetRef ref) { + final customSpotify = ref.read(customSpotifyEndpointProvider); + return useSpotifyQuery( + "friend-activity", + (spotify) { + return customSpotify.getFriendActivity(); + }, + ref: ref, + ); + } } diff --git a/lib/services/sourced_track/enums.dart b/lib/services/sourced_track/enums.dart index 48ce1cbd..e47ee6bd 100644 --- a/lib/services/sourced_track/enums.dart +++ b/lib/services/sourced_track/enums.dart @@ -15,4 +15,4 @@ enum SourceQualities { low, } -typedef SiblingType = ({SourceInfo info, SourceMap? source}); +typedef SiblingType = ({T info, SourceMap? source}); diff --git a/lib/services/sourced_track/sources/jiosaavn.dart b/lib/services/sourced_track/sources/jiosaavn.dart index a447b0c1..281be998 100644 --- a/lib/services/sourced_track/sources/jiosaavn.dart +++ b/lib/services/sourced_track/sources/jiosaavn.dart @@ -12,6 +12,19 @@ import 'package:spotube/extensions/string.dart'; final jiosaavnClient = JioSaavnClient(); +class JioSaavnSourceInfo extends SourceInfo { + JioSaavnSourceInfo({ + required super.id, + required super.title, + required super.artist, + required super.thumbnail, + required super.pageUrl, + required super.duration, + required super.artistUrl, + required super.album, + }); +} + class JioSaavnSourcedTrack extends SourcedTrack { JioSaavnSourcedTrack({ required super.ref, @@ -70,7 +83,7 @@ class JioSaavnSourcedTrack extends SourcedTrack { static SiblingType toSiblingType(SongResponse result) { final SiblingType sibling = ( - info: SourceInfo( + info: JioSaavnSourceInfo( artist: [ result.primaryArtists, if (result.featuredArtists.isNotEmpty) ", ", @@ -155,12 +168,16 @@ class JioSaavnSourcedTrack extends SourcedTrack { @override Future swapWithSibling(SourceInfo sibling) async { - if (sibling.id == sourceInfo.id || - siblings.none((s) => s.id == sibling.id)) { + if (sibling.id == sourceInfo.id) { return null; } - final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); + // 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, sourceInfo); diff --git a/lib/services/sourced_track/sources/piped.dart b/lib/services/sourced_track/sources/piped.dart index 0778a7cf..f9e4368d 100644 --- a/lib/services/sourced_track/sources/piped.dart +++ b/lib/services/sourced_track/sources/piped.dart @@ -22,6 +22,19 @@ final pipedProvider = Provider( }, ); +class PipedSourceInfo extends SourceInfo { + PipedSourceInfo({ + required super.id, + required super.title, + required super.artist, + required super.thumbnail, + required super.pageUrl, + required super.duration, + required super.artistUrl, + required super.album, + }); +} + class PipedSourcedTrack extends SourcedTrack { PipedSourcedTrack({ required super.ref, @@ -71,7 +84,7 @@ class PipedSourcedTrack extends SourcedTrack { ref: ref, siblings: [], source: toSourceMap(manifest), - sourceInfo: SourceInfo( + sourceInfo: PipedSourceInfo( id: manifest.id, artist: manifest.uploader, artistUrl: manifest.uploaderUrl, @@ -122,7 +135,7 @@ class PipedSourcedTrack extends SourcedTrack { } final SiblingType sibling = ( - info: SourceInfo( + info: PipedSourceInfo( id: item.id, artist: item.channelName, artistUrl: "https://www.youtube.com/${item.channelId}", @@ -233,12 +246,16 @@ class PipedSourcedTrack extends SourcedTrack { @override Future swapWithSibling(SourceInfo sibling) async { - if (sibling.id == sourceInfo.id || - siblings.none((s) => s.id == sibling.id)) { + if (sibling.id == sourceInfo.id) { return null; } - final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); + // 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, sourceInfo); diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart index 096de2d4..c4105d75 100644 --- a/lib/services/sourced_track/sources/youtube.dart +++ b/lib/services/sourced_track/sources/youtube.dart @@ -17,6 +17,19 @@ final officialMusicRegex = RegExp( caseSensitive: false, ); +class YoutubeSourceInfo extends SourceInfo { + YoutubeSourceInfo({ + required super.id, + required super.title, + required super.artist, + required super.thumbnail, + required super.pageUrl, + required super.duration, + required super.artistUrl, + required super.album, + }); +} + class YoutubeSourcedTrack extends SourcedTrack { YoutubeSourcedTrack({ required super.source, @@ -64,7 +77,7 @@ class YoutubeSourcedTrack extends SourcedTrack { ref: ref, siblings: [], source: toSourceMap(manifest), - sourceInfo: SourceInfo( + sourceInfo: YoutubeSourceInfo( id: item.id.value, artist: item.author, artistUrl: "https://www.youtube.com/channel/${item.channelId}", @@ -79,14 +92,17 @@ class YoutubeSourcedTrack extends SourcedTrack { } static SourceMap toSourceMap(StreamManifest manifest) { - final m4a = manifest.audioOnly + var m4a = manifest.audioOnly .where((audio) => audio.codec.mimeType == "audio/mp4") .sortByBitrate(); - final weba = manifest.audioOnly + var weba = manifest.audioOnly .where((audio) => audio.codec.mimeType == "audio/webm") .sortByBitrate(); + m4a = m4a.isEmpty ? weba.toList() : m4a; + weba = weba.isEmpty ? m4a.toList() : weba; + return SourceMap( m4a: SourceQualityMap( high: m4a.first.url.toString(), @@ -114,7 +130,7 @@ class YoutubeSourcedTrack extends SourcedTrack { } final SiblingType sibling = ( - info: SourceInfo( + info: YoutubeSourceInfo( id: item.id, artist: item.channelName, artistUrl: "https://www.youtube.com/channel/${item.channelId}", @@ -214,12 +230,16 @@ class YoutubeSourcedTrack extends SourcedTrack { @override Future swapWithSibling(SourceInfo sibling) async { - if (sibling.id == sourceInfo.id || - siblings.none((s) => s.id == sibling.id)) { + if (sibling.id == sourceInfo.id) { return null; } - final newSourceInfo = siblings.firstWhere((s) => s.id == sibling.id); + // 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, sourceInfo); diff --git a/lib/themes/theme.dart b/lib/themes/theme.dart index 8c968e1b..51e98269 100644 --- a/lib/themes/theme.dart +++ b/lib/themes/theme.dart @@ -52,6 +52,7 @@ ThemeData theme(Color seed, Brightness brightness, bool isAmoled) { ), sliderTheme: SliderThemeData(overlayShape: SliderComponentShape.noOverlay), searchBarTheme: SearchBarThemeData( + textStyle: const MaterialStatePropertyAll(TextStyle(fontSize: 15)), constraints: const BoxConstraints(maxWidth: double.infinity), padding: const MaterialStatePropertyAll(EdgeInsets.all(8)), backgroundColor: MaterialStatePropertyAll( @@ -71,5 +72,8 @@ ThemeData theme(Color seed, Brightness brightness, bool isAmoled) { scrollbarTheme: const ScrollbarThemeData( thickness: MaterialStatePropertyAll(14), ), + checkboxTheme: CheckboxThemeData( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + ), ); } diff --git a/lib/utils/type_conversion_utils.dart b/lib/utils/type_conversion_utils.dart index a805272c..662b611c 100644 --- a/lib/utils/type_conversion_utils.dart +++ b/lib/utils/type_conversion_utils.dart @@ -147,7 +147,7 @@ abstract class TypeConversionUtils { track.name = metadata?.title ?? basenameWithoutExtension(file.path); track.type = "track"; track.uri = file.path; - track.durationMs = (metadata?.durationMs?.toInt() ?? 0) * 1000; + track.durationMs = (metadata?.durationMs?.toInt() ?? 0); return track; } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index a07f7f9b..c69c17c0 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { 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); + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_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); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 97d541b3..a4487f4d 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dart_discord_rpc file_selector_linux flutter_secure_storage_linux + gtk local_notifier media_kit_libs_linux screen_retriever diff --git a/linux/my_application.cc b/linux/my_application.cc index 759285af..d1ac5d12 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -17,6 +17,13 @@ G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. static void my_application_activate(GApplication* application) { MyApplication* self = MY_APPLICATION(application); + + GList* windows = gtk_application_get_windows(GTK_APPLICATION(application)); + if (windows) { + gtk_window_present(GTK_WINDOW(windows->data)); + return; + } + GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); @@ -78,7 +85,7 @@ static gboolean my_application_local_command_line(GApplication* application, gch g_application_activate(application); *exit_status = 0; - return TRUE; + return FALSE; } // Implements GObject::dispose. @@ -98,7 +105,7 @@ static void my_application_init(MyApplication* self) {} MyApplication* my_application_new() { return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - "flags", G_APPLICATION_NON_UNIQUE, + "com.github.KRTirtho.Spotube", APPLICATION_ID, + "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN, nullptr)); } diff --git a/linux/packaging/appimage/make_config.yaml b/linux/packaging/appimage/make_config.yaml index 68e36df7..c7332ea2 100644 --- a/linux/packaging/appimage/make_config.yaml +++ b/linux/packaging/appimage/make_config.yaml @@ -11,3 +11,6 @@ keywords: generic_name: Music Streaming Application categories: - Music + +supported_mime_type: + - x-scheme-handler/spotify diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml index 46493122..f4c279b4 100644 --- a/linux/packaging/deb/make_config.yaml +++ b/linux/packaging/deb/make_config.yaml @@ -32,3 +32,6 @@ keywords: generic_name: Music Streaming Application categories: - Music + +supported_mime_type: + - x-scheme-handler/spotify diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml index 00f4c20e..1f952d0e 100644 --- a/linux/packaging/rpm/make_config.yaml +++ b/linux/packaging/rpm/make_config.yaml @@ -28,3 +28,6 @@ categories: - Music startup_notify: true + +supported_mime_type: + - x-scheme-handler/spotify diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 270e6261..a7965e14 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import app_links import audio_service import audio_session import device_info_plus @@ -24,6 +25,7 @@ import window_manager import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) diff --git a/macos/Podfile b/macos/Podfile index fe733905..049abe29 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.13' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 99c0177d..65fe3535 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,101 +1,146 @@ PODS: + - app_links (1.0.0): + - FlutterMacOS - audio_service (0.14.1): - FlutterMacOS - audio_session (0.0.1): - FlutterMacOS - - audioplayers_darwin (0.0.1): + - device_info_plus (0.0.1): - FlutterMacOS - - bitsdojo_window_macos (0.0.1): + - file_selector_macos (0.0.1): - FlutterMacOS - - connectivity_plus_macos (0.0.1): + - flutter_secure_storage_macos (6.1.1): - FlutterMacOS - - ReachabilitySwift - FlutterMacOS (1.0.0) - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) - - macos_ui (0.1.0): + - local_notifier (0.1.0): - FlutterMacOS - - metadata_god (0.0.1): + - media_kit_libs_macos_audio (1.0.4): - FlutterMacOS - - package_info_plus_macos (0.0.1): + - media_kit_native_event_loop (1.0.0): - FlutterMacOS - - path_provider_macos (0.0.1): + - metadata_god (0.0.1) + - package_info_plus (0.0.1): - FlutterMacOS - - ReachabilitySwift (5.0.0) - - shared_preferences_macos (0.0.1): + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - screen_retriever (0.0.1): + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter - FlutterMacOS - sqflite (0.0.2): - FlutterMacOS - FMDB (>= 2.7.5) + - system_theme (0.0.1): + - FlutterMacOS + - system_tray (0.0.1): + - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS + - window_manager (0.2.0): + - FlutterMacOS + - window_size (0.0.2): + - FlutterMacOS DEPENDENCIES: + - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/macos`) - audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`) - - audioplayers_darwin (from `Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos`) - - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) - - connectivity_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus_macos/macos`) + - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) + - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`) + - local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`) + - media_kit_libs_macos_audio (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos`) + - media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`) - metadata_god (from `Flutter/ephemeral/.symlinks/plugins/metadata_god/macos`) - - package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) - - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - - shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) + - system_theme (from `Flutter/ephemeral/.symlinks/plugins/system_theme/macos`) + - system_tray (from `Flutter/ephemeral/.symlinks/plugins/system_tray/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) + - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) SPEC REPOS: trunk: - FMDB - - ReachabilitySwift EXTERNAL SOURCES: + app_links: + :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos audio_service: :path: Flutter/ephemeral/.symlinks/plugins/audio_service/macos audio_session: :path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos - audioplayers_darwin: - :path: Flutter/ephemeral/.symlinks/plugins/audioplayers_darwin/macos - bitsdojo_window_macos: - :path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos - connectivity_plus_macos: - :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus_macos/macos + device_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos + flutter_secure_storage_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos FlutterMacOS: :path: Flutter/ephemeral - macos_ui: - :path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos + local_notifier: + :path: Flutter/ephemeral/.symlinks/plugins/local_notifier/macos + media_kit_libs_macos_audio: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos + media_kit_native_event_loop: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos metadata_god: :path: Flutter/ephemeral/.symlinks/plugins/metadata_god/macos - package_info_plus_macos: - :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos - path_provider_macos: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos - shared_preferences_macos: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + screen_retriever: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos + shared_preferences_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin sqflite: :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos + system_theme: + :path: Flutter/ephemeral/.symlinks/plugins/system_theme/macos + system_tray: + :path: Flutter/ephemeral/.symlinks/plugins/system_tray/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + window_manager: + :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos + window_size: + :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos SPEC CHECKSUMS: + app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67 audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9 audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72 - audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c - bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00 - connectivity_plus_macos: f6e86fd000e971d361e54b5afcadc8c8fa773308 - FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 + device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f + file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 + flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - macos_ui: 125c911559d646194386d84c017ad6819122e2db - metadata_god: 55a71136c95eb75ec28142f6fbfc2bcff6f881b1 - package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c - path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727 + local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff + media_kit_libs_macos_audio: 3871782a4f3f84c77f04d7666c87800a781c24da + media_kit_native_event_loop: 7321675377cb9ae8596a29bddf3a3d2b5e8792c5 + metadata_god: eceae399d0020475069a5cebc35943ce8562b5d7 + package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 + shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea - url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 + system_theme: c7b9f6659a5caa26c9bc2284da096781e9a6fcbc + system_tray: e53c972838c69589ff2e77d6d3abfd71332f9e5d + url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 -PODFILE CHECKSUM: a884f6dd3f7494f3892ee6c81feea3a3abbf9153 +PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 -COCOAPODS: 1.11.3 +COCOAPODS: 1.14.3 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 9b86152a..f7711c83 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -208,7 +208,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -261,6 +261,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -409,7 +410,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -431,7 +432,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -489,7 +490,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -536,7 +537,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -558,7 +559,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -579,7 +580,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.13; + MACOSX_DEPLOYMENT_TARGET = 10.14; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 741e68bc..8f69f0c6 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { - return true + return false } } diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 19f1c02a..1a8bb655 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -2,6 +2,19 @@ + CFBundleURLTypes + + + CFBundleURLName + + Spotify + CFBundleURLSchemes + + + spotify + + + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/pubspec.lock b/pubspec.lock index 02450ff3..3835f711 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + app_links: + dependency: "direct main" + description: + name: app_links + sha256: "4e392b5eba997df356ca6021f28431ce1cfeb16758699553a94b13add874a3bb" + url: "https://pub.dev" + source: hosted + version: "3.5.0" app_package_maker: dependency: transitive description: @@ -535,10 +543,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_picker: dependency: "direct main" description: @@ -907,6 +915,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + flutter_sharing_intent: + dependency: "direct main" + description: + name: flutter_sharing_intent + sha256: "6eb896e6523b735e8230eeb206fd3b9f220f11ce879c2400a90b443147036ff9" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter_svg: dependency: "direct main" description: @@ -998,10 +1014,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: e20ff62b158b96f392bfc8afe29dee1503c94fbea2cbe8186fd59b756b8ae982 + sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "6.1.0" graphs: dependency: transitive description: @@ -1018,6 +1034,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.8" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" hive: dependency: "direct main" description: @@ -1231,6 +1255,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "04be76c4a4bb50f14904e64749237e541e7c7bcf7ec0b196907322ab5d2fc739" + url: "https://pub.dev" + source: hosted + version: "9.0.16" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff + url: "https://pub.dev" + source: hosted + version: "1.0.5" lints: dependency: transitive description: @@ -1283,10 +1323,10 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" media_kit: dependency: "direct main" description: @@ -1355,10 +1395,10 @@ packages: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" metadata_god: dependency: "direct main" description: @@ -1571,10 +1611,10 @@ packages: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" plugin_platform_interface: dependency: transitive description: @@ -1611,10 +1651,10 @@ packages: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "266ca5be5820feefc777793d0a583acfc8c40834893c87c00c6c09e2cf58ea42" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.1" provider: dependency: transitive description: @@ -1732,10 +1772,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_android: dependency: transitive description: @@ -1756,10 +1796,10 @@ packages: dependency: transitive description: name: shared_preferences_linux - sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_platform_interface: dependency: transitive description: @@ -1780,10 +1820,10 @@ packages: dependency: transitive description: name: shared_preferences_windows - sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shelf: dependency: transitive description: @@ -1832,6 +1872,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + skeletonizer: + dependency: "direct main" + description: + name: skeletonizer + sha256: ff4c36e826efd5288d7a84e7619a6e9be8185d3064cecf101a9133762f3b401b + url: "https://pub.dev" + source: hosted + version: "0.8.0" sky_engine: dependency: transitive description: flutter @@ -2177,18 +2225,18 @@ packages: dependency: "direct main" description: name: visibility_detector - sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d" + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 url: "https://pub.dev" source: hosted - version: "0.3.3" + version: "0.4.0+2" vm_service: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "13.0.0" watcher: dependency: transitive description: @@ -2201,10 +2249,10 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: edc8a9573dd8c5a83a183dae1af2b6fd4131377404706ca4e5420474784906fa url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.4.0" web_socket_channel: dependency: transitive description: @@ -2217,10 +2265,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" wikipedia_api: dependency: "direct main" description: @@ -2238,13 +2286,13 @@ packages: source: hosted version: "5.0.7" win32_registry: - dependency: transitive + dependency: "direct main" description: name: win32_registry - sha256: e4506d60b7244251bc59df15656a3093501c37fb5af02105a944d73eb95be4c9 + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" window_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 3226589f..d0c51a15 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Open source Spotify client that doesn't require Premium nor uses El publish_to: "none" -version: 3.2.0+25 +version: 3.4.0+27 homepage: https://spotube.krtirtho.dev repository: https://github.com/KRTirtho/spotube @@ -55,7 +55,7 @@ dependencies: flutter_svg: ^1.1.6 form_validator: ^2.1.1 fuzzywuzzy: ^1.1.6 - google_fonts: ^5.1.0 + google_fonts: ^6.1.0 go_router: ^11.1.2 hive: ^2.2.3 hive_flutter: ^1.1.0 @@ -83,8 +83,8 @@ dependencies: url: https://github.com/KRTirtho/scrobblenaut.git ref: dart-3-support scroll_to_index: ^3.0.1 - shared_preferences: ^2.0.11 sidebarx: ^0.16.3 + shared_preferences: ^2.2.2 skeleton_text: ^3.0.1 smtc_windows: ^0.1.1 spotify: ^0.12.0 @@ -94,7 +94,7 @@ dependencies: url_launcher: ^6.1.7 uuid: ^3.0.7 version: ^3.0.2 - visibility_detector: ^0.3.3 + visibility_detector: ^0.4.0+2 window_manager: ^0.3.1 window_size: git: @@ -118,6 +118,10 @@ dependencies: url: https://github.com/Tommypop2/dart_discord_rpc.git html_unescape: ^2.0.0 wikipedia_api: ^0.1.0 + skeletonizer: ^0.8.0 + app_links: ^3.5.0 + win32_registry: ^1.1.2 + flutter_sharing_intent: ^1.1.0 dev_dependencies: build_runner: ^2.3.2 @@ -148,6 +152,7 @@ flutter: - LICENSE flutter_launcher_icons: + ios: true android: true image_path: "assets/spotube-logo.png" adaptive_icon_foreground: "assets/spotube-logo-foreground.jpg" diff --git a/untranslated_messages.json b/untranslated_messages.json index 9e26dfee..45a6df11 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1 +1,120 @@ -{} \ No newline at end of file +{ + "ar": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "bn": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "ca": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "de": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "es": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "fa": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "fr": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "hi": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "it": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "ja": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "nl": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "pl": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "pt": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "ru": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "tr": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "uk": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ], + + "zh": [ + "step_3_steps", + "step_4_steps", + "friends", + "no_lyrics_available" + ] +} diff --git a/website/components/Footer.tsx b/website/components/Footer.tsx index b940815d..a51cf0a2 100644 --- a/website/components/Footer.tsx +++ b/website/components/Footer.tsx @@ -49,7 +49,7 @@ const Footer = () => { color: "white", }} > - © 2022, Spotube. All rights reserved + © {new Date().getFullYear()}, Spotube. All rights reserved diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index b9c6a481..fcf9927e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -20,6 +21,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); DartDiscordRpcPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DartDiscordRpcPlugin")); FileSelectorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 5cd55ff3..0fe6e076 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + app_links dart_discord_rpc file_selector_windows flutter_secure_storage_windows diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index d5c04f23..9823151c 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -3,6 +3,7 @@ #include #include "resource.h" +#include "app_links/app_links_plugin_c_api.h" namespace { @@ -105,6 +106,9 @@ Win32Window::~Win32Window() { bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin, const Size& size) { + if (SendAppLinkToInstance(title)) { + return false; + } Destroy(); const wchar_t* window_class = @@ -244,3 +248,39 @@ bool Win32Window::OnCreate() { void Win32Window::OnDestroy() { // No-op; provided for subclasses. } + +// app_links +bool Win32Window::SendAppLinkToInstance(const std::wstring& title) { + // Find our exact window + HWND hwnd = ::FindWindow(kWindowClassName, title.c_str()); + + if (hwnd) { + // Dispatch new link to current window + SendAppLink(hwnd); + + // (Optional) Restore our window to front in same state + WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) }; + GetWindowPlacement(hwnd, &place); + + switch(place.showCmd) { + case SW_SHOWMAXIMIZED: + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + break; + case SW_SHOWMINIMIZED: + ShowWindow(hwnd, SW_RESTORE); + break; + default: + ShowWindow(hwnd, SW_NORMAL); + break; + } + + SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(hwnd); + // END Restore + + // Window has been found, don't create another one. + return true; + } + + return false; +} \ No newline at end of file diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h index 17ba4311..1d817bd2 100644 --- a/windows/runner/win32_window.h +++ b/windows/runner/win32_window.h @@ -93,6 +93,10 @@ class Win32Window { // window handle for hosted content. HWND child_content_ = nullptr; + // Dispatches link if any. + // This method enables our app to be with a single instance too. + // This is mandatory if you want to catch further links in same app. + bool SendAppLinkToInstance(const std::wstring& title); }; #endif // RUNNER_WIN32_WINDOW_H_