Merge branch 'master' into master
13
.env.example
@ -1,16 +1,17 @@
|
|||||||
# The format:
|
# The format:
|
||||||
# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2
|
# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2
|
||||||
SPOTIFY_SECRETS=
|
SPOTIFY_SECRETS=$SPOTIFY_SECRETS
|
||||||
|
|
||||||
# 0 or 1
|
# 0 or 1
|
||||||
# 0 = disable
|
# 0 = disable
|
||||||
# 1 = enable
|
# 1 = enable
|
||||||
ENABLE_UPDATE_CHECK=
|
ENABLE_UPDATE_CHECK=$ENABLE_UPDATE_CHECK
|
||||||
|
|
||||||
LASTFM_API_KEY=
|
LASTFM_API_KEY=$LASTFM_API_KEY
|
||||||
LASTFM_API_SECRET=
|
LASTFM_API_SECRET=$LASTFM_API_SECRET
|
||||||
|
|
||||||
# Release channel. Can be: nightly, stable
|
# Release channel. Can be: nightly, stable
|
||||||
RELEASE_CHANNEL=
|
RELEASE_CHANNEL=$RELEASE_CHANNEL
|
||||||
|
|
||||||
HIDE_DONATIONS=
|
HIDE_DONATIONS=$HIDE_DONATIONS
|
||||||
|
DISABLE_SPOTIFY_IMAGES=$DISABLE_SPOTIFY_IMAGES
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"flutterSdkVersion": "3.24.3"
|
"flutterSdkVersion": "3.29.2"
|
||||||
}
|
}
|
2
.fvmrc
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"flutter": "3.24.3",
|
"flutter": "3.29.2",
|
||||||
"flavors": {}
|
"flavors": {}
|
||||||
}
|
}
|
10
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -9,7 +9,8 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this? (Please read the description)
|
label: Is there an existing issue for this? (Please read the description)
|
||||||
description: |
|
description: |
|
||||||
PLEASE! Make sure to check if this issue is a duplicate.
|
🚨 PLEASE! Make sure to check if this issue is a duplicate. 🚨
|
||||||
|
|
||||||
Don't waste our time, we are working hard to make Spotube better for you.
|
Don't waste our time, we are working hard to make Spotube better for you.
|
||||||
|
|
||||||
Try with multiple similar keywords, and check the closed issues too.
|
Try with multiple similar keywords, and check the closed issues too.
|
||||||
@ -60,7 +61,7 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Operating System
|
label: Operating System
|
||||||
description: The OS in which you used Spotube to face the issue.
|
description: The OS in which you used Spotube to face the issue. Use comma to separate multiple OS.
|
||||||
placeholder: Android, Linux, macOS or Windows? Make sure to include the version too.
|
placeholder: Android, Linux, macOS or Windows? Make sure to include the version too.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@ -96,7 +97,10 @@ body:
|
|||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Self grab
|
label: Self grab
|
||||||
description: If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. Any contributions are welcome!
|
description: |
|
||||||
|
If you are a developer and want to work on this issue yourself, you can check this box and wait for maintainer response. Any contributions are welcome!
|
||||||
|
|
||||||
|
This project is maintained by one person. So PRs are always welcome. This is the best way to get your issue fixed faster.
|
||||||
options:
|
options:
|
||||||
- label: I'm ready to work on this issue!
|
- label: I'm ready to work on this issue!
|
||||||
required: false
|
required: false
|
||||||
|
20
.github/workflows/pr-lint.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: 3.22.2
|
FLUTTER_VERSION: 3.29.2
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
@ -17,18 +17,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
|
||||||
|
- name: Dummy Envs
|
||||||
|
run: |
|
||||||
|
envsubst < .env.example > .env
|
||||||
|
env:
|
||||||
|
SPOTIFY_SECRETS: xxx:xxx
|
||||||
|
ENABLE_UPDATE_CHECK: true
|
||||||
|
LASTFM_API_KEY: xxx
|
||||||
|
LASTFM_API_SECRET: xxx
|
||||||
|
RELEASE_CHANNEL: nightly
|
||||||
|
HIDE_DONATIONS: 0
|
||||||
|
|
||||||
- name: Configure repo
|
- name: Configure repo
|
||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
|
||||||
dart run build_runner build --delete-conflicting-outputs
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Lint Dart files
|
- name: Lint Dart files
|
||||||
run: |
|
run: |
|
||||||
dart analyze --no-fatal-warnings
|
dart analyze --no-fatal-warnings
|
||||||
|
|
||||||
- name: Lint translations & config files
|
|
||||||
run: |
|
|
||||||
npm install -g @prantlf/jsonlint
|
|
||||||
jsonlint -q -D --enforce-double-quotes ./lib/l10n/*.arb
|
|
||||||
jsonlint -q -D --enforce-double-quotes -T .vscode/*.json
|
|
2
.github/workflows/spotube-publish-binary.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: Version to publish (x.x.x)
|
description: Version to publish (x.x.x)
|
||||||
default: 3.8.3
|
default: 4.0.0
|
||||||
required: true
|
required: true
|
||||||
dry_run:
|
dry_run:
|
||||||
description: Dry run
|
description: Dry run
|
||||||
|
87
.github/workflows/spotube-release-binary.yml
vendored
@ -20,7 +20,8 @@ on:
|
|||||||
description: Dry run without uploading to release
|
description: Dry run without uploading to release
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: 3.24.3
|
FLUTTER_VERSION: 3.29.2
|
||||||
|
FLUTTER_CHANNEL: master
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@ -30,64 +31,72 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-22.04
|
||||||
platform: linux
|
platform: linux
|
||||||
|
arch: x86
|
||||||
files: |
|
files: |
|
||||||
dist/Spotube-linux-x86_64.deb
|
dist/Spotube-linux-x86_64.deb
|
||||||
dist/Spotube-linux-x86_64.rpm
|
dist/Spotube-linux-x86_64.rpm
|
||||||
dist/spotube-linux-*-x86_64.tar.xz
|
dist/spotube-linux-*-x86_64.tar.xz
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-22.04-arm
|
||||||
platform: linux_arm
|
platform: linux
|
||||||
|
arch: arm64
|
||||||
files: |
|
files: |
|
||||||
dist/Spotube-linux-aarch64.deb
|
dist/Spotube-linux-aarch64.deb
|
||||||
dist/spotube-linux-*-aarch64.tar.xz
|
dist/spotube-linux-*-aarch64.tar.xz
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-22.04
|
||||||
platform: android
|
platform: android
|
||||||
|
arch: all
|
||||||
files: |
|
files: |
|
||||||
build/Spotube-android-all-arch.apk
|
build/Spotube-android-all-arch.apk
|
||||||
build/Spotube-playstore-all-arch.aab
|
build/Spotube-playstore-all-arch.aab
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
platform: windows
|
platform: windows
|
||||||
|
arch: x86
|
||||||
files: |
|
files: |
|
||||||
dist/Spotube-windows-x86_64.nupkg
|
dist/Spotube-windows-x86_64.nupkg
|
||||||
dist/Spotube-windows-x86_64-setup.exe
|
dist/Spotube-windows-x86_64-setup.exe
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
platform: ios
|
platform: ios
|
||||||
|
arch: all
|
||||||
files: |
|
files: |
|
||||||
Spotube-iOS.ipa
|
Spotube-iOS.ipa
|
||||||
- os: macos-14
|
- os: macos-14
|
||||||
platform: macos
|
platform: macos
|
||||||
|
arch: all
|
||||||
files: |
|
files: |
|
||||||
build/Spotube-macos-universal.dmg
|
build/Spotube-macos-universal.dmg
|
||||||
build/Spotube-macos-universal.pkg
|
build/Spotube-macos-universal.pkg
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: subosito/flutter-action@v2.12.0
|
- uses: subosito/flutter-action@v2.18.0
|
||||||
with:
|
with:
|
||||||
cache: true
|
|
||||||
cache-key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.yaml') }}
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
channel: ${{ env.FLUTTER_CHANNEL }}
|
||||||
|
cache: true
|
||||||
|
git-source: https://github.com/flutter/flutter.git
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
if: ${{matrix.platform == 'android'}}
|
if: ${{matrix.platform == 'android'}}
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: "zulu"
|
||||||
java-version: '17'
|
java-version: "17"
|
||||||
cache: 'gradle'
|
cache: "gradle"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Set up QEMU
|
|
||||||
if: ${{matrix.platform == 'linux_arm'}}
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
if: ${{matrix.platform == 'linux_arm'}}
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
if: ${{matrix.platform != 'linux_arm'}}
|
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
|
|
||||||
|
- name: Install Xcode
|
||||||
|
if: ${{matrix.platform == 'ios'}}
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: "16.1"
|
||||||
|
|
||||||
- name: Install ${{matrix.platform}} dependencies
|
- name: Install ${{matrix.platform}} dependencies
|
||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
@ -99,28 +108,16 @@ jobs:
|
|||||||
echo '${{ secrets.KEYSTORE }}' | base64 --decode > android/app/upload-keystore.jks
|
echo '${{ secrets.KEYSTORE }}' | base64 --decode > android/app/upload-keystore.jks
|
||||||
echo '${{ secrets.KEY_PROPERTIES }}' > android/key.properties
|
echo '${{ secrets.KEY_PROPERTIES }}' > android/key.properties
|
||||||
|
|
||||||
- name: Unessary hosted tools
|
|
||||||
if: ${{matrix.platform == 'linux_arm'}}
|
|
||||||
uses: jlumbroso/free-disk-space@main
|
|
||||||
with:
|
|
||||||
tool-cache: false
|
|
||||||
swap-storage: false
|
|
||||||
android: true
|
|
||||||
dotnet: true
|
|
||||||
haskell: true
|
|
||||||
large-packages: true
|
|
||||||
docker-images: true
|
|
||||||
|
|
||||||
- name: Build ${{matrix.platform}} binaries
|
- name: Build ${{matrix.platform}} binaries
|
||||||
run: dart cli/cli.dart build ${{matrix.platform}}
|
run: dart cli/cli.dart build --arch=${{matrix.arch}} ${{matrix.platform}}
|
||||||
env:
|
env:
|
||||||
CHANNEL: ${{inputs.channel}}
|
CHANNEL: ${{inputs.channel}}
|
||||||
DOTENV: ${{secrets.DOTENV_RELEASE}}
|
DOTENV: ${{secrets.DOTENV_RELEASE}}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
name: Spotube-Release-Binaries
|
name: ${{matrix.platform}}-${{matrix.arch}}
|
||||||
path: ${{matrix.files}}
|
path: ${{matrix.files}}
|
||||||
|
|
||||||
- name: Debug With SSH When fails
|
- name: Debug With SSH When fails
|
||||||
@ -130,14 +127,13 @@ jobs:
|
|||||||
limit-access-to-actor: true
|
limit-access-to-actor: true
|
||||||
|
|
||||||
upload:
|
upload:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
needs:
|
needs:
|
||||||
- build_platform
|
- build_platform
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Spotube-Release-Binaries
|
|
||||||
path: ./Spotube-Release-Binaries
|
path: ./Spotube-Release-Binaries
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@ -146,18 +142,19 @@ jobs:
|
|||||||
- name: Generate Checksums
|
- name: Generate Checksums
|
||||||
run: |
|
run: |
|
||||||
tree .
|
tree .
|
||||||
md5sum Spotube-Release-Binaries/* >> RELEASE.md5sum
|
find Spotube-Release-Binaries -type f -exec md5sum {} \; >> RELEASE.md5sum
|
||||||
sha256sum Spotube-Release-Binaries/* >> RELEASE.sha256sum
|
find Spotube-Release-Binaries -type f -exec sha256sum {} \; >> RELEASE.sha256sum
|
||||||
|
sed -i 's|Spotube-Release-Binaries/.*/\([^/]*\)$|\1|' RELEASE.sha256sum RELEASE.md5sum
|
||||||
sed -i 's|Spotube-Release-Binaries/||' RELEASE.sha256sum RELEASE.md5sum
|
sed -i 's|Spotube-Release-Binaries/||' RELEASE.sha256sum RELEASE.md5sum
|
||||||
|
|
||||||
- name: Extract pubspec version
|
- name: Extract pubspec version
|
||||||
run: |
|
run: |
|
||||||
echo "PUBSPEC_VERSION=$(grep -oP 'version:\s*\K[^+]+(?=\+)' pubspec.yaml)" >> $GITHUB_ENV
|
echo "PUBSPEC_VERSION=$(grep -oP 'version:\s*\K[^+]+(?=\+)' pubspec.yaml)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
name: Spotube-Release-Binaries
|
name: sums
|
||||||
path: |
|
path: |
|
||||||
RELEASE.md5sum
|
RELEASE.md5sum
|
||||||
RELEASE.sha256sum
|
RELEASE.sha256sum
|
||||||
@ -172,7 +169,7 @@ jobs:
|
|||||||
omitNameDuringUpdate: true
|
omitNameDuringUpdate: true
|
||||||
omitPrereleaseDuringUpdate: true
|
omitPrereleaseDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: Spotube-Release-Binaries/*,RELEASE.sha256sum,RELEASE.md5sum
|
artifacts: Spotube-Release-Binaries/**/*,RELEASE.sha256sum,RELEASE.md5sum
|
||||||
|
|
||||||
- name: Upload Release Binaries (nightly)
|
- name: Upload Release Binaries (nightly)
|
||||||
if: ${{ !inputs.dry_run && inputs.channel == 'nightly' }}
|
if: ${{ !inputs.dry_run && inputs.channel == 'nightly' }}
|
||||||
@ -184,9 +181,15 @@ jobs:
|
|||||||
omitNameDuringUpdate: true
|
omitNameDuringUpdate: true
|
||||||
omitPrereleaseDuringUpdate: true
|
omitPrereleaseDuringUpdate: true
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
artifacts: Spotube-Release-Binaries/*,RELEASE.sha256sum,RELEASE.md5sum
|
artifacts: Spotube-Release-Binaries/**/*,RELEASE.sha256sum,RELEASE.md5sum
|
||||||
body: |
|
body: |
|
||||||
Build Number: ${{github.run_number}}
|
Build Number: ${{github.run_number}}
|
||||||
|
|
||||||
Nightly release includes newest features but may contain bugs
|
Nightly release includes newest features but may contain bugs
|
||||||
It is preferred to use the stable version unless you know what you're doing
|
It is preferred to use the stable version unless you know what you're doing
|
||||||
|
|
||||||
|
- name: Debug With SSH When fails
|
||||||
|
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
||||||
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
with:
|
||||||
|
limit-access-to-actor: true
|
||||||
|
3
.gitignore
vendored
@ -80,3 +80,6 @@ tm.json
|
|||||||
|
|
||||||
# FVM Version Cache
|
# FVM Version Cache
|
||||||
.fvm/
|
.fvm/
|
||||||
|
|
||||||
|
android/build
|
||||||
|
android/app/.cxx
|
||||||
|
11
.vscode/launch.json
vendored
@ -30,6 +30,17 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "lib/main.dart",
|
"program": "lib/main.dart",
|
||||||
"flutterMode": "release"
|
"flutterMode": "release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spotube (mobile) (release)",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "lib/main.dart",
|
||||||
|
"flutterMode": "release",
|
||||||
|
"args": [
|
||||||
|
"--flavor",
|
||||||
|
"dev"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": []
|
"compounds": []
|
||||||
|
4
.vscode/settings.json
vendored
@ -9,10 +9,12 @@
|
|||||||
"fuzzywuzzy",
|
"fuzzywuzzy",
|
||||||
"gapless",
|
"gapless",
|
||||||
"instrumentalness",
|
"instrumentalness",
|
||||||
|
"isrc",
|
||||||
"Mpris",
|
"Mpris",
|
||||||
"RGBO",
|
"RGBO",
|
||||||
"riverpod",
|
"riverpod",
|
||||||
"Scrobblenaut",
|
"Scrobblenaut",
|
||||||
|
"shadcn",
|
||||||
"skeletonizer",
|
"skeletonizer",
|
||||||
"songlink",
|
"songlink",
|
||||||
"speechiness",
|
"speechiness",
|
||||||
@ -27,5 +29,5 @@
|
|||||||
"README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md",
|
"README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md",
|
||||||
"*.dart": "${capture}.g.dart,${capture}.freezed.dart"
|
"*.dart": "${capture}.g.dart,${capture}.freezed.dart"
|
||||||
},
|
},
|
||||||
"dart.flutterSdkPath": ".fvm/flutter_sdk"
|
"dart.flutterSdkPath": ".fvm/versions/3.29.2"
|
||||||
}
|
}
|
1181
CHANGELOG.md
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
BSD-4-Clause License
|
BSD-4-Clause License
|
||||||
|
|
||||||
Copyright (c) 2023 Kingkor Roy Tirtho. All rights reserved.
|
Copyright (c) 2025 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:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
10
Makefile
@ -43,3 +43,13 @@ apk:
|
|||||||
|
|
||||||
gensums:
|
gensums:
|
||||||
sh -c scripts/gensums.sh
|
sh -c scripts/gensums.sh
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
dart run drift_dev make-migrations
|
||||||
|
|
||||||
|
dmg:
|
||||||
|
flutter build macos &&\
|
||||||
|
if [ -f dist/Spotube-macos-universal.dmg ];\
|
||||||
|
then rm dist/Spotube-macos-universal.dmg;\
|
||||||
|
fi &&\
|
||||||
|
appdmg appdmg.json dist/Spotube-macos-universal.dmg
|
325
README.md
@ -1,9 +1,47 @@
|
|||||||
|
# 🚨 Spotube is banned from using "Spotify™ API" 🚨
|
||||||
|
|
||||||
|
### The developer of Spotube has received a cease and desist letter from Spotify USA Inc. and Spotify AB, asserting a legal threat concerning the distribution and development of any application that utilizes Spotify’s data API in conjunction with content from YouTube® to facilitate ad-free playback of music tracks. The letter contends that this specific use of the Spotify™ APIs contravenes the Spotify™ Agreements and may also infringe upon the rights of music rights holders.
|
||||||
|
|
||||||
|
### Consequently, as the official maintainer of Spotube, I will immediately cease all forms of official distribution and development of Spotube that continue to employ the aforementioned 'Spotify™ APIs'
|
||||||
|
|
||||||
|
### <ins>Their exact reasoning</ins>: (any) "uses of Spotify’s data API in connection with content from YouTube to provide ad-free playback of music tracks. The use of the Spotify APIs in this manner violates the Spotify Agreements and may also violate the rights of music rights holders."
|
||||||
|
|
||||||
|
## So what's now?
|
||||||
|
|
||||||
|
> In short, we are cooked (legally)
|
||||||
|
|
||||||
|
For now, I've to:
|
||||||
|
|
||||||
|
1. Stop distributing/developing Spotube/any app that uses "Spotify™ APIs"
|
||||||
|
|
||||||
|
That means, I can no longer distribute Spotube through the website, GitHub, any app store and immediately have to take down the versions that uses Spotify™ APIs.
|
||||||
|
|
||||||
|
1. Stop using their logo/image/name/intellectual property in a manner that "seems infringement"
|
||||||
|
1. Forever desist from aiding or assisting any other person or entity in the activities described above
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**For the users of Spotube:**
|
||||||
|
|
||||||
|
Don't worry, Spotube is banned only from (or assisting other) using those APIs. As long as the app isn't using them or no way helps anyone else to use them, it's ok.
|
||||||
|
|
||||||
|
In future, I'll try to rewrite Spotube to ensure it operates within the bounds of copyright law and platform policies. And give ways for the users to extend the app to their use cases. Work is already in progress to implement this! So expect some big updates soon!
|
||||||
|
|
||||||
|
But for eternity, you can't download versions of Spotube that still uses "Spotify™ APIs" from official means (website/Github/app stores). Those will be taken down.
|
||||||
|
|
||||||
|
**But newer version of Spotube that _doesn't_ use "Spotify™ APIs" will be available to replace those.**
|
||||||
|
|
||||||
|
That means, in the upcoming new versions, you will no longer be able to login with your "Spotify™ Account", access your saved playlists, albums, tracks, followed artists or perform any action on that account or anything that is from "Spotify™" or owned by "Spotify™" (yes the API public data (e.g. track metadata) as well) through Spotube.
|
||||||
|
|
||||||
|
**Conclusion:** I'm extremely sorry for this disruption to your day to day music listening experience. Spotube existed and it used by a large number of users because they find it better. And we'll continue to be better than others but legally\* from now on
|
||||||
|
|
||||||
|
> Spotube has no affiliation with Spotify™ or any of its subsidiaries.
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">
|
<img width="600" src="assets/spotube_banner.png" alt="Spotube Logo">
|
||||||
|
|
||||||
An open source, cross-platform Spotify client compatible across multiple platforms<br />
|
An open source, cross-platform music client<br />
|
||||||
utilizing Spotify's data API and YouTube, Piped.video or JioSaavn as an audio source,<br />
|
utilizing selected music provider API and YouTube®, Piped.video or JioSaavn as an audio source
|
||||||
eliminating the need for Spotify Premium
|
|
||||||
|
|
||||||
Btw it's not just another Electron app 😉
|
Btw it's not just another Electron app 😉
|
||||||
|
|
||||||
@ -19,15 +57,11 @@ Btw it's not just another Electron app 😉
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 🌃 Features
|
## 🌃 Features
|
||||||
|
|
||||||
- 🚫 No ads, thanks to the use of public & free Spotify and YT Music APIs¹
|
- 🚫 No ads, thanks to the use of public & free music metadata providers and YT Music APIs¹
|
||||||
- ⬇️ Freely downloadable tracks
|
- ⬇️ Freely downloadable tracks
|
||||||
- 🖥️ 📱 Cross-platform support
|
- 🖥️ 📱 Cross-platform support
|
||||||
- 🪶 Small size & less data usage
|
- 🪶 Small size & less data usage
|
||||||
@ -38,288 +72,19 @@ Btw it's not just another Electron app 😉
|
|||||||
- 📖 Open source/libre software
|
- 📖 Open source/libre software
|
||||||
- 🔉 Playback control is done locally, not on the server
|
- 🔉 Playback control is done locally, not on the server
|
||||||
|
|
||||||
**¹** It is still **recommended** to support creators by engaging with their YouTube channels/Spotify tracks (or preferably by buying their merch/concert tickets/physical media).
|
**¹** It is still **recommended** to support creators by engaging with their YouTube channels/tracks in music platforms (or preferably by buying their merch/concert tickets/physical media).
|
||||||
|
|
||||||
### ❌ Unsupported features
|
### ❌ Unsupported features
|
||||||
|
|
||||||
- 🗣️ **Spotify Shows & Podcasts:** Shows and Podcasts will <ins>**never be supported**</ins> because the audio tracks are <ins>_only_</ins> available on Spotify and accessing them would require Spotify Premium.
|
- 🗣️ **Shows & Podcasts:** Shows and Podcasts will <ins>**never be supported**</ins> because the audio tracks are <ins>_only_</ins> available on music providers and accessing them would require premium.
|
||||||
- 🎧 **Spotify Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8)
|
- 🎧 **Listen Along:** [Coming soon!](https://github.com/KRTirtho/spotube/issues/8)
|
||||||
|
|
||||||
## 📜 ⬇️ Installation guide
|
|
||||||
|
|
||||||
New versions usually release every 3-4 months.<br />
|
|
||||||
This handy table lists all the 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>
|
|
||||||
</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>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Android</td>
|
|
||||||
<td>
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=oss.krtirtho.spotube">
|
|
||||||
<img width="220" alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png">
|
|
||||||
</a>
|
|
||||||
<br>
|
|
||||||
<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>
|
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>AppImage</td>
|
|
||||||
<td>AppImage's lacking stability led to it's temporal removal. More information at https://github.com/KRTirtho/spotube/issues/1082</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>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>Macos - <a href="https://brew.sh">Homebrew</a></td>
|
|
||||||
<td>
|
|
||||||
<pre lang="bash">
|
|
||||||
brew tap krtirtho/apps
|
|
||||||
brew install --cask spotube
|
|
||||||
</pre>
|
|
||||||
</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>
|
|
||||||
|
|
||||||
### 🔄 Nightly Builds
|
|
||||||
|
|
||||||
Grab the latest nightly builds of Spotube [from the GitHub Releases](https://github.com/KRTirtho/spotube/releases/tag/nightly).
|
|
||||||
|
|
||||||
## 🕳️ Building from source
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
You can compile Spotube's source code by [following these instructions](CONTRIBUTION.md#your-first-code-contribution).
|
|
||||||
|
|
||||||
## 👥 The Spotube team
|
## 👥 The Spotube team
|
||||||
|
|
||||||
- [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer
|
- [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer
|
||||||
- [RaptaG](https://github.com/RaptaG) - The GitHub Moderator and Community Manager
|
|
||||||
- [Owen Connor](https://github.com/owencz1998) - The Cool Discord Moderator
|
|
||||||
- [Meenbeese](https://github.com/meenbeese) - The Android Developer
|
|
||||||
- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer
|
|
||||||
- [Rusty Apple](https://github.com/RustyApple) - The Mysterious Unknown Guy
|
|
||||||
|
|
||||||
## 💼 License
|
## 💼 License
|
||||||
|
|
||||||
Spotube is open source and licensed under the [BSD-4-Clause](/LICENSE) License.
|
Spotube is open source and licensed under the [BSD-4-Clause](/LICENSE) License.
|
||||||
|
|
||||||
If you are concerned, you can [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
|
If you are concerned, you can [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>
|
|
||||||
<h2><code>[Click to show]</code> 🙏 Services/Package/Plugin Credits</h2>
|
|
||||||
</summary>
|
|
||||||
|
|
||||||
### Services
|
|
||||||
1. [Flutter](https://flutter.dev) - Flutter transforms the app development process. Build, test, and deploy beautiful mobile, web, desktop, and embedded apps from a single codebase
|
|
||||||
1. [Spotify API](https://developer.spotify.com/documentation/web-api) - The Spotify Web API is a RESTful API that provides access to Spotify data
|
|
||||||
1. [Piped](https://piped-docs.kavin.rocks/) - Piped is a privacy friendly alternative YouTube frontend, which is efficient and scalable by design.
|
|
||||||
1. [YouTube](https://youtube.com/) - YouTube is an American online video-sharing platform headquartered in San Bruno, California. Three former PayPal employees—Chad Hurley, Steve Chen, and Jawed Karim—created the service in February 2005
|
|
||||||
1. [JioSaavn](https://www.jiosaavn.com) - JioSaavn is an Indian online music streaming service and a digital distributor of Bollywood, English and other regional Indian music across the world. Since it was founded in 2007 as Saavn, the company has acquired rights to over 5 crore (50 million) music tracks in 15 languages
|
|
||||||
1. [SongLink](https://song.link) - SongLink is a free smart link service that helps you share music with your audience. It's a one-stop-shop for creating smart links for music, podcasts, and other audio content
|
|
||||||
1. [LRCLib](https://lrclib.net/) - A public synced lyric API
|
|
||||||
1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
|
|
||||||
1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users
|
|
||||||
1. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux
|
|
||||||
1. [SponsorBlock](https://sponsor.ajay.app) - SponsorBlock is an open-source crowdsourced browser extension and open API for skipping sponsor segments in YouTube videos.
|
|
||||||
1. [Inno Setup](https://jrsoftware.org/isinfo.php) - Inno Setup is a free installer for Windows programs by Jordan Russell and Martijn Laan
|
|
||||||
1. [F-Droid](https://f-droid.org) - F-Droid is an installable catalogue of FOSS (Free and Open Source Software) applications for the Android platform. The client makes it easy to browse, install, and keep track of updates on your device
|
|
||||||
1. [LastFM](https://last.fm) - Last.fm is a music streaming and discovery platform that helps users discover and share new music. It tracks users' music listening habits across many devices and platforms.
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
1. [app_links](https://github.com/llfbandit/app_links) - Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).
|
|
||||||
1. [args](https://pub.dev/packages/args) - Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.
|
|
||||||
1. [async](https://pub.dev/packages/async) - Utility functions and classes related to the 'dart:async' library.
|
|
||||||
1. [audio_service_mpris](https://github.com/bdrazhzhov/audio-service-mpris) - audio_service platform interface supporting Media Player Remote Interfacing Specification.
|
|
||||||
1. [audio_service](https://pub.dev/packages/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. [auto_size_text](https://github.com/leisim/auto_size_text) - Flutter widget that automatically resizes text to fit perfectly within its bounds.
|
|
||||||
1. [bonsoir](https://bonsoir.skyost.eu) - A Zeroconf library that allows you to discover network services and to broadcast your own. Based on Apple Bonjour and Android NSD.
|
|
||||||
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
|
|
||||||
1. [buttons_tabbar](https://afonsoraposo.com) - A Flutter package that implements a TabBar where each label is a toggle button.
|
|
||||||
1. [cached_network_image](https://github.com/Baseflow/flutter_cached_network_image) - Flutter library to load and cache network images. Can also be used with placeholder and error widgets.
|
|
||||||
1. [catcher_2](https://github.com/ThexXTURBOXx/catcher_2) - Plugin for error catching which provides multiple handlers for dealing with errors when they are not caught by the developer.
|
|
||||||
1. [collection](https://pub.dev/packages/collection) - Collections and utilities functions and classes related to collections.
|
|
||||||
1. [crypto](https://pub.dev/packages/crypto) - Implementations of SHA, MD5, and HMAC cryptographic functions.
|
|
||||||
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. [custom_lint](https://pub.dev/packages/custom_lint) - Lint rules are a powerful way to improve the maintainability of a project. Custom Lint allows package authors and developers to easily write custom lint rules.
|
|
||||||
1. [dart_discord_rpc](https://github.com/alexmercerind/dart_discord_rpc) - Discord Rich Presence for Flutter & Dart apps & games.
|
|
||||||
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. [device_info_plus](https://plus.fluttercommunity.dev/) - Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on.
|
|
||||||
1. [dio](https://github.com/cfug/dio) - A powerful HTTP networking package,supports Interceptors,Aborting and canceling a request,Custom adapters, Transformers, etc.
|
|
||||||
1. [disable_battery_optimization](https://github.com/pvsvamsi/Disable-Battery-Optimizations) - Flutter plugin to check and disable battery optimizations. Also shows custom steps to disable the optimizations in devices like mi, xiaomi, samsung, oppo, huawei, oneplus etc
|
|
||||||
1. [draggable_scrollbar](https://github.com/fluttercommunity/flutter-draggable-scrollbar) - A scrollbar that can be dragged for quickly navigation through a vertical list. Additional option is showing label next to scrollthumb with information about current item.
|
|
||||||
1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration.
|
|
||||||
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
|
|
||||||
1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
|
|
||||||
1. [file_picker](https://github.com/miguelpruivo/plugins_flutter_file_picker) - A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.
|
|
||||||
1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI.
|
|
||||||
1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft.
|
|
||||||
1. [flutter_broadcasts](https://pub.dev/packages/flutter_broadcasts) - A plugin for sending and receiving broadcasts with Android intents and iOS notifications.
|
|
||||||
1. [flutter_cache_manager](https://github.com/Baseflow/flutter_cache_manager/tree/develop/flutter_cache_manager) - Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
|
|
||||||
1. [flutter_displaymode](https://github.com/ajinasokan/flutter_displaymode) - A Flutter plugin to set display mode (resolution, refresh rate) on Android platform. Allows to enable high refresh rate on supported devices.
|
|
||||||
1. [flutter_feather_icons](https://github.com/muj-programmer/flutter_feather_icons) - Feather is a collection of simply beautiful open source icons. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and usability.
|
|
||||||
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_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_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. [flutter_native_splash](https://pub.dev/packages/flutter_native_splash) - Customize Flutter's default white native splash screen with background color and splash image. Supports dark mode, full screen, and more.
|
|
||||||
1. [flutter_riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
|
|
||||||
1. [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) - Flutter Secure Storage provides API to store data in secure storage. Keychain is used in iOS, KeyStore based solution is used in Android.
|
|
||||||
1. [flutter_sharing_intent](https://github.com/bhagat-techind/flutter_sharing_intent.git) - A flutter plugin that allow flutter apps to receive photos, videos, text, urls or any other file types from another app.
|
|
||||||
1. [flutter_svg](https://pub.dev/packages/flutter_svg) - An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files.
|
|
||||||
1. [form_validator](https://github.com/TheMisir/form-validator) - Simplest form validation library for flutter's form field widgets
|
|
||||||
1. [freezed_annotation](https://pub.dev/packages/freezed_annotation) - Annotations for the freezed code-generator. This package does nothing without freezed too.
|
|
||||||
1. [freezed](https://pub.dev/packages/freezed) - Code generation for immutable classes that has a simple syntax/API without compromising on the features.
|
|
||||||
1. [fuzzywuzzy](https://github.com/sphericalkat/dart-fuzzywuzzy) - An implementation of the popular fuzzywuzzy package in Dart, to suit all your fuzzy string matching/searching needs!
|
|
||||||
1. [gap](https://github.com/letsar/gap) - Flutter widgets for easily adding gaps inside Flex widgets such as Columns and Rows or scrolling views.
|
|
||||||
1. [go_router](https://pub.dev/packages/go_router) - A declarative router for Flutter based on Navigation 2 supporting deep linking, data-driven routes and more
|
|
||||||
1. [google_fonts](https://pub.dev/packages/google_fonts) - A Flutter package to use fonts from fonts.google.com. Supports HTTP fetching, caching, and asset bundling.
|
|
||||||
1. [hive_flutter](https://github.com/hivedb/hive/tree/master/hive_flutter) - Extension for Hive. Makes it easier to use Hive in Flutter apps.
|
|
||||||
1. [hive_generator](https://github.com/hivedb/hive/tree/master/hive_generator) - Extension for Hive. Automatically generates TypeAdapters to store any class.
|
|
||||||
1. [hive](https://github.com/hivedb/hive/tree/master/hive) - Lightweight and blazing fast key-value database written in pure Dart. Strongly encrypted using AES-256.
|
|
||||||
1. [hooks_riverpod](https://riverpod.dev) - A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
|
|
||||||
1. [html_unescape](https://github.com/filiph/html_unescape) - A small library for un-escaping HTML. Supports all Named Character References, Decimal Character References and Hexadecimal Character References.
|
|
||||||
1. [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. [image_picker](https://pub.dev/packages/image_picker) - Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.
|
|
||||||
1. [intl](https://pub.dev/packages/intl) - Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
|
|
||||||
1. [introduction_screen](https://pub.dev/packages/introduction_screen) - Introduction/Onboarding package for flutter app with some customizations possibilities
|
|
||||||
1. [io](https://pub.dev/packages/io) - Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values.
|
|
||||||
1. [jiosaavn](https://github.com/KRTirtho/jiosaavn) - Unofficial API client for jiosaavn.com
|
|
||||||
1. [json_annotation](https://pub.dev/packages/json_annotation) - Classes and helper functions that support JSON code generation via the `json_serializable` package.
|
|
||||||
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
|
|
||||||
1. [local_notifier](https://github.com/leanflutter/local_notifier) - This plugin allows Flutter desktop apps to displaying local notifications.
|
|
||||||
1. [logger](https://pub.dev/packages/logger) - Small, easy to use and extensible logger which prints beautiful logs.
|
|
||||||
1. [lrc](https://pub.dev/packages/lrc) - A Dart-only package that creates, parses, and handles LRC, which is a format that stores song lyrics.
|
|
||||||
1. [media_kit_libs_audio](https://github.com/media-kit/media-kit.git) - package:media_kit audio (only) playback native libraries for all platforms.
|
|
||||||
1. [media_kit](https://github.com/media-kit/media-kit) - A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.
|
|
||||||
1. [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.
|
|
||||||
1. [palette_generator](https://pub.dev/packages/palette_generator) - Flutter package for generating palette colors from a source image.
|
|
||||||
1. [path_provider](https://pub.dev/packages/path_provider) - Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.
|
|
||||||
1. [path](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. [permission_handler](https://pub.dev/packages/permission_handler) - Permission plugin for Flutter. This plugin provides a cross-platform (iOS, Android) API to request and check permissions.
|
|
||||||
1. [piped_client](https://github.com/KRTirtho/piped_client) - API Client for piped.video
|
|
||||||
1. [popover](https://github.com/minikin/popover) - A popover is a transient view that appears above other content onscreen when you tap a control or in an area.
|
|
||||||
1. [process_run](https://github.com/tekartik/process_run.dart/blob/master/packages/process_run) - Process run helpers for Linux/Win/Mac and which like feature for finding executables.
|
|
||||||
1. [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. [riverpod_lint](https://riverpod.dev) - Riverpod_lint is a developer tool for users of Riverpod, designed to help stop common issues and simplify repetitive tasks.
|
|
||||||
1. [scrobblenaut](https://github.com/Nebulino/Scrobblenaut) - A deadly simple LastFM API Wrapper for Dart. So deadly simple that it's gonna hit the mark.
|
|
||||||
1. [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. [shelf_router](https://pub.dev/packages/shelf_router) - A convenient request router for the shelf web-framework, with support for URL-parameters, nested routers and routers generated from source annotations.
|
|
||||||
1. [shelf_web_socket](https://pub.dev/packages/shelf_web_socket) - A shelf handler that wires up a listener for every connection.
|
|
||||||
1. [shelf](https://pub.dev/packages/shelf) - A model for web server middleware that encourages composition and easy reuse.
|
|
||||||
1. [sidebarx](https://github.com/Frezyx/sidebarx) - flutter multiplatform navigation sidebar / side navigationbar / drawer widget
|
|
||||||
1. [simple_icons](https://teavelopment.com/) - The Simple Icon pack available as Flutter Icons. Provides over 1500 Free SVG icons for popular brands.
|
|
||||||
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. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort.
|
|
||||||
1. [sliver_tools](https://github.com/Kavantix) - A set of useful sliver tools that are missing from the flutter framework
|
|
||||||
1. [smtc_windows](https://github.com/KRTirtho/smtc_windows) - Windows `SystemMediaTransportControls` implementation for Flutter giving access to Windows OS Media Control applet.
|
|
||||||
1. [spotify](https://github.com/rinukkusu/spotify-dart) - An incomplete dart library for interfacing with the Spotify Web API.
|
|
||||||
1. [stroke_text](https://github.com/MohamedAbd0/stroke_text) - A Simple Flutter plugin for applying stroke (border) style to a text widget
|
|
||||||
1. [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. [timezone](https://pub.dev/packages/timezone) - Time zone database and time zone aware DateTime.
|
|
||||||
1. [titlebar_buttons](https://github.com/gtk-flutter/titlebar_buttons) - A package which provides most of the titlebar buttons from windows, linux and macos.
|
|
||||||
1. [tray_manager](https://github.com/leanflutter/tray_manager) - This plugin allows Flutter desktop apps to defines system tray.
|
|
||||||
1. [url_launcher](https://pub.dev/packages/url_launcher) - Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.
|
|
||||||
1. [uuid](https://pub.dev/packages/uuid) - RFC4122 (v1, v4, v5, v6, v7, v8) UUID Generator and Parser for Dart
|
|
||||||
1. [version](https://github.com/dartninja/version) - Provides a simple class for parsing and comparing semantic versions as defined by http://semver.org/
|
|
||||||
1. [very_good_infinite_list](https://github.com/VeryGoodOpenSource/very_good_infinite_list) - A library for easily displaying paginated data, created by Very Good Ventures. Great for activity feeds, news feeds, and more.
|
|
||||||
1. [visibility_detector](https://pub.dev/packages/visibility_detector) - A widget that detects the visibility of its child and notifies a callback.
|
|
||||||
1. [web_socket_channel](https://pub.dev/packages/web_socket_channel) - StreamChannel wrappers for WebSockets. Provides a cross-platform WebSocketChannel API, a cross-platform implementation of that API that communicates over an underlying StreamChannel.
|
|
||||||
1. [wikipedia_api](https://github.com/KRTirtho/wikipedia_api) - Wikipedia API for dart and flutter
|
|
||||||
1. [win32_registry](https://pub.dev/packages/win32_registry) - A package that provides a friendly Dart API for accessing the Windows Registry.
|
|
||||||
1. [window_manager](https://github.com/leanflutter/window_manager) - This plugin allows Flutter desktop apps to resizing and repositioning the window.
|
|
||||||
1. [xml](https://github.com/renggli/dart-xml) - A lightweight library for parsing, traversing, querying, transforming and building XML documents.
|
|
||||||
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.
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<div align="center"><h4>© Copyright Spotube 2024</h4></div>
|
|
||||||
|
@ -32,10 +32,9 @@ linter:
|
|||||||
analyzer:
|
analyzer:
|
||||||
errors:
|
errors:
|
||||||
invalid_annotation_target: ignore
|
invalid_annotation_target: ignore
|
||||||
plugins:
|
|
||||||
- custom_lint
|
|
||||||
exclude:
|
exclude:
|
||||||
- "**.freezed.dart"
|
- "**.freezed.dart"
|
||||||
- "**.g.dart"
|
- "**.g.dart"
|
||||||
- "**.gr.dart"
|
- "**.gr.dart"
|
||||||
- "**/generated_plugin_registrant.dart"
|
- "**/generated_plugin_registrant.dart"
|
||||||
|
- test/**/*.dart
|
||||||
|
@ -28,12 +28,17 @@ if (keystorePropertiesFile.exists()) {
|
|||||||
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
def composeVersion = "1.4.8"
|
||||||
compileSdkVersion 34
|
|
||||||
|
|
||||||
ndkVersion "25.1.8937393"
|
android {
|
||||||
|
namespace "oss.krtirtho.spotube"
|
||||||
|
|
||||||
|
compileSdkVersion 35
|
||||||
|
|
||||||
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
@ -46,10 +51,18 @@ android {
|
|||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose true
|
||||||
|
}
|
||||||
|
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion "$composeVersion" // Correlates with org.jetbrains.kotlin.android plugin in settings.gradle
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "oss.krtirtho.spotube"
|
applicationId "oss.krtirtho.spotube"
|
||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
targetSdkVersion 34
|
targetSdkVersion 35
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
@ -63,6 +76,7 @@ android {
|
|||||||
storePassword keystoreProperties['storePassword']
|
storePassword keystoreProperties['storePassword']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
@ -96,15 +110,30 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
resources.excludes += "DebugProbesKt.bin"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source '../..'
|
source '../..'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def glanceVersion = "1.1.1"
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
|
||||||
|
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||||
// other deps so just ignore
|
// other deps so just ignore
|
||||||
implementation 'com.android.support:multidex:2.0.1'
|
implementation 'com.android.support:multidex:2.0.1'
|
||||||
|
|
||||||
|
implementation "androidx.glance:glance-appwidget:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-appwidget-preview:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-preview:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-material3:$glanceVersion"
|
||||||
|
implementation "androidx.glance:glance-material:$glanceVersion"
|
||||||
|
implementation "androidx.work:work-runtime-ktx:2.8.1"
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3"
|
||||||
|
implementation 'com.google.code.gson:gson:2.11.0'
|
||||||
}
|
}
|
49
android/app/proguard-rules.pro
vendored
@ -1 +1,50 @@
|
|||||||
-keep class androidx.lifecycle.DefaultLifecycleObserver
|
-keep class androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
|
||||||
|
-keepnames class kotlinx.serialization.** { *; }
|
||||||
|
-keepnames class oss.krtirtho.spotube.glance.models.** { *; }
|
||||||
|
-keep @kotlinx.serialization.Serializable class *
|
||||||
|
-keepclassmembers class ** {
|
||||||
|
@kotlinx.serialization.* <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
## We don't need beans
|
||||||
|
-dontwarn java.beans.BeanDescriptor
|
||||||
|
-dontwarn java.beans.BeanInfo
|
||||||
|
-dontwarn java.beans.IntrospectionException
|
||||||
|
-dontwarn java.beans.Introspector
|
||||||
|
-dontwarn java.beans.PropertyDescriptor
|
||||||
|
|
||||||
|
## Rules for NewPipeExtractor
|
||||||
|
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
|
||||||
|
-keep class org.mozilla.javascript.** { *; }
|
||||||
|
-keep class org.mozilla.classfile.ClassFileWriter
|
||||||
|
-dontwarn org.mozilla.javascript.tools.**
|
||||||
|
|
||||||
|
-dontwarn javax.script.AbstractScriptEngine
|
||||||
|
-dontwarn javax.script.Bindings
|
||||||
|
-dontwarn javax.script.Compilable
|
||||||
|
-dontwarn javax.script.CompiledScript
|
||||||
|
-dontwarn javax.script.Invocable
|
||||||
|
-dontwarn javax.script.ScriptContext
|
||||||
|
-dontwarn javax.script.ScriptEngine
|
||||||
|
-dontwarn javax.script.ScriptEngineFactory
|
||||||
|
-dontwarn javax.script.ScriptException
|
||||||
|
-dontwarn javax.script.SimpleBindings
|
||||||
|
-dontwarn jdk.dynalink.CallSiteDescriptor
|
||||||
|
-dontwarn jdk.dynalink.DynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.DynamicLinkerFactory
|
||||||
|
-dontwarn jdk.dynalink.NamedOperation
|
||||||
|
-dontwarn jdk.dynalink.Namespace
|
||||||
|
-dontwarn jdk.dynalink.NamespaceOperation
|
||||||
|
-dontwarn jdk.dynalink.Operation
|
||||||
|
-dontwarn jdk.dynalink.RelinkableCallSite
|
||||||
|
-dontwarn jdk.dynalink.StandardNamespace
|
||||||
|
-dontwarn jdk.dynalink.StandardOperation
|
||||||
|
-dontwarn jdk.dynalink.linker.GuardedInvocation
|
||||||
|
-dontwarn jdk.dynalink.linker.GuardingDynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.linker.LinkRequest
|
||||||
|
-dontwarn jdk.dynalink.linker.LinkerServices
|
||||||
|
-dontwarn jdk.dynalink.linker.TypeBasedGuardingDynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.linker.support.CompositeTypeBasedGuardingDynamicLinker
|
||||||
|
-dontwarn jdk.dynalink.linker.support.Guards
|
||||||
|
-dontwarn jdk.dynalink.support.ChainedCallSite
|
@ -1,7 +1,19 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="oss.krtirtho.spotube">
|
|
||||||
<!-- Flutter needs it to communicate with the running application
|
<!-- Flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<application
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:fullBackupContent="false"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name_en"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
<!-- Disable Impeller -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
|
android:value="false" />
|
||||||
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@ -1,4 +1,4 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="oss.krtirtho.spotube">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
@ -17,28 +17,26 @@
|
|||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="${applicationName}"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:fullBackupContent="false"
|
android:fullBackupContent="false"
|
||||||
android:label="@string/app_name_en"
|
|
||||||
android:name="${applicationName}"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:usesCleartextTraffic="true"
|
android:label="@string/app_name_en"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
>
|
android:usesCleartextTraffic="true">
|
||||||
<!-- Enable Impeller -->
|
<!-- Enable Impeller -->
|
||||||
<!-- <meta-data
|
<!-- <meta-data
|
||||||
android:name="io.flutter.embedding.android.EnableImpeller"
|
android:name="io.flutter.embedding.android.EnableImpeller"
|
||||||
android:value="true" /> -->
|
android:value="false" /> -->
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:windowSoftInputMode="adjustResize">
|
||||||
android:hardwareAccelerated="true"
|
|
||||||
android:windowSoftInputMode="adjustResize"
|
|
||||||
>
|
|
||||||
<!--
|
<!--
|
||||||
Specifies an Android theme to apply to this Activity as soon as
|
Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@ -47,8 +45,8 @@
|
|||||||
-->
|
-->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme" />
|
||||||
/>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@ -56,12 +54,13 @@
|
|||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:scheme="https"
|
|
||||||
android:host="open.spotify.com"
|
android:host="open.spotify.com"
|
||||||
/>
|
android:scheme="https" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -72,23 +71,30 @@
|
|||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<!-- Accepts URIs that begin with "spotify:// -->
|
<!-- Accepts URIs that begin with "spotify:// -->
|
||||||
<data android:scheme="spotify" />
|
<data android:scheme="spotify" />
|
||||||
|
<data android:scheme="spotube" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<!-- AudioService Config -->
|
<!-- AudioService Config -->
|
||||||
<service android:name="com.ryanheise.audioservice.AudioService"
|
<service
|
||||||
android:foregroundServiceType="mediaPlayback"
|
android:name="com.ryanheise.audioservice.AudioService"
|
||||||
android:exported="true">
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="mediaPlayback">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
<receiver
|
||||||
|
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
@ -96,11 +102,40 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
<!-- =================== -->
|
<!-- =================== -->
|
||||||
|
|
||||||
<meta-data android:name="com.google.android.gms.car.application"
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.car.application"
|
||||||
android:resource="@xml/automotive_app_desc" />
|
android:resource="@xml/automotive_app_desc" />
|
||||||
|
|
||||||
|
<!-- Home Widget config -->
|
||||||
|
<receiver
|
||||||
|
android:name=".glance.HomePlayerWidgetReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/home_player_widget_config" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="es.antonborri.home_widget.action.BACKGROUND" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
<!-- =================== -->
|
||||||
|
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
@ -0,0 +1,207 @@
|
|||||||
|
package oss.krtirtho.spotube.glance
|
||||||
|
|
||||||
|
import HomeWidgetGlanceState
|
||||||
|
import HomeWidgetGlanceStateDefinition
|
||||||
|
import android.R
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Icon
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.glance.GlanceId
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.Image
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.LocalSize
|
||||||
|
import androidx.glance.action.ActionParameters
|
||||||
|
import androidx.glance.action.actionParametersOf
|
||||||
|
import androidx.glance.action.clickable
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.SizeMode
|
||||||
|
import androidx.glance.appwidget.action.ActionCallback
|
||||||
|
import androidx.glance.appwidget.action.actionRunCallback
|
||||||
|
import androidx.glance.appwidget.background
|
||||||
|
import androidx.glance.appwidget.components.CircleIconButton
|
||||||
|
import androidx.glance.appwidget.components.Scaffold
|
||||||
|
import androidx.glance.appwidget.cornerRadius
|
||||||
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.currentState
|
||||||
|
import androidx.glance.layout.Alignment
|
||||||
|
import androidx.glance.layout.Box
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.ContentScale
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxSize
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.padding
|
||||||
|
import androidx.glance.layout.size
|
||||||
|
import androidx.glance.preview.ExperimentalGlancePreviewApi
|
||||||
|
import androidx.glance.preview.Preview
|
||||||
|
import androidx.glance.state.GlanceStateDefinition
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
|
||||||
|
import es.antonborri.home_widget.actionStartActivity
|
||||||
|
import oss.krtirtho.spotube.MainActivity
|
||||||
|
import oss.krtirtho.spotube.glance.models.Track
|
||||||
|
import oss.krtirtho.spotube.glance.widgets.FlutterAssetImageProvider
|
||||||
|
import oss.krtirtho.spotube.glance.widgets.TrackDetailsView
|
||||||
|
import oss.krtirtho.spotube.glance.widgets.TrackProgress
|
||||||
|
|
||||||
|
val gson = Gson()
|
||||||
|
val serverAddressKey = ActionParameters.Key<String>("serverAddress")
|
||||||
|
|
||||||
|
class Breakpoints {
|
||||||
|
companion object {
|
||||||
|
val SMALL_SQUARE = DpSize(100.dp, 100.dp)
|
||||||
|
val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp)
|
||||||
|
val BIG_SQUARE = DpSize(250.dp, 250.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePlayerWidget : GlanceAppWidget() {
|
||||||
|
|
||||||
|
override val sizeMode = SizeMode.Responsive(
|
||||||
|
setOf(
|
||||||
|
Breakpoints.SMALL_SQUARE,
|
||||||
|
Breakpoints.HORIZONTAL_RECTANGLE,
|
||||||
|
Breakpoints.BIG_SQUARE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override val stateDefinition: GlanceStateDefinition<*>?
|
||||||
|
get() = HomeWidgetGlanceStateDefinition()
|
||||||
|
|
||||||
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
provideContent {
|
||||||
|
GlanceContent(context, currentState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalGlancePreviewApi::class)
|
||||||
|
@Preview(widthDp = 100, heightDp = 100)
|
||||||
|
@Composable
|
||||||
|
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
||||||
|
val prefs = currentState.preferences
|
||||||
|
val size = LocalSize.current
|
||||||
|
|
||||||
|
val activeTrackStr = prefs.getString("activeTrack", null)
|
||||||
|
|
||||||
|
val isPlaying = prefs.getBoolean("isPlaying", false)
|
||||||
|
val playbackServerAddress = prefs.getString("playbackServerAddress", null) ?: ""
|
||||||
|
|
||||||
|
var activeTrack: Track? = null
|
||||||
|
if (activeTrackStr != null) {
|
||||||
|
activeTrack = gson.fromJson(activeTrackStr, Track::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val playIcon = Icon.createWithResource(context, R.drawable.ic_media_play);
|
||||||
|
val pauseIcon = Icon.createWithResource(context, R.drawable.ic_media_pause);
|
||||||
|
val previousIcon = Icon.createWithResource(context, R.drawable.ic_media_previous);
|
||||||
|
val nextIcon = Icon.createWithResource(context, R.drawable.ic_media_next);
|
||||||
|
|
||||||
|
GlanceTheme {
|
||||||
|
Box(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.cornerRadius(8.dp)
|
||||||
|
.background(
|
||||||
|
color = GlanceTheme.colors.surface.getColor(context)
|
||||||
|
)
|
||||||
|
.clickable {
|
||||||
|
actionStartActivity<MainActivity>(context)
|
||||||
|
}
|
||||||
|
,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.background(
|
||||||
|
color =
|
||||||
|
GlanceTheme.colors.surface.getColor(context)
|
||||||
|
.copy(alpha = 0.5f),
|
||||||
|
)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {}
|
||||||
|
Column(
|
||||||
|
modifier = GlanceModifier.padding(top = 10.dp, start = 10.dp, end = 10.dp)
|
||||||
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.Vertical.CenterVertically) {
|
||||||
|
TrackDetailsView(activeTrack)
|
||||||
|
}
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
if (size != Breakpoints.SMALL_SQUARE) {
|
||||||
|
TrackProgress(prefs)
|
||||||
|
}
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Row(
|
||||||
|
modifier = GlanceModifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
|
||||||
|
) {
|
||||||
|
CircleIconButton(
|
||||||
|
imageProvider = ImageProvider(previousIcon),
|
||||||
|
contentDescription = "Previous",
|
||||||
|
onClick = actionRunCallback<PreviousAction>(
|
||||||
|
parameters = actionParametersOf(serverAddressKey to playbackServerAddress)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
CircleIconButton(
|
||||||
|
imageProvider =
|
||||||
|
if (isPlaying) ImageProvider(pauseIcon)
|
||||||
|
else ImageProvider(playIcon),
|
||||||
|
contentDescription = "Play/Pause",
|
||||||
|
onClick = actionRunCallback<PlayPauseAction>(
|
||||||
|
parameters = actionParametersOf(serverAddressKey to playbackServerAddress)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
CircleIconButton(
|
||||||
|
imageProvider = ImageProvider(nextIcon),
|
||||||
|
contentDescription = "Previous",
|
||||||
|
onClick = actionRunCallback<NextAction>(
|
||||||
|
parameters = actionParametersOf(
|
||||||
|
serverAddressKey to playbackServerAddress
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlayPauseAction : InteractiveAction("toggle-playback")
|
||||||
|
class NextAction : InteractiveAction("next")
|
||||||
|
class PreviousAction : InteractiveAction("previous")
|
||||||
|
|
||||||
|
|
||||||
|
abstract class InteractiveAction(val command: String) : ActionCallback {
|
||||||
|
override suspend fun onAction(
|
||||||
|
context: Context,
|
||||||
|
glanceId: GlanceId,
|
||||||
|
parameters: ActionParameters
|
||||||
|
) {
|
||||||
|
val serverAddress = parameters[serverAddressKey] ?: ""
|
||||||
|
|
||||||
|
Log.d("HomePlayerWidget", "Sending command $command to $serverAddress")
|
||||||
|
|
||||||
|
if (serverAddress == null || serverAddress.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
Uri.parse("spotube://playback/$command?serverAddress=$serverAddress")
|
||||||
|
)
|
||||||
|
backgroundIntent.send()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package oss.krtirtho.spotube.glance
|
||||||
|
|
||||||
|
import HomeWidgetGlanceWidgetReceiver
|
||||||
|
|
||||||
|
class HomePlayerWidgetReceiver : HomeWidgetGlanceWidgetReceiver<HomePlayerWidget>() {
|
||||||
|
override val glanceAppWidget = HomePlayerWidget()
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AlbumSimple(
|
||||||
|
@SerializedName("album_type")
|
||||||
|
val albumType: AlbumType?,
|
||||||
|
|
||||||
|
@SerializedName("available_markets")
|
||||||
|
val availableMarkets: List<Market>?,
|
||||||
|
|
||||||
|
val href: String?,
|
||||||
|
val id: String?,
|
||||||
|
val images: List<Image>?,
|
||||||
|
val name: String?,
|
||||||
|
|
||||||
|
@SerializedName("release_date")
|
||||||
|
val releaseDate: String?,
|
||||||
|
|
||||||
|
@SerializedName("release_date_precision")
|
||||||
|
val releaseDatePrecision: DatePrecision?,
|
||||||
|
|
||||||
|
val type: String?,
|
||||||
|
val uri: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class AlbumType {
|
||||||
|
album,
|
||||||
|
single,
|
||||||
|
compilation
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DatePrecision {
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
day
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Artist(
|
||||||
|
val href: String?,
|
||||||
|
val id: String?,
|
||||||
|
val name: String?,
|
||||||
|
val type: String?,
|
||||||
|
val uri: String?,
|
||||||
|
|
||||||
|
val followers: Followers?,
|
||||||
|
val genres: List<String>?,
|
||||||
|
val images: List<Image>?,
|
||||||
|
|
||||||
|
@SerializedName("popularity")
|
||||||
|
val popularity: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Followers(
|
||||||
|
val total: Int?
|
||||||
|
)
|
@ -0,0 +1,10 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Image(
|
||||||
|
val height: Int?,
|
||||||
|
val width: Int?,
|
||||||
|
val path: String,
|
||||||
|
)
|
@ -0,0 +1,37 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Track(
|
||||||
|
val album: AlbumSimple?, val artists: List<Artist>?,
|
||||||
|
|
||||||
|
@SerializedName("available_markets") val availableMarkets: List<Market>?,
|
||||||
|
|
||||||
|
@SerializedName("disc_number") val discNumber: Int?,
|
||||||
|
|
||||||
|
@SerializedName("duration_ms") val durationMs: Int,
|
||||||
|
|
||||||
|
val explicit: Boolean?, val href: String?, val id: String?,
|
||||||
|
|
||||||
|
@SerializedName("is_playable") val isPlayable: Boolean?,
|
||||||
|
|
||||||
|
val name: String?,
|
||||||
|
|
||||||
|
@SerializedName("popularity") val popularity: Int?,
|
||||||
|
|
||||||
|
@SerializedName("preview_url") val previewUrl: String?,
|
||||||
|
|
||||||
|
@SerializedName("track_number") val trackNumber: Int?,
|
||||||
|
|
||||||
|
val type: String?, val uri: String?
|
||||||
|
) {
|
||||||
|
val duration: kotlin.time.Duration
|
||||||
|
get() = durationMs.toLong().milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Market {
|
||||||
|
AD, AE, AF, AG, AI, AL, AM, AO, AQ, AR, AS, AT, AU, AW, AX, AZ, BA, BB, BD, BE, BF, BG, BH, BI, BJ, BL, BM, BN, BO, BQ, BR, BS, BT, BV, BW, BY, BZ, CA, CC, CD, CF, CG, CH, CI, CK, CL, CM, CN, CO, CR, CU, CV, CW, CX, CY, CZ, DE, DJ, DK, DM, DO, DZ, EC, EE, EG, EH, ER, ES, ET, FI, FJ, FK, FM, FO, FR, GA, GB, GD, GE, GF, GG, GH, GI, GL, GM, GN, GP, GQ, GR, GS, GT, GU, GW, GY, HK, HM, HN, HR, HT, HU, ID, IE, IL, IM, IN, IO, IQ, IR, IS, IT, JE, JM, JO, JP, KE, KG, KH, KI, KM, KN, KP, KR, KW, KY, KZ, LA, LB, LC, LI, LK, LR, LS, LT, LU, LV, LY, MA, MC, MD, ME, MF, MG, MH, MK, ML, MM, MN, MO, MP, MQ, MR, MS, MT, MU, MV, MW, MX, MY, MZ, NA, NC, NE, NF, NG, NI, NL, NO, NP, NR, NU, NZ, OM, PA, PE, PF, PG, PH, PK, PL, PM, PN, PR, PS, PT, PW, PY, QA, RE, RO, RS, RU, RW, SA, SB, SC, SD, SE, SG, SH, SI, SJ, SK, SL, SM, SN, SO, SR, SS, ST, SV, SX, SY, SZ, TC, TD, TF, TG, TH, TJ, TK, TL, TM, TN, TO, TR, TT, TV, TW, TZ, UA, UG, UM, US, UY, UZ, VA, VC, VE, VG, VI, VN, VU, WF, WS, XK, YE, YT, ZA, ZM, ZW,
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.util.Base64
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun Base64ImageProvider(base64: String): ImageProvider {
|
||||||
|
var bytes = Base64.decode(base64, Base64.DEFAULT);
|
||||||
|
|
||||||
|
var bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size);
|
||||||
|
|
||||||
|
return ImageProvider(bitmap)
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun FlutterAssetImageProvider(context: Context, path: String): ImageProvider {
|
||||||
|
var inputStream = context.assets.open("flutter_assets/$path")
|
||||||
|
|
||||||
|
return ImageProvider(
|
||||||
|
BitmapFactory.decodeStream(inputStream)
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.Image
|
||||||
|
import androidx.glance.ImageProvider
|
||||||
|
import androidx.glance.LocalContext
|
||||||
|
import androidx.glance.LocalSize
|
||||||
|
import androidx.glance.appwidget.cornerRadius
|
||||||
|
import androidx.glance.layout.Alignment
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.ContentScale
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.size
|
||||||
|
import androidx.glance.text.FontWeight
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import oss.krtirtho.spotube.glance.Breakpoints
|
||||||
|
import oss.krtirtho.spotube.glance.models.Track
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TrackDetailsView(activeTrack: Track?) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val size = LocalSize.current
|
||||||
|
|
||||||
|
val artistStr = activeTrack?.artists?.map { it.name }?.joinToString(", ") ?: "<No Artist>"
|
||||||
|
val imgLocalPath = activeTrack?.album?.images?.get(0)?.path;
|
||||||
|
val title = activeTrack?.name ?: "<No Track>"
|
||||||
|
|
||||||
|
|
||||||
|
Image(
|
||||||
|
provider =
|
||||||
|
if (imgLocalPath == null)
|
||||||
|
ImageProvider(
|
||||||
|
BitmapFactory.decodeResource(
|
||||||
|
context.resources,
|
||||||
|
android.R.drawable.ic_delete
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else ImageProvider(BitmapFactory.decodeFile(imgLocalPath)),
|
||||||
|
contentDescription = "Album Art",
|
||||||
|
modifier = GlanceModifier.cornerRadius(8.dp)
|
||||||
|
.size(
|
||||||
|
if (size.height < 200.dp) 50.dp
|
||||||
|
else 100.dp
|
||||||
|
),
|
||||||
|
contentScale = ContentScale.Fit
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = GlanceTheme.colors.onBackground
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (size != Breakpoints.SMALL_SQUARE) {
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Text(
|
||||||
|
text = artistStr,
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = GlanceTheme.colors.onBackground
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package oss.krtirtho.spotube.glance.widgets
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.LocalSize
|
||||||
|
import androidx.glance.appwidget.LinearProgressIndicator
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.size
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import oss.krtirtho.spotube.glance.Breakpoints
|
||||||
|
|
||||||
|
fun Duration.format(): String {
|
||||||
|
return this.toComponents { hour, minutes, seconds, nanoseconds ->
|
||||||
|
var paddedSeconds = seconds.toString().padStart(2, '0')
|
||||||
|
var paddedMinutes = minutes.toString().padStart(2, '0')
|
||||||
|
var paddedHour = hour.toString().padStart(2, '0')
|
||||||
|
if (hour == 0L) {
|
||||||
|
"$paddedMinutes:$paddedSeconds"
|
||||||
|
} else {
|
||||||
|
"$paddedHour:$paddedMinutes:$paddedSeconds"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TrackProgress(prefs: SharedPreferences) {
|
||||||
|
val size = LocalSize.current
|
||||||
|
val position = prefs.getInt("position", 0).seconds
|
||||||
|
var duration = prefs.getInt("duration", 0).seconds
|
||||||
|
|
||||||
|
var progress = position.inWholeSeconds.toFloat() / max(duration.inWholeSeconds.toFloat(), 1.0f)
|
||||||
|
|
||||||
|
var textStyle =
|
||||||
|
TextStyle(
|
||||||
|
color = GlanceTheme.colors.onBackground,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (size == Breakpoints.HORIZONTAL_RECTANGLE) {
|
||||||
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
|
Text(text = position.format(), style = textStyle)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = progress,
|
||||||
|
modifier = GlanceModifier.defaultWeight(),
|
||||||
|
color = GlanceTheme.colors.primary,
|
||||||
|
backgroundColor = GlanceTheme.colors.primaryContainer,
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Text(text = duration.format(), style = textStyle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = progress,
|
||||||
|
modifier = GlanceModifier.fillMaxWidth(),
|
||||||
|
color = GlanceTheme.colors.primary,
|
||||||
|
backgroundColor = GlanceTheme.colors.primaryContainer,
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.size(6.dp))
|
||||||
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
|
Text(text = position.format(), style = textStyle)
|
||||||
|
Spacer(modifier = GlanceModifier.defaultWeight())
|
||||||
|
Text(text = duration.format(), style = textStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 247 KiB |
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.0 MiB |
@ -6,7 +6,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item android:bottom="0dp">
|
||||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 247 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.0 MiB |
@ -6,7 +6,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item android:bottom="0dp">
|
||||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
<inset
|
||||||
|
android:drawable="@drawable/ic_launcher_foreground"
|
||||||
|
android:inset="16%" />
|
||||||
|
</foreground>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 23 KiB |
@ -0,0 +1,7 @@
|
|||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:initialLayout="@layout/glance_default_loading_layout"
|
||||||
|
android:minWidth="100dp"
|
||||||
|
android:minHeight="100dp"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:updatePeriodMillis="10000">
|
||||||
|
</appwidget-provider>
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.0 MiB |
@ -6,7 +6,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item android:bottom="0dp">
|
||||||
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
<bitmap android:gravity="bottom" android:src="@drawable/branding"/>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |