mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-10 17:07:30 +00:00
Merge branch 'KRTirtho:master' into master
This commit is contained in:
commit
ba70961079
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
7
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -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
|
||||
9
.github/ISSUE_TEMPLATE/new_feature.yml
vendored
9
.github/ISSUE_TEMPLATE/new_feature.yml
vendored
@ -35,4 +35,11 @@ body:
|
||||
label: Additional information
|
||||
description: Anything else you'd like to include?
|
||||
validations:
|
||||
required: false
|
||||
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
|
||||
42
.github/workflows/build-iPA.yml
vendored
42
.github/workflows/build-iPA.yml
vendored
@ -1,42 +0,0 @@
|
||||
name: Build iPA
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- uses: actions/checkout@v4
|
||||
- name: submodules-init
|
||||
uses: snickerbockers/submodules-init@v4
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
- name: Build
|
||||
run: |
|
||||
cp .env.example .env
|
||||
flutter pub get && dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
||||
flutter build ios --release --no-codesign --flavor dev
|
||||
flutter build ios --release --no-codesign --flavor stable
|
||||
flutter build ios --release --no-codesign --flavor nightly
|
||||
ln -sf ./build/ios/iphoneos Payload
|
||||
zip -r9 spotube-dev.ipa Payload/dev.app
|
||||
zip -r9 spotube-stable.ipa Payload/stable.app
|
||||
zip -r9 spotube-nightly.ipa Payload/nightly.app
|
||||
- name: Upload spotube-dev.ipa
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "spotube-dev.ipa"
|
||||
path: "spotube-dev.ipa"
|
||||
- name: Upload spotube-stable.ipa
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "spotube-stable.ipa"
|
||||
path: "spotube-stable.ipa"
|
||||
- name: Upload spotube-nightly.ipa
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "spotube-nightly.ipa"
|
||||
path: "spotube-nightly.ipa"
|
||||
29
.github/workflows/spotube-release-binary.yml
vendored
29
.github/workflows/spotube-release-binary.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
inputs:
|
||||
version:
|
||||
description: Version to release (x.x.x)
|
||||
default: 3.4.0
|
||||
default: 3.4.1
|
||||
required: true
|
||||
channel:
|
||||
type: choice
|
||||
@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: windows-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 }}
|
||||
@ -107,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 }}
|
||||
@ -200,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 }}
|
||||
@ -276,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 }}
|
||||
@ -304,8 +304,8 @@ jobs:
|
||||
|
||||
- name: Generate Secrets
|
||||
run: |
|
||||
dart pub global activate flutter_distributor
|
||||
flutter pub get
|
||||
flutter pub remove media_kit_native_event_loop
|
||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
||||
|
||||
- name: Build Macos App
|
||||
@ -320,9 +320,24 @@ jobs:
|
||||
npm install -g appdmg
|
||||
mkdir -p build/${{ env.BUILD_VERSION }}
|
||||
appdmg appdmg.json build/Spotube-macos-universal.dmg
|
||||
flutter_distributor package --platform=macos --targets pkg --skip-clean
|
||||
mv dist/**/spotube-*-macos.pkg build/Spotube-macos-universal.pkg
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: Spotube-Release-Binaries
|
||||
path: |
|
||||
build/Spotube-macos-universal.dmg
|
||||
build/Spotube-macos-universal.pkg
|
||||
|
||||
- name: Debug With SSH When fails
|
||||
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
|
||||
iOS:
|
||||
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@ -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"
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@ -2,6 +2,39 @@
|
||||
|
||||
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.1](https://personal.github.com/krtirtho/spotube/compare/v3.4.0...v3.4.1) (2024-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add create playlist button in add playlist dialog ([2168a64](https://personal.github.com/krtirtho/spotube/commit/2168a640af3104a43139c303d78e2c2326a1bda7))
|
||||
* add spotify friends activity ([#1130](https://personal.github.com/krtirtho/spotube/issues/1130)) ([7983932](https://personal.github.com/krtirtho/spotube/commit/79839329b0970acccb0c566a31eee508adbc8557))
|
||||
* **deep-link:** add track opening page ([988a975](https://personal.github.com/krtirtho/spotube/commit/988a975bf1a675df0cfc7b17776bcec74c67f1f2))
|
||||
* haptic feedback on long press and reordering actions ([6242200](https://personal.github.com/krtirtho/spotube/commit/624220090572eb643dce37ca8ffd85d2b3f5c9df))
|
||||
* improve youtube/piped matching by suffixing "- Topic" ([8184555](https://personal.github.com/krtirtho/spotube/commit/8184555ee89fd30aaf886af9fc1d52c142fdebb0))
|
||||
* **translations:** add Nepali (नेपाली) translations ([#1111](https://personal.github.com/krtirtho/spotube/issues/1111)) ([c3ebf56](https://personal.github.com/krtirtho/spotube/commit/c3ebf56ac149b0af8815a5533fe6c386df743440)), closes [#1074](https://personal.github.com/krtirtho/spotube/issues/1074) [#1100](https://personal.github.com/krtirtho/spotube/issues/1100)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* alternative searched sources doesn't play [#1059](https://personal.github.com/krtirtho/spotube/issues/1059) ([a8e9b82](https://personal.github.com/krtirtho/spotube/commit/a8e9b824f33add8f6a83f0d147e889eb6beeb442))
|
||||
* alternative source doesn't persist on next restart [#840](https://personal.github.com/krtirtho/spotube/issues/840) ([62fde50](https://personal.github.com/krtirtho/spotube/commit/62fde50442f04f93255b5b1b1dcca23d116a13ec))
|
||||
* **android:** download failing for permission issues [#1015](https://personal.github.com/krtirtho/spotube/issues/1015) ([5509cae](https://personal.github.com/krtirtho/spotube/commit/5509cae91c8b1f5cb9fac179060f477397a4a27f))
|
||||
* artist page error [#1018](https://personal.github.com/krtirtho/spotube/issues/1018) ([8cd650b](https://personal.github.com/krtirtho/spotube/commit/8cd650b07e5f4c4c2f296bf4374e5ee67fb3eb50))
|
||||
* audio resumes after a phone call even if it was paused before [#926](https://personal.github.com/krtirtho/spotube/issues/926) ([fd1899f](https://personal.github.com/krtirtho/spotube/commit/fd1899f162395752142d7aa7320d1c39b0995070))
|
||||
* better error message for failing to find lyrics [#1085](https://personal.github.com/krtirtho/spotube/issues/1085) ([e58e18d](https://personal.github.com/krtirtho/spotube/commit/e58e18de33d7bc6fb0e4ddd7ccf6ea14472642b1))
|
||||
* Black window flash when starting the app ([#1003](https://personal.github.com/krtirtho/spotube/issues/1003)) ([02e44fc](https://personal.github.com/krtirtho/spotube/commit/02e44fc6b849a873adad382f5d46ed8caf32359f))
|
||||
* **linux:** crash after login ([0dfd401](https://personal.github.com/krtirtho/spotube/commit/0dfd40153714b7a4b83ac30f0c56830bc0c05ffd))
|
||||
* **macos:** backbutton and window button overlap and unused empty space on home ([b9417ca](https://personal.github.com/krtirtho/spotube/commit/b9417ca3575992673357230dab49e0124dd576b1))
|
||||
* **macos:** download folder unchangeable ([9d74cf5](https://personal.github.com/krtirtho/spotube/commit/9d74cf5fc250a6a143321d49b8e045519b4c2872))
|
||||
* **macos:** Respect Minimize to tray option ([#1001](https://personal.github.com/krtirtho/spotube/issues/1001)) ([69559ba](https://personal.github.com/krtirtho/spotube/commit/69559ba24285636e42b2f2231f956c31388c5cf3))
|
||||
* **macos:** system tray shows name and sidebar weird gap [#1083](https://personal.github.com/krtirtho/spotube/issues/1083) ([27057ea](https://personal.github.com/krtirtho/spotube/commit/27057ea0c8d83c9701057c18b473f1af4e4e82be))
|
||||
* releases section is empty when user doesn't follow any artists [#1104](https://personal.github.com/krtirtho/spotube/issues/1104) ([682e88e](https://personal.github.com/krtirtho/spotube/commit/682e88e0c55bc0f4708bc0b4681b129e5c61c999))
|
||||
* search page vertical scrollbar moves on horizontal scroll [#1017](https://personal.github.com/krtirtho/spotube/issues/1017) ([c203ac6](https://personal.github.com/krtirtho/spotube/commit/c203ac69ee74ba8722dae3da4b47761cd8d59c34))
|
||||
* songs doesn't play when sources with preferred audio codec is empty ([#976](https://personal.github.com/krtirtho/spotube/issues/976)) ([ba4e11a](https://personal.github.com/krtirtho/spotube/commit/ba4e11a40ab18308437a05333a46eace6f8eeb5a))
|
||||
* track index not showing after 200 ([a752cf4](https://personal.github.com/krtirtho/spotube/commit/a752cf4c978d1b05851aabb6c84c7862de551320))
|
||||
* track pad horizontal scrolling not working ([59e0e6b](https://personal.github.com/krtirtho/spotube/commit/59e0e6bb659b70831f6e0ae064100381c57f149c))
|
||||
|
||||
## [3.4.0](https://github.com/KRTirtho/spotube/compare/v3.3.0...v3.4.0) (2023-12-30)
|
||||
|
||||
|
||||
|
||||
@ -145,7 +145,7 @@ Do the following:
|
||||
flutter run -d <window|macos|linux|(<android-device-id>)>
|
||||
```
|
||||
|
||||
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
|
||||
- Finally, submit a PR against the development branch (dev) & we'll review your code
|
||||
|
||||
30
README.md
30
README.md
@ -2,10 +2,10 @@
|
||||
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">
|
||||
|
||||
An open source, cross-platform Spotify client compatible across multiple platforms<br />
|
||||
utilizing Spotify's data API and YouTube (or Piped.video or JioSaavn) as an audio source,<br />
|
||||
utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source,<br />
|
||||
eliminating the need for Spotify Premium
|
||||
|
||||
Btw it's not another Electron app😉
|
||||
Btw it's not just another Electron app 😉
|
||||
|
||||
<a href="https://spotube.netlify.app"><img alt="Visit the website" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/documentation/website_vector.svg"></a>
|
||||
<a href="https://discord.gg/uJ94vxB6vg"><img alt="Discord Server" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/social/discord-plural_vector.svg"></a>
|
||||
@ -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 <ins>**never be supported**</ins> because the audio tracks are _only_ available on Spotify and accessing them would require Spotify Premium.
|
||||
- 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts will <ins>**never be supported**</ins> because the audio tracks are <ins>_only_</ins> 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.<br />
|
||||
This handy table lists all methods you can use to install Spotube:
|
||||
New versions usually release every 3-4 months.<br />
|
||||
This handy table lists all the methods you can use to install Spotube:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@ -209,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.
|
||||
@ -222,9 +222,9 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
||||
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. [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_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. [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.
|
||||
@ -235,9 +235,9 @@ 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. [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. [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. [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. [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. [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.
|
||||
1. [hive_flutter](https://github.com/hivedb/hive/tree/master/hive_flutter) - Extension for Hive. Makes it easier to use Hive in Flutter apps.
|
||||
1. [hooks_riverpod](https://riverpod.dev) - A simple way to access state from anywhere in your application while robust and testable.
|
||||
@ -255,13 +255,13 @@ 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.
|
||||
1. [scroll_to_index](https://github.com/quire-io/scroll-to-index) - Scroll to a specific child of any scrollable widget in Flutter
|
||||
1. [shared_preferences](https://pub.dev/packages/shared_preferences) - Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.
|
||||
1. [sidebarx](https://github.com/Frezyx/sidebarx) - flutter multiplatform navigation sidebar / side navigationbar / drawer widget
|
||||
1. [shared_preferences](https://pub.dev/packages/shared_preferences) - Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.
|
||||
1. [skeleton_text](https://github.com/101Loop/Skeleton-Text) - A package that provides an easy way to add skeleton text loading animation in Flutter project. This project is a part of 101Loop community.
|
||||
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.
|
||||
@ -304,4 +304,4 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
||||
1. [dart_discord_rpc](https://github.com/alexmercerind/dart_discord_rpc) - Discord Rich Presence for Flutter & Dart apps & games.
|
||||
</details>
|
||||
|
||||
<div align="center"><h4>© Copyright Spotube 2023</h4></div>
|
||||
<div align="center"><h4>© Copyright Spotube 2024</h4></div>
|
||||
|
||||
@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 34
|
||||
|
||||
ndkVersion "21.4.7075529"
|
||||
|
||||
|
||||
BIN
assets/jiosaavn.png
Normal file
BIN
assets/jiosaavn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/liked-tracks.jpg
Normal file
BIN
assets/liked-tracks.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@ -882,6 +882,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1010,6 +1011,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1032,6 +1034,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1172,6 +1175,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1268,6 +1272,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1360,6 +1365,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1585,6 +1591,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1703,6 +1710,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -1816,6 +1824,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -2126,6 +2135,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -2266,6 +2276,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -2400,6 +2411,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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()
|
||||
@ -164,4 +165,35 @@ abstract class FakeData {
|
||||
..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",
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -17,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';
|
||||
@ -144,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(
|
||||
|
||||
@ -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;
|
||||
@ -109,4 +110,5 @@ abstract class SpotubeIcons {
|
||||
static const normalize = FeatherIcons.barChart2;
|
||||
static const wikipedia = SimpleIcons.wikipedia;
|
||||
static const discord = SimpleIcons.discord;
|
||||
static const youtube = SimpleIcons.youtube;
|
||||
}
|
||||
|
||||
@ -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(
|
||||
|
||||
95
lib/components/home/sections/friends.dart
Normal file
95
lib/components/home/sections/friends.dart
Normal file
@ -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<List<List<SpotifyFriendActivity>>>(
|
||||
[],
|
||||
(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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
136
lib/components/home/sections/friends/friend_item.dart
Normal file
136
lib/components/home/sections/friends/friend_item.dart
Normal file
@ -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, dynamic>(
|
||||
"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, dynamic>(
|
||||
"album/${friend.track.album.id}",
|
||||
() => spotify.albums.get(
|
||||
friend.track.album.id,
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
context.push(
|
||||
"/album/${friend.track.album.id}",
|
||||
extra: album,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -21,16 +21,21 @@ class HomeNewReleasesSection extends HookConsumerWidget {
|
||||
userArtistsQuery.data?.map((s) => s.id!).toList() ?? const [];
|
||||
|
||||
final albums = useMemoized(
|
||||
() => newReleases.pages
|
||||
.whereType<Page<AlbumSimple>>()
|
||||
.expand((page) => page.items ?? const <AlbumSimple>[])
|
||||
.where((album) {
|
||||
return album.artists
|
||||
?.any((artist) => userArtists.contains(artist.id!)) ==
|
||||
true;
|
||||
})
|
||||
.map((album) => TypeConversionUtils.simpleAlbum_X_Album(album))
|
||||
.toList(),
|
||||
() {
|
||||
final allReleases = newReleases.pages
|
||||
.whereType<Page<AlbumSimple>>()
|
||||
.expand((page) => page.items ?? const <AlbumSimple>[])
|
||||
.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],
|
||||
);
|
||||
|
||||
|
||||
@ -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(
|
||||
() {
|
||||
|
||||
@ -210,6 +210,12 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
itemCount: tracks.length,
|
||||
shrinkWrap: true,
|
||||
buildDefaultDragHandles: false,
|
||||
onReorderStart: (index) {
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
onReorderEnd: (index) {
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
itemBuilder: (context, i) {
|
||||
final track = tracks.elementAt(i);
|
||||
return AutoScrollTag(
|
||||
|
||||
@ -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),
|
||||
),
|
||||
|
||||
@ -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({
|
||||
@ -63,18 +82,50 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
||||
if (searchTerm.trim().isEmpty) {
|
||||
return <SourceInfo>[];
|
||||
}
|
||||
|
||||
final results = await youtubeClient.search.search(searchTerm.trim());
|
||||
|
||||
return await Future.wait(
|
||||
results.map(YoutubeVideoInfo.fromVideo).mapIndexed((i, video) async {
|
||||
final siblingType = await YoutubeSourcedTrack.toSiblingType(i, video);
|
||||
if (preferences.audioSource == AudioSource.jiosaavn) {
|
||||
final resultsJioSaavn =
|
||||
await jiosaavnClient.search.songs(searchTerm.trim());
|
||||
final results = await Future.wait(
|
||||
resultsJioSaavn.results.mapIndexed((i, song) async {
|
||||
final siblingType = JioSaavnSourcedTrack.toSiblingType(song);
|
||||
return siblingType.info;
|
||||
}),
|
||||
);
|
||||
}));
|
||||
|
||||
final activeSourceInfo =
|
||||
(playlist.activeTrack! as SourcedTrack).sourceInfo;
|
||||
|
||||
return results
|
||||
..removeWhere((element) => element.id == activeSourceInfo.id)
|
||||
..insert(
|
||||
0,
|
||||
activeSourceInfo,
|
||||
);
|
||||
} else {
|
||||
final resultsYt = await youtubeClient.search.search(searchTerm.trim());
|
||||
|
||||
final searchResults = await Future.wait(
|
||||
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,
|
||||
preferences.audioSource,
|
||||
]);
|
||||
|
||||
final siblings = useMemoized(
|
||||
@ -104,6 +155,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 +170,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 +194,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,
|
||||
|
||||
@ -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(
|
||||
@ -182,6 +182,9 @@ class Sidebar extends HookConsumerWidget {
|
||||
),
|
||||
itemTextPadding: const EdgeInsets.only(left: 10),
|
||||
selectedItemTextPadding: const EdgeInsets.only(left: 10),
|
||||
hoverTextStyle: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import 'package:fl_query_hooks/fl_query_hooks.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:spotify/spotify.dart';
|
||||
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/spotify_provider.dart';
|
||||
@ -22,6 +24,7 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final ThemeData(:textTheme) = Theme.of(context);
|
||||
final spotify = ref.watch(spotifyProvider);
|
||||
final userPlaylists = useQueries.playlist.ofMineAll(ref);
|
||||
|
||||
@ -69,7 +72,18 @@ class PlaylistAddTrackDialog extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(context.l10n.add_to_playlist),
|
||||
insetPadding: EdgeInsets.zero,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.add_to_playlist,
|
||||
style: textTheme.titleMedium,
|
||||
),
|
||||
const Gap(20),
|
||||
const PlaylistCreateDialogButton(),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
OutlinedButton(
|
||||
child: Text(context.l10n.cancel),
|
||||
|
||||
@ -55,48 +55,49 @@ class HorizontalPlaybuttonCardView<T> extends HookWidget {
|
||||
),
|
||||
SizedBox(
|
||||
height: height,
|
||||
child: ScrollConfiguration(
|
||||
behavior: ScrollConfiguration.of(context).copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.mouse,
|
||||
},
|
||||
),
|
||||
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),
|
||||
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),
|
||||
),
|
||||
_ => const SizedBox.shrink(),
|
||||
};
|
||||
}),
|
||||
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(),
|
||||
};
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -8,6 +8,7 @@ class LinkText<T> 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<T> extends StatelessWidget {
|
||||
this.extra,
|
||||
this.overflow,
|
||||
this.style = const TextStyle(),
|
||||
this.maxLines,
|
||||
this.push = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -37,6 +39,7 @@ class LinkText<T> extends StatelessWidget {
|
||||
overflow: overflow,
|
||||
style: style,
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,14 +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';
|
||||
|
||||
class PageWindowTitleBar extends StatefulHookConsumerWidget
|
||||
implements PreferredSizeWidget {
|
||||
@ -64,28 +62,45 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onHorizontalDragStart: onDrag,
|
||||
onVerticalDragStart: onDrag,
|
||||
child: AppBar(
|
||||
leading: widget.leading,
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
actions: [
|
||||
...?widget.actions,
|
||||
WindowTitleBarButtons(foregroundColor: widget.foregroundColor),
|
||||
],
|
||||
backgroundColor: widget.backgroundColor,
|
||||
foregroundColor: widget.foregroundColor,
|
||||
actionsIconTheme: widget.actionsIconTheme,
|
||||
centerTitle: widget.centerTitle,
|
||||
titleSpacing: widget.titleSpacing,
|
||||
toolbarOpacity: widget.toolbarOpacity,
|
||||
leadingWidth: widget.leadingWidth,
|
||||
toolbarTextStyle: widget.toolbarTextStyle,
|
||||
titleTextStyle: widget.titleTextStyle,
|
||||
title: widget.title,
|
||||
),
|
||||
);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
|
||||
return LayoutBuilder(builder: (context, constrains) {
|
||||
final hasFullscreen = mediaQuery.size.width == constrains.maxWidth;
|
||||
final hasLeadingOrCanPop =
|
||||
widget.leading != null || Navigator.canPop(context);
|
||||
|
||||
return GestureDetector(
|
||||
onHorizontalDragStart: onDrag,
|
||||
onVerticalDragStart: onDrag,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: DesktopTools.platform.isMacOS &&
|
||||
hasFullscreen &&
|
||||
hasLeadingOrCanPop
|
||||
? 65
|
||||
: 0,
|
||||
),
|
||||
child: AppBar(
|
||||
leading: widget.leading,
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
actions: [
|
||||
...?widget.actions,
|
||||
WindowTitleBarButtons(foregroundColor: widget.foregroundColor),
|
||||
],
|
||||
backgroundColor: widget.backgroundColor,
|
||||
foregroundColor: widget.foregroundColor,
|
||||
actionsIconTheme: widget.actionsIconTheme,
|
||||
centerTitle: widget.centerTitle,
|
||||
titleSpacing: widget.titleSpacing,
|
||||
toolbarOpacity: widget.toolbarOpacity,
|
||||
leadingWidth: widget.leadingWidth,
|
||||
toolbarTextStyle: widget.toolbarTextStyle,
|
||||
titleTextStyle: widget.titleTextStyle,
|
||||
title: widget.title,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import 'package:buttons_tabbar/buttons_tabbar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||
import 'package:spotube/hooks/utils/use_brightness_value.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class ThemedButtonsTabBar extends HookWidget implements PreferredSizeWidget {
|
||||
final List<Widget> tabs;
|
||||
@ -17,16 +15,8 @@ class ThemedButtonsTabBar extends HookWidget implements PreferredSizeWidget {
|
||||
Color.lerp(theme.colorScheme.primary, Colors.black, 0.7)!,
|
||||
);
|
||||
|
||||
final breakpoint = useBreakpointValue(
|
||||
xs: 85.0,
|
||||
sm: 85.0,
|
||||
md: 35.0,
|
||||
others: 0.0,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: kIsMacOS ? breakpoint : 0,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
),
|
||||
|
||||
@ -43,12 +43,14 @@ class TrackOptions extends HookConsumerWidget {
|
||||
final bool userPlaylist;
|
||||
final String? playlistId;
|
||||
final ObjectRef<ValueChanged<RelativeRect>?>? 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) {
|
||||
@ -207,7 +209,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
break;
|
||||
}
|
||||
},
|
||||
icon: const Icon(SpotubeIcons.moreHorizontal),
|
||||
icon: icon ?? const Icon(SpotubeIcons.moreHorizontal),
|
||||
headings: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
|
||||
@ -110,7 +110,7 @@ class TrackTile extends HookConsumerWidget {
|
||||
...?leadingActions,
|
||||
if (index != null && onChanged == null && constrains.mdAndUp)
|
||||
SizedBox(
|
||||
width: 34,
|
||||
width: 50,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Text(
|
||||
@ -193,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,
|
||||
),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
@ -8,7 +9,6 @@ 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/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';
|
||||
@ -117,6 +117,7 @@ class TrackViewBodySection extends HookConsumerWidget {
|
||||
},
|
||||
onLongPress: () {
|
||||
trackViewState.selectTrack(track.id!);
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
onTap: () async {
|
||||
if (trackViewState.isSelecting) {
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@ -48,6 +48,11 @@ void useDeepLinking(WidgetRef ref) {
|
||||
),
|
||||
);
|
||||
break;
|
||||
case "track":
|
||||
router.push(
|
||||
"/track/${url.pathSegments.last}",
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -80,6 +85,9 @@ void useDeepLinking(WidgetRef ref) {
|
||||
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",
|
||||
|
||||
@ -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: [
|
||||
|
||||
@ -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 الذي سيتم استخدامه لمطابقة المقطوعة",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "وجود ديسكورد الغني",
|
||||
"browse_all": "تصفح الكل",
|
||||
"genres": "الأنواع الموسيقية",
|
||||
"explore_genres": "استكشاف الأنواع"
|
||||
"explore_genres": "استكشاف الأنواع",
|
||||
"step_3_steps": "انسخ قيمة الكوكي \"sp_dc\"",
|
||||
"step_4_steps": "الصق قيمة \"sp_dc\" المنسوخة",
|
||||
"friends": "أصدقاء",
|
||||
"no_lyrics_available": "عذرًا، تعذر العثور على كلمات الأغنية لهذه العنصر"
|
||||
}
|
||||
@ -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": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "وجود ديسكورد الغني",
|
||||
"browse_all": "تصفح الكل",
|
||||
"genres": "الأنواع الموسيقية",
|
||||
"explore_genres": "استكشاف الأنواع"
|
||||
"explore_genres": "استكشاف الأنواع",
|
||||
"step_3_steps": "কুকি \"sp_dc\" এর মানটি কপি করুন",
|
||||
"step_4_steps": "কপি করা \"sp_dc\" মানটি পেস্ট করুন",
|
||||
"friends": "বন্ধু",
|
||||
"no_lyrics_available": "দুঃখিত, এই ট্র্যাকের জন্য কথা খুঁজে পাওয়া গেলনা"
|
||||
}
|
||||
@ -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",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Presència rica de Discord",
|
||||
"browse_all": "Navega per tot",
|
||||
"genres": "Gèneres",
|
||||
"explore_genres": "Explora els gèneres"
|
||||
"explore_genres": "Explora els gèneres",
|
||||
"step_3_steps": "Copia el valor de la cookie \"sp_dc\"",
|
||||
"step_4_steps": "Pega el valor copiado de \"sp_dc\"",
|
||||
"friends": "Amics",
|
||||
"no_lyrics_available": "Ho sentim, no es poden trobar les lletres d'aquesta pista"
|
||||
}
|
||||
@ -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",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Discord Rich Presence",
|
||||
"browse_all": "Alles durchsuchen",
|
||||
"genres": "Genres",
|
||||
"explore_genres": "Genres erkunden"
|
||||
"explore_genres": "Genres erkunden",
|
||||
"step_3_steps": "Kopiere den Wert des Cookies \"sp_dc\"",
|
||||
"step_4_steps": "Füge den kopierten Wert von \"sp_dc\" ein",
|
||||
"friends": "Freunde",
|
||||
"no_lyrics_available": "Entschuldigung, Texte für diesen Track konnten nicht gefunden werden"
|
||||
}
|
||||
@ -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",
|
||||
@ -284,5 +284,7 @@
|
||||
"discord_rich_presence": "Discord Rich Presence",
|
||||
"browse_all": "Browse All",
|
||||
"genres": "Genres",
|
||||
"explore_genres": "Explore Genres"
|
||||
"explore_genres": "Explore Genres",
|
||||
"friends": "Friends",
|
||||
"no_lyrics_available": "Sorry, unable find lyrics for this track"
|
||||
}
|
||||
@ -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",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Presencia rica en Discord",
|
||||
"browse_all": "Explorar todo",
|
||||
"genres": "Géneros",
|
||||
"explore_genres": "Explorar géneros"
|
||||
"explore_genres": "Explorar géneros",
|
||||
"step_3_steps": "Copia el valor de la cookie \"sp_dc\"",
|
||||
"step_4_steps": "Pega el valor copiado de \"sp_dc\"",
|
||||
"friends": "Amigos",
|
||||
"no_lyrics_available": "Lo siento, no se pueden encontrar las letras de esta pista"
|
||||
}
|
||||
@ -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": "مشکل در ارتباط با سرور در دریافت آهنگ ها",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "حضور غنی دیسکورد",
|
||||
"browse_all": "مرور همه",
|
||||
"genres": "ژانرها",
|
||||
"explore_genres": "استکشاف ژانرها"
|
||||
"explore_genres": "استکشاف ژانرها",
|
||||
"step_3_steps": "مقدار کوکی \"sp_dc\" را کپی کنید",
|
||||
"step_4_steps": "مقدار کپی شده \"sp_dc\" را الصاق کنید",
|
||||
"friends": "دوستان",
|
||||
"no_lyrics_available": "متاسفیم، قادر به یافتن متن این قطعه نیستیم"
|
||||
}
|
||||
@ -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",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Présence riche de Discord",
|
||||
"browse_all": "Parcourir tout",
|
||||
"genres": "Genres",
|
||||
"explore_genres": "Explorer les genres"
|
||||
"explore_genres": "Explorer les genres",
|
||||
"step_3_steps": "Copiez la valeur du cookie \"sp_dc\"",
|
||||
"step_4_steps": "Collez la valeur copiée de \"sp_dc\"",
|
||||
"friends": "Amis",
|
||||
"no_lyrics_available": "Désolé, impossible de trouver les paroles de cette piste"
|
||||
}
|
||||
@ -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": "पाइप किए गए सर्वर",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "डिस्कॉर्ड रिच प्रेजेंस",
|
||||
"browse_all": "सभी को ब्राउज़ करें",
|
||||
"genres": "शैलियाँ",
|
||||
"explore_genres": "शैलियों का अन्वेषण करें"
|
||||
"explore_genres": "शैलियों का अन्वेषण करें",
|
||||
"step_3_steps": "\"sp_dc\" कुकी का मूल्य कॉपी करें",
|
||||
"step_4_steps": "कॉपी किए गए \"sp_dc\" मूल्य को पेस्ट करें",
|
||||
"friends": "दोस्त",
|
||||
"no_lyrics_available": "क्षमा करें, इस ट्रैक के लिए गाने नहीं मिल सके"
|
||||
}
|
||||
@ -177,11 +177,9 @@
|
||||
"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",
|
||||
"step_3_steps": "Copia il valore dei cookie \"sp_dc\" e \"sp_key\" (o sp_gaid)",
|
||||
"success_emoji": "Successo🥳",
|
||||
"success_message": "Ora hai correttamente effettuato il login al tuo account Spotify. Bel lavoro, amico!",
|
||||
"step_4": "Passo 4",
|
||||
"step_4_steps": "Incolla i valori copiati di \"sp_dc\" e \"sp_key\" (o sp_gaid) nei campi rispettivi",
|
||||
"something_went_wrong": "Qualcosa è andato storto",
|
||||
"piped_instance": "Istanza Server Piped",
|
||||
"piped_description": "L'istanza server Piped da usare per il match della tracccia",
|
||||
@ -285,5 +283,9 @@
|
||||
"discord_rich_presence": "Presenza ricca di Discord",
|
||||
"browse_all": "Esplora tutto",
|
||||
"genres": "Generi",
|
||||
"explore_genres": "Esplora generi"
|
||||
"explore_genres": "Esplora generi",
|
||||
"step_3_steps": "Copia il valore del cookie \"sp_dc\"",
|
||||
"step_4_steps": "Incolla il valore copiato di \"sp_dc\"",
|
||||
"friends": "Amici",
|
||||
"no_lyrics_available": "Spiacente, impossibile trovare il testo di questa traccia"
|
||||
}
|
||||
@ -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 サーバーのインスタンス",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "ディスコードリッチプレゼンス",
|
||||
"browse_all": "すべてを閲覧",
|
||||
"genres": "ジャンル",
|
||||
"explore_genres": "ジャンルを探索"
|
||||
"explore_genres": "ジャンルを探索",
|
||||
"step_3_steps": "\"sp_dc\" Cookieの値をコピー",
|
||||
"step_4_steps": "コピーした\"sp_dc\"の値を貼り付け",
|
||||
"friends": "友達",
|
||||
"no_lyrics_available": "申し訳ありませんが、このトラックの歌詞を見つけることができません"
|
||||
}
|
||||
290
lib/l10n/app_ne.arb
Normal file
290
lib/l10n/app_ne.arb
Normal file
@ -0,0 +1,290 @@
|
||||
{
|
||||
"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": "शैलीहरू अन्वेषण गर्नुहोस्",
|
||||
"friends": "साथीहरू",
|
||||
"no_lyrics_available": "क्षमा गर्दैछौं, यस ट्र्याकका लागि गीतका शब्दहरू फेला परेन"
|
||||
}
|
||||
@ -177,11 +177,9 @@
|
||||
"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",
|
||||
"step_3_steps": "Kopieer de waarden van \"sp_dc\" en \"sp_key\" (of sp_gaid) Cookies",
|
||||
"success_emoji": "Succes🥳",
|
||||
"success_message": "Je bent nu succesvol ingelogd met je Spotify account. Goed gedaan, maat!",
|
||||
"step_4": "Stap 4",
|
||||
"step_4_steps": "Plak de gekopieerde \"sp_dc\" en \"sp_key\" (of sp_gaid) waarden in de respectievelijke velden",
|
||||
"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",
|
||||
@ -285,5 +283,9 @@
|
||||
"discord_rich_presence": "Discord Rich Presence",
|
||||
"browse_all": "Alles bekijken",
|
||||
"genres": "Genres",
|
||||
"explore_genres": "Verken genres"
|
||||
"explore_genres": "Verken genres",
|
||||
"step_3_steps": "Kopieer de waarde van de \"sp_dc\"-cookie",
|
||||
"step_4_steps": "Plak de gekopieerde waarde van \"sp_dc\"",
|
||||
"friends": "Vrienden",
|
||||
"no_lyrics_available": "Sorry, kan geen teksten vinden voor deze track"
|
||||
}
|
||||
@ -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.",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Obecność na Discordzie",
|
||||
"browse_all": "Przeglądaj wszystko",
|
||||
"genres": "Gatunki muzyczne",
|
||||
"explore_genres": "Eksploruj gatunki"
|
||||
"explore_genres": "Eksploruj gatunki",
|
||||
"step_3_steps": "Skopiuj wartość ciasteczka \"sp_dc\"",
|
||||
"step_4_steps": "Wklej skopiowaną wartość \"sp_dc\"",
|
||||
"friends": "Przyjaciele",
|
||||
"no_lyrics_available": "Przepraszamy, nie można znaleźć tekstu dla tego utworu"
|
||||
}
|
||||
@ -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",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Presença rica no Discord",
|
||||
"browse_all": "Navegar por tudo",
|
||||
"genres": "Gêneros",
|
||||
"explore_genres": "Explorar gêneros"
|
||||
"explore_genres": "Explorar gêneros",
|
||||
"step_3_steps": "Copie o valor do cookie \"sp_dc\"",
|
||||
"step_4_steps": "Cole o valor copiado de \"sp_dc\"",
|
||||
"friends": "Amigos",
|
||||
"no_lyrics_available": "Desculpe, não foi possível encontrar a letra desta faixa"
|
||||
}
|
||||
@ -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 для сопоставления треков",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Богатое присутствие в Discord",
|
||||
"browse_all": "Просмотреть все",
|
||||
"genres": "Жанры",
|
||||
"explore_genres": "Исследовать жанры"
|
||||
"explore_genres": "Исследовать жанры",
|
||||
"step_3_steps": "Скопируйте значение файла cookie \"sp_dc\"",
|
||||
"step_4_steps": "Вставьте скопированное значение \"sp_dc\"",
|
||||
"friends": "Друзья",
|
||||
"no_lyrics_available": "Извините, не удается найти текст для этого трека"
|
||||
}
|
||||
@ -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",
|
||||
@ -284,5 +282,9 @@
|
||||
"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"
|
||||
"explore_genres": "Türleri Keşfet",
|
||||
"step_3_steps": "\"sp_dc\" Çerezinin değerini kopyala",
|
||||
"step_4_steps": "Kopyalanan \"sp_dc\" değerini yapıştır",
|
||||
"friends": "Arkadaşlar",
|
||||
"no_lyrics_available": "Üzgünüz, bu parça için şarkı sözleri bulunamıyor"
|
||||
}
|
||||
@ -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, який використовуватиметься для зіставлення треків",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Багата присутність у Discord",
|
||||
"browse_all": "Переглянути все",
|
||||
"genres": "Жанри",
|
||||
"explore_genres": "Досліджувати жанри"
|
||||
"explore_genres": "Досліджувати жанри",
|
||||
"step_3_steps": "Скопіюйте значення cookie \"sp_dc\"",
|
||||
"step_4_steps": "Вставте скопійоване значення \"sp_dc\"",
|
||||
"friends": "Друзі",
|
||||
"no_lyrics_available": "Вибачте, не вдалося знайти текст для цього треку"
|
||||
}
|
||||
@ -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": "管道服务器实例用于匹配歌曲",
|
||||
@ -284,5 +282,9 @@
|
||||
"discord_rich_presence": "Discord 丰富展现",
|
||||
"browse_all": "浏览全部",
|
||||
"genres": "音乐类型",
|
||||
"explore_genres": "探索音乐类型"
|
||||
"explore_genres": "探索音乐类型",
|
||||
"step_3_steps": "复制\"sp_dc\" Cookie的值",
|
||||
"step_4_steps": "粘贴复制的\"sp_dc\"值",
|
||||
"friends": "朋友",
|
||||
"no_lyrics_available": "抱歉,无法找到此曲的歌词"
|
||||
}
|
||||
@ -21,6 +21,7 @@ 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'),
|
||||
|
||||
@ -58,15 +58,6 @@ Future<void> main(List<String> rawArgs) async {
|
||||
await DesktopTools.window.setPreventClose(true);
|
||||
}
|
||||
|
||||
await DesktopTools.ensureInitialized(
|
||||
DesktopWindowOptions(
|
||||
hideTitleBar: true,
|
||||
title: "Spotube",
|
||||
backgroundColor: Colors.transparent,
|
||||
minimumSize: const Size(300, 700),
|
||||
),
|
||||
);
|
||||
|
||||
await SystemTheme.accentColor.load();
|
||||
|
||||
if (!kIsWeb) {
|
||||
@ -107,6 +98,15 @@ Future<void> main(List<String> 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(
|
||||
@ -228,7 +228,9 @@ class SpotubeState extends ConsumerState<Spotube> {
|
||||
builder: (context, child) {
|
||||
return DevicePreview.appBuilder(
|
||||
context,
|
||||
DragToResizeArea(child: child!),
|
||||
DesktopTools.platform.isDesktop
|
||||
? DragToResizeArea(child: child!)
|
||||
: child,
|
||||
);
|
||||
},
|
||||
themeMode: themeMode,
|
||||
|
||||
111
lib/models/spotify_friends.dart
Normal file
111
lib/models/spotify_friends.dart
Normal file
@ -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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> json) =>
|
||||
_$SpotifyFriendActivityFromJson(json);
|
||||
}
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class SpotifyFriends {
|
||||
List<SpotifyFriendActivity> friends;
|
||||
|
||||
SpotifyFriends({required this.friends});
|
||||
|
||||
factory SpotifyFriends.fromJson(Map<String, dynamic> json) =>
|
||||
_$SpotifyFriendsFromJson(json);
|
||||
}
|
||||
65
lib/models/spotify_friends.g.dart
Normal file
65
lib/models/spotify_friends.g.dart
Normal file
@ -0,0 +1,65 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'spotify_friends.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SpotifyFriend _$SpotifyFriendFromJson(Map<String, dynamic> json) =>
|
||||
SpotifyFriend(
|
||||
uri: json['uri'] as String,
|
||||
name: json['name'] as String,
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
);
|
||||
|
||||
SpotifyActivityArtist _$SpotifyActivityArtistFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
SpotifyActivityArtist(
|
||||
uri: json['uri'] as String,
|
||||
name: json['name'] as String,
|
||||
);
|
||||
|
||||
SpotifyActivityAlbum _$SpotifyActivityAlbumFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
SpotifyActivityAlbum(
|
||||
uri: json['uri'] as String,
|
||||
name: json['name'] as String,
|
||||
);
|
||||
|
||||
SpotifyActivityContext _$SpotifyActivityContextFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
SpotifyActivityContext(
|
||||
uri: json['uri'] as String,
|
||||
name: json['name'] as String,
|
||||
index: json['index'] as num,
|
||||
);
|
||||
|
||||
SpotifyActivityTrack _$SpotifyActivityTrackFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
SpotifyActivityTrack(
|
||||
uri: json['uri'] as String,
|
||||
name: json['name'] as String,
|
||||
imageUrl: json['imageUrl'] as String,
|
||||
artist: SpotifyActivityArtist.fromJson(
|
||||
json['artist'] as Map<String, dynamic>),
|
||||
album:
|
||||
SpotifyActivityAlbum.fromJson(json['album'] as Map<String, dynamic>),
|
||||
context: SpotifyActivityContext.fromJson(
|
||||
json['context'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
SpotifyFriendActivity _$SpotifyFriendActivityFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
SpotifyFriendActivity(
|
||||
user: SpotifyFriend.fromJson(json['user'] as Map<String, dynamic>),
|
||||
track:
|
||||
SpotifyActivityTrack.fromJson(json['track'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
SpotifyFriends _$SpotifyFriendsFromJson(Map<String, dynamic> json) =>
|
||||
SpotifyFriends(
|
||||
friends: (json['friends'] as List<dynamic>)
|
||||
.map((e) => SpotifyFriendActivity.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
@ -35,7 +35,7 @@ class ArtistPage extends HookConsumerWidget {
|
||||
),
|
||||
extendBodyBehindAppBar: true,
|
||||
body: Builder(builder: (context) {
|
||||
if (artistQuery.hasError) {
|
||||
if (artistQuery.hasError && artistQuery.data == null) {
|
||||
return Center(child: Text(artistQuery.error.toString()));
|
||||
}
|
||||
return Skeletonizer(
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:gap/gap.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';
|
||||
@ -18,19 +22,19 @@ class HomePage extends HookConsumerWidget {
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Scaffold(
|
||||
appBar: DesktopTools.platform.isMobile
|
||||
? null
|
||||
: const PageWindowTitleBar(),
|
||||
appBar:
|
||||
DesktopTools.platform.isLinux || DesktopTools.platform.isWindows
|
||||
? const PageWindowTitleBar()
|
||||
: null,
|
||||
body: CustomScrollView(
|
||||
controller: controller,
|
||||
slivers: [
|
||||
if (DesktopTools.platform.isMacOS || DesktopTools.platform.isWeb)
|
||||
const SliverGap(20),
|
||||
const HomeGenresSection(),
|
||||
SliverList.list(
|
||||
children: const [
|
||||
HomeFeaturedSection(),
|
||||
HomeNewReleasesSection(),
|
||||
],
|
||||
),
|
||||
const SliverToBoxAdapter(child: HomeFeaturedSection()),
|
||||
const HomePageFriendsSection(),
|
||||
const SliverToBoxAdapter(child: HomeNewReleasesSection()),
|
||||
const SliverSafeArea(sliver: HomeMadeForUserSection()),
|
||||
],
|
||||
),
|
||||
|
||||
@ -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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -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';
|
||||
@ -188,12 +190,19 @@ class SyncedLyrics extends HookConsumerWidget {
|
||||
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,
|
||||
)
|
||||
else if (isUnSyncLyric == true)
|
||||
(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(
|
||||
|
||||
@ -55,12 +55,7 @@ class WebViewLogin extends HookConsumerWidget {
|
||||
final cookies =
|
||||
await CookieManager.instance().getCookies(url: action);
|
||||
final cookieHeader =
|
||||
cookies.fold<String>("", (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),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -18,7 +18,7 @@ class SettingsDownloadsSection extends HookConsumerWidget {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
|
||||
final pickDownloadLocation = useCallback(() async {
|
||||
if (DesktopTools.platform.isMobile) {
|
||||
if (DesktopTools.platform.isMobile || DesktopTools.platform.isMacOS) {
|
||||
final dirStr = await FilePicker.platform.getDirectoryPath(
|
||||
initialDirectory: preferences.downloadLocation,
|
||||
);
|
||||
|
||||
227
lib/pages/track/track.dart
Normal file
227
lib/pages/track/track.dart
Normal file
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,17 @@
|
||||
import 'package:catcher_2/catcher_2.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:piped_client/piped_client.dart';
|
||||
import 'package:spotube/services/sourced_track/sources/piped.dart';
|
||||
|
||||
final pipedInstancesFutureProvider = FutureProvider<List<PipedInstance>>(
|
||||
(ref) async {
|
||||
final pipedClient = ref.watch(pipedProvider);
|
||||
try {
|
||||
final pipedClient = ref.watch(pipedProvider);
|
||||
|
||||
return await pipedClient.instanceList();
|
||||
return await pipedClient.instanceList();
|
||||
} catch (e, stack) {
|
||||
Catcher2.reportCheckedError(e, stack);
|
||||
return <PipedInstance>[];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -17,6 +17,9 @@ class MobileAudioService extends BaseAudioHandler {
|
||||
AudioSession.instance.then((s) {
|
||||
session = s;
|
||||
session?.configure(const AudioSessionConfiguration.music());
|
||||
|
||||
bool wasPausedByBeginEvent = false;
|
||||
|
||||
s.interruptionEventStream.listen((event) async {
|
||||
if (event.begin) {
|
||||
switch (event.type) {
|
||||
@ -25,17 +28,23 @@ class MobileAudioService extends BaseAudioHandler {
|
||||
break;
|
||||
case AudioInterruptionType.pause:
|
||||
case AudioInterruptionType.unknown:
|
||||
await audioPlayer.pause();
|
||||
break;
|
||||
{
|
||||
wasPausedByBeginEvent = audioPlayer.isPlaying;
|
||||
await audioPlayer.pause();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (event.type) {
|
||||
case AudioInterruptionType.duck:
|
||||
await audioPlayer.setVolume(1.0);
|
||||
break;
|
||||
case AudioInterruptionType.pause:
|
||||
case AudioInterruptionType.unknown:
|
||||
case AudioInterruptionType.pause when wasPausedByBeginEvent:
|
||||
case AudioInterruptionType.unknown when wasPausedByBeginEvent:
|
||||
await audioPlayer.resume();
|
||||
wasPausedByBeginEvent = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<SpotifyFriends> 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> 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<List<Artist>> 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<dynamic, Artist>(
|
||||
data["artists"]
|
||||
.map((artist) => Artist.fromJson(_purifyArtistResponse(artist)))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _purifyArtistResponse(Map<String, dynamic> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,9 +94,8 @@ class PlaylistMutations {
|
||||
|
||||
return playlist;
|
||||
},
|
||||
refreshInfiniteQueries: [
|
||||
"current-user-playlists",
|
||||
],
|
||||
refreshInfiniteQueries: ["current-user-playlists"],
|
||||
refreshQueries: ["current-user-all-playlists"],
|
||||
ref: ref,
|
||||
onError: (error, recoveryData) {
|
||||
onError?.call(error);
|
||||
@ -135,6 +134,7 @@ class PlaylistMutations {
|
||||
"playlist/$playlistId",
|
||||
"current-user-playlists",
|
||||
],
|
||||
refreshQueries: ["current-user-all-playlists"],
|
||||
ref: ref,
|
||||
onError: (error, recoveryData) {
|
||||
onError?.call(error);
|
||||
|
||||
@ -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, dynamic>(
|
||||
"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<Iterable<Artist>, dynamic>(
|
||||
"artist-related-artist-query/$artist",
|
||||
(spotify) {
|
||||
return spotify.artists.relatedArtists(artist);
|
||||
return customSpotify.relatedArtists(id: artist);
|
||||
},
|
||||
ref: ref,
|
||||
);
|
||||
|
||||
@ -63,8 +63,8 @@ class LyricsQueries {
|
||||
/// Special thanks to [raptag](https://github.com/raptag) for discovering this
|
||||
/// jem
|
||||
|
||||
Query<SubtitleSimple, dynamic> spotifySynced(WidgetRef ref, Track? track) {
|
||||
return useSpotifyQuery<SubtitleSimple, dynamic>(
|
||||
Query<SubtitleSimple, Exception> spotifySynced(WidgetRef ref, Track? track) {
|
||||
return useSpotifyQuery<SubtitleSimple, Exception>(
|
||||
"spotify-synced-lyrics/${track?.id}}",
|
||||
(spotify) async {
|
||||
if (track == null) {
|
||||
|
||||
@ -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._();
|
||||
|
||||
16
lib/services/queries/tracks.dart
Normal file
16
lib/services/queries/tracks.dart
Normal file
@ -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, dynamic> track(WidgetRef ref, String id) {
|
||||
return useSpotifyQuery(
|
||||
"track/$id",
|
||||
(spotify) => spotify.tracks.get(id),
|
||||
ref: ref,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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<SpotifyFriends, dynamic> friendActivity(WidgetRef ref) {
|
||||
final customSpotify = ref.read(customSpotifyEndpointProvider);
|
||||
return useSpotifyQuery<SpotifyFriends, dynamic>(
|
||||
"friend-activity",
|
||||
(spotify) {
|
||||
return customSpotify.getFriendActivity();
|
||||
},
|
||||
ref: ref,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,4 +15,4 @@ enum SourceQualities {
|
||||
low,
|
||||
}
|
||||
|
||||
typedef SiblingType = ({SourceInfo info, SourceMap? source});
|
||||
typedef SiblingType<T extends SourceInfo> = ({T info, SourceMap? source});
|
||||
|
||||
@ -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<JioSaavnSourcedTrack?> 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);
|
||||
|
||||
@ -168,6 +185,16 @@ class JioSaavnSourcedTrack extends SourcedTrack {
|
||||
|
||||
final (:info, :source) = toSiblingType(item);
|
||||
|
||||
await SourceMatch.box.put(
|
||||
id!,
|
||||
SourceMatch(
|
||||
id: id!,
|
||||
sourceType: SourceType.jiosaavn,
|
||||
createdAt: DateTime.now(),
|
||||
sourceId: info.id,
|
||||
),
|
||||
);
|
||||
|
||||
return JioSaavnSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: newSiblings,
|
||||
|
||||
@ -22,6 +22,19 @@ final pipedProvider = Provider<PipedClient>(
|
||||
},
|
||||
);
|
||||
|
||||
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}",
|
||||
@ -147,7 +160,7 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
final query = SourcedTrack.getSearchTerm(track);
|
||||
|
||||
final PipedSearchResult(items: searchResults) = await pipedClient.search(
|
||||
query,
|
||||
"$query - Topic",
|
||||
preference.searchMode == SearchMode.youtube
|
||||
? PipedFilter.video
|
||||
: PipedFilter.musicSongs,
|
||||
@ -233,12 +246,16 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
|
||||
@override
|
||||
Future<SourcedTrack?> 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);
|
||||
|
||||
@ -246,6 +263,16 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
|
||||
final manifest = await pipedClient.streams(newSourceInfo.id);
|
||||
|
||||
await SourceMatch.box.put(
|
||||
id!,
|
||||
SourceMatch(
|
||||
id: id!,
|
||||
sourceType: SourceType.jiosaavn,
|
||||
createdAt: DateTime.now(),
|
||||
sourceId: newSourceInfo.id,
|
||||
),
|
||||
);
|
||||
|
||||
return PipedSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: newSiblings,
|
||||
|
||||
@ -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}",
|
||||
@ -194,7 +210,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
final query = SourcedTrack.getSearchTerm(track);
|
||||
|
||||
final searchResults = await youtubeClient.search.search(
|
||||
query,
|
||||
"$query - Topic",
|
||||
filter: TypeFilters.video,
|
||||
);
|
||||
|
||||
@ -214,18 +230,32 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
|
||||
@override
|
||||
Future<YoutubeSourcedTrack?> 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);
|
||||
|
||||
final manifest =
|
||||
await youtubeClient.videos.streamsClient.getManifest(newSourceInfo.id);
|
||||
|
||||
await SourceMatch.box.put(
|
||||
id!,
|
||||
SourceMatch(
|
||||
id: id!,
|
||||
sourceType: SourceType.jiosaavn,
|
||||
createdAt: DateTime.now(),
|
||||
sourceId: newSourceInfo.id,
|
||||
),
|
||||
);
|
||||
|
||||
return YoutubeSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: newSiblings,
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
@ -228,7 +228,7 @@
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
compatibilityVersion = "Xcode 12.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@ -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;
|
||||
@ -426,12 +427,15 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Spotube;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@ -489,7 +493,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 +540,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;
|
||||
@ -553,12 +557,15 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Spotube;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(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;
|
||||
@ -574,12 +581,15 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 88NVGSJ5N3;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Spotube;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.music";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@ -4,6 +4,6 @@ import FlutterMacOS
|
||||
@NSApplicationMain
|
||||
class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,26 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true />
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true />
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true />
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true />
|
||||
<!-- Just Audio Config -->
|
||||
<!-- <key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true />
|
||||
</dict> -->
|
||||
<!-- Requires Certification -->
|
||||
<!-- <key>keychain-access-groups</key>
|
||||
<array /> -->
|
||||
<!-- FilePicker -->
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true />
|
||||
</dict>
|
||||
</plist>
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.music.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1,24 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true />
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true />
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true />
|
||||
<!-- Just Audio Config -->
|
||||
<!-- <key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true />
|
||||
</dict> -->
|
||||
<!-- Requires Certification -->
|
||||
<!-- <key>keychain-access-groups</key>
|
||||
<array /> -->
|
||||
<!-- FilePicker -->
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true />
|
||||
</dict>
|
||||
</plist>
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.music.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -4,8 +4,14 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.music.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
|
||||
1
macos/packaging/pkg/make_config.yaml
Normal file
1
macos/packaging/pkg/make_config.yaml
Normal file
@ -0,0 +1 @@
|
||||
install-path: /Applications
|
||||
96
pubspec.lock
96
pubspec.lock
@ -543,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:
|
||||
@ -1006,18 +1006,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: a07c781bf55bf11ae85133338e4850f0b4e33e261c44a66c750fc707d65d8393
|
||||
sha256: "3b40e751eaaa855179b416974d59d29669e750d2e50fcdb2b37f1cb0ca8c803a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.1.2"
|
||||
version: "13.0.1"
|
||||
google_fonts:
|
||||
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:
|
||||
@ -1255,6 +1255,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1299,18 +1323,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
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:
|
||||
@ -1379,10 +1403,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:
|
||||
@ -1467,10 +1491,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
version: "1.9.0"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1595,10 +1619,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.4"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1635,10 +1659,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
||||
sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
version: "5.0.2"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1756,10 +1780,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:
|
||||
@ -1780,10 +1804,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:
|
||||
@ -1804,10 +1828,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:
|
||||
@ -1836,10 +1860,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sidebarx
|
||||
sha256: "26a8392ceddb659c8f2c688beba6c04bcbf520b4d5decb143c5fd7253653081f"
|
||||
sha256: "7042d64844b8e64ca5c17e70d89b49df35b54a26c015b90000da9741eab70bc0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.0"
|
||||
version: "0.16.3"
|
||||
simple_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -2209,18 +2233,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:
|
||||
@ -2229,14 +2253,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2249,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:
|
||||
|
||||
12
pubspec.yaml
12
pubspec.yaml
@ -3,7 +3,7 @@ description: Open source Spotify client that doesn't require Premium nor uses El
|
||||
|
||||
publish_to: "none"
|
||||
|
||||
version: 3.4.0+27
|
||||
version: 3.4.1+28
|
||||
|
||||
homepage: https://spotube.krtirtho.dev
|
||||
repository: https://github.com/KRTirtho/spotube
|
||||
@ -55,8 +55,8 @@ dependencies:
|
||||
flutter_svg: ^1.1.6
|
||||
form_validator: ^2.1.1
|
||||
fuzzywuzzy: ^1.1.6
|
||||
google_fonts: ^5.1.0
|
||||
go_router: ^11.1.2
|
||||
go_router: ^13.0.1
|
||||
google_fonts: ^6.1.0
|
||||
hive: ^2.2.3
|
||||
hive_flutter: ^1.1.0
|
||||
hooks_riverpod: ^2.4.3
|
||||
@ -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.15.0
|
||||
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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user