Merge pull request #536 from KRTirtho/dev

Version 3 🚀
This commit is contained in:
Kingkor Roy Tirtho 2023-07-02 22:47:06 +06:00 committed by GitHub
commit 902b66a71c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
283 changed files with 17610 additions and 8059 deletions

View File

@ -1,7 +1,11 @@
POCKETBASE_URL=
USERNAME=
PASSWORD=
SUPABASE_URL=
SUPABASE_API_KEY=
# The format:
# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2
SPOTIFY_SECRETS=
# 0 or 1
# 0 = disable
# 1 = enable
ENABLE_UPDATE_CHECK=

View File

@ -46,7 +46,7 @@ body:
attributes:
label: Spotube version
description: In which version of Spotube did you encounter this bug?
placeholder: (e.g.) v2.7.1
placeholder: (e.g.) v3.0.0
- type: dropdown
attributes:
label: Installation source

View File

@ -4,7 +4,7 @@ on:
inputs:
version:
description: Version to publish (x.x.x)
default: 2.7.1
default: 3.0.0
required: true
dry_run:
description: Dry run

View File

@ -4,7 +4,7 @@ on:
inputs:
version:
description: Version to release (x.x.x)
default: 2.7.1
default: 3.0.0
required: true
channel:
type: choice
@ -14,12 +14,20 @@ on:
- stable
- nightly
default: nightly
debug:
description: Debug on failed when channel is nightly
required: true
type: boolean
default: false
dry_run:
description: Dry run
required: true
type: boolean
default: true
env:
FLUTTER_VERSION: '3.10.0'
jobs:
windows:
runs-on: windows-latest
@ -28,6 +36,7 @@ jobs:
- uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
if: ${{ inputs.channel == 'nightly' }}
@ -61,7 +70,7 @@ jobs:
run: |
flutter config --enable-windows-desktop
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
- name: Build Windows Executable
run: |
@ -78,6 +87,12 @@ jobs:
make choco
mv dist/spotube.*.nupkg dist/Spotube-windows-x86_64.nupkg
- name: Debug With SSH When fails
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
@ -91,6 +106,7 @@ jobs:
- uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Get current date
id: date
@ -99,7 +115,7 @@ jobs:
- name: Install Dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools patchelf desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libunwind-dev locate patchelf libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev
sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp1 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev
- name: Install AppImage Tool
run: |
@ -136,15 +152,15 @@ jobs:
run: |
flutter config --enable-linux-desktop
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
- name: Build Linux Packages
run: |
dart pub global activate flutter_distributor
alias dpkg-deb="dpkg-deb --Zxz"
flutter_distributor package --platform=linux --targets=deb
flutter_distributor package --platform=linux --targets=appimage
flutter_distributor package --platform=linux --targets=rpm
flutter_distributor package --platform=linux --targets=deb
- name: Create tar.xz (stable)
if: ${{ inputs.channel == 'stable' }}
@ -161,18 +177,25 @@ jobs:
mv dist/**/spotube-*-linux.rpm dist/Spotube-linux-x86_64.rpm
mv dist/**/spotube-*-linux.AppImage dist/Spotube-linux-x86_64.AppImage
- name: Debug With SSH When fails
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
- uses: actions/upload-artifact@v3
with:
name: Spotube-Release-Binaries
path: dist/
android:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Install Dependencies
run: |
@ -203,7 +226,7 @@ jobs:
- name: Generate Secrets
run: |
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
- name: Sign Apk
run: |
@ -215,6 +238,12 @@ jobs:
flutter build apk
mv build/app/outputs/apk/release/app-release.apk build/Spotube-android-all-arch.apk
- name: Debug With SSH When fails
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
- uses: actions/upload-artifact@v3
with:
name: Spotube-Release-Binaries
@ -228,6 +257,7 @@ jobs:
- uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
if: ${{ inputs.channel == 'nightly' }}
@ -253,7 +283,8 @@ jobs:
- name: Generate Secrets
run: |
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
flutter pub remove media_kit_native_event_loop
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
- name: Build Macos App
run: |
@ -265,16 +296,22 @@ jobs:
run: |
npm install -g appdmg
mkdir -p build/${{ env.BUILD_VERSION }}
appdmg appdmg.json build/Spotube-macos-x86_64.dmg
appdmg appdmg.json build/Spotube-macos-universal.dmg
- name: Debug With SSH When fails
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
uses: mxschmitt/action-tmate@v3
with:
limit-access-to-actor: true
- uses: actions/upload-artifact@v3
with:
name: Spotube-Release-Binaries
path: |
build/Spotube-macos-x86_64.dmg
build/Spotube-macos-universal.dmg
upload:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs:
- windows
- linux

18
.vscode/launch.json vendored
View File

@ -2,11 +2,25 @@
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"name": "spotube",
"type": "dart",
"request": "launch",
"program": "${workspaceFolder}/lib/main.dart"
"program": "lib/main.dart"
},
{
"name": "spotube (profile)",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"flutterMode": "profile"
},
{
"name": "spotube (release)",
"type": "dart",
"request": "launch",
"program": "lib/main.dart",
"flutterMode": "release"
}
],
"compounds": []
}

View File

@ -1,7 +1,11 @@
{
"cmake.configureOnOpen": false,
"cSpell.words": [
"acousticness",
"danceability",
"instrumentalness",
"Mpris",
"speechiness",
"Spotube",
"winget"
]

View File

@ -2,6 +2,158 @@
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.0.0](https://github.com/KRTirtho/spotube/compare/v2.7.1...v3.0.0) (2023-07-02)
### Features
* adaptive controllers ([c8b7de0](https://github.com/KRTirtho/spotube/commit/c8b7de087917ec3037c015d5b55693cb3dbdecca))
* adaptive popup and bottom sheet list widget ([ddc1c5f](https://github.com/KRTirtho/spotube/commit/ddc1c5f373a4d72cc231c35dd70c3d577b84f7f5))
* add generated to playlist(s) ([c91d8c8](https://github.com/KRTirtho/spotube/commit/c91d8c8efa8b526c64881fa829992b8c250e7c89))
* add german locale ([ba3f428](https://github.com/KRTirtho/spotube/commit/ba3f4281f1a6bc7ddf38775e9d40dad863ed3692))
* add piped search mode ([17a25a5](https://github.com/KRTirtho/spotube/commit/17a25a501e0d5e2512d8de0921fd602ea906d30f))
* add sleep timer support ([4a75f3d](https://github.com/KRTirtho/spotube/commit/4a75f3dbd1e7e6f68899de001df70e809533f142))
* adjust lyric page blurriness and player playbutton ([54d5907](https://github.com/KRTirtho/spotube/commit/54d5907f14df04f3f983ba2b0401ba05785da03b))
* album art dominant color as accent color ([#447](https://github.com/KRTirtho/spotube/issues/447)) ([31b9249](https://github.com/KRTirtho/spotube/commit/31b9249cc8f7313a132a514a9ca825c2ae1e2256))
* **android:** add splash screen ([c232fcc](https://github.com/KRTirtho/spotube/commit/c232fcc6dd1479ed33a8baa9887de2702a8ea22e))
* **android:** disable battery optimization for better playback ([fe5b429](https://github.com/KRTirtho/spotube/commit/fe5b429ddacc576fc9fdb5e66718782cda163b27))
* artist card redesign ([92a418c](https://github.com/KRTirtho/spotube/commit/92a418c8a8a9df99e27407b628e5e3cc9ccb4115))
* Better download manager with download progress ([6752adc](https://github.com/KRTirtho/spotube/commit/6752adc9398818f51b69fced226b4b8410fb9e9b))
* better language picker, adaptive select tile and settings section contrast ([6430a25](https://github.com/KRTirtho/spotube/commit/6430a2587075aa24483ab26ce6f0f6b2b630e139))
* cache encryption for sensitive data ([b110d83](https://github.com/KRTirtho/spotube/commit/b110d834561ac53129ac9ec80238c014c84832ec))
* color scheme picker dialog vertical list view instead of wrap ([bb60b01](https://github.com/KRTirtho/spotube/commit/bb60b01ef2f2ba3da8f6fe3a19add168b2ee8a4e))
* compact and adaptive playbutton card design ([eeb8cab](https://github.com/KRTirtho/spotube/commit/eeb8cabf491d5242bd434b3c71c39363f24bdcf9))
* compact button tabbar ([67380f6](https://github.com/KRTirtho/spotube/commit/67380f68765f18e4dcd3d60117083c7e9c6761c2))
* create a basic installer script ([1763a36](https://github.com/KRTirtho/spotube/commit/1763a36a262178306df61d4588c17ff795a32790))
* curved navigation bar ([776edf8](https://github.com/KRTirtho/spotube/commit/776edf84afcf99f96cf6e337b0c84ed89034ca8e))
* custom error toast ([96f04c1](https://github.com/KRTirtho/spotube/commit/96f04c17565c0ab7f115d5c1f167f6660a69480d))
* custom playlist generator ([f4b0d13](https://github.com/KRTirtho/spotube/commit/f4b0d134ca724b75bf65b885bce4dba206f1e090))
* desktop mini player support ([471812d](https://github.com/KRTirtho/spotube/commit/471812d789eb2c861268ab8451d50104ac2fbe2e))
* **desktop:** close button for minimize notification ([1688f99](https://github.com/KRTirtho/spotube/commit/1688f99096af940ead65e67832c6f061a6f635ac))
* **desktop:** show minimized to system tray notification ([296f96c](https://github.com/KRTirtho/spotube/commit/296f96cf17cf21ff09b406cc24952ff60da52d5e))
* disable/enable smtc on demand ([7fa50e5](https://github.com/KRTirtho/spotube/commit/7fa50e5c5ee9ca3ba95dca55f8a4831047f17570))
* download button on each track ([925fa86](https://github.com/KRTirtho/spotube/commit/925fa86271fa10ff77b8137ba8d09b8067d0e819))
* enable caching of queue ([ec11af5](https://github.com/KRTirtho/spotube/commit/ec11af53a16c435fbea3d0c81910dca371be9ce7))
* heart button animation ([8432dc6](https://github.com/KRTirtho/spotube/commit/8432dc6286fbdfda52bbeb39c6d4ababa05881bc))
* improved track item API and UI ([617aa89](https://github.com/KRTirtho/spotube/commit/617aa89409ce29eb3c197aee5c1189d763a3913c))
* **installer:** get latest version from Github API ([957c085](https://github.com/KRTirtho/spotube/commit/957c085e1243ec0af5bc45d38691c74e2ba91ad8))
* **local_tracks:** delete local track ([#484](https://github.com/KRTirtho/spotube/issues/484)) ([52835b2](https://github.com/KRTirtho/spotube/commit/52835b2ce2212925f80e1a1595c0ca30e6860a8d))
* locale category/genre title ([88137f0](https://github.com/KRTirtho/spotube/commit/88137f01b27150b306327a01e67ec8a35a60e82e))
* **locale:** add bengali translations for search page ([a1cdbad](https://github.com/KRTirtho/spotube/commit/a1cdbad18782a74b43f0625facfe3e35c516bf43))
* **locale:** localize search, library, lyrics, artist with both Bengali and English ([11fe9ec](https://github.com/KRTirtho/spotube/commit/11fe9ec74462441b67a0ab0df73824f36dd15e2d))
* **locale:** player, playlist view, track tile bengali and english translations ([c55133d](https://github.com/KRTirtho/spotube/commit/c55133dc8bba307823e5b67a30cfab03c923cb7f))
* localize settings, about, login, player queue with Bengali and English translations ([a5c36bb](https://github.com/KRTirtho/spotube/commit/a5c36bbb20cc69d609bfb5ab973c7e288c1ea9de))
* logs page in settings ([b78e7f5](https://github.com/KRTirtho/spotube/commit/b78e7f57a05db344aae59206cbb0f43b3ee199a9))
* macos title bar spacing and lyrics page margin separate ([a0b3771](https://github.com/KRTirtho/spotube/commit/a0b377104f9822561d3b46dbc6551bb561842480))
* make snackbar floating ([9dbb817](https://github.com/KRTirtho/spotube/commit/9dbb8171a6d6b81120ca7ccd74577e5c890ff930))
* merge floating player with nav bar and nav bar translucent bg ([a90261e](https://github.com/KRTirtho/spotube/commit/a90261ed199f6cff9e8d0fe24934f8f1d8e9ed98))
* **mini_player:** remove window shadow ([6259014](https://github.com/KRTirtho/spotube/commit/625901482ada4b441b838f640c0ab7167119b321))
* **mini_player:** show/hide UI on hover toggle ([2e8b647](https://github.com/KRTirtho/spotube/commit/2e8b647a51f87840c2bd39f0a1dc25ddc91528fc))
* new sidebar widget and translucent bottom player ([4ba1e70](https://github.com/KRTirtho/spotube/commit/4ba1e70636b4ba43697663128fc5422b1d0b2a2f))
* newly released albums of user followed artist ([33cb794](https://github.com/KRTirtho/spotube/commit/33cb7947d63d0a2692a004f87a2ccd5777bf054e))
* optimize image load + genre page and reduce page size of loaded categories ([7131efa](https://github.com/KRTirtho/spotube/commit/7131efa07fdbcf17965fc59ff635a6198b0e5e25))
* persistent volume percentage ([3724bd5](https://github.com/KRTirtho/spotube/commit/3724bd5a10eef7a099d6f596fd038e6fea228359))
* personal playlist recommendations ([ae820a2](https://github.com/KRTirtho/spotube/commit/ae820a22f291082c49554d621c25cc62212a6708))
* piped instance picker on settings ([bed0d3b](https://github.com/KRTirtho/spotube/commit/bed0d3bd70438df413633ee03fd258a2ca4a1688))
* platform specific title bar buttons ([6267720](https://github.com/KRTirtho/spotube/commit/62677209a23172162defb7a8e542d981569eba08))
* **playback:** integrate android, ios, macos with JustAudio ([d487fe5](https://github.com/KRTirtho/spotube/commit/d487fe55630993e2b729050ebd0bf4e1e4be1fb3))
* **playback:** use assets_audio_player to fix macos double duration problems and android high loading latency ([1fff0f1](https://github.com/KRTirtho/spotube/commit/1fff0f1bd0d811c348f293f733cbcb7cd57e02f8))
* player details dialog and separate location of lyrics button in player page ([ce38233](https://github.com/KRTirtho/spotube/commit/ce38233de8f4775018a1d01e951b1635776fe743))
* **player:** add playlist related methods to audio player ([f1080e1](https://github.com/KRTirtho/spotube/commit/f1080e1675aee1208d05658adfabfbed04ff45b6))
* **player:** animated gradient background ([49b5d0e](https://github.com/KRTirtho/spotube/commit/49b5d0e6948d80abb8ee09203e0d655d68377245))
* **player:** custom playlist implementation for media_kit to replace unpredictable playlist of mpv ([eaf65b6](https://github.com/KRTirtho/spotube/commit/eaf65b6db208aaad745821d4d42afc05f51cee7c))
* **player:** proper coloring of elements ([b2c4ea1](https://github.com/KRTirtho/spotube/commit/b2c4ea13f6157c2b7bec3957e1f7f50fbf0002c7))
* **player:** replace bg blur with gradient, proper fg color and align title and artist name ([159f03e](https://github.com/KRTirtho/spotube/commit/159f03e7ca62e6b3b86389e2795da84de61fba78))
* playlist create support for generated playlist ([91c72f9](https://github.com/KRTirtho/spotube/commit/91c72f9ec9556f301c5d129fc82e19e791a02fbe))
* playlist generation all parameters support ([9877d5f](https://github.com/KRTirtho/spotube/commit/9877d5f51736db03d5839dadf164d11d0cce82f0))
* **playlist,album page:** play and shuffle take full width on smaller screens, add new xs breakpoint ([dce1b88](https://github.com/KRTirtho/spotube/commit/dce1b88694cfcb6b7e63d6ee614ac1dbbd017f6e))
* **queue:** add track(s) for playing next ([#460](https://github.com/KRTirtho/spotube/issues/460)) ([cac8ea6](https://github.com/KRTirtho/spotube/commit/cac8ea638812f5d9cb4305144b6351141a2cf407))
* **queue:** reorder tracks support ([441b43b](https://github.com/KRTirtho/spotube/commit/441b43bef6b92fd7df6c4e1bef39d67b4a76cd22))
* re-designed playlist/album page ([0cedc7a](https://github.com/KRTirtho/spotube/commit/0cedc7a4187771efce8152003f890e242116c78c))
* re-introduce youtube API along with piped ([b54ee96](https://github.com/KRTirtho/spotube/commit/b54ee96233b29d7517eba66e3f8dd9270c2790df))
* reactive volume slider and slicker bottom bar with lowered height ([9d14517](https://github.com/KRTirtho/spotube/commit/9d14517202d5c9d993a947808bf0c6520ed54ea3))
* remove SponsorBlock in favor of YT Music and remove pocketbase backend track support ([fb780da](https://github.com/KRTirtho/spotube/commit/fb780da327a213d7a82cbc3b567ece858dc2f0e8))
* repeat button all 3 mode and disable player controls when track is fetching ([1418378](https://github.com/KRTirtho/spotube/commit/14183781dd3f1e16c121e78ad637a326de7b5dcf))
* replace YouTube API with piped API ([1ecc36d](https://github.com/KRTirtho/spotube/commit/1ecc36da57af61fd9c2ca928589088cd4325f605))
* responsive playlist generate page and scrollable multi autocomplete ([d57aad5](https://github.com/KRTirtho/spotube/commit/d57aad5612f7622dcd638ea8c0ec4d96f741de2b))
* search alternative track source ([dfea195](https://github.com/KRTirtho/spotube/commit/dfea195ec178de733717cfe3226cede7521ee2d3))
* setup localization (l10n) and language switcher, add sidebar and navbar locale ([f12d812](https://github.com/KRTirtho/spotube/commit/f12d81259f9e7005e681a7ca9867291d9228a8b1))
* show album release year ([#387](https://github.com/KRTirtho/spotube/issues/387)) and fix layout of artist's album ([6a6ddf6](https://github.com/KRTirtho/spotube/commit/6a6ddf6e1f6dc72b794cae49adf8348da272babd))
* show country code piped instance list ([60328a6](https://github.com/KRTirtho/spotube/commit/60328a6bafcbff1b7d0ee5099825f0e3d545b60f))
* show loading when track metadata is being fetched, android, ios, macos enable shuffling ([bf59570](https://github.com/KRTirtho/spotube/commit/bf59570251720a80efe0aa6be481899864da5079))
* sort tracks by newest and oldest dates ([b4713e3](https://github.com/KRTirtho/spotube/commit/b4713e377a938cbebe70089874216f86fe550c34))
* supabase integration ([8bcce92](https://github.com/KRTirtho/spotube/commit/8bcce9282eae08c5996a27f16f89cbc187a06823))
* system tray support ([#31](https://github.com/KRTirtho/spotube/issues/31)) ([06a0437](https://github.com/KRTirtho/spotube/commit/06a043764d9f65fb448fcf088ccf2737145e23e8))
* track populate sibling support ([3aeb026](https://github.com/KRTirtho/spotube/commit/3aeb026776716b6e2eb89c8406a4996a86c7ca60))
* **translation:** add hindi and french translations using ChatGPT ([6d836bd](https://github.com/KRTirtho/spotube/commit/6d836bdb658c180ca8e2c71e7e290fafa3520727))
* **translation:** add Japanase locale ([4b52a71](https://github.com/KRTirtho/spotube/commit/4b52a71c0914bda6c831d8f637a5934f7bcf8fcb))
* use system color scheme ([862c4b8](https://github.com/KRTirtho/spotube/commit/862c4b8faf2c751d803e373e29981a116bf08ed5))
* volume slider in player page ([7abe2c1](https://github.com/KRTirtho/spotube/commit/7abe2c10735bc38c644487139557a731d25e80e6))
* windows OS media control panel support ([f0b426a](https://github.com/KRTirtho/spotube/commit/f0b426ae89f2e01f4a9c8757ef4e0b4a21b50c7b))
### Bug Fixes
* add to playlist dialog not showing playlist name ([8944581](https://github.com/KRTirtho/spotube/commit/8944581c09eec0162220e7ff684205484fafb599))
* album sync not working ([74906f3](https://github.com/KRTirtho/spotube/commit/74906f393250934c36530a73ad7312f59f8627ed))
* alternative track source not playing new source ([a9b5a71](https://github.com/KRTirtho/spotube/commit/a9b5a714e47d40407d799966ae95f84338f9b59a))
* **android:** use multi assetAudioPlayer instance fix patch and disable Pre-download and play by default in Android too ([cdb3268](https://github.com/KRTirtho/spotube/commit/cdb32685e4bbb899706ed16d58ef9a3a074e283a))
* **artist:** follower count shows as float when < 1000 ([#482](https://github.com/KRTirtho/spotube/issues/482)) ([fd1846e](https://github.com/KRTirtho/spotube/commit/fd1846eecf9632e59e4b70fb70e97c556b6374f5))
* bottom navbar first item icon color not changing on primary color change ([6eb4244](https://github.com/KRTirtho/spotube/commit/6eb4244f3244a96fe6858261534cc03eb3de803c))
* cached currently playing track infinite loading ([9401718](https://github.com/KRTirtho/spotube/commit/94017189c6b9bf55ec62cbf29cd6b0e9fffca42a))
* cached queue tracks expired stream ([ed29ab5](https://github.com/KRTirtho/spotube/commit/ed29ab5137416d9fb2e7e9fe840f56ef52df6f61))
* collection currently playing state persist on restart ([1c89e3e](https://github.com/KRTirtho/spotube/commit/1c89e3efb0f05c648fc1c8e09039e62333de18d1))
* color not syncing and add new screenshot ([6205501](https://github.com/KRTirtho/spotube/commit/62055018feade0b895663a0bfc5f85f265ae2154))
* content going below bottom player or nav bar ([1bdce9f](https://github.com/KRTirtho/spotube/commit/1bdce9fe964de88a667bb160846c11dc70b77c00))
* disable background_downloader due to android build failures ([7d23bee](https://github.com/KRTirtho/spotube/commit/7d23beec5ef07c4d649185a69e7a2b9697dc6953))
* disable play when loading track and buffering event ([30c933c](https://github.com/KRTirtho/spotube/commit/30c933cdf3d4524be164e171094afdd27b0252b7))
* error log ([e3d8239](https://github.com/KRTirtho/spotube/commit/e3d8239b9f5700bfb17c4758d95ba1db1f0e718a))
* excessive repaints caused by Player progress bar ([09b24cf](https://github.com/KRTirtho/spotube/commit/09b24cf1fd1644c549f85904545db54b39cc2431))
* failed download no error icon ([1266a3f](https://github.com/KRTirtho/spotube/commit/1266a3f1607de11e793a294071850996527d494a))
* **home:** bottom player transparency ([20c424c](https://github.com/KRTirtho/spotube/commit/20c424c77fe273a693213ebf88d50e4025bc8608))
* language changer not working ([7b7b1f2](https://github.com/KRTirtho/spotube/commit/7b7b1f2647591b7cd4cc7841526716c6a2877e55))
* less frequent position updates ([0a49b56](https://github.com/KRTirtho/spotube/commit/0a49b56566abd00cf7703e4207cfa90f93c381fd))
* linux mpris not showing up and overall media notification service ([1abcad1](https://github.com/KRTirtho/spotube/commit/1abcad1de510c209a34196f2de17045af4dd3bc2))
* local tracks getting fetched on first load ([73c012c](https://github.com/KRTirtho/spotube/commit/73c012c71ab5050636f79e010d654b4390978ee7))
* local tracks not working when there's a invalid music file in the folder ([5855820](https://github.com/KRTirtho/spotube/commit/5855820569dfad7cd26f1e0f0c985babd0d9485d))
* lyrics page blur in player and cut off text when line too big ([6b4584e](https://github.com/KRTirtho/spotube/commit/6b4584e91bd4f4aee0c56e48a7aec7015c7c418b))
* macos build by removing media_kit native event loop ([62fc773](https://github.com/KRTirtho/spotube/commit/62fc7739b508f0e874978408a2bab0a1d422deb6))
* macos build error, mobile player duration and playing state and background disposal of player ([be91e33](https://github.com/KRTirtho/spotube/commit/be91e33828630c7062886cd15e4d57496daaa4d5))
* **macos,ios:** use regular shared prefs ([1b5bfec](https://github.com/KRTirtho/spotube/commit/1b5bfec27fbcfe9faabff64d46296bdeebe00161))
* memoize child of animated widget and make player bg animation faster ([fcb5c8f](https://github.com/KRTirtho/spotube/commit/fcb5c8f8dabd0d4e3033f80ea3e5d006243cdfb5))
* mini player not working in release mode ([28ff321](https://github.com/KRTirtho/spotube/commit/28ff3216efee81184798eedfbb10ba66395bbf36))
* **mkPlayer:** remove method and wrong active index on modifying playlist ([3bafa7b](https://github.com/KRTirtho/spotube/commit/3bafa7b80c963fa52b90ed4cb1393fb121cac713))
* mobile audio notification not working ([8f9303b](https://github.com/KRTirtho/spotube/commit/8f9303bc0fddb9d179303a1f0eb76dd5b02410e7))
* multiple instance of theme ([4ec0424](https://github.com/KRTirtho/spotube/commit/4ec04240a5bde6af5c920a61ab6260e7a93bfc54))
* navigation to settings not working ([ce10aa1](https://github.com/KRTirtho/spotube/commit/ce10aa1fe2c95d4738835687f613930cf7829f3a))
* no progress update when track changed ([6ae8964](https://github.com/KRTirtho/spotube/commit/6ae896441a787fce1bc6e5eb5379856dc2f4e96d))
* null exception on proxy playlist and audio player ([a455a89](https://github.com/KRTirtho/spotube/commit/a455a89c5861fd455f6950c7b68beae24bdcc6ed))
* overflowing clickable artists links ([4077fac](https://github.com/KRTirtho/spotube/commit/4077fac39fb667b87e959e53d2dcaceefb63cd2d))
* personalized playlists not loading ([caa3408](https://github.com/KRTirtho/spotube/commit/caa340803fdf3859fe5a8a996abae1502ef2e4e7))
* playback not moving to next track after a track ends ([27e8acb](https://github.com/KRTirtho/spotube/commit/27e8acbfe75a37c0a8fa69a444fdd86e92dbe4f0))
* **player:** gradient bg not taking full height ([62ad86e](https://github.com/KRTirtho/spotube/commit/62ad86e88d74b5114af78138b221314192e5a801))
* **player:** playback element placement ([5e47faa](https://github.com/KRTirtho/spotube/commit/5e47faa6060d7a8aa0d143060e812dc06b8dd790))
* **player:** queue button not showing when not logged in ([6c2d655](https://github.com/KRTirtho/spotube/commit/6c2d65587b0e6e167be1d0b086df103c7e72d4b2))
* **player:** volume slider, prefetching of media_kit and stuttering on sponsorblock skip ([1f32554](https://github.com/KRTirtho/spotube/commit/1f3255481f058c50968561db88172e56b58494f4))
* playlist generate slider shape ([2b35c04](https://github.com/KRTirtho/spotube/commit/2b35c044adb15a97a58692e7880694a251899732))
* pop sheet list not scrollable ([cca5625](https://github.com/KRTirtho/spotube/commit/cca5625df7e432da8581a4504306baad154deb48))
* re-enable add to queue and play next support, favorite button query exceptions ([e529c79](https://github.com/KRTirtho/spotube/commit/e529c79c4f0cd964b7d89e010d3fe51378ea7222))
* re-enable download manager ([ea45c4f](https://github.com/KRTirtho/spotube/commit/ea45c4f42ae89b8991e470e84a5290b3be3b0f36))
* remove unnecessary broadcast stream conversions ([bf04962](https://github.com/KRTirtho/spotube/commit/bf04962e90ea5345a2bc0d4793999f7db712cab2))
* remove useBreakpoints as it clogs up memory with unnecessary state updates ([e1c0f5c](https://github.com/KRTirtho/spotube/commit/e1c0f5cf1e4cece2c4aa235bfbf8511ad7b1fe59))
* replace download multiple pops and add translations ([4a21249](https://github.com/KRTirtho/spotube/commit/4a21249ee386426b0974451542a93e84f532fb3f))
* screen breakpoints and persist lyrics delay across screens ([df79638](https://github.com/KRTirtho/spotube/commit/df79638fb622a55aaa2b36c9a1425c2d9c4a8e52))
* sidebar task counter badge and bottom player play button progress color ([af278d8](https://github.com/KRTirtho/spotube/commit/af278d8feaa08c14528627766bce6d724b846954))
* status bar color of playlist/album page ([65fa3cb](https://github.com/KRTirtho/spotube/commit/65fa3cb624c240360de5a06778a1f72ad10bbe2d))
* system color scheme not persisting on restart when system color scheme changed ([e04515d](https://github.com/KRTirtho/spotube/commit/e04515d8e213b4c7f85d11385959a33b042bd9b1))
* track collection view status bar not transparent ([9251121](https://github.com/KRTirtho/spotube/commit/9251121ba0154599975e33819a43719477c644f8))
* track doesn't play after change ([17e5ab6](https://github.com/KRTirtho/spotube/commit/17e5ab611cc417cce7c17cafc7045f5aa2eb970e))
* track stops at last second ([f554f6d](https://github.com/KRTirtho/spotube/commit/f554f6d43bb714f662a27977f501d7ad44b070c3))
* **track_collection_view:** keyboard focus on scroll and no space for search results in playlist/album ([7a8bd92](https://github.com/KRTirtho/spotube/commit/7a8bd921047e3766dbbf24449e2873afe3dbecf8))
* track_table_view table headers ([d88d287](https://github.com/KRTirtho/spotube/commit/d88d287fc586ec33351de9f3b4359f189054868b))
* track_tile active and blacklist color, playbutton card action positioning ([3f5a1b9](https://github.com/KRTirtho/spotube/commit/3f5a1b9587efe9b7b2c69008345867933f79ec67))
* use id based source getters instead of index ([a074463](https://github.com/KRTirtho/spotube/commit/a0744630ba2dc713babdb8db6500f9dd0f1e6096))
### [2.7.1](https://github.com/KRTirtho/spotube/compare/v2.7.0...v2.7.1) (2023-04-10)

View File

@ -118,26 +118,26 @@ Enhancement suggestions are tracked as [GitHub issues](https://github.com/KRTirt
Do the following:
- Download the latest Flutter SDK (>=2.15.1) & enable desktop support
- Download the latest Flutter SDK (>=3.10.0) & enable desktop support
- Install Development dependencies in linux
- Debian/Ubuntu
- Debian (>=12/Bookworm)/Ubuntu
```bash
$ apt-get install libjsoncpp1 libjsoncpp-dev libsecret-1-0 libsecret-1-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
$ apt-get install mpv libmpv-dev libappindicator3-1 gir1.2-appindicator3-0.1 libappindicator3-dev libsecret-1-0 libjsoncpp1 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev
```
- Use `libjsoncpp25` instead of `libjsoncpp1` (for Ubuntu >= 22.04)
- Arch/Manjaro
```bash
yay -S libsecret jsoncpp gstreamer gst-libav gst-plugins-base gst-plugins-good
yay -S mpv libappindicator-gtk3 libsecret jsoncpp libnotify
```
- Fedora
```bash
dnf install libsecret libsecret-devel jsoncpp gstreamer1-devel gstreamer1-plugins-base-tools gstreamer1-doc gstreamer1-plugins-base-devel gstreamer1-plugins-good gstreamer1-plugins-good-extras
dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel
```
- Clone the Repo
- Create a `.env` in root of the project following the `.env.example` template
- Now run the following to bootstrap the project
```bash
flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs
flutter pub get && dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
```
- Finally run these following commands in the root of the project to start the Spotube Locally
```bash

View File

@ -1,6 +1,6 @@
BSD-4-Clause License
Copyright (c) 2022 Kingkor Roy Tirtho. All rights reserved.
Copyright (c) 2023 Kingkor Roy Tirtho. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

348
README.md
View File

@ -1,158 +1,214 @@
<p align="center"><img width="700" src="assets/spotube_banner.png" alt="Spotube Logo"></p>
<div align="center">
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">
<p align="center">
<a href="https://spotube.netlify.app/">spotube.netlify.app</a>
</p>
An open source, cross-platform Spotify client that doesn't require Premium nor uses Electron!<br />
<p align="center">
<a href="https://github.com/KRTirtho/spotube/actions/workflows/spotube-release-binary.yml">
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/KRTirtho/spotube/spotube-release-binary.yml?color=1585be&style=flat-square">
<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>
<a href="https://patreon.com/krtirtho"><img alt="Support me on Patron" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/patreon-singular_vector.svg"></a>
<a href="https://www.buymeacoffee.com/krtirtho"><img alt="Buy me a Coffee" height="56" src="https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/donate/buymeacoffee-singular_vector.svg"></a>
<a href="https://opencollective.com/spotube"><img src="https://opencollective.com/spotube/donate/button.png?color=blue" alt="Donate to our Open Collective" height="45"></a>
---
<img src="assets/spotube-screenshot.png" alt="Spotube Desktop" width="600"> <img src="assets/mobile-screenshots/player-view.jpg" alt="Spotube Mobile" height="340">
</div>
## 🌃 Features
- 🚫 No ads, thanks to the use of public & free Spotify and YT Music APIs¹
- ⬇️ Downloadable tracks
- 🖥️ 📱 Cross-platform support
- 🪶 Small size & less data usage
- 🕵️ Anonymous/guest login
- 🕒 Time synced lyrics
- ✋ No telemetry, diagnostics or user data collection
- 🚀 Native performance
- 📖 Open source/libre software
- 🔉 Playback control is done locally, not on the server
**¹** 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).
### ❌ Unsupported features
- 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts can **never be supported** because the audio tracks are _only_ available on Spotify and accessing them would require Spotify Premium.
- 🎧 **Spotify 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:
<table>
<tr>
<th>Platform</th>
<th>Package/Installation Method</th>
</tr>
<tr>
<td>Windows</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-windows-x86_64-setup.exe">
<img width="220" alt="Windows Download" src="https://get.todoist.help/hc/article_attachments/4403191721234/WindowsButton.svg">
</a>
<a href="https://github.com/KRTirtho/Spotube/releases">
<img alt="GitHub release" src="https://img.shields.io/github/v/release/KRTirtho/spotube?color=1585be&style=flat-square"/>
</tr>
<tr>
<td>MacOS</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-macos-universal.dmg">
<img width="220" alt="MacOS Download" src="https://reachify.io/wp-content/uploads/2018/09/mac-download-button-1.png">
</a>
<a href="LICENSE">
<img alt="License" src="https://img.shields.io/aur/license/spotube-bin?color=1585be&style=flat-square"/>
</td>
</tr>
<tr>
<td>Android</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-android-all-arch.apk">
<img width="220" alt="APK download" src="https://user-images.githubusercontent.com/114044633/223920025-83687de0-e463-4c5d-8122-e06e4bb7d40c.png">
</a>
<a href="https://github.com/KRTirtho">
<img alt="Maintainer" src="https://img.shields.io/badge/Maintainer-KRTirtho-1585be?style=flat-square"/>
<br/>
<a href="https://f-droid.org/packages/oss.krtirtho.spotube">
<img width="220" alt="Download from F-Droid" src="https://user-images.githubusercontent.com/61944859/174589876-bace24c0-b3fd-4c4a-bdb4-6fa82b5853ec.png">
</a>
<a href="https://opencollective.com/spotube">
<img alt="Open Collective backers and sponsors" src="https://img.shields.io/opencollective/all/spotube?color=1585be&style=flat-square"/>
</td>
</tr>
<tr>
<td>Flatpak</td>
<td>
<p><code>flatpak install com.github.KRTirtho.Spotube</code></p>
<a href="https://flathub.org/apps/details/com.github.KRTirtho.Spotube">
<img width="220" alt="Download on Flathub" src="https://flathub.org/assets/badges/flathub-badge-en.png">
</a>
<a href="https://discord.gg/uJ94vxB6vg">
<img alt="Discord" src="https://img.shields.io/discord/1012234096237350943?color=1585be&label=Discord%20Server&logoColor=1585be&style=flat-square">
</td>
</tr>
<tr>
<td>AppImage</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.AppImage">
<img width="220" alt="Download AppImage" src="https://user-images.githubusercontent.com/61944859/169455015-13385466-8901-48fe-ba90-b62d58b0be64.png">
</a>
</p>
Spotube is a [Flutter](https://flutter.dev) based lightweight Spotify client. It utilizes the power of Spotify & Youtube's public APIs to create a reliable, robust & resource-light user experience.
#### <p align="center">Desktop</p>
![Application Desktop Screenshot](assets/spotube-screenshot.jpg)
#### <p align="center">Mobile</p>
![Application Mobile Screenshot](assets/mobile-screenshots/mobile-combined.jpg)
<p align="center">
<a href="https://discord.gg/uJ94vxB6vg">
<img src="https://discord.com/api/guilds/1012234096237350943/widget.png?style=banner2">
<p><b>Note:</b> <a href="https://github.com/TheAssassin/AppImageLauncher">AppimageLauncher</a> is required!</p>
</td>
</tr>
<tr>
<td>Debian/Ubuntu</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.deb">
<img width="220" alt="Debian/Ubuntu Download" src="https://user-images.githubusercontent.com/61944859/169097994-e92aff78-fd75-4c93-b6e4-f072a4b5a7ed.png">
</a>
</p>
<p>Then run: <code>sudo apt install Spotube-linux-x86_64.deb</code></p>
</td>
</tr>
<tr>
<td>Arch/Manjaro</td>
<td>
<p>With pamac: <code>sudo pamac install spotube-bin</code></p>
<p>With yay: <code>yay -Sy spotube-bin</code></p>
</td>
</tr>
<tr>
<td>Fedora/OpenSuse</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.rpm">
<img width="220" alt="Fedora/OpenSuse Download" src="https://user-images.githubusercontent.com/61944859/223638350-5926b9da-04d6-4edd-931d-ad533e4ff058.png">
</a>
<p>For Fedora: <code>sudo dnf install ./Spotube-linux-x86_64.rpm</code></p>
<p>For OpenSuse: <code>sudo zypper in ./Spotube-linux-x86_64.rpm</code></p>
</td>
</tr>
<tr>
<td>Linux (tarball)</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest">
<img width="220" alt="Tarball Download" src="https://user-images.githubusercontent.com/61944859/169456985-e0ba1fd4-10e8-4cc0-ab94-337acc6e0295.png">
</a>
</td>
</tr>
<tr>
<td>Windows - <a href="https://chocolatey.org">Chocolatey</a></td>
<td>
<p><code>choco install spotube</code></p>
</td>
</tr>
<tr>
<td>Windows - <a href="https://scoop.sh">Scoop</a></td>
<td>
<p><code>scoop bucket add extras</code></p>
<p><code>scoop install spotube</code></p>
</td>
</tr>
<tr>
<td>Windows - <a href="https://github.com/microsoft/winget-cli">WinGet</a></td>
<td>
<p><code>winget install --id KRTirtho.Spotube</code></p>
</td>
</tr>
</table>
# Features
### 🔄 Nightly Builds
Following are the features that currently Spotube offers:
Grab the latest nightly builds of Spotube [from the GitHub Releases](https://github.com/KRTirtho/spotube/releases/tag/nightly).
- Open source/libre software
- Anonymous/guest login
- Cross platform support
- No telemetry, diagnostics or user data collection
- Lightweight & resource-friendly
- Native performance (Thanks to Flutter+Skia)
- Playback control is done locally instead of on the server
- Small size & less data usage
- No Spotify or YouTube ads since it uses all public & free APIs (It is still recommended to support the creators by watching/liking/subscribing to the artists' YouTube channels or liking their tracks on Spotify. Purchasing Spotify Premium is usually the best way to support their valuable creations.)
- Time synced lyrics
- Downloadable tracks
## 🕳️ Building from source
# Support development
<a href="https://github.com/KRTirtho/spotube/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/KRTirtho/spotube/spotube-release-binary.yml?+label=Build%20Status"></a>
<a href="https://patreon.com/krtirtho"><img src="https://user-images.githubusercontent.com/61944859/180249027-678b01b8-c336-451e-b147-6d84a5b9d0e7.png" width="250"/></a>
[!["Donate to out Collective"](https://opencollective.com/spotube/donate/button.png?color=blue)](https://opencollective.com/spotube)
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/krtirtho)
You can compile Spotube's source code by [following these instructions](CONTRIBUTION.md#your-first-code-contribution).
# Installation
## 👥 The Spotube team
I'm always releasing newer versions of binaries of the software every 2-3 months with minor changes & every 6-8 months with major changes. Grab them!
- [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer
- [Owen Connor](https://github.com/owencz1998) - The Cool Discord Moderator
- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer
- [RaptaG](https://github.com/RaptaG) - The GitHub Moderator and Community Manager
- [Rusty Apple](https://github.com/RustyApple) - The Mysterious Unknown Guy
| Platform | Package/Installation Method |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Android | [<img width='220' alt='Android Download' src='https://user-images.githubusercontent.com/114044633/223920025-83687de0-e463-4c5d-8122-e06e4bb7d40c.png'/>][android-dlink]<br/>[<img width='220' alt='Android Download' src='https://user-images.githubusercontent.com/61944859/174589876-bace24c0-b3fd-4c4a-bdb4-6fa82b5853ec.png'/>][fdroid-dlink] |
| Debian/Ubuntu | [<img width='220' alt='Linux Debian/Ubuntu Download' src='https://user-images.githubusercontent.com/61944859/169097994-e92aff78-fd75-4c93-b6e4-f072a4b5a7ed.png'/>][deb-dlink] <br/> Then run: `sudo apt install Spotube-linux-x86_64.deb` |
| Fedora/OpenSuse | [<img width='220' alt='Linux Fedora/OpenSuse Download' src='https://user-images.githubusercontent.com/61944859/223638350-5926b9da-04d6-4edd-931d-ad533e4ff058.png'/>][rpm-dlink] <br/> For Fedora: `sudo dnf install ./Spotube-linux-x86_64.rpm`<br/> For OpenSuse: `sudo zypper in ./Spotube-linux-x86_64.rpm` |
| Flatpak | `flatpak install com.github.KRTirtho.Spotube` <br/> <a href='https://flathub.org/apps/details/com.github.KRTirtho.Spotube'><img width='220' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a> |
| Arch/Manjaro | pamac: `pamac install spotube-bin` <br/> yay: `yay -Sy spotube-bin` |
| AppImage | [<img width='220' alt='AppImage Download' src='https://user-images.githubusercontent.com/61944859/169455015-13385466-8901-48fe-ba90-b62d58b0be64.png'/>][appimage-dlink]<br/> **Note**: AppImages require [appimage-launcher](https://github.com/TheAssassin/AppImageLauncher) to be installed |
| Linux (tarball) | [<img width='220' alt='Tarball Download' src='https://user-images.githubusercontent.com/61944859/169456985-e0ba1fd4-10e8-4cc0-ab94-337acc6e0295.png'/>][linux-dlink] |
| Windows | [<img width='220' alt='Windows Download' src='https://get.todoist.help/hc/article_attachments/4403191721234/WindowsButton.svg'/>][win32-dlink] |
| Windows (<a href="https://chocolatey.org">Chocolatey</a>) | `choco install spotube` |
| Windows (<a href="https://scoop.sh/">Scoop</a>) | `scoop bucket add extras` <br/> `scoop install spotube` |
| Windows (<a href="https://github.com/microsoft/winget-cli">WinGet</a>) | `winget install --id KRTirtho.Spotube` |
| MacOS | [<img width='220' alt='MacOS Download' src='https://reachify.io/wp-content/uploads/2018/09/mac-download-button-1.png'/>][mac-dlink] |
## 💼 License
> **Note!:** If you don't understand this download table. You can read [installation instructions][wiki-installation-instructions] from the wiki
Spotube is open source and licensed under the [BSD-4-Clause](/LICENSE) License.
## Nightly Builds
Get the latest nightly builds of Spotube [here](https://github.com/KRTirtho/spotube/releases/tag/nightly).
If you are concerned, feel free to [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
# TODO:
- [ ] Windows OS Media Control & Media Keys Support
- [ ] Spotify Listen Along
- [x] Skip non-music sections from Audio Track
- [ ] Language Translations/Localization
<details>
<summary>
<h2><code>[Click to show]</code> 🙏 Library/Plugin/Framework Credits</h2>
</summary>
# Building from source
You can find the details [here](CONTRIBUTION.md#your-first-code-contribution).
# Things that do not work
- Shows & Podcasts are not supported, as a premium subscription would be needed for that functionality.
# License
[BSD-4-Clause](/LICENSE)
But why? You can learn about it [here](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
# Financial contributors
## Backers
![Backers](https://opencollective.com/spotube/backer.svg?button=false)
![Donors](https://opencollective.com/spotube/tiers/donor.svg?button=false)
## Sponsors
<p align="center">
<img src="https://opencollective.com/spotube/tiers/sponsor.svg" alt="Sponsor Spotube" width="69%"> <!-- nice. -->
</p>
# Library/Plugin/Framework Credits
1. [Flutter](https://flutter.dev/) - Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase
1. [Spotify API](https://developer.spotify.com/documentation/web-api/) - The Spotify Web API is a RESTful API that provides access to Spotify data
1. [Linux](https://www.linux.org/) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
1. [AUR](https://aur.archlinux.org/) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users
1. [Flatpak](https://flatpak.org/) - Flatpak is a utility for software deployment and package management for Linux
1. [rentanadviser](https://www.rentanadviser.com/) - Generous Synced lyrics API provider service
1. [SponsorBlock](https://sponsor.ajay.app/) - SponsorBlock is an open-source crowdsourced browser extension and open API for skipping sponsor segments in YouTube videos.
1. [Flutter](https://flutter.dev) - Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase
1. [Spotify API](https://developer.spotify.com/documentation/web-api) - The Spotify Web API is a RESTful API that provides access to Spotify data
1. [Piped](https://piped-docs.kavin.rocks/) - Piped is a privacy friendly alternative YouTube frontend, which is efficient and scalable by design.
1. [YouTube](https://youtube.com/) - YouTube is an American online video-sharing platform headquartered in San Bruno, California. Three former PayPal employees—Chad Hurley, Steve Chen, and Jawed Karim—created the service in February 2005
1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users
1. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux
1. [SponsorBlock](https://sponsor.ajay.app) - SponsorBlock is an open-source crowdsourced browser extension and open API for skipping sponsor segments in YouTube videos.
1. [Inno Setup](https://jrsoftware.org/isinfo.php) - Inno Setup is a free installer for Windows programs by Jordan Russell and Martijn Laan
1. [F-Droid](https://f-droid.org/) - F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device
1. [adwaita](https://github.com/gtk-flutter/adwaita) - Adwaita style - The default theme for GTK+ for your Flutter app.
1. [F-Droid](https://f-droid.org) - F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device
1. [args](https://pub.dev/packages/args) - Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.
1. [async](https://pub.dev/packages/async) - Utility functions and classes related to the 'dart:async' library.
1. [audio_service](https://github.com/ryanheise/audio_service/tree/master/audio_service) - Flutter plugin to play audio in the background while the screen is off.
1. [audio_session](https://github.com/ryanheise/audio_session) - Sets the iOS audio session category and Android audio attributes for your app, and manages your app's audio focus, mixing and ducking behaviour.
1. [audioplayers](https://github.com/bluefireteam/audioplayers) - A Flutter plugin to play multiple audio files simultaneously
1. [auto_size_text](https://github.com/leisim/auto_size_text) - Flutter widget that automatically resizes text to fit perfectly within its bounds.
1. [badges](https://pub.dev/packages/badges) - A package for creating badges. Badges can be used for an additional marker for any widget, e.g. show a number of items in a shopping cart.
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. [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. [dbus](https://github.com/canonical/dbus.dart) - A native Dart implementation of the D-Bus message bus client. This package allows Dart applications to directly access services on the Linux desktop.
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.
1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
1. [file_picker](https://github.com/miguelpruivo/plugins_flutter_file_picker) - A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.
1. [fl_query](https://fl-query.vercel.app) - Asynchronous data caching, refetching & invalidation library for Flutter
1. [fl_query_hooks](https://fl-query.vercel.app) - Elite flutter_hooks compatible library for fl_query, the Asynchronous data caching, refetching & invalidation library for Flutter
1. [fluent_ui](https://github.com/bdlukaa/fluent_ui) - Implements Windows UI in Flutter. Based on the official documentation
1. [fl_query_connectivity_plus_adapter](https://fl-query.vercel.app) - Connectivity Plus adapter for FlQuery Connectivity
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/master/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_dotenv](https://github.com/java-james/flutter_dotenv) - Easily configure any flutter application with global variables using a `.env` file.
1. [flutter_feather_icons](https://github.com/muj-programmer/flutter_feather_icons) - Feather is a collection of simply beautiful open source icons. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and usability.
1. [flutter_hooks](https://github.com/rrousselGit/flutter_hooks) - A flutter implementation of React hooks. It adds a new kind of widget with enhanced code reuse.
1. [flutter_inappwebview](https://inappwebview.dev/) - A Flutter plugin that allows you to add an inline webview, to use an headless webview, and to open an in-app browser window.
1. [flutter_native_splash](https://pub.dev/packages/flutter_native_splash) - Customize Flutter's default white native splash screen with background color and splash image. Supports dark mode, full screen, and more.
1. [flutter_riverpod](https://riverpod.dev) - A simple way to access state from anywhere in your application while robust and testable.
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. [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
@ -161,13 +217,16 @@ But why? You can learn about it [here](https://dev.to/krtirtho/choosing-open-sou
1. [hooks_riverpod](https://riverpod.dev) - A simple way to access state from anywhere in your application while robust and testable.
1. [html](https://pub.dev/packages/html) - APIs for parsing and manipulating HTML content outside the browser.
1. [http](https://pub.dev/packages/http) - A composable, multi-platform, Future-based API for HTTP requests.
1. [intl](https://pub.dev/packages/intl) - Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
1. [introduction_screen](https://github.com/pyozer/introduction_screen) - Introduction/Onboarding package for flutter app with some customizations possibilities
1. [json_annotation](https://pub.dev/packages/json_annotation) - Classes and helper functions that support JSON code generation via the `json_serializable` package.
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
1. [libadwaita](https://github.com/gtk-flutter/libadwaita) - Libadwaita's widgets for Flutter. Following Gnome HIG and available on all platforms.
1. [logger](https://pub.dev/packages/logger) - Small, easy to use and extensible logger which prints beautiful logs.
1. [macos_ui](https://macosui.dev) - Flutter widgets and themes implementing the current macOS design language.
1. [marquee](https://pub.dev/packages/marquee) - A Flutter widget that scrolls text infinitely. Provides many customizations including custom scroll directions, durations, curves as well as pauses after every round.
1. [media_kit](https://github.com/alexmercerind/media_kit) - A complete video & audio playback library for Flutter & Dart. Performant, stable, feature-proof & modular.
1. [media_kit_libs_android_audio](https://github.com/alexmercerind/media_kit.git) - Android package providing audio (only) native libraries for package:media_kit.
1. [media_kit_libs_ios_audio](https://github.com/alexmercerind/media_kit.git) - iOS package providing audio native libraries for package:media_kit.
1. [media_kit_libs_linux](https://github.com/alexmercerind/media_kit.git) - GNU/Linux dependency package for package:media_kit. Necessary for initialization.
1. [media_kit_libs_macos_audio](https://github.com/alexmercerind/media_kit.git) - macOS package providing audio native libraries for package:media_kit.
1. [media_kit_libs_windows_audio](https://github.com/alexmercerind/media_kit.git) - Windows package providing audio (only) native libraries for package:media_kit.
1. [metadata_god](https://github.com/KRTirtho/metadata_god) - Plugin for retrieving and writing audio tags/metadata from audio files
1. [mime](https://pub.dev/packages/mime) - Utilities for handling media (MIME) types, including determining a type from a file extension and file contents.
1. [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.
@ -175,54 +234,43 @@ But why? You can learn about it [here](https://dev.to/krtirtho/choosing-open-sou
1. [path](https://pub.dev/packages/path) - A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.
1. [path_provider](https://pub.dev/packages/path_provider) - Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.
1. [permission_handler](https://pub.dev/packages/permission_handler) - Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
1. [pocketbase](https://pub.dev/packages/pocketbase) - Multi-platform Dart SDK for PocketBase, an open source realtime backend in 1 file.
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. [queue](https://github.com/rknell/dart_queue) - Queue up futures from multiple sources and await their return anywhere in your code.
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. [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. [tuple](https://pub.dev/packages/tuple) - A library providing a tuple data structure.
1. [smtc_windows](https://github.com/KRTirtho/smtc_windows) - Windows `SystemMediaTransportControls` implementation for Flutter giving access to Windows OS Media Control applet.
1. [spotify](https://github.com/rinukkusu/spotify-dart) - An incomplete dart library for interfacing with the Spotify Web API.
1. [system_theme](https://pub.dev/packages/system_theme) - A plugin to get the current system theme info. Supports Android, Web, Windows, Linux and macOS
1. [titlebar_buttons](https://github.com/gtk-flutter/titlebar_buttons) - A package which provides most of the titlebar buttons from windows, linux and macos.
1. [url_launcher](https://pub.dev/packages/url_launcher) - Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.
1. [uuid](https://github.com/Daegalus/dart-uuid) - RFC4122 (v1, v4, v5) UUID Generator and Parser for all Dart platforms (Web, VM, Flutter)
1. [version](https://github.com/dartninja/version) - Provides a simple class for parsing and comparing semantic versions as defined by http://semver.org/
1. [visibility_detector](https://pub.dev/packages/visibility_detector) - A widget that detects the visibility of its child and notifies a callback.
1. [window_manager](https://github.com/leanflutter/window_manager) - This plugin allows Flutter desktop apps to resizing and repositioning the window.
1. [piped_client](https://github.com/KRTirtho/piped_client) - API Client for piped.video
1. [supabase_flutter](https://supabase.com) - Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products.
1. [device_preview](https://github.com/aloisdeniel/flutter_device_preview) - Approximate how your Flutter app looks and performs on another device.
1. [media_kit_native_event_loop](https://github.com/alexmercerind/media_kit) - Platform specific threaded event handling for media_kit. Enables support for higher number of concurrent instances.
1. [dbus](https://github.com/canonical/dbus.dart) - A native Dart implementation of the D-Bus message bus client. This package allows Dart applications to directly access services on the Linux desktop.
1. [background_downloader](https://pub.dev/packages/background_downloader) - A multi-platform background file downloader and uploader. Define the task, enqueue and monitor progress
1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration.
1. [disable_battery_optimization](https://github.com/pvsvamsi/Disable-Battery-Optimizations) - Flutter plugin to check and disable battery optimizations. Also shows custom steps to disable the optimizations in devices like mi, xiaomi, samsung, oppo, huawei, oneplus etc
1. [youtube_explode_dart](https://github.com/Hexer10/youtube_explode_dart) - A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
1. [flutter_distributor](https://github.com/leanflutter/flutter_distributor) - A complete tool for packaging and publishing your Flutter apps.
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
1. [flutter_distributor](https://distributor.leanflutter.org) - A complete tool for packaging and publishing your Flutter apps.
1. [flutter_gen_runner](https://github.com/FlutterGen/flutter_gen) - The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
1. [flutter_launcher_icons](https://github.com/fluttercommunity/flutter_launcher_icons) - A package which simplifies the task of updating your Flutter app's launcher icon.
1. [flutter_lints](https://pub.dev/packages/flutter_lints) - Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.
1. [hive_generator](https://github.com/hivedb/hive/tree/master/hive_generator) - Extension for Hive. Automatically generates TypeAdapters to store any class.
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
1. [pub_api_client](https://github.com/leoafarias/pub_api_client) - An API Client for Pub to interact with public package information.
1. [pubspec_parse](https://pub.dev/packages/pubspec_parse) - Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.
1. [catcher](https://github.com/jhomlala/catcher) - Plugin for error catching which provides multiple handlers for dealing with errors when they are not caught by the developer.
1. [platform_ui](https://github.com/KRTirtho/platform_ui) - Platform specific Widgets and UI toolkit
1. [spotify](https://github.com/rinukkusu/spotify-dart) - An incomplete dart library for interfacing with the Spotify Web API.
1. [flutter_desktop_tools](https://github.com/KRTirtho/flutter_desktop_tools) - Essential collection of tools for flutter desktop app development
1. [window_size](https://github.com/google/flutter-desktop-embedding.git) - Allows resizing and repositioning the window containing Flutter.
# The team
</details>
- [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer
- [Owen Conor](https://github.com/owencz1998) - The Cool Discord Moderator
- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer
- [RaptaG](https://github.com/raptag) - The GitHub Moderator and Community Manager
- [Rusty Apple](https://github.com/RustyApple) - The Mysterious Unknown Guy
# Social platforms
Follow me on [Twitter](https://twitter.com/@krtirtho), or join our amazing [Discord](https://discord.gg/uJ94vxB6vg) server to always get newer updates about the application.
<b><p align="center">&copy; 2023 Spotube</p></b>
<!-- Variables/Text References -->
[win32-dlink]: https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-windows-x86_64-setup.exe
[deb-dlink]: https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.deb
[rpm-dlink]: https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.rpm
[linux-dlink]: https://github.com/KRTirtho/spotube/releases/latest
[appimage-dlink]: https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.AppImage
[mac-dlink]: https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-macos-x86_64.dmg
[android-dlink]: https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-android-all-arch.apk
[fdroid-dlink]: https://f-droid.org/packages/oss.krtirtho.spotube/
[wiki-installation-instructions]: https://github.com/KRTirtho/spotube/wiki/Installation-Instrcutions
<div align="center"><h4>© Copyright Spotube 2023</h4></div>

View File

@ -28,3 +28,7 @@ linter:
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
analyzer:
enable-experiment:
- records
- patterns

View File

@ -34,6 +34,8 @@ if (keystorePropertiesFile.exists()) {
android {
compileSdkVersion 33
ndkVersion "21.4.7075529"
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -50,7 +52,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "oss.krtirtho.spotube"
minSdkVersion 19
minSdkVersion 24
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@ -77,6 +79,17 @@ flutter {
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
constraints {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version") {
because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") {
because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
}
}
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
// other deps so just ignore
implementation 'com.android.support:multidex:2.0.1'
}

View File

@ -49,17 +49,21 @@
</activity>
<service android:name="com.ryanheise.audioservice.AudioService" android:exported="false">
<!-- AudioService Config -->
<service android:name="com.ryanheise.audioservice.AudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver" android:exported="false">
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<!-- =================== -->
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
<item>
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
<item>
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
</item>
</layer-list>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#000000</item>
<item name="android:windowSplashScreenBrandingImage">@drawable/android12branding</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#000000</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#000000</item>
<item name="android:windowSplashScreenBrandingImage">@drawable/android12branding</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#000000</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.7.21'
ext.kotlin_version = '1.8.22'
repositories {
google()
mavenCentral()
@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

BIN
assets/branding.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 711 KiB

After

Width:  |  Height:  |  Size: 410 KiB

BIN
assets/spotube-logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

View File

@ -5,10 +5,11 @@ pkgbase = spotube-bin
url = https://github.com/KRTirtho/spotube/
arch = x86_64
license = BSD-4-Clause
depends = gstreamer
depends = gst-libav
depends = gst-plugins-base
depends = gst-plugins-good
depends = mpv
depends = libappindicator-gtk3
depends = libsecret
depends = jsoncpp
depends = libnotify
source = https://github.com/KRTirtho/spotube/releases/download/v2.3.0/Spotube-linux-x86_64.tar.xz
md5sums = 8cd6a7385c5c75d203dccd762f1d63ec

View File

@ -8,7 +8,7 @@ arch=(x86_64)
url="https://github.com/KRTirtho/spotube/"
license=('BSD-4-Clause')
groups=()
depends=('gstreamer' 'gst-libav' 'gst-plugins-base' 'gst-plugins-good')
depends=('mpv' 'libappindicator-gtk3' 'libsecret' 'jsoncpp' 'libnotify')
makedepends=()
checkdepends=()
optdepends=()

View File

@ -0,0 +1,43 @@
import 'dart:convert';
import 'dart:io';
/// Generate JSON output for untranslated messages with English values
/// for quick translation in ChatGPT
///
/// Usage: dart bin/untranslated_messages.dart [locale?]
///
/// Example: dart bin/untranslated_messages.dart
///
/// or with specific locale (e.g. bn (Bengali))
///
/// Example: dart bin/untranslated_messages.dart bn
void main(List<String> args) {
final file = jsonDecode(
File('untranslated_messages.json').readAsStringSync(),
) as Map<String, dynamic>;
final englishMessages =
jsonDecode(File('lib/l10n/app_en.arb').readAsStringSync())
as Map<String, dynamic>;
final messagesWithValues = <String, dynamic>{};
for (final MapEntry(key: locale, value: messages) in file.entries) {
messagesWithValues[locale] = Map.fromEntries(
messages
.map(
(message) =>
MapEntry<String, dynamic>(message, englishMessages[message]),
)
.toList()
.cast<MapEntry<String, dynamic>>(),
);
}
print(
const JsonEncoder.withIndent(' ').convert(
args.isNotEmpty ? messagesWithValues[args.first] : messagesWithValues,
),
);
}

5
build.yaml Normal file
View File

@ -0,0 +1,5 @@
targets:
$default:
sources:
exclude:
- bin/*.dart

View File

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:spotube/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('check if app is successfully starting', (tester) async {
await app.main([]);
await tester.pumpAndSettle();
expect(find.byType(MaterialApp), findsOneWidget);
});
});
}

View File

@ -37,6 +37,7 @@ end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
# Just Audio Config
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "BrandingImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "BrandingImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "BrandingImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -16,13 +16,22 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="BrandingImage" translatesAutoresizingMaskIntoConstraints="NO" id="Uyq-Kz-ftE"/>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
<constraint firstAttribute="bottom" secondItem="Uyq-Kz-ftE" secondAttribute="bottom" id="8Yb-q4-8bl"/>
<constraint firstItem="Uyq-Kz-ftE" firstAttribute="centerX" secondItem="YRO-k0-Ey4" secondAttribute="centerX" id="3kg-TC-cPP"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints>
</view>
</viewController>
@ -32,6 +41,8 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="509" height="509"/>
<image name="LaunchBackground" width="1" height="1"/>
<image name="BrandingImage" width="1" height="1"/>
</resources>
</document>

View File

@ -52,5 +52,7 @@
</dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIStatusBarHidden</key>
<false/>
</dict>
</plist>

4
l10n.yaml Normal file
View File

@ -0,0 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
untranslated-messages-file: untranslated_messages.json

View File

@ -30,16 +30,22 @@ class Assets {
static const AssetGenImage albumPlaceholder =
AssetGenImage('assets/album-placeholder.png');
static const AssetGenImage bengaliPatternsBg =
AssetGenImage('assets/bengali-patterns-bg.jpg');
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
static const AssetGenImage placeholder =
AssetGenImage('assets/placeholder.png');
static const AssetGenImage spotubeLogoForeground =
AssetGenImage('assets/spotube-logo-foreground.jpg');
static const String spotubeLogoIco = 'assets/spotube-logo.ico';
static const AssetGenImage spotubeLogoPng =
AssetGenImage('assets/spotube-logo.png');
static const String spotubeLogoSvg = 'assets/spotube-logo.svg';
static const AssetGenImage spotubeLogoAndroid12 =
AssetGenImage('assets/spotube-logo_android12.png');
static const AssetGenImage spotubeScreenshot =
AssetGenImage('assets/spotube-screenshot.jpg');
AssetGenImage('assets/spotube-screenshot.png');
static const AssetGenImage spotubeBanner =
AssetGenImage('assets/spotube_banner.png');
static const AssetGenImage success = AssetGenImage('assets/success.png');
@ -50,11 +56,15 @@ class Assets {
/// List of all assets
List<dynamic> get values => [
albumPlaceholder,
bengaliPatternsBg,
branding,
emptyBox,
placeholder,
spotubeLogoForeground,
spotubeLogoIco,
spotubeLogoPng,
spotubeLogoSvg,
spotubeLogoAndroid12,
spotubeScreenshot,
spotubeBanner,
success,

View File

@ -4,14 +4,11 @@ part 'env.g.dart';
@Envied(obfuscate: true, requireEnvFile: true, path: ".env")
abstract class Env {
@EnviedField(varName: 'POCKETBASE_URL', defaultValue: 'http://127.0.0.1:8090')
static final pocketbaseUrl = _Env.pocketbaseUrl;
@EnviedField(varName: 'SUPABASE_URL')
static final supabaseUrl = _Env.supabaseUrl;
@EnviedField(varName: 'USERNAME', defaultValue: 'root')
static final username = _Env.username;
@EnviedField(varName: 'PASSWORD', defaultValue: '12345678')
static final password = _Env.password;
@EnviedField(varName: 'SUPABASE_API_KEY')
static final supabaseAnonKey = _Env.supabaseAnonKey;
@EnviedField(varName: 'SPOTIFY_SECRETS')
static final spotifySecrets = _Env.spotifySecrets.split(',').map((e) {
@ -21,4 +18,9 @@ abstract class Env {
"clientSecret": secrets.last,
};
}).toList();
@EnviedField(varName: 'ENABLE_UPDATE_CHECK', defaultValue: "1")
static final _enableUpdateChecker = _Env._enableUpdateChecker;
static bool get enableUpdateChecker => _enableUpdateChecker == "1";
}

View File

@ -5,8 +5,8 @@ import 'package:go_router/go_router.dart';
import 'package:spotube/components/player/player_controls.dart';
import 'package:spotube/collections/routes.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/provider/playlist_queue_provider.dart';
import 'package:spotube/services/audio_player.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/utils/platform.dart';
import 'package:window_manager/window_manager.dart';
@ -23,14 +23,11 @@ class PlayPauseAction extends Action<PlayPauseIntent> {
if (PlayerControls.focusNode.canRequestFocus) {
PlayerControls.focusNode.requestFocus();
}
final playlist = intent.ref.read(PlaylistQueueNotifier.provider);
final playlistNotifier = intent.ref.read(PlaylistQueueNotifier.notifier);
if (playlist == null) {
return null;
} else if (!PlaylistQueueNotifier.isPlaying) {
await playlistNotifier.play();
if (!audioPlayer.isPlaying) {
await audioPlayer.resume();
} else {
await playlistNotifier.pause();
await audioPlayer.pause();
}
return null;
}
@ -93,9 +90,8 @@ class SeekIntent extends Intent {
class SeekAction extends Action<SeekIntent> {
@override
invoke(intent) async {
final playlist = intent.ref.read(PlaylistQueueNotifier.provider);
final playlistNotifier = intent.ref.read(PlaylistQueueNotifier.notifier);
if (playlist == null || playlist.isLoading) {
final playlist = intent.ref.read(ProxyPlaylistNotifier.provider);
if (playlist.isFetching) {
DirectionalFocusAction().invoke(
DirectionalFocusIntent(
intent.forward ? TraversalDirection.right : TraversalDirection.left,
@ -103,9 +99,8 @@ class SeekAction extends Action<SeekIntent> {
);
return null;
}
final position =
(await audioPlayer.getCurrentPosition() ?? Duration.zero).inSeconds;
await playlistNotifier.seek(
final position = (await audioPlayer.position ?? Duration.zero).inSeconds;
await audioPlayer.seek(
Duration(
seconds: intent.forward ? position + 5 : position - 5,
),

View File

@ -0,0 +1,752 @@
class ISOLanguageName {
final String name;
final String nativeName;
const ISOLanguageName({
required this.name,
required this.nativeName,
});
}
// Uncomment the languages as we add support for them
// Currently supported: bn,en,fr,hi
abstract class LanguageLocals {
static final Map isoLangs = {
// "ab": const ISOLanguageName(
// name: "Abkhaz",
// nativeName: "аҧсуа",
// ),
// "aa": const ISOLanguageName(
// name: "Afar",
// nativeName: "Afaraf",
// ),
// "af": const ISOLanguageName(
// name: "Afrikaans",
// nativeName: "Afrikaans",
// ),
// "ak": const ISOLanguageName(
// name: "Akan",
// nativeName: "Akan",
// ),
// "sq": const ISOLanguageName(
// name: "Albanian",
// nativeName: "Shqip",
// ),
// "am": const ISOLanguageName(
// name: "Amharic",
// nativeName: "አማርኛ",
// ),
// "ar": const ISOLanguageName(
// name: "Arabic",
// nativeName: "العربية",
// ),
// "an": const ISOLanguageName(
// name: "Aragonese",
// nativeName: "Aragonés",
// ),
// "hy": const ISOLanguageName(
// name: "Armenian",
// nativeName: "Հայերեն",
// ),
// "as": const ISOLanguageName(
// name: "Assamese",
// nativeName: "অসমীয়া",
// ),
// "av": const ISOLanguageName(
// name: "Avaric",
// nativeName: "авар мацӀ, магӀарул мацӀ",
// ),
// "ae": const ISOLanguageName(
// name: "Avestan",
// nativeName: "avesta",
// ),
// "ay": const ISOLanguageName(
// name: "Aymara",
// nativeName: "aymar aru",
// ),
// "az": const ISOLanguageName(
// name: "Azerbaijani",
// nativeName: "azərbaycan dili",
// ),
// "bm": const ISOLanguageName(
// name: "Bambara",
// nativeName: "bamanankan",
// ),
// "ba": const ISOLanguageName(
// name: "Bashkir",
// nativeName: "башҡорт теле",
// ),
// "eu": const ISOLanguageName(
// name: "Basque",
// nativeName: "euskara,",
// ),
// "be": const ISOLanguageName(
// name: "Belarusian",
// nativeName: "Беларуская",
// ),
"bn": const ISOLanguageName(
name: "Bengali",
nativeName: "বাংলা",
),
// "bh": const ISOLanguageName(
// name: "Bihari",
// nativeName: "भोजपुरी",
// ),
// "bi": const ISOLanguageName(
// name: "Bislama",
// nativeName: "Bislama",
// ),
// "bs": const ISOLanguageName(
// name: "Bosnian",
// nativeName: "bosanski jezik",
// ),
// "br": const ISOLanguageName(
// name: "Breton",
// nativeName: "brezhoneg",
// ),
// "bg": const ISOLanguageName(
// name: "Bulgarian",
// nativeName: "български език",
// ),
// "my": const ISOLanguageName(
// name: "Burmese",
// nativeName: "ဗမာစာ",
// ),
// "ca": const ISOLanguageName(
// name: "Catalan; Valencian",
// nativeName: "Català",
// ),
// "ch": const ISOLanguageName(
// name: "Chamorro",
// nativeName: "Chamoru",
// ),
// "ce": const ISOLanguageName(
// name: "Chechen",
// nativeName: "нохчийн мотт",
// ),
// "ny": const ISOLanguageName(
// name: "Chichewa",
// nativeName: "chiCheŵa",
// ),
// "zh": const ISOLanguageName(
// name: "Chinese",
// nativeName: "汉语",
// ),
// "cv": const ISOLanguageName(
// name: "Chuvash",
// nativeName: "чӑваш чӗлхи",
// ),
// "kw": const ISOLanguageName(
// name: "Cornish",
// nativeName: "Kernewek",
// ),
// "co": const ISOLanguageName(
// name: "Corsican",
// nativeName: "lingua corsa",
// ),
// "cr": const ISOLanguageName(
// name: "Cree",
// nativeName: "ᓀᐦᐃᔭᐍᐏᐣ",
// ),
// "hr": const ISOLanguageName(
// name: "Croatian",
// nativeName: "hrvatski",
// ),
// "cs": const ISOLanguageName(
// name: "Czech",
// nativeName: "česky, čeština",
// ),
// "da": const ISOLanguageName(
// name: "Danish",
// nativeName: "dansk",
// ),
// "dv": const ISOLanguageName(
// name: "Maldivian;",
// nativeName: "ދިވެހި",
// ),
// "nl": const ISOLanguageName(
// name: "Dutch",
// nativeName: "Vlaams",
// ),
"en": const ISOLanguageName(
name: "English",
nativeName: "English",
),
// "eo": const ISOLanguageName(
// name: "Esperanto",
// nativeName: "Esperanto",
// ),
// "et": const ISOLanguageName(
// name: "Estonian",
// nativeName: "eesti",
// ),
// "ee": const ISOLanguageName(
// name: "Ewe",
// nativeName: "Eʋegbe",
// ),
// "fo": const ISOLanguageName(
// name: "Faroese",
// nativeName: "føroyskt",
// ),
// "fj": const ISOLanguageName(
// name: "Fijian",
// nativeName: "vosa Vakaviti",
// ),
// "fi": const ISOLanguageName(
// name: "Finnish",
// nativeName: "suomi",
// ),
"fr": const ISOLanguageName(
name: "French",
nativeName: "français",
),
// "ff": const ISOLanguageName(
// name: "Fula; Fulah; Pulaar; Pular",
// nativeName: "Fulfulde, Pulaar, Pular",
// ),
// "gl": const ISOLanguageName(
// name: "Galician",
// nativeName: "Galego",
// ),
// "ka": const ISOLanguageName(
// name: "Georgian",
// nativeName: "ქართული",
// ),
"de": const ISOLanguageName(
name: "German",
nativeName: "Deutsch",
),
// "el": const ISOLanguageName(
// name: "Greek, Modern",
// nativeName: "Ελληνικά",
// ),
// "gn": const ISOLanguageName(
// name: "Guaraní",
// nativeName: "Avañeẽ",
// ),
// "gu": const ISOLanguageName(
// name: "Gujarati",
// nativeName: "ગુજરાતી",
// ),
// "ht": const ISOLanguageName(
// name: "Haitian; Haitian Creole",
// nativeName: "Kreyòl ayisyen",
// ),
// "ha": const ISOLanguageName(
// name: "Hausa",
// nativeName: "Hausa, هَوُسَ",
// ),
// "he": const ISOLanguageName(
// name: "Hebrew (modern)",
// nativeName: "עברית",
// ),
// "hz": const ISOLanguageName(
// name: "Herero",
// nativeName: "Otjiherero",
// ),
"hi": const ISOLanguageName(
name: "Hindi",
nativeName: "हिन्दी, हिंदी",
),
// "ho": const ISOLanguageName(
// name: "Hiri Motu",
// nativeName: "Hiri Motu",
// ),
// "hu": const ISOLanguageName(
// name: "Hungarian",
// nativeName: "Magyar",
// ),
// "ia": const ISOLanguageName(
// name: "Interlingua",
// nativeName: "Interlingua",
// ),
// "id": const ISOLanguageName(
// name: "Indonesian",
// nativeName: "Bahasa Indonesia",
// ),
// "ie": const ISOLanguageName(
// name: "Interlingue",
// nativeName: "Occidental",
// ),
// "ga": const ISOLanguageName(
// name: "Irish",
// nativeName: "Gaeilge",
// ),
// "ig": const ISOLanguageName(
// name: "Igbo",
// nativeName: "Asụsụ Igbo",
// ),
// "ik": const ISOLanguageName(
// name: "Inupiaq",
// nativeName: "Iñupiaq, Iñupiatun",
// ),
// "io": const ISOLanguageName(
// name: "Ido",
// nativeName: "Ido",
// ),
// "is": const ISOLanguageName(
// name: "Icelandic",
// nativeName: "Íslenska",
// ),
// "it": const ISOLanguageName(
// name: "Italian",
// nativeName: "Italiano",
// ),
// "iu": const ISOLanguageName(
// name: "Inuktitut",
// nativeName: "ᐃᓄᒃᑎᑐᑦ",
// ),
"ja": const ISOLanguageName(
name: "Japanese",
nativeName: "日本語",
),
// "jv": const ISOLanguageName(
// name: "Javanese",
// nativeName: "basa Jawa",
// ),
// "kl": const ISOLanguageName(
// name: "Kalaallisut, Greenlandic",
// nativeName: "kalaallisut, kalaallit oqaasii",
// ),
// "kn": const ISOLanguageName(
// name: "Kannada",
// nativeName: "ಕನ್ನಡ",
// ),
// "kr": const ISOLanguageName(
// name: "Kanuri",
// nativeName: "Kanuri",
// ),
// "ks": const ISOLanguageName(
// name: "Kashmiri",
// nativeName: "कश्मीरी, كشميري‎",
// ),
// "kk": const ISOLanguageName(
// name: "Kazakh",
// nativeName: "Қазақ тілі",
// ),
// "km": const ISOLanguageName(
// name: "Khmer",
// nativeName: "ភាសាខ្មែរ",
// ),
// "ki": const ISOLanguageName(
// name: "Kikuyu, Gikuyu",
// nativeName: "Gĩkũyũ",
// ),
// "rw": const ISOLanguageName(
// name: "Kinyarwanda",
// nativeName: "Ikinyarwanda",
// ),
// "ky": const ISOLanguageName(
// name: "Kirghiz, Kyrgyz",
// nativeName: "кыргыз тили",
// ),
// "kv": const ISOLanguageName(
// name: "Komi",
// nativeName: "коми кыв",
// ),
// "kg": const ISOLanguageName(
// name: "Kongo",
// nativeName: "KiKongo",
// ),
// "ko": const ISOLanguageName(
// name: "Korean",
// nativeName: "한국어 (韓國語), 조선말 (朝鮮語)",
// ),
// "ku": const ISOLanguageName(
// name: "Kurdish",
// nativeName: "Kurdî, كوردی‎",
// ),
// "kj": const ISOLanguageName(
// name: "Kwanyama, Kuanyama",
// nativeName: "Kuanyama",
// ),
// "la": const ISOLanguageName(
// name: "Latin",
// nativeName: "latine, lingua latina",
// ),
// "lb": const ISOLanguageName(
// name: "Luxembourgish, Letzeburgesch",
// nativeName: "Lëtzebuergesch",
// ),
// "lg": const ISOLanguageName(
// name: "Luganda",
// nativeName: "Luganda",
// ),
// "li": const ISOLanguageName(
// name: "Limburgish, Limburgan, Limburger",
// nativeName: "Limburgs",
// ),
// "ln": const ISOLanguageName(
// name: "Lingala",
// nativeName: "Lingála",
// ),
// "lo": const ISOLanguageName(
// name: "Lao",
// nativeName: "ພາສາລາວ",
// ),
// "lt": const ISOLanguageName(
// name: "Lithuanian",
// nativeName: "lietuvių kalba",
// ),
// "lu": const ISOLanguageName(
// name: "Luba-Katanga",
// nativeName: "",
// ),
// "lv": const ISOLanguageName(
// name: "Latvian",
// nativeName: "latviešu valoda",
// ),
// "gv": const ISOLanguageName(
// name: "Manx",
// nativeName: "Gaelg, Gailck",
// ),
// "mk": const ISOLanguageName(
// name: "Macedonian",
// nativeName: "македонски јазик",
// ),
// "mg": const ISOLanguageName(
// name: "Malagasy",
// nativeName: "Malagasy fiteny",
// ),
// "ms": const ISOLanguageName(
// name: "Malay",
// nativeName: "bahasa Melayu, بهاس ملايو‎",
// ),
// "ml": const ISOLanguageName(
// name: "Malayalam",
// nativeName: "മലയാളം",
// ),
// "mt": const ISOLanguageName(
// name: "Maltese",
// nativeName: "Malti",
// ),
// "mi": const ISOLanguageName(
// name: "Māori",
// nativeName: "te reo Māori",
// ),
// "mr": const ISOLanguageName(
// name: "Marathi (Marāṭhī)",
// nativeName: "मराठी",
// ),
// "mh": const ISOLanguageName(
// name: "Marshallese",
// nativeName: "Kajin M̧ajeļ",
// ),
// "mn": const ISOLanguageName(
// name: "Mongolian",
// nativeName: "монгол",
// ),
// "na": const ISOLanguageName(
// name: "Nauru",
// nativeName: "Ekakairũ Naoero",
// ),
// "nv": const ISOLanguageName(
// name: "Navajo, Navaho",
// nativeName: "Diné bizaad, Dinékʼehǰí",
// ),
// "nb": const ISOLanguageName(
// name: "Norwegian Bokmål",
// nativeName: "Norsk bokmål",
// ),
// "nd": const ISOLanguageName(
// name: "North Ndebele",
// nativeName: "isiNdebele",
// ),
// "ne": const ISOLanguageName(
// name: "Nepali",
// nativeName: "नेपाली",
// ),
// "ng": const ISOLanguageName(
// name: "Ndonga",
// nativeName: "Owambo",
// ),
// "nn": const ISOLanguageName(
// name: "Norwegian Nynorsk",
// nativeName: "Norsk nynorsk",
// ),
// "no": const ISOLanguageName(
// name: "Norwegian",
// nativeName: "Norsk",
// ),
// "ii": const ISOLanguageName(
// name: "Nuosu",
// nativeName: "ꆈꌠ꒿ Nuosuhxop",
// ),
// "nr": const ISOLanguageName(
// name: "South Ndebele",
// nativeName: "isiNdebele",
// ),
// "oc": const ISOLanguageName(
// name: "Occitan",
// nativeName: "Occitan",
// ),
// "oj": const ISOLanguageName(
// name: "Ojibwe, Ojibwa",
// nativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ",
// ),
// "cu": const ISOLanguageName(
// name: "Old Church Slavonic",
// nativeName: "ѩзыкъ словѣньскъ",
// ),
// "om": const ISOLanguageName(
// name: "Oromo",
// nativeName: "Afaan Oromoo",
// ),
// "or": const ISOLanguageName(
// name: "Oriya",
// nativeName: "ଓଡ଼ିଆ",
// ),
// "os": const ISOLanguageName(
// name: "Ossetian, Ossetic",
// nativeName: "ирон æвзаг",
// ),
// "pa": const ISOLanguageName(
// name: "Panjabi, Punjabi",
// nativeName: "ਪੰਜਾਬੀ, پنجابی‎",
// ),
// "pi": const ISOLanguageName(
// name: "Pāli",
// nativeName: "पाऴि",
// ),
// "fa": const ISOLanguageName(
// name: "Persian",
// nativeName: "فارسی",
// ),
// "pl": const ISOLanguageName(
// name: "Polish",
// nativeName: "polski",
// ),
// "ps": const ISOLanguageName(
// name: "Pashto, Pushto",
// nativeName: "پښتو",
// ),
// "pt": const ISOLanguageName(
// name: "Portuguese",
// nativeName: "Português",
// ),
// "qu": const ISOLanguageName(
// name: "Quechua",
// nativeName: "Runa Simi, Kichwa",
// ),
// "rm": const ISOLanguageName(
// name: "Romansh",
// nativeName: "rumantsch grischun",
// ),
// "rn": const ISOLanguageName(
// name: "Kirundi",
// nativeName: "kiRundi",
// ),
// "ro": const ISOLanguageName(
// name: "Romanian, Moldavian, Moldovan",
// nativeName: "română",
// ),
// "ru": const ISOLanguageName(
// name: "Russian",
// nativeName: "русский язык",
// ),
// "sa": const ISOLanguageName(
// name: "Sanskrit (Saṁskṛta)",
// nativeName: "संस्कृतम्",
// ),
// "sc": const ISOLanguageName(
// name: "Sardinian",
// nativeName: "sardu",
// ),
// "sd": const ISOLanguageName(
// name: "Sindhi",
// nativeName: "सिन्धी, سنڌي، سندھی‎",
// ),
// "se": const ISOLanguageName(
// name: "Northern Sami",
// nativeName: "Davvisámegiella",
// ),
// "sm": const ISOLanguageName(
// name: "Samoan",
// nativeName: "gagana faa Samoa",
// ),
// "sg": const ISOLanguageName(
// name: "Sango",
// nativeName: "yângâ tî sängö",
// ),
// "sr": const ISOLanguageName(
// name: "Serbian",
// nativeName: "српски језик",
// ),
// "gd": const ISOLanguageName(
// name: "Scottish Gaelic; Gaelic",
// nativeName: "Gàidhlig",
// ),
// "sn": const ISOLanguageName(
// name: "Shona",
// nativeName: "chiShona",
// ),
// "si": const ISOLanguageName(
// name: "Sinhala, Sinhalese",
// nativeName: "සිංහල",
// ),
// "sk": const ISOLanguageName(
// name: "Slovak",
// nativeName: "slovenčina",
// ),
// "sl": const ISOLanguageName(
// name: "Slovene",
// nativeName: "slovenščina",
// ),
// "so": const ISOLanguageName(
// name: "Somali",
// nativeName: "Soomaaliga, af Soomaali",
// ),
// "st": const ISOLanguageName(
// name: "Southern Sotho",
// nativeName: "Sesotho",
// ),
// "es": const ISOLanguageName(
// name: "Spanish; Castilian",
// nativeName: "español, castellano",
// ),
// "su": const ISOLanguageName(
// name: "Sundanese",
// nativeName: "Basa Sunda",
// ),
// "sw": const ISOLanguageName(
// name: "Swahili",
// nativeName: "Kiswahili",
// ),
// "ss": const ISOLanguageName(
// name: "Swati",
// nativeName: "SiSwati",
// ),
// "sv": const ISOLanguageName(
// name: "Swedish",
// nativeName: "svenska",
// ),
// "ta": const ISOLanguageName(
// name: "Tamil",
// nativeName: "தமிழ்",
// ),
// "te": const ISOLanguageName(
// name: "Telugu",
// nativeName: "తెలుగు",
// ),
// "tg": const ISOLanguageName(
// name: "Tajik",
// nativeName: "тоҷикӣ, toğikī, تاجیکی‎",
// ),
// "th": const ISOLanguageName(
// name: "Thai",
// nativeName: "ไทย",
// ),
// "ti": const ISOLanguageName(
// name: "Tigrinya",
// nativeName: "ትግርኛ",
// ),
// "bo": const ISOLanguageName(
// name: "Tibetan Standard, Tibetan, Central",
// nativeName: "བོད་ཡིག",
// ),
// "tk": const ISOLanguageName(
// name: "Turkmen",
// nativeName: "Türkmen, Түркмен",
// ),
// "tl": const ISOLanguageName(
// name: "Tagalog",
// nativeName: "Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔",
// ),
// "tn": const ISOLanguageName(
// name: "Tswana",
// nativeName: "Setswana",
// ),
// "to": const ISOLanguageName(
// name: "Tonga (Tonga Islands)",
// nativeName: "faka Tonga",
// ),
// "tr": const ISOLanguageName(
// name: "Turkish",
// nativeName: "Türkçe",
// ),
// "ts": const ISOLanguageName(
// name: "Tsonga",
// nativeName: "Xitsonga",
// ),
// "tt": const ISOLanguageName(
// name: "Tatar",
// nativeName: "татарча, tatarça, تاتارچا‎",
// ),
// "tw": const ISOLanguageName(
// name: "Twi",
// nativeName: "Twi",
// ),
// "ty": const ISOLanguageName(
// name: "Tahitian",
// nativeName: "Reo Tahiti",
// ),
// "ug": const ISOLanguageName(
// name: "Uighur, Uyghur",
// nativeName: "Uyƣurqə, ئۇيغۇرچە‎",
// ),
// "uk": const ISOLanguageName(
// name: "Ukrainian",
// nativeName: "українська",
// ),
// "ur": const ISOLanguageName(
// name: "Urdu",
// nativeName: "اردو",
// ),
// "uz": const ISOLanguageName(
// name: "Uzbek",
// nativeName: "zbek, Ўзбек, أۇزبېك‎",
// ),
// "ve": const ISOLanguageName(
// name: "Venda",
// nativeName: "Tshivenḓa",
// ),
// "vi": const ISOLanguageName(
// name: "Vietnamese",
// nativeName: "Tiếng Việt",
// ),
// "vo": const ISOLanguageName(
// name: "Volapük",
// nativeName: "Volapük",
// ),
// "wa": const ISOLanguageName(
// name: "Walloon",
// nativeName: "Walon",
// ),
// "cy": const ISOLanguageName(
// name: "Welsh",
// nativeName: "Cymraeg",
// ),
// "wo": const ISOLanguageName(
// name: "Wolof",
// nativeName: "Wollof",
// ),
// "fy": const ISOLanguageName(
// name: "Western Frisian",
// nativeName: "Frysk",
// ),
// "xh": const ISOLanguageName(
// name: "Xhosa",
// nativeName: "isiXhosa",
// ),
// "yi": const ISOLanguageName(
// name: "Yiddish",
// nativeName: "ייִדיש",
// ),
// "yo": const ISOLanguageName(
// name: "Yoruba",
// nativeName: "Yorùbá",
// ),
// "za": const ISOLanguageName(
// name: "Zhuang, Chuang",
// nativeName: "Saɯ cueŋƅ, Saw cuengh",
// )
};
static ISOLanguageName getDisplayLanguage(key) {
if (isoLangs.containsKey(key)) {
return isoLangs[key]!;
} else {
throw Exception("Language key incorrect");
}
}
}

View File

@ -3,9 +3,12 @@ import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:spotify/spotify.dart' hide Search;
import 'package:spotube/pages/home/home.dart';
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
import 'package:spotube/pages/lyrics/mini_lyrics.dart';
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/utils/platform.dart';
import 'package:spotube/components/shared/spotube_page_route.dart';
import 'package:spotube/pages/album/album.dart';
@ -20,6 +23,8 @@ import 'package:spotube/pages/root/root_app.dart';
import 'package:spotube/pages/settings/settings.dart';
import 'package:spotube/pages/mobile_login/mobile_login.dart';
import '../pages/library/playlist_generate/playlist_generate_result.dart';
final rootNavigatorKey = Catcher.navigatorKey;
final shellRouteNavigatorKey = GlobalKey<NavigatorState>();
final router = GoRouter(
@ -31,41 +36,63 @@ final router = GoRouter(
routes: [
GoRoute(
path: "/",
pageBuilder: (context, state) => SpotubePage(child: const HomePage()),
pageBuilder: (context, state) => const SpotubePage(child: HomePage()),
),
GoRoute(
path: "/search",
name: "Search",
pageBuilder: (context, state) =>
SpotubePage(child: const SearchPage()),
const SpotubePage(child: SearchPage()),
),
GoRoute(
path: "/library",
name: "Library",
pageBuilder: (context, state) =>
SpotubePage(child: const LibraryPage()),
const SpotubePage(child: LibraryPage()),
routes: [
GoRoute(
path: "generate",
pageBuilder: (context, state) =>
const SpotubePage(child: PlaylistGeneratorPage()),
routes: [
GoRoute(
path: "result",
pageBuilder: (context, state) => SpotubePage(
child: PlaylistGenerateResultPage(
state:
state.extra as PlaylistGenerateResultRouteState,
),
),
),
]),
]),
GoRoute(
path: "/lyrics",
name: "Lyrics",
pageBuilder: (context, state) =>
SpotubePage(child: const LyricsPage()),
const SpotubePage(child: LyricsPage()),
),
GoRoute(
path: "/settings",
pageBuilder: (context, state) => SpotubePage(
child: const SettingsPage(),
pageBuilder: (context, state) => const SpotubePage(
child: SettingsPage(),
),
routes: [
GoRoute(
path: "blacklist",
pageBuilder: (context, state) => SpotubePage(
pageBuilder: (context, state) => SpotubeSlidePage(
child: const BlackListPage(),
),
),
GoRoute(
path: "logs",
pageBuilder: (context, state) => SpotubeSlidePage(
child: const LogsPage(),
),
),
GoRoute(
path: "about",
pageBuilder: (context, state) => SpotubePage(
pageBuilder: (context, state) => SpotubeSlidePage(
child: const AboutSpotube(),
),
),
@ -96,6 +123,13 @@ final router = GoRouter(
),
],
),
GoRoute(
path: "/mini-player",
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => const SpotubePage(
child: MiniLyricsPage(),
),
),
GoRoute(
path: "/login",
parentNavigatorKey: rootNavigatorKey,
@ -106,16 +140,16 @@ final router = GoRouter(
GoRoute(
path: "/login-tutorial",
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) => SpotubePage(
child: const LoginTutorial(),
pageBuilder: (context, state) => const SpotubePage(
child: LoginTutorial(),
),
),
GoRoute(
path: "/player",
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (context, state) {
return SpotubePage(
child: const PlayerView(),
return const SpotubePage(
child: PlayerView(),
);
},
),

View File

@ -1,22 +1,33 @@
import 'package:flutter/material.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class SideBarTiles {
final IconData icon;
final String title;
SideBarTiles({required this.icon, required this.title});
final String id;
SideBarTiles({required this.icon, required this.title, required this.id});
}
List<SideBarTiles> sidebarTileList = [
SideBarTiles(icon: SpotubeIcons.home, title: "Browse"),
SideBarTiles(icon: SpotubeIcons.search, title: "Search"),
SideBarTiles(icon: SpotubeIcons.library, title: "Library"),
SideBarTiles(icon: SpotubeIcons.music, title: "Lyrics")
List<SideBarTiles> getSidebarTileList(AppLocalizations l10n) => [
SideBarTiles(id: "browse", icon: SpotubeIcons.home, title: l10n.browse),
SideBarTiles(id: "search", icon: SpotubeIcons.search, title: l10n.search),
SideBarTiles(
id: "library", icon: SpotubeIcons.library, title: l10n.library),
SideBarTiles(id: "lyrics", icon: SpotubeIcons.music, title: l10n.lyrics),
];
List<SideBarTiles> navbarTileList = [
SideBarTiles(icon: SpotubeIcons.home, title: "Browse"),
SideBarTiles(icon: SpotubeIcons.search, title: "Search"),
SideBarTiles(icon: SpotubeIcons.library, title: "Library"),
SideBarTiles(icon: SpotubeIcons.settings, title: "Settings")
List<SideBarTiles> getNavbarTileList(AppLocalizations l10n) => [
SideBarTiles(id: "browse", icon: SpotubeIcons.home, title: l10n.browse),
SideBarTiles(id: "search", icon: SpotubeIcons.search, title: l10n.search),
SideBarTiles(
id: "library",
icon: SpotubeIcons.library,
title: l10n.library,
),
SideBarTiles(
id: "settings",
icon: SpotubeIcons.settings,
title: l10n.settings,
)
];

View File

@ -1,188 +1,187 @@
// Country Codes contributed by momobobe <https://github.com/momobobe>
final spotifyMarkets = [
["AL", "Albania (AL)"],
["DZ", "Algeria (DZ)"],
["AD", "Andorra (AD)"],
["AO", "Angola (AO)"],
["AG", "Antigua and Barbuda (AG)"],
["AR", "Argentina (AR)"],
["AM", "Armenia (AM)"],
["AU", "Australia (AU)"],
["AT", "Austria (AT)"],
["AZ", "Azerbaijan (AZ)"],
["BH", "Bahrain (BH)"],
["BD", "Bangladesh (BD)"],
["BB", "Barbados (BB)"],
["BY", "Belarus (BY)"],
["BE", "Belgium (BE)"],
["BZ", "Belize (BZ)"],
["BJ", "Benin (BJ)"],
["BT", "Bhutan (BT)"],
["BO", "Bolivia (BO)"],
["BA", "Bosnia and Herzegovina (BA)"],
["BW", "Botswana (BW)"],
["BR", "Brazil (BR)"],
["BN", "Brunei Darussalam (BN)"],
["BG", "Bulgaria (BG)"],
["BF", "Burkina Faso (BF)"],
["BI", "Burundi (BI)"],
["CV", "Cabo Verde / Cape Verde (CV)"],
["KH", "Cambodia (KH)"],
["CM", "Cameroon (CM)"],
["CA", "Canada (CA)"],
["TD", "Chad (TD)"],
["CL", "Chile (CL)"],
["CO", "Colombia (CO)"],
["KM", "Comoros (KM)"],
["CR", "Costa Rica (CR)"],
["HR", "Croatia (HR)"],
["CW", "Curaçao (CW)"],
["CY", "Cyprus (CY)"],
["CZ", "Czech Republic (CZ)"],
["CI", "Côte d'Ivoire / Ivory Coast (CI)"],
["CD", "Democratic Republic of the Congo (CD)"],
["DK", "Denmark (DK)"],
["DJ", "Djibouti (DJ)"],
["DM", "Dominica (DM)"],
["DO", "Dominican Republic (DO)"],
["EC", "Ecuador (EC)"],
["EG", "Egypt (EG)"],
["SV", "El Salvador (SV)"],
["GQ", "Equatorial Guinea (GQ)"],
["EE", "Estonia (EE)"],
["SZ", "Eswatini (SZ)"],
["FJ", "Fiji (FJ)"],
["FI", "Finland (FI)"],
["FR", "France (FR)"],
["GA", "Gabon (GA)"],
["GE", "Georgia (GE)"],
["DE", "Germany (DE)"],
["GH", "Ghana (GH)"],
["GR", "Greece (GR)"],
["GD", "Grenada (GD)"],
["GT", "Guatemala (GT)"],
["GN", "Guinea (GN)"],
["GW", "Guinea-Bissau (GW)"],
["GY", "Guyana (GY)"],
["HT", "Haiti (HT)"],
["HN", "Honduras (HN)"],
["HK", "Hong Kong (HK)"],
["HU", "Hungary (HU)"],
["IS", "Iceland (IS)"],
["IN", "India (IN)"],
["ID", "Indonesia (ID)"],
["IQ", "Iraq (IQ)"],
["IE", "Ireland (IE)"],
["IL", "Israel (IL)"],
["IT", "Italy (IT)"],
["JM", "Jamaica (JM)"],
["JP", "Japan (JP)"],
["JO", "Jordan (JO)"],
["KZ", "Kazakhstan (KZ)"],
["KE", "Kenya (KE)"],
["KI", "Kiribati (KI)"],
["XK", "Kosovo (XK)"],
["KW", "Kuwait (KW)"],
["KG", "Kyrgyzstan (KG)"],
["LA", "Laos (LA)"],
["LV", "Latvia (LV)"],
["LB", "Lebanon (LB)"],
["LS", "Lesotho (LS)"],
["LR", "Liberia (LR)"],
["LY", "Libya (LY)"],
["LI", "Liechtenstein (LI)"],
["LT", "Lithuania (LT)"],
["LU", "Luxembourg (LU)"],
["MO", "Macao / Macau (MO)"],
["MG", "Madagascar (MG)"],
["MW", "Malawi (MW)"],
["MY", "Malaysia (MY)"],
["MV", "Maldives (MV)"],
["ML", "Mali (ML)"],
["MT", "Malta (MT)"],
["MH", "Marshall Islands (MH)"],
["MR", "Mauritania (MR)"],
["MU", "Mauritius (MU)"],
["MX", "Mexico (MX)"],
["FM", "Micronesia (FM)"],
["MD", "Moldova (MD)"],
["MC", "Monaco (MC)"],
["MN", "Mongolia (MN)"],
["ME", "Montenegro (ME)"],
["MA", "Morocco (MA)"],
["MZ", "Mozambique (MZ)"],
["NA", "Namibia (NA)"],
["NR", "Nauru (NR)"],
["NP", "Nepal (NP)"],
["NL", "Netherlands (NL)"],
["NZ", "New Zealand (NZ)"],
["NI", "Nicaragua (NI)"],
["NE", "Niger (NE)"],
["NG", "Nigeria (NG)"],
["MK", "North Macedonia (MK)"],
["NO", "Norway (NO)"],
["OM", "Oman (OM)"],
["PK", "Pakistan (PK)"],
["PW", "Palau (PW)"],
["PS", "Palestine (PS)"],
["PA", "Panama (PA)"],
["PG", "Papua New Guinea (PG)"],
["PY", "Paraguay (PY)"],
["PE", "Peru (PE)"],
["PH", "Philippines (PH)"],
["PL", "Poland (PL)"],
["PT", "Portugal (PT)"],
["QA", "Qatar (QA)"],
["CG", "Republic of the Congo (CG)"],
["RO", "Romania (RO)"],
["RU", "Russia (RU)"],
["RW", "Rwanda (RW)"],
["WS", "Samoa (WS)"],
["SM", "San Marino (SM)"],
["SA", "Saudi Arabia (SA)"],
["SN", "Senegal (SN)"],
["RS", "Serbia (RS)"],
["SC", "Seychelles (SC)"],
["SL", "Sierra Leone (SL)"],
["SG", "Singapore (SG)"],
["SK", "Slovakia (SK)"],
["SI", "Slovenia (SI)"],
["SB", "Solomon Islands (SB)"],
["ZA", "South Africa (ZA)"],
["KR", "South Korea (KR)"],
["ES", "Spain (ES)"],
["LK", "Sri Lanka (LK)"],
["VC", "St Vincent and the Grenadines (VC)"],
["KN", "St. Kitts and Nevis (KN)"],
["LC", "St. Lucia (LC)"],
["SR", "Suriname (SR)"],
["SE", "Sweden (SE)"],
["CH", "Switzerland (CH)"],
["ST", "São Tomé and Príncipe (ST)"],
["TW", "Taiwan (TW)"],
["TJ", "Tajikistan (TJ)"],
["TZ", "Tanzania (TZ)"],
["TH", "Thailand (TH)"],
["BS", "The Bahamas (BS)"],
["GM", "The Gambia (GM)"],
["TL", "Timor-Leste / East Timor (TL)"],
["TG", "Togo (TG)"],
["TO", "Tonga (TO)"],
["TT", "Trinidad and Tobago (TT)"],
["TN", "Tunisia (TN)"],
["TR", "Turkey (TR)"],
["TV", "Tuvalu (TV)"],
["UG", "Uganda (UG)"],
["UA", "Ukraine (UA)"],
["AE", "United Arab Emirates (AE)"],
["GB", "United Kingdom (GB)"],
["US", "United States (US)"],
["UY", "Uruguay (UY)"],
["UZ", "Uzbekistan (UZ)"],
["VU", "Vanuatu (VU)"],
["VE", "Venezuela (VE)"],
["VN", "Vietnam (VN)"],
["ZM", "Zambia (ZM)"],
["Z", "Zimbabwe (ZW)"],
("AL", "Albania (AL)"),
("DZ", "Algeria (DZ)"),
("AD", "Andorra (AD)"),
("AO", "Angola (AO)"),
("AG", "Antigua and Barbuda (AG)"),
("AR", "Argentina (AR)"),
("AM", "Armenia (AM)"),
("AU", "Australia (AU)"),
("AT", "Austria (AT)"),
("AZ", "Azerbaijan (AZ)"),
("BH", "Bahrain (BH)"),
("BD", "Bangladesh (BD)"),
("BB", "Barbados (BB)"),
("BY", "Belarus (BY)"),
("BE", "Belgium (BE)"),
("BZ", "Belize (BZ)"),
("BJ", "Benin (BJ)"),
("BT", "Bhutan (BT)"),
("BO", "Bolivia (BO)"),
("BA", "Bosnia and Herzegovina (BA)"),
("BW", "Botswana (BW)"),
("BR", "Brazil (BR)"),
("BN", "Brunei Darussalam (BN)"),
("BG", "Bulgaria (BG)"),
("BF", "Burkina Faso (BF)"),
("BI", "Burundi (BI)"),
("CV", "Cabo Verde / Cape Verde (CV)"),
("KH", "Cambodia (KH)"),
("CM", "Cameroon (CM)"),
("CA", "Canada (CA)"),
("TD", "Chad (TD)"),
("CL", "Chile (CL)"),
("CO", "Colombia (CO)"),
("KM", "Comoros (KM)"),
("CR", "Costa Rica (CR)"),
("HR", "Croatia (HR)"),
("CW", "Curaçao (CW)"),
("CY", "Cyprus (CY)"),
("CZ", "Czech Republic (CZ)"),
("CI", "Ivory Coast (CI)"),
("CD", "Congo (CD)"),
("DK", "Denmark (DK)"),
("DJ", "Djibouti (DJ)"),
("DM", "Dominica (DM)"),
("DO", "Dominican Republic (DO)"),
("EC", "Ecuador (EC)"),
("EG", "Egypt (EG)"),
("SV", "El Salvador (SV)"),
("GQ", "Equatorial Guinea (GQ)"),
("EE", "Estonia (EE)"),
("SZ", "Eswatini (SZ)"),
("FJ", "Fiji (FJ)"),
("FI", "Finland (FI)"),
("FR", "France (FR)"),
("GA", "Gabon (GA)"),
("GE", "Georgia (GE)"),
("DE", "Germany (DE)"),
("GH", "Ghana (GH)"),
("GR", "Greece (GR)"),
("GD", "Grenada (GD)"),
("GT", "Guatemala (GT)"),
("GN", "Guinea (GN)"),
("GW", "Guinea-Bissau (GW)"),
("GY", "Guyana (GY)"),
("HT", "Haiti (HT)"),
("HN", "Honduras (HN)"),
("HK", "Hong Kong (HK)"),
("HU", "Hungary (HU)"),
("IS", "Iceland (IS)"),
("IN", "India (IN)"),
("ID", "Indonesia (ID)"),
("IQ", "Iraq (IQ)"),
("IE", "Ireland (IE)"),
("IL", "Israel (IL)"),
("IT", "Italy (IT)"),
("JM", "Jamaica (JM)"),
("JP", "Japan (JP)"),
("JO", "Jordan (JO)"),
("KZ", "Kazakhstan (KZ)"),
("KE", "Kenya (KE)"),
("KI", "Kiribati (KI)"),
("XK", "Kosovo (XK)"),
("KW", "Kuwait (KW)"),
("KG", "Kyrgyzstan (KG)"),
("LA", "Laos (LA)"),
("LV", "Latvia (LV)"),
("LB", "Lebanon (LB)"),
("LS", "Lesotho (LS)"),
("LR", "Liberia (LR)"),
("LY", "Libya (LY)"),
("LI", "Liechtenstein (LI)"),
("LT", "Lithuania (LT)"),
("LU", "Luxembourg (LU)"),
("MO", "Macao / Macau (MO)"),
("MG", "Madagascar (MG)"),
("MW", "Malawi (MW)"),
("MY", "Malaysia (MY)"),
("MV", "Maldives (MV)"),
("ML", "Mali (ML)"),
("MT", "Malta (MT)"),
("MH", "Marshall Islands (MH)"),
("MR", "Mauritania (MR)"),
("MU", "Mauritius (MU)"),
("MX", "Mexico (MX)"),
("FM", "Micronesia (FM)"),
("MD", "Moldova (MD)"),
("MC", "Monaco (MC)"),
("MN", "Mongolia (MN)"),
("ME", "Montenegro (ME)"),
("MA", "Morocco (MA)"),
("MZ", "Mozambique (MZ)"),
("NA", "Namibia (NA)"),
("NR", "Nauru (NR)"),
("NP", "Nepal (NP)"),
("NL", "Netherlands (NL)"),
("NZ", "New Zealand (NZ)"),
("NI", "Nicaragua (NI)"),
("NE", "Niger (NE)"),
("NG", "Nigeria (NG)"),
("MK", "North Macedonia (MK)"),
("NO", "Norway (NO)"),
("OM", "Oman (OM)"),
("PK", "Pakistan (PK)"),
("PW", "Palau (PW)"),
("PS", "Palestine (PS)"),
("PA", "Panama (PA)"),
("PG", "Papua New Guinea (PG)"),
("PY", "Paraguay (PY)"),
("PE", "Peru (PE)"),
("PH", "Philippines (PH)"),
("PL", "Poland (PL)"),
("PT", "Portugal (PT)"),
("QA", "Qatar (QA)"),
("CG", "Congo (CG)"),
("RO", "Romania (RO)"),
("RU", "Russia (RU)"),
("RW", "Rwanda (RW)"),
("WS", "Samoa (WS)"),
("SM", "San Marino (SM)"),
("SA", "Saudi Arabia (SA)"),
("SN", "Senegal (SN)"),
("RS", "Serbia (RS)"),
("SC", "Seychelles (SC)"),
("SL", "Sierra Leone (SL)"),
("SG", "Singapore (SG)"),
("SK", "Slovakia (SK)"),
("SI", "Slovenia (SI)"),
("SB", "Solomon Islands (SB)"),
("ZA", "South Africa (ZA)"),
("KR", "South Korea (KR)"),
("ES", "Spain (ES)"),
("LK", "Sri Lanka (LK)"),
("KN", "St. Kitts and Nevis (KN)"),
("LC", "St. Lucia (LC)"),
("SR", "Suriname (SR)"),
("SE", "Sweden (SE)"),
("CH", "Switzerland (CH)"),
("ST", "São Tomé and Príncipe (ST)"),
("TW", "Taiwan (TW)"),
("TJ", "Tajikistan (TJ)"),
("TZ", "Tanzania (TZ)"),
("TH", "Thailand (TH)"),
("BS", "The Bahamas (BS)"),
("GM", "The Gambia (GM)"),
("TL", "East Timor (TL)"),
("TG", "Togo (TG)"),
("TO", "Tonga (TO)"),
("TT", "Trinidad and Tobago (TT)"),
("TN", "Tunisia (TN)"),
("TR", "Turkey (TR)"),
("TV", "Tuvalu (TV)"),
("UG", "Uganda (UG)"),
("UA", "Ukraine (UA)"),
("AE", "United Arab Emirates (AE)"),
("GB", "United Kingdom (GB)"),
("US", "United States (US)"),
("UY", "Uruguay (UY)"),
("UZ", "Uzbekistan (UZ)"),
("VU", "Vanuatu (VU)"),
("VE", "Venezuela (VE)"),
("VN", "Vietnam (VN)"),
("ZM", "Zambia (ZM)"),
("ZW", "Zimbabwe (ZW)"),
];

View File

@ -1,13 +1,13 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:flutter_feather_icons/flutter_feather_icons.dart';
abstract class SpotubeIcons {
static const home = FluentIcons.home;
static const home = FluentIcons.home_12_regular;
static const search = FeatherIcons.search;
static const library = FluentIcons.library;
static const library = FluentIcons.library_16_regular;
static const music = FeatherIcons.music;
static const play = FluentIcons.play;
static const play = FluentIcons.play_12_regular;
static const pause = FeatherIcons.pause;
static const skipForward = FeatherIcons.skipForward;
static const skipBack = FeatherIcons.skipBack;
@ -16,8 +16,8 @@ abstract class SpotubeIcons {
static const refresh = FeatherIcons.refreshCw;
static const settings = FeatherIcons.settings;
static const shuffle = FeatherIcons.shuffle;
static const repeat = FluentIcons.repeat_all;
static const repeatOne = FluentIcons.repeat_one;
static const repeat = FluentIcons.arrow_repeat_all_16_regular;
static const repeatOne = Icons.repeat_one_rounded;
static const remove = FeatherIcons.minus;
static const removeFilled = FeatherIcons.minusCircle;
static const add = FeatherIcons.plus;
@ -67,4 +67,28 @@ abstract class SpotubeIcons {
static const genres = FeatherIcons.music;
static const zoomIn = FeatherIcons.zoomIn;
static const zoomOut = FeatherIcons.zoomOut;
static const tray = FeatherIcons.chevronDown;
static const miniPlayer = Icons.picture_in_picture_rounded;
static const maximize = FeatherIcons.maximize2;
static const pinOn = Icons.push_pin_rounded;
static const pinOff = Icons.push_pin_outlined;
static const hoverOn = Icons.back_hand_rounded;
static const hoverOff = Icons.back_hand_outlined;
static const dragHandle = Icons.drag_indicator;
static const lightning = Icons.flash_on_rounded;
static const colorSync = FeatherIcons.activity;
static const language = FeatherIcons.globe;
static const error = FeatherIcons.alertTriangle;
static const piped = FeatherIcons.cloud;
static const magic = Icons.auto_fix_high_outlined;
static const selectionCheck = Icons.checklist_rounded;
static const volumeHigh = FeatherIcons.volume2;
static const volumeMedium = FeatherIcons.volume1;
static const volumeLow = FeatherIcons.volume;
static const volumeMute = FeatherIcons.volumeX;
static const timer = FeatherIcons.clock;
static const logs = FeatherIcons.fileText;
static const clipboard = FeatherIcons.clipboard;
static const youtube = FeatherIcons.youtube;
static const skip = FeatherIcons.fastForward;
}

View File

@ -5,8 +5,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/playbutton_card.dart';
import 'package:spotube/hooks/use_breakpoint_value.dart';
import 'package:spotube/provider/playlist_queue_provider.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/spotify_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
@ -33,29 +34,24 @@ enum AlbumType {
class AlbumCard extends HookConsumerWidget {
final Album album;
final PlaybuttonCardViewType viewType;
const AlbumCard(
this.album, {
Key? key,
this.viewType = PlaybuttonCardViewType.square,
}) : super(key: key);
@override
Widget build(BuildContext context, ref) {
final playlist = ref.watch(PlaylistQueueNotifier.provider);
final playing = useStream(PlaylistQueueNotifier.playing).data ??
PlaylistQueueNotifier.isPlaying;
final playlistNotifier = ref.watch(PlaylistQueueNotifier.notifier);
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
final playing =
useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying;
final playlistNotifier = ref.watch(ProxyPlaylistNotifier.notifier);
final queryClient = useQueryClient();
final query = queryClient
.getQuery<List<TrackSimple>, dynamic>("album-tracks/${album.id}");
bool isPlaylistPlaying = useMemoized(
() =>
playlistNotifier.isPlayingPlaylist(query?.data ?? album.tracks ?? []),
[playlistNotifier, query?.data, album.tracks],
() => playlist.containsCollection(album.id!),
[playlist, album.id],
);
final int marginH =
useBreakpointValue(sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
useBreakpointValue(xs: 10, sm: 10, md: 15, lg: 20, xl: 20, xxl: 20);
final updating = useState(false);
final spotify = ref.watch(spotifyProvider);
@ -65,30 +61,33 @@ class AlbumCard extends HookConsumerWidget {
album.images,
placeholder: ImagePlaceholder.collection,
),
viewType: viewType,
margin: EdgeInsets.symmetric(horizontal: marginH.toDouble()),
isPlaying: isPlaylistPlaying,
isLoading: isPlaylistPlaying && playlist?.isLoading == true,
isLoading: isPlaylistPlaying && playlist.isFetching == true,
title: album.name!,
description:
"${AlbumType.from(album.albumType!).formatted}${TypeConversionUtils.artists_X_String<ArtistSimple>(album.artists ?? [])}",
onTap: () {
ServiceUtils.navigate(context, "/album/${album.id}", extra: album);
ServiceUtils.push(context, "/album/${album.id}", extra: album);
},
onPlaybuttonPressed: () async {
updating.value = true;
try {
if (isPlaylistPlaying && playing) {
return playlistNotifier.pause();
return audioPlayer.pause();
} else if (isPlaylistPlaying && !playing) {
return playlistNotifier.resume();
return audioPlayer.resume();
}
await playlistNotifier.loadAndPlay(album.tracks
await playlistNotifier.load(
album.tracks
?.map((e) =>
TypeConversionUtils.simpleTrack_X_Track(e, album))
.toList() ??
[]);
[],
autoPlay: true,
);
playlistNotifier.addCollection(album.id!);
} finally {
updating.value = false;
}
@ -117,16 +116,16 @@ class AlbumCard extends HookConsumerWidget {
);
if (fetchedTracks == null || fetchedTracks.isEmpty) return;
playlistNotifier.add(
fetchedTracks,
);
playlistNotifier.addTracks(fetchedTracks);
playlistNotifier.addCollection(album.id!);
if (context.mounted) {
final snackbar = SnackBar(
content: Text("Added ${album.tracks?.length} tracks to queue"),
action: SnackBarAction(
label: "Undo",
onPressed: () {
playlistNotifier.remove(fetchedTracks);
playlistNotifier
.removeTracks(fetchedTracks.map((e) => e.id!));
},
),
);

View File

@ -33,9 +33,9 @@ class ArtistAlbumList extends HookConsumerWidget {
? false
: (albumsQuery.pages.last.items?.length ?? 0) == 5;
return SizedBox(
height: 300,
child: ScrollConfiguration(
return Column(
children: [
ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
@ -48,20 +48,21 @@ class ArtistAlbumList extends HookConsumerWidget {
child: Waypoint(
controller: scrollController,
onTouchEdge: albumsQuery.fetchNext,
child: ListView.builder(
itemCount: albums.length,
child: SingleChildScrollView(
controller: scrollController,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
if (index == albums.length - 1 && hasNextPage) {
return const ShimmerPlaybuttonCard(count: 1);
}
return AlbumCard(albums[index]);
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...albums.map((album) => AlbumCard(album)),
if (hasNextPage) const ShimmerPlaybuttonCard(count: 1),
],
),
),
),
),
),
],
);
}
}

View File

@ -1,12 +1,12 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:fluent_ui/fluent_ui.dart' hide Colors;
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/shared/hover_builder.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/hooks/use_platform_property.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/use_breakpoint_value.dart';
import 'package:spotube/hooks/use_brightness_value.dart';
import 'package:spotube/provider/blacklist_provider.dart';
import 'package:spotube/utils/service_utils.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
@ -17,6 +17,7 @@ class ArtistCard extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final theme = Theme.of(context);
final backgroundImage = UniversalImage.imageProvider(
TypeConversionUtils.image_X_UrlString(
artist.images,
@ -30,87 +31,55 @@ class ArtistCard extends HookConsumerWidget {
),
),
);
final boxShadow = usePlatformProperty<BoxShadow?>(
(context) => PlatformProperty(
android: BoxShadow(
blurRadius: 10,
offset: const Offset(0, 3),
spreadRadius: 5,
color: Theme.of(context).colorScheme.shadow,
),
ios: null,
macos: null,
linux: BoxShadow(
blurRadius: 6,
color: Theme.of(context).shadowColor.withOpacity(0.3),
),
windows: null,
),
final radius = BorderRadius.circular(15);
final double size = useBreakpointValue<double>(
xs: 130,
sm: 130,
md: 150,
others: 170,
);
final splash = usePlatformProperty<InteractiveInkFeatureFactory?>(
(context) => PlatformProperty.only(
android: InkRipple.splashFactory,
other: NoSplash.splashFactory,
return Container(
width: size,
margin: const EdgeInsets.symmetric(vertical: 5),
child: Material(
shadowColor: theme.colorScheme.background,
color: Color.lerp(
theme.colorScheme.surfaceVariant,
theme.colorScheme.surface,
useBrightnessValue(.9, .7),
),
);
return SizedBox(
height: 240,
width: 200,
child: InkWell(
splashFactory: splash,
onTap: () {
ServiceUtils.navigate(context, "/artist/${artist.id}");
},
customBorder: platform == TargetPlatform.windows
? Border.all(
color: FluentTheme.maybeOf(context)
?.micaBackgroundColor
.withOpacity(.7) ??
Colors.transparent,
width: 1,
)
: null,
borderRadius: BorderRadius.circular(
platform == TargetPlatform.windows ? 5 : 8,
),
child: HoverBuilder(builder: (context, isHovering) {
return Ink(
width: 200,
decoration: BoxDecoration(
color: PlatformTheme.of(context).secondaryBackgroundColor,
borderRadius: BorderRadius.circular(
platform == TargetPlatform.windows ? 5 : 8,
),
boxShadow: [
if (boxShadow != null) boxShadow,
],
border: isBlackListed
? Border.all(
color: Colors.red[400]!,
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: radius,
side: isBlackListed
? const BorderSide(
color: Colors.red,
width: 2,
)
: [TargetPlatform.windows, TargetPlatform.macOS]
.contains(platform)
? Border.all(
color: PlatformTheme.of(context).borderColor ??
Colors.transparent,
width: 1,
)
: null,
: BorderSide.none,
),
child: InkWell(
onTap: () {
ServiceUtils.push(context, "/artist/${artist.id}");
},
borderRadius: radius,
child: Padding(
padding: const EdgeInsets.all(15),
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Stack(
children: [
CircleAvatar(
maxRadius: 80,
minRadius: 20,
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: size,
),
child: CircleAvatar(
backgroundImage: backgroundImage,
radius: size / 2,
),
),
Positioned(
right: 0,
@ -122,9 +91,9 @@ class ArtistCard extends HookConsumerWidget {
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(50)),
child: const Text(
"Artist",
style: TextStyle(
child: Text(
context.l10n.artist,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
@ -134,19 +103,19 @@ class ArtistCard extends HookConsumerWidget {
),
],
),
const SizedBox(height: 10),
AutoSizeText(
artist.name!,
maxLines: 2,
maxLines: 1,
textAlign: TextAlign.center,
style: PlatformTextTheme.of(context).body?.copyWith(
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}),
)),
),
);
}

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication_provider.dart';
class TokenLoginForm extends HookConsumerWidget {
@ -25,27 +26,31 @@ class TokenLoginForm extends HookConsumerWidget {
),
child: Column(
children: [
PlatformTextField(
TextField(
controller: directCodeController,
placeholder: "Spotify \"sp_dc\" Cookie",
label: "sp_dc Cookie",
decoration: InputDecoration(
hintText: context.l10n.spotify_cookie("\"sp_dc\""),
labelText: context.l10n.cookie_name_cookie("sp_dc"),
),
keyboardType: TextInputType.visiblePassword,
),
const SizedBox(height: 10),
PlatformTextField(
TextField(
controller: keyCodeController,
placeholder: "Spotify \"sp_key\" Cookie",
label: "sp_key Cookie",
decoration: InputDecoration(
hintText: context.l10n.spotify_cookie("\"sp_key\""),
labelText: context.l10n.cookie_name_cookie("sp_key"),
),
keyboardType: TextInputType.visiblePassword,
),
const SizedBox(height: 20),
PlatformFilledButton(
FilledButton(
onPressed: () async {
if (keyCodeController.text.isEmpty ||
directCodeController.text.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: PlatformText("Please fill in all fields"),
SnackBar(
content: Text(context.l10n.fill_in_all_fields),
behavior: SnackBarBehavior.floating,
),
);
@ -61,7 +66,7 @@ class TokenLoginForm extends HookConsumerWidget {
onDone?.call();
}
},
child: const PlatformText("Submit"),
child: Text(context.l10n.submit),
)
],
),

View File

@ -1,12 +1,13 @@
import 'package:flutter/gestures.dart';
import 'dart:ui';
import 'package:flutter/material.dart' hide Page;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/components/playlist/playlist_card.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/services/queries/queries.dart';
@ -27,49 +28,42 @@ class CategoryCard extends HookConsumerWidget {
category.id!,
);
final playlists = playlistQuery.pages
.expand(
(page) => page.items ?? const Iterable.empty(),
)
.toList();
return Column(
children: [
Padding(
if (playlistQuery.hasErrors && !playlistQuery.hasPageData) {
return const SizedBox.shrink();
}
final playlists = playlistQuery.pages.expand(
(page) {
return page.items?.where((i) => i != null) ?? const Iterable.empty();
},
).toList();
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PlatformText.headline(category.name ?? "Unknown"),
],
Text(
category.name!,
style: Theme.of(context).textTheme.titleMedium,
),
),
playlistQuery.hasPageError && !playlistQuery.hasPageData
? PlatformText(
"Something Went Wrong\n${playlistQuery.errors.first}")
: SizedBox(
height: 245,
child: ScrollConfiguration(
ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
},
),
child: Scrollbar(
controller: scrollController,
interactive: false,
child: Waypoint(
controller: scrollController,
onTouchEdge: () {
playlistQuery.fetchNext();
},
child: ListView(
onTouchEdge: playlistQuery.fetchNext,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
controller: scrollController,
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...playlists
.map((playlist) => PlaylistCard(playlist)),
...playlists.map((playlist) => PlaylistCard(playlist)),
if (playlistQuery.hasNextPage)
const ShimmerPlaybuttonCard(count: 1),
],
@ -77,8 +71,8 @@ class CategoryCard extends HookConsumerWidget {
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,272 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
class MultiSelectField<T> extends HookWidget {
final List<T> options;
final List<T> selectedOptions;
final Widget Function(T option, VoidCallback onSelect)? optionBuilder;
final Widget Function(T option)? selectedOptionBuilder;
final ValueChanged<List<T>> onSelected;
final Widget? dialogTitle;
final Object Function(T option) getValueForOption;
final Widget label;
final String? helperText;
final bool enabled;
const MultiSelectField({
Key? key,
required this.options,
required this.selectedOptions,
required this.getValueForOption,
required this.label,
this.optionBuilder,
this.selectedOptionBuilder,
required this.onSelected,
this.dialogTitle,
this.helperText,
this.enabled = true,
}) : super(key: key);
Widget defaultSelectedOptionBuilder(T option) {
return Chip(
label: Text(option.toString()),
onDeleted: () {
onSelected(
selectedOptions.where((e) => e != getValueForOption(option)).toList(),
);
},
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
MaterialButton(
elevation: 0,
focusElevation: 0,
hoverElevation: 0,
disabledElevation: 0,
highlightElevation: 0,
padding: const EdgeInsets.symmetric(vertical: 22),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
side: BorderSide(
color: enabled
? theme.colorScheme.onSurface
: theme.colorScheme.onSurface.withOpacity(0.1),
),
),
mouseCursor: MaterialStateMouseCursor.textable,
onPressed: !enabled
? null
: () async {
final selected = await showDialog<List<T>>(
context: context,
builder: (context) {
return _MultiSelectDialog<T>(
dialogTitle: dialogTitle,
options: options,
getValueForOption: getValueForOption,
optionBuilder: optionBuilder,
initialSelection: selectedOptions,
helperText: helperText,
);
},
);
if (selected != null) {
onSelected(selected);
}
},
child: Container(
alignment: Alignment.centerLeft,
margin: const EdgeInsets.symmetric(horizontal: 10),
child: DefaultTextStyle(
style: theme.textTheme.titleMedium!,
child: label,
),
),
),
if (helperText != null)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
helperText!,
style: Theme.of(context).textTheme.bodySmall,
),
),
Wrap(
children: [
...selectedOptions.map(
(option) => Padding(
padding: const EdgeInsets.all(4.0),
child: (selectedOptionBuilder ??
defaultSelectedOptionBuilder)(option),
),
),
],
)
],
);
}
}
class _MultiSelectDialog<T> extends HookWidget {
final Widget? dialogTitle;
final List<T> options;
final Widget Function(T option, VoidCallback onSelect)? optionBuilder;
final Object Function(T option) getValueForOption;
final List<T> initialSelection;
final String? helperText;
const _MultiSelectDialog({
Key? key,
required this.dialogTitle,
required this.options,
required this.getValueForOption,
this.optionBuilder,
this.initialSelection = const [],
this.helperText,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final selected = useState(initialSelection.map(getValueForOption));
final searchController = useTextEditingController();
// creates render update
useValueListenable(searchController);
final filteredOptions = useMemoized(
() {
if (searchController.text.isEmpty) {
return options;
}
return options
.map((e) => (
weightedRatio(
getValueForOption(e).toString(), searchController.text),
e
))
.sorted((a, b) => b.$1.compareTo(a.$1))
.where((e) => e.$1 > 50)
.map((e) => e.$2)
.toList();
},
[searchController.text, options, getValueForOption],
);
Widget defaultOptionBuilder(T option, VoidCallback onSelect) {
final isSelected = selected.value.contains(getValueForOption(option));
return ChoiceChip(
label: Text("${!isSelected ? " " : ""}${option.toString()}"),
selected: isSelected,
side: BorderSide.none,
onSelected: (_) {
onSelect();
},
);
}
return AlertDialog(
scrollable: true,
title: dialogTitle ?? const Text('Select'),
contentPadding: mediaQuery.mdAndUp ? null : const EdgeInsets.all(16),
insetPadding: const EdgeInsets.all(16),
actions: [
OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(context.l10n.cancel),
),
ElevatedButton(
onPressed: () {
Navigator.pop(
context,
options
.where(
(option) =>
selected.value.contains(getValueForOption(option)),
)
.toList(),
);
},
child: Text(context.l10n.done),
),
],
content: SizedBox(
height: mediaQuery.size.height * 0.5,
width: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(
autofocus: true,
controller: searchController,
decoration: InputDecoration(
hintText: context.l10n.search,
prefixIcon: const Icon(SpotubeIcons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
),
),
),
const SizedBox(height: 10),
Expanded(
child: SingleChildScrollView(
child: Wrap(
spacing: 5,
runSpacing: 5,
children: [
...filteredOptions.map(
(option) => Padding(
padding: const EdgeInsets.all(4.0),
child: (optionBuilder ?? defaultOptionBuilder)(
option,
() {
final value = getValueForOption(option);
if (selected.value.contains(value)) {
selected.value = selected.value
.where((e) => e != value)
.toList();
} else {
selected.value = [...selected.value, value];
}
},
),
),
),
],
),
),
),
if (helperText != null)
Text(
helperText!,
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
);
}
}

View File

@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
typedef RecommendationAttribute = ({double min, double target, double max});
RecommendationAttribute lowValues(double base) =>
(min: 1 * base, target: 0.3 * base, max: 0.3 * base);
RecommendationAttribute moderateValues(double base) =>
(min: 0.5 * base, target: 1 * base, max: 0.5 * base);
RecommendationAttribute highValues(double base) =>
(min: 0.3 * base, target: 0.3 * base, max: 1 * base);
class RecommendationAttributeDials extends HookWidget {
final Widget title;
final RecommendationAttribute values;
final ValueChanged<RecommendationAttribute> onChanged;
final double base;
const RecommendationAttributeDials({
Key? key,
required this.values,
required this.onChanged,
required this.title,
this.base = 1,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final animation = useAnimationController(
duration: const Duration(milliseconds: 300),
);
final labelStyle = Theme.of(context).textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.w500,
);
final minSlider = Row(
children: [
Text(context.l10n.min, style: labelStyle),
Expanded(
child: Slider.adaptive(
value: values.min / base,
min: 0,
max: 1,
onChanged: (value) => onChanged((
min: value * base,
target: values.target,
max: values.max,
)),
),
),
],
);
final targetSlider = Row(
children: [
Text(context.l10n.target, style: labelStyle),
Expanded(
child: Slider.adaptive(
value: values.target / base,
min: 0,
max: 1,
onChanged: (value) => onChanged((
min: values.min,
target: value * base,
max: values.max,
)),
),
),
],
);
final maxSlider = Row(
children: [
Text(context.l10n.max, style: labelStyle),
Expanded(
child: Slider.adaptive(
value: values.max / base,
min: 0,
max: 1,
onChanged: (value) => onChanged((
min: values.min,
target: values.target,
max: value * base,
)),
),
),
],
);
return LayoutBuilder(builder: (context, constrain) {
return Card(
child: ExpansionTile(
title: DefaultTextStyle(
style: Theme.of(context).textTheme.titleSmall!,
child: title,
),
shape: const Border(),
leading: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.rotate(
angle: (animation.value * 3.14) / 2,
child: child,
);
},
child: const Icon(Icons.chevron_right),
),
trailing: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ToggleButtons(
borderRadius: BorderRadius.circular(8),
textStyle: labelStyle,
isSelected: [
values == lowValues(base),
values == moderateValues(base),
values == highValues(base),
],
onPressed: (index) {
RecommendationAttribute newValues = zeroValues;
switch (index) {
case 0:
newValues = lowValues(base);
break;
case 1:
newValues = moderateValues(base);
break;
case 2:
newValues = highValues(base);
break;
}
if (newValues == values) {
onChanged(zeroValues);
} else {
onChanged(newValues);
}
},
children: [
Text(context.l10n.low),
Text(" ${context.l10n.moderate} "),
Text(context.l10n.high),
],
),
),
onExpansionChanged: (value) {
if (value) {
animation.forward();
} else {
animation.reverse();
}
},
children: [
if (constrain.mdAndUp)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const SizedBox(width: 16),
Expanded(child: minSlider),
Expanded(child: targetSlider),
Expanded(child: maxSlider),
],
)
else
Padding(
padding: const EdgeInsets.only(left: 16),
child: Column(
children: [
minSlider,
targetSlider,
maxSlider,
],
),
),
],
),
);
});
}
}

View File

@ -0,0 +1,179 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spotube/components/library/playlist_generate/recommendation_attribute_dials.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
class RecommendationAttributeFields extends HookWidget {
final Widget title;
final RecommendationAttribute values;
final ValueChanged<RecommendationAttribute> onChanged;
final Map<String, RecommendationAttribute>? presets;
const RecommendationAttributeFields({
Key? key,
required this.values,
required this.onChanged,
required this.title,
this.presets,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final animation = useAnimationController(
duration: const Duration(milliseconds: 300),
);
final labelStyle = Theme.of(context).textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.w500,
);
final minController = useTextEditingController(text: values.min.toString());
final targetController =
useTextEditingController(text: values.target.toString());
final maxController = useTextEditingController(text: values.max.toString());
useEffect(() {
listener() {
onChanged((
min: double.tryParse(minController.text) ?? 0,
target: double.tryParse(targetController.text) ?? 0,
max: double.tryParse(maxController.text) ?? 0,
));
}
minController.addListener(listener);
targetController.addListener(listener);
maxController.addListener(listener);
return () {
minController.removeListener(listener);
targetController.removeListener(listener);
maxController.removeListener(listener);
};
}, [values]);
final minField = TextField(
controller: minController,
decoration: InputDecoration(
labelText: context.l10n.min,
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
);
final targetField = TextField(
controller: targetController,
decoration: InputDecoration(
labelText: context.l10n.target,
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
);
final maxField = TextField(
controller: maxController,
decoration: InputDecoration(
labelText: context.l10n.max,
isDense: true,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: false,
signed: true,
),
);
return LayoutBuilder(builder: (context, constrain) {
return Card(
child: ExpansionTile(
title: DefaultTextStyle(
style: Theme.of(context).textTheme.titleSmall!,
child: title,
),
shape: const Border(),
leading: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Transform.rotate(
angle: (animation.value * 3.14) / 2,
child: child,
);
},
child: const Icon(Icons.chevron_right),
),
trailing: presets == null
? const SizedBox.shrink()
: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: ToggleButtons(
borderRadius: BorderRadius.circular(8),
textStyle: labelStyle,
isSelected: presets!.values
.map((value) => value == values)
.toList(),
onPressed: (index) {
RecommendationAttribute newValues =
presets!.values.elementAt(index);
if (newValues == values) {
onChanged(zeroValues);
minController.text = zeroValues.min.toString();
targetController.text = zeroValues.target.toString();
maxController.text = zeroValues.max.toString();
} else {
onChanged(newValues);
minController.text = newValues.min.toString();
targetController.text = newValues.target.toString();
maxController.text = newValues.max.toString();
}
},
children: presets!.keys.map((key) => Text(key)).toList(),
),
),
onExpansionChanged: (value) {
if (value) {
animation.forward();
} else {
animation.reverse();
}
},
children: [
const SizedBox(height: 8),
if (constrain.mdAndUp)
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const SizedBox(width: 16),
Expanded(child: minField),
const SizedBox(width: 16),
Expanded(child: targetField),
const SizedBox(width: 16),
Expanded(child: maxField),
const SizedBox(width: 16),
],
)
else
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
minField,
const SizedBox(height: 16),
targetField,
const SizedBox(height: 16),
maxField,
],
),
),
const SizedBox(height: 8),
],
),
);
});
}
}

View File

@ -0,0 +1,144 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spotube/extensions/constrains.dart';
enum SelectedItemDisplayType {
wrap,
list,
}
class SeedsMultiAutocomplete<T extends Object> extends HookWidget {
final ValueNotifier<List<T>> seeds;
final FutureOr<List<T>> Function(TextEditingValue textEditingValue)
fetchSeeds;
final Widget Function(T option, ValueChanged<T> onSelected)
autocompleteOptionBuilder;
final Widget Function(T option) selectedSeedBuilder;
final String Function(T option) displayStringForOption;
final InputDecoration? inputDecoration;
final bool enabled;
final SelectedItemDisplayType selectedItemDisplayType;
const SeedsMultiAutocomplete({
Key? key,
required this.seeds,
required this.fetchSeeds,
required this.autocompleteOptionBuilder,
required this.displayStringForOption,
required this.selectedSeedBuilder,
this.inputDecoration,
this.enabled = true,
this.selectedItemDisplayType = SelectedItemDisplayType.wrap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
useValueListenable(seeds);
final theme = Theme.of(context);
final mediaQuery = MediaQuery.of(context);
final seedController = useTextEditingController();
final containerKey = useRef(GlobalKey());
final box =
containerKey.value.currentContext?.findRenderObject() as RenderBox?;
final position = box?.localToGlobal(Offset.zero); //this is global position
final containerYPos = position?.dy ?? 0; //th
final containerHeight = box?.size.height ?? 0;
final listHeight = mediaQuery.size.height -
(containerYPos + containerHeight) -
// bottom player bar height
(mediaQuery.mdAndUp ? 80 : 0);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
LayoutBuilder(builder: (context, constrains) {
return Container(
key: containerKey.value,
child: Autocomplete<T>(
optionsBuilder: (textEditingValue) async {
if (textEditingValue.text.isEmpty) return [];
return fetchSeeds(textEditingValue);
},
onSelected: (value) {
seeds.value = [...seeds.value, value];
seedController.clear();
},
optionsViewBuilder: (context, onSelected, options) {
return Align(
alignment: Alignment.topLeft,
child: Container(
constraints: BoxConstraints(
maxWidth: constrains.maxWidth,
),
height: max(listHeight, 0),
child: Card(
child: ListView.builder(
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (context, index) {
final option = options.elementAt(index);
return autocompleteOptionBuilder(option, onSelected);
},
),
),
),
);
},
displayStringForOption: displayStringForOption,
fieldViewBuilder: (
context,
textEditingController,
focusNode,
onFieldSubmitted,
) {
return TextFormField(
controller: seedController,
onChanged: (value) => textEditingController.text = value,
focusNode: focusNode,
onFieldSubmitted: (_) => onFieldSubmitted(),
enabled: enabled,
decoration: inputDecoration,
);
},
),
);
}),
const SizedBox(height: 8),
switch (selectedItemDisplayType) {
SelectedItemDisplayType.wrap => Wrap(
spacing: 4,
runSpacing: 4,
children: seeds.value.map(selectedSeedBuilder).toList(),
),
SelectedItemDisplayType.list => Card(
margin: EdgeInsets.zero,
child: Column(
children: [
for (final seed in seeds.value) ...[
selectedSeedBuilder(seed),
if (seeds.value.length > 1 && seed != seeds.value.last)
Divider(
color: theme.colorScheme.primaryContainer,
height: 1,
indent: 12,
endIndent: 12,
),
],
],
),
),
},
],
);
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/utils/type_conversion_utils.dart';
class SimpleTrackTile extends HookWidget {
final Track track;
final VoidCallback? onDelete;
const SimpleTrackTile({
Key? key,
required this.track,
this.onDelete,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: UniversalImage(
path: TypeConversionUtils.image_X_UrlString(
track.album?.images,
placeholder: ImagePlaceholder.artist,
),
height: 40,
width: 40,
),
),
horizontalTitleGap: 10,
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
title: Text(track.name!),
trailing: onDelete == null
? null
: IconButton(
icon: const Icon(SpotubeIcons.close),
onPressed: onDelete,
),
subtitle: Text(
track.artists?.map((e) => e.name).join(", ") ?? track.album?.name ?? "",
),
);
}
}

View File

@ -3,18 +3,17 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:collection/collection.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/album/album_card.dart';
import 'package:spotube/components/shared/playbutton_card.dart';
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/use_breakpoint_value.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:spotube/utils/type_conversion_utils.dart';
import 'package:tuple/tuple.dart';
class UserAlbums extends HookConsumerWidget {
const UserAlbums({Key? key}) : super(key: key);
@ -25,12 +24,10 @@ class UserAlbums extends HookConsumerWidget {
final albumsQuery = useQueries.album.ofMine(ref);
final spacing = useBreakpointValue<double>(
xs: 0,
sm: 0,
others: 20,
);
final viewType = MediaQuery.of(context).size.width < 480
? PlaybuttonCardViewType.list
: PlaybuttonCardViewType.square;
final searchText = useState('');
@ -39,13 +36,13 @@ class UserAlbums extends HookConsumerWidget {
return albumsQuery.data?.toList() ?? [];
}
return albumsQuery.data
?.map((e) => Tuple2(
?.map((e) => (
weightedRatio(e.name!, searchText.value),
e,
))
.sorted((a, b) => b.item1.compareTo(a.item1))
.where((e) => e.item1 > 50)
.map((e) => e.item2)
.sorted((a, b) => b.$1.compareTo(a.$1))
.where((e) => e.$1 > 50)
.map((e) => e.$2)
.toList() ??
[];
}, [albumsQuery.data, searchText.value]);
@ -53,9 +50,6 @@ class UserAlbums extends HookConsumerWidget {
if (auth == null) {
return const AnonymousFallback();
}
if (albumsQuery.isLoading || !albumsQuery.hasData) {
return const Center(child: ShimmerPlaybuttonCard(count: 7));
}
return RefreshIndicator(
onRefresh: () async {
@ -63,31 +57,41 @@ class UserAlbums extends HookConsumerWidget {
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Material(
type: MaterialType.transparency,
textStyle: PlatformTheme.of(context).textTheme!.body!,
color: PlatformTheme.of(context).scaffoldBackgroundColor,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PlatformTextField(
SearchBar(
onChanged: (value) => searchText.value = value,
prefixIcon: SpotubeIcons.filter,
placeholder: 'Filter Albums...',
leading: const Icon(SpotubeIcons.filter),
hintText: context.l10n.filter_albums,
),
const SizedBox(height: 20),
Wrap(
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
firstChild: Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.all(16.0),
child: const ShimmerPlaybuttonCard(count: 7),
),
secondChild: Wrap(
spacing: spacing, // gap between adjacent chips
runSpacing: 20, // gap between lines
alignment: WrapAlignment.center,
children: albums
.map((album) => AlbumCard(
viewType: viewType,
TypeConversionUtils.simpleAlbum_X_Album(album),
))
.toList(),
),
crossFadeState: albumsQuery.isLoading ||
!albumsQuery.hasData ||
searchText.value.isNotEmpty
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
),
],
),
),

View File

@ -3,48 +3,44 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:collection/collection.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:platform_ui/platform_ui.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/components/artist/artist_card.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/services/queries/queries.dart';
import 'package:tuple/tuple.dart';
class UserArtists extends HookConsumerWidget {
const UserArtists({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, ref) {
final theme = Theme.of(context);
final auth = ref.watch(AuthenticationNotifier.provider);
final artistQuery = useQueries.artist.followedByMe(ref);
final hasNextPage = artistQuery.pages.isEmpty
? false
: (artistQuery.pages.last.items?.length ?? 0) == 15;
final artistQuery = useQueries.artist.followedByMeAll(ref);
final searchText = useState('');
final filteredArtists = useMemoized(() {
final artists = artistQuery.pages
.expand<Artist>((page) => page.items ?? const Iterable.empty());
final artists = artistQuery.data ?? [];
if (searchText.value.isEmpty) {
return artists.toList();
}
return artists
.map((e) => Tuple2(
.map((e) => (
weightedRatio(e.name!, searchText.value),
e,
))
.sorted((a, b) => b.item1.compareTo(a.item1))
.where((e) => e.item1 > 50)
.map((e) => e.item2)
.sorted((a, b) => b.$1.compareTo(a.$1))
.where((e) => e.$1 > 50)
.map((e) => e.$2)
.toList();
}, [artistQuery.pages, searchText.value]);
}, [artistQuery.data, searchText.value]);
final controller = useScrollController();
if (auth == null) {
return const AnonymousFallback();
@ -56,57 +52,48 @@ class UserArtists extends HookConsumerWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ColoredBox(
color: PlatformTheme.of(context).scaffoldBackgroundColor!,
child: PlatformTextField(
color: theme.scaffoldBackgroundColor,
child: SearchBar(
onChanged: (value) => searchText.value = value,
prefixIcon: SpotubeIcons.filter,
placeholder: 'Filter artists...',
leading: const Icon(SpotubeIcons.filter),
hintText: context.l10n.filter_artist,
),
),
),
),
backgroundColor: PlatformTheme.of(context).scaffoldBackgroundColor,
body: artistQuery.pages.isEmpty
backgroundColor: theme.scaffoldBackgroundColor,
body: artistQuery.data?.isEmpty == true
? Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
PlatformCircularProgressIndicator(),
SizedBox(width: 10),
PlatformText("Loading..."),
children: [
const CircularProgressIndicator(),
const SizedBox(width: 10),
Text(context.l10n.loading),
],
),
)
: RefreshIndicator(
onRefresh: () async {
await artistQuery.refreshAll();
await artistQuery.refresh();
},
child: GridView.builder(
itemCount: filteredArtists.length,
physics: const AlwaysScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200,
mainAxisExtent: 250,
crossAxisSpacing: 20,
mainAxisSpacing: 20,
child: SingleChildScrollView(
controller: controller,
child: SizedBox(
width: double.infinity,
child: SafeArea(
child: Center(
child: Wrap(
spacing: 15,
runSpacing: 5,
children: filteredArtists
.mapIndexed((index, artist) => ArtistCard(artist))
.toList(),
),
),
),
),
padding: const EdgeInsets.all(10),
itemBuilder: (context, index) {
return HookBuilder(builder: (context) {
if (index == artistQuery.pages.length - 1 && hasNextPage) {
return Waypoint(
controller: useScrollController(),
isGrid: true,
onTouchEdge: () {
artistQuery.fetchNext();
},
child: ArtistCard(filteredArtists[index]),
);
}
return ArtistCard(filteredArtists[index]);
});
},
),
),
);

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