mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-08 16:27:31 +00:00
Merge branch 'dev' into feat/stats
This commit is contained in:
commit
db209ac449
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
build
|
||||||
|
dist
|
||||||
|
.dart_tool
|
||||||
|
.idea
|
||||||
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"flutterSdkVersion": "3.19.1",
|
"flutterSdkVersion": "3.19.5",
|
||||||
"flavors": {}
|
"flavors": {}
|
||||||
}
|
}
|
||||||
32
.github/Dockerfile
vendored
Normal file
32
.github/Dockerfile
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
ARG FLUTTER_VERSION
|
||||||
|
ARG BUILD_VERSION
|
||||||
|
|
||||||
|
FROM --platform=arm64 fischerscode/flutter-sudo:${FLUTTER_VERSION}
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN sudo apt-get update &&\
|
||||||
|
sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools desktop-file-utils libgdk-pixbuf2.0-dev fakeroot strace fuse libunwind-dev locate patchelf gir1.2-appindicator3-0.1 libappindicator3-1 libappindicator3-dev libsecret-1-0 libjsoncpp25 libsecret-1-dev libjsoncpp-dev libnotify-bin libnotify-dev mpv libmpv-dev rpm &&\
|
||||||
|
sudo rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN sudo chown -R $(whoami) /app
|
||||||
|
|
||||||
|
RUN flutter pub get &&\
|
||||||
|
flutter config --enable-linux-desktop &&\
|
||||||
|
flutter pub get &&\
|
||||||
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
|
RUN dart pub global activate flutter_distributor &&\
|
||||||
|
alias dpkg-deb="dpkg-deb --Zxz" &&\
|
||||||
|
flutter_distributor package --platform=linux --targets=deb &&\
|
||||||
|
flutter_distributor package --platform=linux --targets=rpm
|
||||||
|
|
||||||
|
|
||||||
|
RUN make tar VERSION=${BUILD_VERSION} ARCH=arm64 PKG_ARCH=aarch64
|
||||||
|
|
||||||
|
RUN mv build/spotube-linux-*-aarch64.tar.xz dist/ &&\
|
||||||
|
mv dist/**/spotube-*-linux.deb dist/Spotube-linux-aarch64.deb &&\
|
||||||
|
mv dist/**/spotube-*-linux.rpm dist/Spotube-linux-aarch64.rpm
|
||||||
2
.github/workflows/spotube-publish-binary.yml
vendored
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.1.0
|
default: 3.6.0
|
||||||
required: true
|
required: true
|
||||||
dry_run:
|
dry_run:
|
||||||
description: Dry run
|
description: Dry run
|
||||||
|
|||||||
78
.github/workflows/spotube-release-binary.yml
vendored
78
.github/workflows/spotube-release-binary.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: Version to release (x.x.x)
|
description: Version to release (x.x.x)
|
||||||
default: 3.4.1
|
default: 3.6.0
|
||||||
required: true
|
required: true
|
||||||
channel:
|
channel:
|
||||||
type: choice
|
type: choice
|
||||||
@ -26,7 +26,7 @@ on:
|
|||||||
default: true
|
default: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: '3.19.1'
|
FLUTTER_VERSION: '3.19.5'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows:
|
windows:
|
||||||
@ -70,7 +70,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
flutter config --enable-windows-desktop
|
flutter config --enable-windows-desktop
|
||||||
flutter pub get
|
flutter pub get
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Build Windows Executable
|
- name: Build Windows Executable
|
||||||
run: |
|
run: |
|
||||||
@ -156,7 +156,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
flutter config --enable-linux-desktop
|
flutter config --enable-linux-desktop
|
||||||
flutter pub get
|
flutter pub get
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Build Linux Packages
|
- name: Build Linux Packages
|
||||||
run: |
|
run: |
|
||||||
@ -206,6 +206,66 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
limit-access-to-actor: true
|
limit-access-to-actor: true
|
||||||
|
|
||||||
|
linux_arm:
|
||||||
|
runs-on: macos-14
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Docker
|
||||||
|
run: brew install --cask docker
|
||||||
|
|
||||||
|
- name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
||||||
|
if: ${{ inputs.channel == 'nightly' }}
|
||||||
|
run: |
|
||||||
|
brew install yq
|
||||||
|
yq -i '.version |= sub("\+\d+", "+${{ inputs.channel }}.")' pubspec.yaml
|
||||||
|
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
||||||
|
echo "BUILD_VERSION=${{ inputs.version }}+${{ inputs.channel }}.${{ github.run_number }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
|
||||||
|
- name: BUILD_VERSION Env (stable)
|
||||||
|
if: ${{ inputs.channel == 'stable' }}
|
||||||
|
run: |
|
||||||
|
echo "BUILD_VERSION=${{ inputs.version }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Get current date
|
||||||
|
id: date
|
||||||
|
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||||
|
|
||||||
|
- name: Replace Version in files
|
||||||
|
run: |
|
||||||
|
sed -i '' 's|%{{APPDATA_RELEASE}}%|<release version="${{ env.BUILD_VERSION }}" date="${{ steps.date.outputs.date }}" />|' linux/com.github.KRTirtho.Spotube.appdata.xml
|
||||||
|
|
||||||
|
- name: Create Stable .env
|
||||||
|
if: ${{ inputs.channel == 'stable' }}
|
||||||
|
run: echo '${{ secrets.DOTENV_RELEASE }}' > .env
|
||||||
|
|
||||||
|
- name: Create Nightly .env
|
||||||
|
if: ${{ inputs.channel == 'nightly' }}
|
||||||
|
run: echo '${{ secrets.DOTENV_NIGHTLY }}' > .env
|
||||||
|
|
||||||
|
- name: Build Linux Arm
|
||||||
|
run: |
|
||||||
|
docker build -t spotube-linux-arm -f .github/Dockerfile . --build-arg BUILD_VERSION=${{ env.BUILD_VERSION }} --build-arg FLUTTER_VERSION=${{ env.FLUTTER_VERSION }}
|
||||||
|
docker create --name spotube-linux-arm spotube-linux-arm
|
||||||
|
docker cp spotube-linux-arm:/app/dist .
|
||||||
|
docker rm -f spotube-linux-arm
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
if-no-files-found: error
|
||||||
|
name: Spotube-Release-Binaries
|
||||||
|
path: |
|
||||||
|
dist/Spotube-linux-aarch64.deb
|
||||||
|
dist/Spotube-linux-aarch64.rpm
|
||||||
|
dist/spotube-linux-nightly-aarch64.tar.xz
|
||||||
|
|
||||||
|
- name: Debug With SSH When fails
|
||||||
|
if: ${{ failure() && inputs.debug && inputs.channel == 'nightly' }}
|
||||||
|
uses: mxschmitt/action-tmate@v3
|
||||||
|
with:
|
||||||
|
limit-access-to-actor: true
|
||||||
|
|
||||||
|
|
||||||
android:
|
android:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -245,7 +305,7 @@ jobs:
|
|||||||
- name: Generate Secrets
|
- name: Generate Secrets
|
||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Sign Apk
|
- name: Sign Apk
|
||||||
run: |
|
run: |
|
||||||
@ -260,7 +320,7 @@ jobs:
|
|||||||
- name: Build Playstore AppBundle
|
- name: Build Playstore AppBundle
|
||||||
run: |
|
run: |
|
||||||
echo 'ENABLE_UPDATE_CHECK=0' >> .env
|
echo 'ENABLE_UPDATE_CHECK=0' >> .env
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
export MANIFEST=android/app/src/main/AndroidManifest.xml
|
export MANIFEST=android/app/src/main/AndroidManifest.xml
|
||||||
xmlstarlet ed -d '//meta-data[@android:name="com.google.android.gms.car.application"]' $MANIFEST > $MANIFEST.tmp
|
xmlstarlet ed -d '//meta-data[@android:name="com.google.android.gms.car.application"]' $MANIFEST > $MANIFEST.tmp
|
||||||
mv $MANIFEST.tmp $MANIFEST
|
mv $MANIFEST.tmp $MANIFEST
|
||||||
@ -283,7 +343,6 @@ jobs:
|
|||||||
limit-access-to-actor: true
|
limit-access-to-actor: true
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
|
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@ -317,7 +376,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
dart pub global activate flutter_distributor
|
dart pub global activate flutter_distributor
|
||||||
flutter pub get
|
flutter pub get
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Build Macos App
|
- name: Build Macos App
|
||||||
run: |
|
run: |
|
||||||
@ -381,7 +440,7 @@ jobs:
|
|||||||
- name: Generate Secrets
|
- name: Generate Secrets
|
||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
dart run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Build iOS iPA
|
- name: Build iOS iPA
|
||||||
run: |
|
run: |
|
||||||
@ -408,6 +467,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
|
- linux_arm
|
||||||
- android
|
- android
|
||||||
- macos
|
- macos
|
||||||
- iOS
|
- iOS
|
||||||
|
|||||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -24,5 +24,6 @@
|
|||||||
"explorer.fileNesting.patterns": {
|
"explorer.fileNesting.patterns": {
|
||||||
"pubspec.yaml": "pubspec.lock,analysis_options.yaml,.packages,.flutter-plugins,.flutter-plugins-dependencies,flutter_launcher_icons*.yaml,flutter_native_splash*.yaml",
|
"pubspec.yaml": "pubspec.lock,analysis_options.yaml,.packages,.flutter-plugins,.flutter-plugins-dependencies,flutter_launcher_icons*.yaml,flutter_native_splash*.yaml",
|
||||||
"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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
21
CHANGELOG.md
21
CHANGELOG.md
@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [3.6.0-0](https://github.com/krtirtho/spotube/compare/v3.5.0...v3.6.0-0) (2024-04-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add Spotify homepage personalized recommendations ([#1402](https://github.com/krtirtho/spotube/issues/1402)) ([9e25c74](https://github.com/krtirtho/spotube/commit/9e25c742d4e43e4e10d2b48afb8e6d90288ffa11))
|
||||||
|
* add user profile page ([39e97ee](https://github.com/krtirtho/spotube/commit/39e97eef34d87348a264843e145f31f82832d12e))
|
||||||
|
* **android:** Filter Device To Force High Frame Rate ([#880](https://github.com/krtirtho/spotube/issues/880)) ([6e41b10](https://github.com/krtirtho/spotube/commit/6e41b106fa989adee393d3ce2535e75446ad3eea))
|
||||||
|
* improved caching based on riverpod ([#1343](https://github.com/krtirtho/spotube/issues/1343)) ([6673e5a](https://github.com/krtirtho/spotube/commit/6673e5a8a86b9667cf9dbff9bb7c40ea6b7de771))
|
||||||
|
* LAN connect a.k.a control remote Spotube playback and local output device selection ([#1355](https://github.com/krtirtho/spotube/issues/1355)) ([68374ef](https://github.com/krtirtho/spotube/commit/68374efd3ec556f31b937e5b96920787b54eec78))
|
||||||
|
* **lyrics:** add LRCLIB lyrics provider as fallback ([5afe823](https://github.com/krtirtho/spotube/commit/5afe823abdb198340b55d138d8173d886a811632))
|
||||||
|
* search history support [#1236](https://github.com/krtirtho/spotube/issues/1236) ([82b1cfa](https://github.com/krtirtho/spotube/commit/82b1cfa0d775e3958c666280943a893c9113d468))
|
||||||
|
* **translations:** Add Czech translation ([#1401](https://github.com/krtirtho/spotube/issues/1401)) ([5a6b800](https://github.com/krtirtho/spotube/commit/5a6b80091259359bc38c4b91cd8cb496c4270fa4))
|
||||||
|
* **translations:** add Thai Language ([#1319](https://github.com/krtirtho/spotube/issues/1319)) ([b70f250](https://github.com/krtirtho/spotube/commit/b70f250e8d5137fd990787ec9e3d058126cf14f3)), closes [#1310](https://github.com/krtirtho/spotube/issues/1310) [#1311](https://github.com/krtirtho/spotube/issues/1311)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* instance of Artist bug [#1362](https://github.com/krtirtho/spotube/issues/1362) ([c8dd802](https://github.com/krtirtho/spotube/commit/c8dd8025ec96bd78ed77cae35f1429aa48c16fde))
|
||||||
|
* **playback:** sponsor block skips and stutters in same position ([0d080b7](https://github.com/krtirtho/spotube/commit/0d080b77b72529c0be5ebc27ace1c52307511f73))
|
||||||
|
|
||||||
## [3.5.0](https://github.com/krtirtho/spotube/compare/v3.4.1...v3.5.0) (2024-03-08)
|
## [3.5.0](https://github.com/krtirtho/spotube/compare/v3.4.1...v3.5.0) (2024-03-08)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
31
README.md
31
README.md
@ -97,12 +97,7 @@ This handy table lists all the methods you can use to install Spotube:
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>AppImage</td>
|
<td>AppImage</td>
|
||||||
<td>
|
<td>AppImage's lacking stability led to it's temporal removal. More information at https://github.com/KRTirtho/spotube/issues/1082</td>
|
||||||
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.AppImage">
|
|
||||||
<img width="220" alt="Download AppImage" src="https://user-images.githubusercontent.com/61944859/169455015-13385466-8901-48fe-ba90-b62d58b0be64.png">
|
|
||||||
</a>
|
|
||||||
<p><b>Note:</b> <a href="https://github.com/TheAssassin/AppImageLauncher">AppimageLauncher</a> is required!</p>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Debian/Ubuntu</td>
|
<td>Debian/Ubuntu</td>
|
||||||
@ -205,6 +200,7 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
|||||||
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. [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. [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. [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. [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. [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. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux
|
||||||
@ -233,9 +229,6 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
|||||||
1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration.
|
1. [duration](https://github.com/desktop-dart/duration) - Utilities to make working with 'Duration's easier. Formats duration in human readable form and also parses duration in human readable form to Dart's Duration.
|
||||||
1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
|
1. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
|
||||||
1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI.
|
1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI.
|
||||||
1. [fl_query](https://fl-query.krtirtho.dev) - Asynchronous data caching, refetching & invalidation library for Flutter
|
|
||||||
1. [fl_query_hooks](https://fl-query.krtirtho.dev) - Elite flutter_hooks compatible library for fl_query, the Asynchronous data caching, refetching & invalidation library for Flutter
|
|
||||||
1. [fl_query_devtools](https://fl-query.krtirtho.dev) - Devtools support for Fl-Query
|
|
||||||
1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft.
|
1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft.
|
||||||
1. [flutter_cache_manager](https://github.com/Baseflow/flutter_cache_manager/tree/develop/flutter_cache_manager) - Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
|
1. [flutter_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_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.
|
||||||
@ -257,7 +250,7 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
|||||||
1. [http](https://pub.dev/packages/http) - A composable, multi-platform, Future-based API for HTTP requests.
|
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. [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. [intl](https://pub.dev/packages/intl) - Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
|
||||||
1. [introduction_screen](https://github.com/pyozer/introduction_screen) - Introduction/Onboarding package for flutter app with some customizations possibilities
|
1. [introduction_screen](https://pub.dev/packages/introduction_screen) - Introduction/Onboarding package for flutter app with some customizations possibilities
|
||||||
1. [json_annotation](https://pub.dev/packages/json_annotation) - Classes and helper functions that support JSON code generation via the `json_serializable` package.
|
1. [json_annotation](https://pub.dev/packages/json_annotation) - Classes and helper functions that support JSON code generation via the `json_serializable` package.
|
||||||
1. [logger](https://pub.dev/packages/logger) - Small, easy to use and extensible logger which prints beautiful logs.
|
1. [logger](https://pub.dev/packages/logger) - Small, easy to use and extensible logger which prints beautiful logs.
|
||||||
1. [media_kit](https://github.com/media-kit/media-kit) - A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.
|
1. [media_kit](https://github.com/media-kit/media-kit) - A cross-platform video player & audio player for Flutter & Dart. Performant, stable, feature-proof & modular.
|
||||||
@ -295,22 +288,32 @@ If you are concerned, you can [read the reason of choosing this license](https:/
|
|||||||
1. [wikipedia_api](https://github.com/KRTirtho/wikipedia_api) - Wikipedia API for dart and flutter
|
1. [wikipedia_api](https://github.com/KRTirtho/wikipedia_api) - Wikipedia API for dart and flutter
|
||||||
1. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort.
|
1. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort.
|
||||||
1. [app_links](https://github.com/llfbandit/app_links) - Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).
|
1. [app_links](https://github.com/llfbandit/app_links) - Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).
|
||||||
1. [win32_registry](https://win32.pub) - A package that provides a friendly Dart API for accessing the Windows Registry.
|
1. [win32_registry](https://pub.dev/packages/win32_registry) - A package that provides a friendly Dart API for accessing the Windows Registry.
|
||||||
1. [flutter_sharing_intent](https://github.com/bhagat-techind/flutter_sharing_intent.git) - A flutter plugin that allow flutter apps to receive photos, videos, text, urls or any other file types from another app.
|
1. [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_broadcasts](https://pub.dev/packages/flutter_broadcasts) - A plugin for sending and receiving broadcasts with Android intents and iOS notifications.
|
1. [flutter_broadcasts](https://pub.dev/packages/flutter_broadcasts) - A plugin for sending and receiving broadcasts with Android intents and iOS notifications.
|
||||||
1. [freezed_annotation](https://pub.dev/packages/freezed_annotation) - Annotations for the freezed code-generator. This package does nothing without freezed too.
|
1. [freezed_annotation](https://pub.dev/packages/freezed_annotation) - Annotations for the freezed code-generator. This package does nothing without freezed too.
|
||||||
1. [spotify](https://github.com/rinukkusu/spotify-dart) - An incomplete dart library for interfacing with the Spotify Web API.
|
1. [spotify](https://github.com/rinukkusu/spotify-dart) - An incomplete dart library for interfacing with the Spotify Web API.
|
||||||
|
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. [shelf](https://pub.dev/packages/shelf) - A model for web server middleware that encourages composition and easy reuse.
|
||||||
|
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. [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. [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. [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. [timezone](https://pub.dev/packages/timezone) - Time zone database and time zone aware DateTime.
|
||||||
|
1. [crypto](https://pub.dev/packages/crypto) - Implementations of SHA, MD5, and HMAC cryptographic functions.
|
||||||
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
|
1. [build_runner](https://pub.dev/packages/build_runner) - A build system for Dart code generation and modular compilation.
|
||||||
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
|
1. [envied_generator](https://github.com/petercinibulk/envied) - Generator for the Envied package. See https://pub.dev/packages/envied.
|
||||||
1. [flutter_distributor](https://distributor.leanflutter.org) - A complete tool for packaging and publishing your Flutter apps.
|
1. [flutter_distributor](https://distributor.leanflutter.dev) - A complete tool for packaging and publishing your Flutter apps.
|
||||||
1. [flutter_gen_runner](https://github.com/FlutterGen/flutter_gen) - The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
|
1. [flutter_gen_runner](https://github.com/FlutterGen/flutter_gen) - The Flutter code generator for your assets, fonts, colors, … — Get rid of all String-based APIs.
|
||||||
1. [flutter_launcher_icons](https://github.com/fluttercommunity/flutter_launcher_icons) - A package which simplifies the task of updating your Flutter app's launcher icon.
|
1. [flutter_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_lints](https://pub.dev/packages/flutter_lints) - Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.
|
||||||
1. [hive_generator](https://github.com/hivedb/hive/tree/master/hive_generator) - Extension for Hive. Automatically generates TypeAdapters to store any class.
|
1. [hive_generator](https://github.com/hivedb/hive/tree/master/hive_generator) - Extension for Hive. Automatically generates TypeAdapters to store any class.
|
||||||
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
|
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
|
||||||
1. [pub_api_client](https://github.com/leoafarias/pub_api_client) - An API Client for Pub to interact with public package information.
|
|
||||||
1. [pubspec_parse](https://pub.dev/packages/pubspec_parse) - Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.
|
|
||||||
1. [freezed](https://pub.dev/packages/freezed) - Code generation for immutable classes that has a simple syntax/API without compromising on the features.
|
1. [freezed](https://pub.dev/packages/freezed) - Code generation for immutable classes that has a simple syntax/API without compromising on the features.
|
||||||
|
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. [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. [flutter_desktop_tools](https://github.com/KRTirtho/flutter_desktop_tools) - Essential collection of tools for flutter desktop app development
|
1. [flutter_desktop_tools](https://github.com/KRTirtho/flutter_desktop_tools) - Essential collection of tools for flutter desktop app development
|
||||||
1. [piped_client](https://github.com/KRTirtho/piped_client) - API Client for piped.video
|
1. [piped_client](https://github.com/KRTirtho/piped_client) - API Client for piped.video
|
||||||
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. [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,5 +1,5 @@
|
|||||||
import 'package:envied/envied.dart';
|
import 'package:envied/envied.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
part 'env.g.dart';
|
part 'env.g.dart';
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ abstract class Env {
|
|||||||
static final String _enableUpdateChecker = _Env._enableUpdateChecker;
|
static final String _enableUpdateChecker = _Env._enableUpdateChecker;
|
||||||
|
|
||||||
static bool get enableUpdateChecker =>
|
static bool get enableUpdateChecker =>
|
||||||
DesktopTools.platform.isFlatpak || _enableUpdateChecker == "1";
|
kIsFlatpak || _enableUpdateChecker == "1";
|
||||||
|
|
||||||
static String discordAppId = "1176718791388975124";
|
static String discordAppId = "1176718791388975124";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:win32_registry/win32_registry.dart';
|
import 'package:win32_registry/win32_registry.dart';
|
||||||
|
|
||||||
Future<void> registerWindowsScheme(String scheme) async {
|
Future<void> registerWindowsScheme(String scheme) async {
|
||||||
if (!DesktopTools.platform.isWindows) return;
|
if (!kIsWindows) return;
|
||||||
String appPath = Platform.resolvedExecutable;
|
String appPath = Platform.resolvedExecutable;
|
||||||
|
|
||||||
String protocolRegKey = 'Software\\Classes\\$scheme';
|
String protocolRegKey = 'Software\\Classes\\$scheme';
|
||||||
|
|||||||
@ -157,10 +157,10 @@ abstract class LanguageLocals {
|
|||||||
// name: "Croatian",
|
// name: "Croatian",
|
||||||
// nativeName: "hrvatski",
|
// nativeName: "hrvatski",
|
||||||
// ),
|
// ),
|
||||||
// "cs": const ISOLanguageName(
|
"cs": const ISOLanguageName(
|
||||||
// name: "Czech",
|
name: "Czech",
|
||||||
// nativeName: "česky, čeština",
|
nativeName: "česky, čeština",
|
||||||
// ),
|
),
|
||||||
// "da": const ISOLanguageName(
|
// "da": const ISOLanguageName(
|
||||||
// name: "Danish",
|
// name: "Danish",
|
||||||
// nativeName: "dansk",
|
// nativeName: "dansk",
|
||||||
|
|||||||
@ -52,52 +52,58 @@ class ConnectDeviceButton extends HookConsumerWidget {
|
|||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
fit: StackFit.loose,
|
fit: StackFit.loose,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Material(
|
||||||
child: InkWell(
|
type: MaterialType.transparency,
|
||||||
onTap: () {
|
child: Center(
|
||||||
ServiceUtils.push(context, "/connect");
|
child: ClipRect(
|
||||||
},
|
clipBehavior: Clip.hardEdge,
|
||||||
borderRadius: BorderRadius.circular(50),
|
child: InkWell(
|
||||||
child: Ink(
|
onTap: () {
|
||||||
decoration: BoxDecoration(
|
ServiceUtils.push(context, "/connect");
|
||||||
|
},
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
color: colorScheme.primaryContainer,
|
child: Ink(
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
padding:
|
borderRadius: BorderRadius.circular(50),
|
||||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
color: colorScheme.primaryContainer,
|
||||||
child: Row(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
padding:
|
||||||
children: [
|
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
if (connectClients.asData?.value.resolvedService !=
|
child: Row(
|
||||||
null) ...[
|
mainAxisSize: MainAxisSize.min,
|
||||||
Container(
|
children: [
|
||||||
width: 7,
|
if (connectClients.asData?.value.resolvedService !=
|
||||||
height: 7,
|
null) ...[
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
color: Colors.greenAccent,
|
width: 7,
|
||||||
borderRadius: BorderRadius.circular(50),
|
height: 7,
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
),
|
color: Colors.greenAccent,
|
||||||
const Gap(5),
|
borderRadius: BorderRadius.circular(50),
|
||||||
],
|
),
|
||||||
Text(context.l10n.devices),
|
),
|
||||||
if (connectClients.asData?.value.services.isNotEmpty ==
|
const Gap(5),
|
||||||
true)
|
],
|
||||||
Text(
|
Text(context.l10n.devices),
|
||||||
" (${connectClients.asData?.value.services.length})",
|
if (connectClients.asData?.value.services.isNotEmpty ==
|
||||||
style: TextStyle(
|
true)
|
||||||
color:
|
Text(
|
||||||
colorScheme.onPrimaryContainer.withOpacity(0.5),
|
" (${connectClients.asData?.value.services.length})",
|
||||||
),
|
style: TextStyle(
|
||||||
),
|
color: colorScheme.onPrimaryContainer
|
||||||
const Gap(35),
|
.withOpacity(0.5),
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
const Gap(35),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: -3,
|
||||||
child: IconButton.filled(
|
child: IconButton.filled(
|
||||||
icon: const Icon(SpotubeIcons.speaker),
|
icon: const Icon(SpotubeIcons.speaker),
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
|
|||||||
@ -16,7 +16,6 @@ class TokenLoginForm extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final authenticationNotifier = ref.watch(authenticationProvider.notifier);
|
final authenticationNotifier = ref.watch(authenticationProvider.notifier);
|
||||||
final directCodeController = useTextEditingController();
|
final directCodeController = useTextEditingController();
|
||||||
final mounted = useIsMounted();
|
|
||||||
|
|
||||||
final isLoading = useState(false);
|
final isLoading = useState(false);
|
||||||
|
|
||||||
@ -57,7 +56,7 @@ class TokenLoginForm extends HookConsumerWidget {
|
|||||||
await AuthenticationCredentials.fromCookie(
|
await AuthenticationCredentials.fromCookie(
|
||||||
cookieHeader),
|
cookieHeader),
|
||||||
);
|
);
|
||||||
if (mounted()) {
|
if (context.mounted) {
|
||||||
onDone?.call();
|
onDone?.call();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/components/home/sections/friends/friend_item.dart';
|
import 'package:spotube/components/home/sections/friends/friend_item.dart';
|
||||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/models/spotify_friends.dart';
|
import 'package:spotube/models/spotify_friends.dart';
|
||||||
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class HomePageFriendsSection extends HookConsumerWidget {
|
class HomePageFriendsSection extends HookConsumerWidget {
|
||||||
@ -14,6 +16,7 @@ class HomePageFriendsSection extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final auth = ref.watch(authenticationProvider);
|
||||||
final friendsQuery = ref.watch(friendsProvider);
|
final friendsQuery = ref.watch(friendsProvider);
|
||||||
final friends =
|
final friends =
|
||||||
friendsQuery.asData?.value.friends ?? FakeData.friends.friends;
|
friendsQuery.asData?.value.friends ?? FakeData.friends.friends;
|
||||||
@ -27,32 +30,36 @@ class HomePageFriendsSection extends HookConsumerWidget {
|
|||||||
xxl: 7,
|
xxl: 7,
|
||||||
);
|
);
|
||||||
|
|
||||||
final friendGroup = friends.fold<List<List<SpotifyFriendActivity>>>(
|
final friendGroup = useMemoized(
|
||||||
[],
|
() => friends.fold<List<List<SpotifyFriendActivity>>>(
|
||||||
(previousValue, element) {
|
[],
|
||||||
if (previousValue.isEmpty) {
|
(previousValue, element) {
|
||||||
|
if (previousValue.isEmpty) {
|
||||||
|
return [
|
||||||
|
[element]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
final lastGroup = previousValue.last;
|
||||||
|
if (lastGroup.length < groupCount) {
|
||||||
|
return [
|
||||||
|
...previousValue.sublist(0, previousValue.length - 1),
|
||||||
|
[...lastGroup, element]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
...previousValue,
|
||||||
[element]
|
[element]
|
||||||
];
|
];
|
||||||
}
|
},
|
||||||
|
),
|
||||||
final lastGroup = previousValue.last;
|
[friends, groupCount],
|
||||||
if (lastGroup.length < groupCount) {
|
|
||||||
return [
|
|
||||||
...previousValue.sublist(0, previousValue.length - 1),
|
|
||||||
[...lastGroup, element]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
...previousValue,
|
|
||||||
[element]
|
|
||||||
];
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (friendsQuery.isLoading ||
|
if (friendsQuery.isLoading ||
|
||||||
friendsQuery.asData?.value.friends.isEmpty == true) {
|
friendsQuery.asData?.value.friends.isEmpty == true ||
|
||||||
|
auth == null) {
|
||||||
return const SliverToBoxAdapter(
|
return const SliverToBoxAdapter(
|
||||||
child: SizedBox.shrink(),
|
child: SizedBox.shrink(),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,17 +2,17 @@ import 'package:flutter/material.dart' hide Image;
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/album/album_card.dart';
|
import 'package:spotube/components/album/album_card.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/not_found.dart';
|
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
import 'package:spotube/extensions/album_simple.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
@ -50,71 +50,65 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
return const AnonymousFallback();
|
return const AnonymousFallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
final theme = Theme.of(context);
|
return SafeArea(
|
||||||
|
child: Scaffold(
|
||||||
return RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
ref.invalidate(favoriteAlbumsProvider);
|
ref.invalidate(favoriteAlbumsProvider);
|
||||||
},
|
},
|
||||||
child: SafeArea(
|
child: InterScrollbar(
|
||||||
child: Scaffold(
|
controller: controller,
|
||||||
appBar: PreferredSize(
|
child: CustomScrollView(
|
||||||
preferredSize: const Size.fromHeight(50),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
|
||||||
child: ColoredBox(
|
|
||||||
color: theme.scaffoldBackgroundColor,
|
|
||||||
child: SearchBar(
|
|
||||||
onChanged: (value) => searchText.value = value,
|
|
||||||
leading: const Icon(SpotubeIcons.filter),
|
|
||||||
hintText: context.l10n.filter_albums,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: SizedBox.expand(
|
|
||||||
child: InterScrollbar(
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: SingleChildScrollView(
|
slivers: [
|
||||||
padding: const EdgeInsets.all(8.0),
|
SliverAppBar(
|
||||||
controller: controller,
|
floating: true,
|
||||||
child: Skeletonizer(
|
flexibleSpace: Padding(
|
||||||
enabled: albumsQuery.isLoading,
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Center(
|
child: SearchBar(
|
||||||
child: Wrap(
|
onChanged: (value) => searchText.value = value,
|
||||||
runSpacing: 20,
|
leading: const Icon(SpotubeIcons.filter),
|
||||||
alignment: WrapAlignment.center,
|
hintText: context.l10n.filter_albums,
|
||||||
runAlignment: WrapAlignment.center,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
children: [
|
|
||||||
if (albumsQuery.asData?.value == null ||
|
|
||||||
albumsQuery.asData!.value.items.isEmpty)
|
|
||||||
...List.generate(
|
|
||||||
10,
|
|
||||||
(index) => AlbumCard(FakeData.album),
|
|
||||||
)
|
|
||||||
else if (albums.isEmpty)
|
|
||||||
const Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [NotFound()],
|
|
||||||
),
|
|
||||||
for (final album in albums) AlbumCard(album.toAlbum()),
|
|
||||||
if (albums.isNotEmpty &&
|
|
||||||
albumsQuery.asData?.value.hasMore == true)
|
|
||||||
Skeletonizer(
|
|
||||||
enabled: true,
|
|
||||||
child: Waypoint(
|
|
||||||
controller: controller,
|
|
||||||
isGrid: true,
|
|
||||||
onTouchEdge: albumsQueryNotifier.fetchMore,
|
|
||||||
child: AlbumCard(FakeData.album),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SliverGap(10),
|
||||||
|
Skeletonizer.sliver(
|
||||||
|
enabled: albumsQuery.isLoading,
|
||||||
|
child: SliverLayoutBuilder(builder: (context, constrains) {
|
||||||
|
return SliverGrid.builder(
|
||||||
|
itemCount: albums.isEmpty ? 6 : albums.length + 1,
|
||||||
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 200,
|
||||||
|
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (albums.isNotEmpty && index == albums.length) {
|
||||||
|
if (albumsQuery.asData?.value.hasMore != true) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Waypoint(
|
||||||
|
controller: controller,
|
||||||
|
isGrid: true,
|
||||||
|
onTouchEdge: albumsQueryNotifier.fetchMore,
|
||||||
|
child: Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
child: AlbumCard(FakeData.albumSimple),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AlbumCard(
|
||||||
|
albums.elementAtOrNull(index) ?? FakeData.albumSimple,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
@ -9,8 +10,9 @@ import 'package:spotube/collections/fake.dart';
|
|||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/artist/artist_card.dart';
|
import 'package:spotube/components/artist/artist_card.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/not_found.dart';
|
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
@ -20,10 +22,10 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
final auth = ref.watch(authenticationProvider);
|
final auth = ref.watch(authenticationProvider);
|
||||||
|
|
||||||
final artistQuery = ref.watch(followedArtistsProvider);
|
final artistQuery = ref.watch(followedArtistsProvider);
|
||||||
|
final artistQueryNotifier = ref.watch(followedArtistsProvider.notifier);
|
||||||
|
|
||||||
final searchText = useState('');
|
final searchText = useState('');
|
||||||
|
|
||||||
@ -50,77 +52,73 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
return const AnonymousFallback();
|
return const AnonymousFallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return SafeArea(
|
||||||
appBar: PreferredSize(
|
child: Scaffold(
|
||||||
preferredSize: const Size.fromHeight(50),
|
body: RefreshIndicator(
|
||||||
child: Padding(
|
onRefresh: () async {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
ref.invalidate(followedArtistsProvider);
|
||||||
child: ColoredBox(
|
},
|
||||||
color: theme.scaffoldBackgroundColor,
|
child: InterScrollbar(
|
||||||
child: SearchBar(
|
controller: controller,
|
||||||
onChanged: (value) => searchText.value = value,
|
child: Padding(
|
||||||
leading: const Icon(SpotubeIcons.filter),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
hintText: context.l10n.filter_artist,
|
child: CustomScrollView(
|
||||||
|
controller: controller,
|
||||||
|
slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
floating: true,
|
||||||
|
flexibleSpace: SearchBar(
|
||||||
|
onChanged: (value) => searchText.value = value,
|
||||||
|
leading: const Icon(SpotubeIcons.filter),
|
||||||
|
hintText: context.l10n.filter_artist,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SliverGap(10),
|
||||||
|
Skeletonizer.sliver(
|
||||||
|
enabled: artistQuery.isLoading,
|
||||||
|
child: SliverLayoutBuilder(builder: (context, constrains) {
|
||||||
|
return SliverGrid.builder(
|
||||||
|
itemCount: filteredArtists.isEmpty
|
||||||
|
? 6
|
||||||
|
: filteredArtists.length + 1,
|
||||||
|
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 200,
|
||||||
|
mainAxisExtent: constrains.smAndDown ? 225 : 250,
|
||||||
|
crossAxisSpacing: 8,
|
||||||
|
mainAxisSpacing: 8,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (filteredArtists.isNotEmpty &&
|
||||||
|
index == filteredArtists.length) {
|
||||||
|
if (artistQuery.asData?.value.hasMore != true) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Waypoint(
|
||||||
|
controller: controller,
|
||||||
|
isGrid: true,
|
||||||
|
onTouchEdge: artistQueryNotifier.fetchMore,
|
||||||
|
child: Skeletonizer(
|
||||||
|
enabled: true,
|
||||||
|
child: ArtistCard(FakeData.artist),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ArtistCard(
|
||||||
|
filteredArtists.elementAtOrNull(index) ??
|
||||||
|
FakeData.artist,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
|
||||||
body: artistQuery.asData?.value.items.isEmpty == true
|
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const CircularProgressIndicator(),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(context.l10n.loading),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: RefreshIndicator(
|
|
||||||
onRefresh: () async {
|
|
||||||
ref.invalidate(followedArtistsProvider);
|
|
||||||
},
|
|
||||||
child: InterScrollbar(
|
|
||||||
controller: controller,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: controller,
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: SafeArea(
|
|
||||||
child: Center(
|
|
||||||
child: Skeletonizer(
|
|
||||||
enabled: artistQuery.isLoading,
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 15,
|
|
||||||
runSpacing: 5,
|
|
||||||
children: artistQuery.isLoading
|
|
||||||
? List.generate(
|
|
||||||
10, (index) => ArtistCard(FakeData.artist))
|
|
||||||
: filteredArtists.isEmpty
|
|
||||||
? [
|
|
||||||
const Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
NotFound(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
]
|
|
||||||
: filteredArtists
|
|
||||||
.mapIndexed(
|
|
||||||
(index, artist) => ArtistCard(artist),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -176,7 +176,7 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 5),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: trackSnapshot.asData?.value != null
|
onPressed: trackSnapshot.asData?.value != null
|
||||||
? () async {
|
? () async {
|
||||||
@ -212,7 +212,7 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
sortBy.value = value;
|
sortBy.value = value;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 5),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
child: const Icon(SpotubeIcons.refresh),
|
child: const Icon(SpotubeIcons.refresh),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart' hide Image;
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
@ -18,6 +19,7 @@ import 'package:spotube/extensions/constrains.dart';
|
|||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class UserPlaylists extends HookConsumerWidget {
|
class UserPlaylists extends HookConsumerWidget {
|
||||||
const UserPlaylists({super.key});
|
const UserPlaylists({super.key});
|
||||||
@ -86,39 +88,37 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverToBoxAdapter(
|
SliverAppBar(
|
||||||
child: Column(
|
floating: true,
|
||||||
mainAxisSize: MainAxisSize.min,
|
flexibleSpace: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
Padding(
|
child: SearchBar(
|
||||||
padding: const EdgeInsets.all(10),
|
onChanged: (value) => searchText.value = value,
|
||||||
child: SearchBar(
|
hintText: context.l10n.filter_playlists,
|
||||||
onChanged: (value) => searchText.value = value,
|
leading: const Icon(SpotubeIcons.filter),
|
||||||
hintText: context.l10n.filter_playlists,
|
),
|
||||||
leading: const Icon(SpotubeIcons.filter),
|
),
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize:
|
||||||
|
Size.fromHeight(kIsDesktop ? 35 : kToolbarHeight),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Gap(10),
|
||||||
|
const PlaylistCreateDialogButton(),
|
||||||
|
const Gap(10),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(SpotubeIcons.magic),
|
||||||
|
label: Text(context.l10n.generate_playlist),
|
||||||
|
onPressed: () {
|
||||||
|
GoRouter.of(context).push("/library/generate");
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
const Gap(10),
|
||||||
Row(
|
],
|
||||||
children: [
|
),
|
||||||
const SizedBox(width: 10),
|
|
||||||
const PlaylistCreateDialogButton(),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
ElevatedButton.icon(
|
|
||||||
icon: const Icon(SpotubeIcons.magic),
|
|
||||||
label: Text(context.l10n.generate_playlist),
|
|
||||||
onPressed: () {
|
|
||||||
GoRouter.of(context).push("/library/generate");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SliverToBoxAdapter(
|
const SliverGap(10),
|
||||||
child: SizedBox(height: 10),
|
|
||||||
),
|
|
||||||
SliverLayoutBuilder(builder: (context, constrains) {
|
SliverLayoutBuilder(builder: (context, constrains) {
|
||||||
return SliverGrid.builder(
|
return SliverGrid.builder(
|
||||||
itemCount: playlists.isEmpty ? 6 : playlists.length + 1,
|
itemCount: playlists.isEmpty ? 6 : playlists.length + 1,
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -24,6 +23,7 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart
|
|||||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||||
import 'package:spotube/provider/volume_provider.dart';
|
import 'package:spotube/provider/volume_provider.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class BottomPlayer extends HookConsumerWidget {
|
class BottomPlayer extends HookConsumerWidget {
|
||||||
BottomPlayer({super.key});
|
BottomPlayer({super.key});
|
||||||
@ -95,19 +95,19 @@ class BottomPlayer extends HookConsumerWidget {
|
|||||||
tooltip: context.l10n.mini_player,
|
tooltip: context.l10n.mini_player,
|
||||||
icon: const Icon(SpotubeIcons.miniPlayer),
|
icon: const Icon(SpotubeIcons.miniPlayer),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final prevSize =
|
if (!kIsDesktop) return;
|
||||||
await DesktopTools.window.getSize();
|
|
||||||
await DesktopTools.window.setMinimumSize(
|
final prevSize = await windowManager.getSize();
|
||||||
|
await windowManager.setMinimumSize(
|
||||||
const Size(300, 300),
|
const Size(300, 300),
|
||||||
);
|
);
|
||||||
await DesktopTools.window.setAlwaysOnTop(true);
|
await windowManager.setAlwaysOnTop(true);
|
||||||
if (!kIsLinux) {
|
if (!kIsLinux) {
|
||||||
await DesktopTools.window.setHasShadow(false);
|
await windowManager.setHasShadow(false);
|
||||||
}
|
}
|
||||||
await DesktopTools.window
|
await windowManager
|
||||||
.setAlignment(Alignment.topRight);
|
.setAlignment(Alignment.topRight);
|
||||||
await DesktopTools.window
|
await windowManager.setSize(const Size(400, 500));
|
||||||
.setSize(const Size(400, 500));
|
|
||||||
await Future.delayed(
|
await Future.delayed(
|
||||||
const Duration(milliseconds: 100),
|
const Duration(milliseconds: 100),
|
||||||
() async {
|
() async {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class InterScrollbar extends HookWidget {
|
class InterScrollbar extends HookWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@ -15,7 +15,7 @@ class InterScrollbar extends HookWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (DesktopTools.platform.isDesktop) return child;
|
if (kIsDesktop) return child;
|
||||||
|
|
||||||
return DraggableScrollbar.semicircle(
|
return DraggableScrollbar.semicircle(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import 'package:titlebar_buttons/titlebar_buttons.dart';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class PageWindowTitleBar extends StatefulHookConsumerWidget
|
class PageWindowTitleBar extends StatefulHookConsumerWidget
|
||||||
implements PreferredSizeWidget {
|
implements PreferredSizeWidget {
|
||||||
@ -89,7 +90,7 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
|
|||||||
final systemTitleBar =
|
final systemTitleBar =
|
||||||
ref.read(userPreferencesProvider.select((s) => s.systemTitleBar));
|
ref.read(userPreferencesProvider.select((s) => s.systemTitleBar));
|
||||||
if (kIsDesktop && !systemTitleBar) {
|
if (kIsDesktop && !systemTitleBar) {
|
||||||
DesktopTools.window.startDragging();
|
windowManager.startDragging();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,11 +108,7 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
|
|||||||
|
|
||||||
return SliverPadding(
|
return SliverPadding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: DesktopTools.platform.isMacOS &&
|
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
|
||||||
hasFullscreen &&
|
|
||||||
hasLeadingOrCanPop
|
|
||||||
? 65
|
|
||||||
: 0,
|
|
||||||
),
|
),
|
||||||
sliver: SliverAppBar(
|
sliver: SliverAppBar(
|
||||||
leading: widget.leading,
|
leading: widget.leading,
|
||||||
@ -149,11 +146,7 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
|
|||||||
onVerticalDragStart: onDrag,
|
onVerticalDragStart: onDrag,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: DesktopTools.platform.isMacOS &&
|
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
|
||||||
hasFullscreen &&
|
|
||||||
hasLeadingOrCanPop
|
|
||||||
? 65
|
|
||||||
: 0,
|
|
||||||
),
|
),
|
||||||
child: AppBar(
|
child: AppBar(
|
||||||
leading: widget.leading,
|
leading: widget.leading,
|
||||||
@ -193,12 +186,12 @@ class WindowTitleBarButtons extends HookConsumerWidget {
|
|||||||
const type = ThemeType.auto;
|
const type = ThemeType.auto;
|
||||||
|
|
||||||
Future<void> onClose() async {
|
Future<void> onClose() async {
|
||||||
await DesktopTools.window.close();
|
await windowManager.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (kIsDesktop) {
|
if (kIsDesktop) {
|
||||||
DesktopTools.window.isMaximized().then((value) {
|
windowManager.isMaximized().then((value) {
|
||||||
isMaximized.value = value;
|
isMaximized.value = value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -235,14 +228,14 @@ class WindowTitleBarButtons extends HookConsumerWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
MinimizeWindowButton(
|
MinimizeWindowButton(
|
||||||
onPressed: DesktopTools.window.minimize,
|
onPressed: windowManager.minimize,
|
||||||
colors: colors,
|
colors: colors,
|
||||||
),
|
),
|
||||||
if (isMaximized.value != true)
|
if (isMaximized.value != true)
|
||||||
MaximizeWindowButton(
|
MaximizeWindowButton(
|
||||||
colors: colors,
|
colors: colors,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
DesktopTools.window.maximize();
|
windowManager.maximize();
|
||||||
isMaximized.value = true;
|
isMaximized.value = true;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -250,7 +243,7 @@ class WindowTitleBarButtons extends HookConsumerWidget {
|
|||||||
RestoreWindowButton(
|
RestoreWindowButton(
|
||||||
colors: colors,
|
colors: colors,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
DesktopTools.window.unmaximize();
|
windowManager.unmaximize();
|
||||||
isMaximized.value = false;
|
isMaximized.value = false;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -270,16 +263,16 @@ class WindowTitleBarButtons extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
DecoratedMinimizeButton(
|
DecoratedMinimizeButton(
|
||||||
type: type,
|
type: type,
|
||||||
onPressed: DesktopTools.window.minimize,
|
onPressed: windowManager.minimize,
|
||||||
),
|
),
|
||||||
DecoratedMaximizeButton(
|
DecoratedMaximizeButton(
|
||||||
type: type,
|
type: type,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (await DesktopTools.window.isMaximized()) {
|
if (await windowManager.isMaximized()) {
|
||||||
await DesktopTools.window.unmaximize();
|
await windowManager.unmaximize();
|
||||||
isMaximized.value = false;
|
isMaximized.value = false;
|
||||||
} else {
|
} else {
|
||||||
await DesktopTools.window.maximize();
|
await windowManager.maximize();
|
||||||
isMaximized.value = true;
|
isMaximized.value = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
@ -12,6 +12,7 @@ import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/hooks/utils/use_palette_color.dart';
|
import 'package:spotube/hooks/utils/use_palette_color.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class TrackViewFlexHeader extends HookConsumerWidget {
|
class TrackViewFlexHeader extends HookConsumerWidget {
|
||||||
const TrackViewFlexHeader({super.key});
|
const TrackViewFlexHeader({super.key});
|
||||||
@ -53,7 +54,7 @@ class TrackViewFlexHeader extends HookConsumerWidget {
|
|||||||
floating: false,
|
floating: false,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
expandedHeight: 450,
|
expandedHeight: 450,
|
||||||
automaticallyImplyLeading: DesktopTools.platform.isMobile,
|
automaticallyImplyLeading: kIsMobile,
|
||||||
backgroundColor: palette.color,
|
backgroundColor: palette.color,
|
||||||
title: isExpanded ? null : Text(props.title, style: headingStyle),
|
title: isExpanded ? null : Text(props.title, style: headingStyle),
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:sliver_tools/sliver_tools.dart';
|
import 'package:sliver_tools/sliver_tools.dart';
|
||||||
@ -8,6 +8,7 @@ import 'package:spotube/components/shared/page_window_title_bar.dart';
|
|||||||
import 'package:spotube/components/shared/tracks_view/sections/header/flexible_header.dart';
|
import 'package:spotube/components/shared/tracks_view/sections/header/flexible_header.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body.dart';
|
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class TrackView extends HookConsumerWidget {
|
class TrackView extends HookConsumerWidget {
|
||||||
const TrackView({super.key});
|
const TrackView({super.key});
|
||||||
@ -18,7 +19,7 @@ class TrackView extends HookConsumerWidget {
|
|||||||
final controller = useScrollController();
|
final controller = useScrollController();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: DesktopTools.platform.isDesktop
|
appBar: kIsDesktop
|
||||||
? const PageWindowTitleBar(
|
? const PageWindowTitleBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
|
|||||||
@ -20,8 +20,6 @@ class Waypoint extends HookWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isMounted = useIsMounted();
|
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (isGrid) {
|
if (isGrid) {
|
||||||
return null;
|
return null;
|
||||||
@ -32,19 +30,19 @@ class Waypoint extends HookWidget {
|
|||||||
|
|
||||||
// scrollController fetches the next paginated data when the current
|
// scrollController fetches the next paginated data when the current
|
||||||
// position of the user on the screen has surpassed
|
// position of the user on the screen has surpassed
|
||||||
if (controller.position.pixels >= nextPageTrigger && isMounted()) {
|
if (controller.position.pixels >= nextPageTrigger && context.mounted) {
|
||||||
await onTouchEdge?.call();
|
await onTouchEdge?.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (controller.hasClients && isMounted()) {
|
if (controller.hasClients && context.mounted) {
|
||||||
listener();
|
listener();
|
||||||
controller.addListener(listener);
|
controller.addListener(listener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return () => controller.removeListener(listener);
|
return () => controller.removeListener(listener);
|
||||||
}, [controller, onTouchEdge, isMounted]);
|
}, [controller, onTouchEdge]);
|
||||||
|
|
||||||
if (isGrid) {
|
if (isGrid) {
|
||||||
return VisibilityDetector(
|
return VisibilityDetector(
|
||||||
|
|||||||
@ -1,29 +1,31 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_window_listener.dart';
|
import 'package:spotube/hooks/configurators/use_window_listener.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||||
// ignore: depend_on_referenced_packages
|
|
||||||
import 'package:local_notifier/local_notifier.dart';
|
import 'package:local_notifier/local_notifier.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
final closeNotification = DesktopTools.createNotification(
|
final closeNotification = !kIsDesktop
|
||||||
title: 'Spotube',
|
? null
|
||||||
message: 'Running in background. Minimized to System Tray',
|
: (LocalNotification(
|
||||||
actions: [
|
title: 'Spotube',
|
||||||
LocalNotificationAction(text: 'Close The App'),
|
body: 'Running in background. Minimized to System Tray',
|
||||||
],
|
actions: [
|
||||||
)?..onClickAction = (value) {
|
LocalNotificationAction(text: 'Close The App'),
|
||||||
exit(0);
|
],
|
||||||
};
|
)..onClickAction = (value) {
|
||||||
|
exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
void useCloseBehavior(WidgetRef ref) {
|
void useCloseBehavior(WidgetRef ref) {
|
||||||
useWindowListener(
|
useWindowListener(
|
||||||
onWindowClose: () async {
|
onWindowClose: () async {
|
||||||
final preferences = ref.read(userPreferencesProvider);
|
final preferences = ref.read(userPreferencesProvider);
|
||||||
if (preferences.closeBehavior == CloseBehavior.minimizeToTray) {
|
if (preferences.closeBehavior == CloseBehavior.minimizeToTray) {
|
||||||
await DesktopTools.window.hide();
|
await windowManager.hide();
|
||||||
closeNotification?.show();
|
closeNotification?.show();
|
||||||
} else {
|
} else {
|
||||||
exit(0);
|
exit(0);
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import 'package:spotube/collections/routes.dart';
|
|||||||
import 'package:spotube/provider/spotify_provider.dart';
|
import 'package:spotube/provider/spotify_provider.dart';
|
||||||
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
|
||||||
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
import 'package:flutter_sharing_intent/model/sharing_file.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
final appLinks = AppLinks();
|
final appLinks = AppLinks();
|
||||||
final linkStream = appLinks.allStringLinkStream.asBroadcastStream();
|
final linkStream = appLinks.allStringLinkStream.asBroadcastStream();
|
||||||
@ -53,7 +53,7 @@ void useDeepLinking(WidgetRef ref) {
|
|||||||
|
|
||||||
StreamSubscription? mediaStream;
|
StreamSubscription? mediaStream;
|
||||||
|
|
||||||
if (DesktopTools.platform.isMobile) {
|
if (kIsMobile) {
|
||||||
FlutterSharingIntent.instance.getInitialSharing().then(uriListener);
|
FlutterSharingIntent.instance.getInitialSharing().then(uriListener);
|
||||||
|
|
||||||
mediaStream =
|
mediaStream =
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import 'package:disable_battery_optimization/disable_battery_optimization.dart';
|
import 'package:disable_battery_optimization/disable_battery_optimization.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:spotube/hooks/utils/use_async_effect.dart';
|
import 'package:spotube/hooks/utils/use_async_effect.dart';
|
||||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
void useDisableBatteryOptimizations() {
|
void useDisableBatteryOptimizations() {
|
||||||
useAsyncEffect(() async {
|
useAsyncEffect(() async {
|
||||||
if (!DesktopTools.platform.isAndroid ||
|
if (!kIsAndroid || KVStoreService.askedForBatteryOptimization) return;
|
||||||
KVStoreService.askedForBatteryOptimization) return;
|
|
||||||
|
|
||||||
await DisableBatteryOptimization.showDisableBatteryOptimizationSettings();
|
await DisableBatteryOptimization.showDisableBatteryOptimizationSettings();
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:spotube/components/library/user_local_tracks.dart';
|
import 'package:spotube/components/library/user_local_tracks.dart';
|
||||||
import 'package:spotube/hooks/utils/use_async_effect.dart';
|
import 'package:spotube/hooks/utils/use_async_effect.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
void useGetStoragePermissions(WidgetRef ref) {
|
void useGetStoragePermissions(WidgetRef ref) {
|
||||||
final isMounted = useIsMounted();
|
final context = useContext();
|
||||||
|
|
||||||
useAsyncEffect(
|
useAsyncEffect(
|
||||||
() async {
|
() async {
|
||||||
if (!DesktopTools.platform.isMobile) return;
|
if (!kIsMobile) return;
|
||||||
|
|
||||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
|
|
||||||
@ -25,11 +26,11 @@ void useGetStoragePermissions(WidgetRef ref) {
|
|||||||
|
|
||||||
if (hasNoStoragePerm) {
|
if (hasNoStoragePerm) {
|
||||||
await Permission.storage.request();
|
await Permission.storage.request();
|
||||||
if (isMounted()) ref.invalidate(localTracksProvider);
|
if (context.mounted) ref.invalidate(localTracksProvider);
|
||||||
}
|
}
|
||||||
if (hasNoAudioPerm) {
|
if (hasNoAudioPerm) {
|
||||||
await Permission.audio.request();
|
await Permission.audio.request();
|
||||||
if (isMounted()) ref.invalidate(localTracksProvider);
|
if (context.mounted) ref.invalidate(localTracksProvider);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
|
|||||||
@ -1,128 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotube/collections/intents.dart';
|
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
|
|
||||||
void useInitSysTray(WidgetRef ref) {
|
|
||||||
final context = useContext();
|
|
||||||
final systemTray = useRef<SystemTray?>(null);
|
|
||||||
|
|
||||||
final initializeMenu = useCallback(() async {
|
|
||||||
systemTray.value?.destroy();
|
|
||||||
final playlist = ref.read(proxyPlaylistProvider);
|
|
||||||
final playlistQueue = ref.read(proxyPlaylistProvider.notifier);
|
|
||||||
final preferences = ref.read(userPreferencesProvider);
|
|
||||||
if (!preferences.showSystemTrayIcon) {
|
|
||||||
await systemTray.value?.destroy();
|
|
||||||
systemTray.value = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final enabled = !playlist.isFetching;
|
|
||||||
systemTray.value = await DesktopTools.createSystemTrayMenu(
|
|
||||||
title: DesktopTools.platform.isWindows ? "Spotube" : "",
|
|
||||||
iconPath: "assets/spotube-logo.png",
|
|
||||||
windowsIconPath: "assets/spotube-logo.ico",
|
|
||||||
items: [
|
|
||||||
MenuItemLabel(
|
|
||||||
label: "Show/Hide",
|
|
||||||
name: "show-hide",
|
|
||||||
onClicked: (item) async {
|
|
||||||
if (await DesktopTools.window.isVisible()) {
|
|
||||||
await DesktopTools.window.hide();
|
|
||||||
} else {
|
|
||||||
await DesktopTools.window.show();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuSeparator(),
|
|
||||||
MenuItemLabel(
|
|
||||||
label: "Play/Pause",
|
|
||||||
name: "play-pause",
|
|
||||||
enabled: enabled,
|
|
||||||
onClicked: (_) async {
|
|
||||||
Actions.maybeInvoke<PlayPauseIntent>(
|
|
||||||
context, PlayPauseIntent(ref)) ??
|
|
||||||
PlayPauseAction().invoke(PlayPauseIntent(ref));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuItemLabel(
|
|
||||||
label: "Next",
|
|
||||||
name: "next",
|
|
||||||
enabled: enabled && (playlist.tracks.length) > 1,
|
|
||||||
onClicked: (p0) async {
|
|
||||||
await playlistQueue.next();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuItemLabel(
|
|
||||||
label: "Previous",
|
|
||||||
name: "previous",
|
|
||||||
enabled: enabled && (playlist.tracks.length) > 1,
|
|
||||||
onClicked: (p0) async {
|
|
||||||
await playlistQueue.previous();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuSeparator(),
|
|
||||||
MenuItemLabel(
|
|
||||||
label: "Quit",
|
|
||||||
name: "quit",
|
|
||||||
onClicked: (item) async {
|
|
||||||
exit(0);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onEvent: (event, tray) async {
|
|
||||||
if (DesktopTools.platform.isWindows) {
|
|
||||||
switch (event) {
|
|
||||||
case SystemTrayEvent.click:
|
|
||||||
await DesktopTools.window.show();
|
|
||||||
break;
|
|
||||||
case SystemTrayEvent.rightClick:
|
|
||||||
await tray.popUpContextMenu();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (event) {
|
|
||||||
case SystemTrayEvent.rightClick:
|
|
||||||
await DesktopTools.window.show();
|
|
||||||
break;
|
|
||||||
case SystemTrayEvent.click:
|
|
||||||
await tray.popUpContextMenu();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}, [ref]);
|
|
||||||
|
|
||||||
useReassemble(initializeMenu);
|
|
||||||
|
|
||||||
ref.listen<ProxyPlaylist?>(
|
|
||||||
proxyPlaylistProvider,
|
|
||||||
(previous, next) {
|
|
||||||
initializeMenu();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
ref.listen(
|
|
||||||
userPreferencesProvider.select((s) => s.showSystemTrayIcon),
|
|
||||||
(previous, next) {
|
|
||||||
initializeMenu();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
initializeMenu();
|
|
||||||
});
|
|
||||||
return () async {
|
|
||||||
await systemTray.value?.destroy();
|
|
||||||
};
|
|
||||||
}, [initializeMenu]);
|
|
||||||
}
|
|
||||||
@ -62,7 +62,7 @@ void useUpdateChecker(WidgetRef ref) {
|
|||||||
barrierColor: Colors.black26,
|
barrierColor: Colors.black26,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
const url =
|
const url =
|
||||||
"https://spotube.krtirtho.dev/other-downloads/stable-downloads";
|
"https://spotube.krtirtho.dev/downloads";
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text("Spotube has an update"),
|
title: const Text("Spotube has an update"),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class CallbackWindowListener implements WindowListener {
|
class CallbackWindowListener implements WindowListener {
|
||||||
final VoidCallback? _onWindowClose;
|
final VoidCallback? _onWindowClose;
|
||||||
@ -154,6 +156,8 @@ void useWindowListener({
|
|||||||
VoidCallback? onWindowEvent,
|
VoidCallback? onWindowEvent,
|
||||||
}) {
|
}) {
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
if (!kIsDesktop) return null;
|
||||||
|
|
||||||
final listener = CallbackWindowListener(
|
final listener = CallbackWindowListener(
|
||||||
onWindowClose: onWindowClose,
|
onWindowClose: onWindowClose,
|
||||||
onWindowFocus: onWindowFocus,
|
onWindowFocus: onWindowFocus,
|
||||||
@ -172,9 +176,9 @@ void useWindowListener({
|
|||||||
onWindowUndocked: onWindowUndocked,
|
onWindowUndocked: onWindowUndocked,
|
||||||
onWindowEvent: onWindowEvent,
|
onWindowEvent: onWindowEvent,
|
||||||
);
|
);
|
||||||
DesktopTools.window.addListener(listener);
|
windowManager.addListener(listener);
|
||||||
return () {
|
return () {
|
||||||
DesktopTools.window.removeListener(listener);
|
windowManager.removeListener(listener);
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
onWindowClose,
|
onWindowClose,
|
||||||
|
|||||||
@ -14,7 +14,6 @@ PaletteColor usePaletteColor(String imageUrl, WidgetRef ref) {
|
|||||||
final context = useContext();
|
final context = useContext();
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final paletteColor = ref.watch(_paletteColorState);
|
final paletteColor = ref.watch(_paletteColorState);
|
||||||
final mounted = useIsMounted();
|
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||||
@ -25,7 +24,7 @@ PaletteColor usePaletteColor(String imageUrl, WidgetRef ref) {
|
|||||||
width: 50,
|
width: 50,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (!mounted()) return;
|
if (!context.mounted) return;
|
||||||
final color = theme.brightness == Brightness.light
|
final color = theme.brightness == Brightness.light
|
||||||
? palette.lightMutedColor ?? palette.lightVibrantColor
|
? palette.lightMutedColor ?? palette.lightVibrantColor
|
||||||
: palette.darkMutedColor ?? palette.darkVibrantColor;
|
: palette.darkMutedColor ?? palette.darkVibrantColor;
|
||||||
@ -41,7 +40,7 @@ PaletteColor usePaletteColor(String imageUrl, WidgetRef ref) {
|
|||||||
|
|
||||||
PaletteGenerator usePaletteGenerator(String imageUrl) {
|
PaletteGenerator usePaletteGenerator(String imageUrl) {
|
||||||
final palette = useState(PaletteGenerator.fromColors([]));
|
final palette = useState(PaletteGenerator.fromColors([]));
|
||||||
final mounted = useIsMounted();
|
final context = useContext();
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||||
@ -52,7 +51,7 @@ PaletteGenerator usePaletteGenerator(String imageUrl) {
|
|||||||
width: 50,
|
width: 50,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (!mounted()) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
palette.value = newPalette;
|
palette.value = newPalette;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube هو مشروع مفتوح المصدر. يمكنك مساعدة هذا المشروع في النمو عن طريق المساهمة في المشروع، أو الإبلاغ عن الأخطاء، أو اقتراح ميزات جديدة.",
|
"help_project_grow_description": "Spotube هو مشروع مفتوح المصدر. يمكنك مساعدة هذا المشروع في النمو عن طريق المساهمة في المشروع، أو الإبلاغ عن الأخطاء، أو اقتراح ميزات جديدة.",
|
||||||
"contribute_on_github": "المساهمة على GitHub",
|
"contribute_on_github": "المساهمة على GitHub",
|
||||||
"donate_on_open_collective": "التبرع على Open Collective",
|
"donate_on_open_collective": "التبرع على Open Collective",
|
||||||
"browse_anonymously": "تصفح بشكل مجهول"
|
"browse_anonymously": "تصفح بشكل مجهول",
|
||||||
|
"enable_connect": "تمكين الاتصال",
|
||||||
|
"enable_connect_description": "التحكم في Spotube من الأجهزة الأخرى",
|
||||||
|
"devices": "الأجهزة",
|
||||||
|
"select": "اختر",
|
||||||
|
"connect_client_alert": "أنت تتم التحكم بواسطة {client}",
|
||||||
|
"this_device": "هذا الجهاز",
|
||||||
|
"remote": "بعيد"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "স্পটুব একটি ওপেন সোর্স প্রকল্প। আপনি প্রকল্পে অবদান রাখেন, বাগ রিপোর্ট করেন, বা নতুন বৈশিষ্ট্যগুলি সুপারিশ করেন।",
|
"help_project_grow_description": "স্পটুব একটি ওপেন সোর্স প্রকল্প। আপনি প্রকল্পে অবদান রাখেন, বাগ রিপোর্ট করেন, বা নতুন বৈশিষ্ট্যগুলি সুপারিশ করেন।",
|
||||||
"contribute_on_github": "গিটহাবে অবদান রাখুন",
|
"contribute_on_github": "গিটহাবে অবদান রাখুন",
|
||||||
"donate_on_open_collective": "ওপেন কলেক্টিভে অনুদান করুন",
|
"donate_on_open_collective": "ওপেন কলেক্টিভে অনুদান করুন",
|
||||||
"browse_anonymously": "অজানে ব্রাউজ করুন"
|
"browse_anonymously": "অজানে ব্রাউজ করুন",
|
||||||
|
"enable_connect": "সংযোগ সক্রিয় করুন",
|
||||||
|
"enable_connect_description": "অন্যান্য ডিভাইস থেকে Spotube নিয়ন্ত্রণ করুন",
|
||||||
|
"devices": "ডিভাইস",
|
||||||
|
"select": "নির্বাচন করুন",
|
||||||
|
"connect_client_alert": "আপনি {client} দ্বারা নিয়ন্ত্রিত হচ্ছেন",
|
||||||
|
"this_device": "এই ডিভাইস",
|
||||||
|
"remote": "রিমোট"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube és un projecte de codi obert. Podeu ajudar a fer créixer aquest projecte contribuint al projecte, informant d'errors o suggerint noves funcionalitats.",
|
"help_project_grow_description": "Spotube és un projecte de codi obert. Podeu ajudar a fer créixer aquest projecte contribuint al projecte, informant d'errors o suggerint noves funcionalitats.",
|
||||||
"contribute_on_github": "Contribueix a GitHub",
|
"contribute_on_github": "Contribueix a GitHub",
|
||||||
"donate_on_open_collective": "Fes una donació a Open Collective",
|
"donate_on_open_collective": "Fes una donació a Open Collective",
|
||||||
"browse_anonymously": "Navega de manera anònima"
|
"browse_anonymously": "Navega de manera anònima",
|
||||||
|
"enable_connect": "Habilita la connexió",
|
||||||
|
"enable_connect_description": "Controla Spotube des d'altres dispositius",
|
||||||
|
"devices": "Dispositius",
|
||||||
|
"select": "Selecciona",
|
||||||
|
"connect_client_alert": "Estàs sent controlat per {client}",
|
||||||
|
"this_device": "Aquest dispositiu",
|
||||||
|
"remote": "Remot"
|
||||||
}
|
}
|
||||||
324
lib/l10n/app_cs.arb
Normal file
324
lib/l10n/app_cs.arb
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
{
|
||||||
|
"guest": "Host",
|
||||||
|
"browse": "Procházet",
|
||||||
|
"search": "Hledat",
|
||||||
|
"library": "Knihovna",
|
||||||
|
"lyrics": "Texty",
|
||||||
|
"settings": "Nastavení",
|
||||||
|
"genre_categories_filter": "Filtrovat kategorie nebo žánry...",
|
||||||
|
"genre": "Žánr",
|
||||||
|
"personalized": "Personalizované",
|
||||||
|
"featured": "Doporučené",
|
||||||
|
"new_releases": "Nově vydané",
|
||||||
|
"songs": "Skladby",
|
||||||
|
"playing_track": "Hraje {track}",
|
||||||
|
"queue_clear_alert": "Toto vymaže aktuální frontu. {track_length} skladeb bude odstraněno\nChcete pokračovat?",
|
||||||
|
"load_more": "Načíst více",
|
||||||
|
"playlists": "Playlisty",
|
||||||
|
"artists": "Umělci",
|
||||||
|
"albums": "Alba",
|
||||||
|
"tracks": "Skladby",
|
||||||
|
"downloads": "Stahování",
|
||||||
|
"filter_playlists": "Filtrovat playlisty...",
|
||||||
|
"liked_tracks": "Oblíbené skladby",
|
||||||
|
"liked_tracks_description": "Všechny vaše oblíbené skladby",
|
||||||
|
"create_playlist": "Vytvořit playlist",
|
||||||
|
"create_a_playlist": "Vytvořit playlist",
|
||||||
|
"update_playlist": "Aktualizovat playlist",
|
||||||
|
"create": "Vytvořit",
|
||||||
|
"cancel": "Zrušit",
|
||||||
|
"update": "Aktualizovat",
|
||||||
|
"playlist_name": "Název playlistu",
|
||||||
|
"name_of_playlist": "Název playlistu",
|
||||||
|
"description": "Popis",
|
||||||
|
"public": "Veřejné",
|
||||||
|
"collaborative": "Společný",
|
||||||
|
"search_local_tracks": "Hledat místní skladby...",
|
||||||
|
"play": "Přehrát",
|
||||||
|
"delete": "Smazat",
|
||||||
|
"none": "Žádné",
|
||||||
|
"sort_a_z": "Seřadit od A-Z",
|
||||||
|
"sort_z_a": "Seřadit od Z-A",
|
||||||
|
"sort_artist": "Seřadit podle umělce",
|
||||||
|
"sort_album": "Seřadit podle alba",
|
||||||
|
"sort_duration": "Seřadit podle délky",
|
||||||
|
"sort_tracks": "Seřadit skladby",
|
||||||
|
"currently_downloading": "Právě se stahuje ({tracks_length})",
|
||||||
|
"cancel_all": "Zrušit vše",
|
||||||
|
"filter_artist": "Filtrovat umělce...",
|
||||||
|
"followers": "{followers} Sledující",
|
||||||
|
"add_artist_to_blacklist": "Přidat umělce na černou listinu",
|
||||||
|
"top_tracks": "Top skladby",
|
||||||
|
"fans_also_like": "Fanoušci mají také rádi",
|
||||||
|
"loading": "Načítání...",
|
||||||
|
"artist": "Umělec",
|
||||||
|
"blacklisted": "Na černé listině",
|
||||||
|
"following": "Sleduje",
|
||||||
|
"follow": "Sledovat",
|
||||||
|
"artist_url_copied": "URL umělce zkopírována do schránky",
|
||||||
|
"added_to_queue": "Přidáno {tracks} skladeb do fronty",
|
||||||
|
"filter_albums": "Filtrovat alba...",
|
||||||
|
"synced": "Synchronizováno",
|
||||||
|
"plain": "Jednoduché",
|
||||||
|
"shuffle": "Zamíchat",
|
||||||
|
"search_tracks": "Hledat skladby...",
|
||||||
|
"released": "Vydáno",
|
||||||
|
"error": "Chyba {error}",
|
||||||
|
"title": "Název",
|
||||||
|
"time": "Čas",
|
||||||
|
"more_actions": "Více akcí",
|
||||||
|
"download_count": "Stáhnout ({count})",
|
||||||
|
"add_count_to_playlist": "Přidat ({count}) do playlistu",
|
||||||
|
"add_count_to_queue": "Přidat ({count}) do fronty",
|
||||||
|
"play_count_next": "Přehrát ({count}) dalších",
|
||||||
|
"album": "Album",
|
||||||
|
"copied_to_clipboard": "Zkopírováno {data} do schránky",
|
||||||
|
"add_to_following_playlists": "Přidat {track} do následujících playlistů",
|
||||||
|
"add": "Přidat",
|
||||||
|
"added_track_to_queue": "Přidána skladba {track} do fronty",
|
||||||
|
"add_to_queue": "Přidat do fronty",
|
||||||
|
"track_will_play_next": "{track} se přehraje jako další",
|
||||||
|
"play_next": "Přehrát další",
|
||||||
|
"removed_track_from_queue": "Odstraněna skladba {track} z fronty",
|
||||||
|
"remove_from_queue": "Odstranit z fronty",
|
||||||
|
"remove_from_favorites": "Odstranit z oblíbených",
|
||||||
|
"save_as_favorite": "Uložit jako oblíbené",
|
||||||
|
"add_to_playlist": "Přidat do playlistu",
|
||||||
|
"remove_from_playlist": "Odstranit z playlistu",
|
||||||
|
"add_to_blacklist": "Přidat na černou listinu",
|
||||||
|
"remove_from_blacklist": "Odstranit z černé listiny",
|
||||||
|
"share": "Sdílet",
|
||||||
|
"mini_player": "Mini přehrávač",
|
||||||
|
"slide_to_seek": "Táhněte pro posunutí vpřed nebo vzad",
|
||||||
|
"shuffle_playlist": "Zamíchat playlist",
|
||||||
|
"unshuffle_playlist": "Zrušit zamíchání playlistu",
|
||||||
|
"previous_track": "Předchozí skladba",
|
||||||
|
"next_track": "Další skladba",
|
||||||
|
"pause_playback": "Pozastavit přehrávání",
|
||||||
|
"resume_playback": "Pokračovat v přehrávání",
|
||||||
|
"loop_track": "Opakovat skladbu",
|
||||||
|
"repeat_playlist": "Opakovat playlist",
|
||||||
|
"queue": "Fronta",
|
||||||
|
"alternative_track_sources": "Alternativní zdroje skladeb",
|
||||||
|
"download_track": "Stáhnout skladbu",
|
||||||
|
"tracks_in_queue": "{tracks} skladeb ve frontě",
|
||||||
|
"clear_all": "Vymazat vše",
|
||||||
|
"show_hide_ui_on_hover": "Zobrazit/Skrýt UI při najetí",
|
||||||
|
"always_on_top": "Vždy nahoře",
|
||||||
|
"exit_mini_player": "Zavřít mini přehrávač",
|
||||||
|
"download_location": "Umístění stahování",
|
||||||
|
"account": "Účet",
|
||||||
|
"login_with_spotify": "Přihlásit se pomocí Spotify účtu",
|
||||||
|
"connect_with_spotify": "Připojit k Spotify",
|
||||||
|
"logout": "Odhlásit se",
|
||||||
|
"logout_of_this_account": "Odhlásit se z tohoto účtu",
|
||||||
|
"language_region": "Jazyk a region",
|
||||||
|
"language": "Jazyk",
|
||||||
|
"system_default": "Systém",
|
||||||
|
"market_place_region": "Region",
|
||||||
|
"recommendation_country": "Země pro doporučení",
|
||||||
|
"appearance": "Vzhled",
|
||||||
|
"layout_mode": "Režim rozložení",
|
||||||
|
"override_layout_settings": "Přepsat režim rozložení",
|
||||||
|
"adaptive": "Adaptivní",
|
||||||
|
"compact": "Kompaktní",
|
||||||
|
"extended": "Rozšířený",
|
||||||
|
"theme": "Téma",
|
||||||
|
"dark": "Tmavé",
|
||||||
|
"light": "Světlé",
|
||||||
|
"system": "Systém",
|
||||||
|
"accent_color": "Barva akcentu",
|
||||||
|
"sync_album_color": "Synchronizovat barvu alba",
|
||||||
|
"sync_album_color_description": "Používá dominantní barvu obalu alba jako barvu akcentu",
|
||||||
|
"playback": "Přehrávání",
|
||||||
|
"audio_quality": "Kvalita zvuku",
|
||||||
|
"high": "Vysoká",
|
||||||
|
"low": "Nízká",
|
||||||
|
"pre_download_play": "Předstáhnout a přehrát",
|
||||||
|
"pre_download_play_description": "Místo streamování audia stáhnout skladbu a přehrát (doporučeno pro uživatele s rychlejším internetem)",
|
||||||
|
"skip_non_music": "Přeskočit nehudební segmenty (SponsorBlock)",
|
||||||
|
"blacklist_description": "Zakázané skladby a umělci",
|
||||||
|
"wait_for_download_to_finish": "Počkejte, až se dokončí stahování",
|
||||||
|
"desktop": "Desktop",
|
||||||
|
"close_behavior": "Chování při zavření",
|
||||||
|
"close": "Zavřít",
|
||||||
|
"minimize_to_tray": "Minimalizovat do lišty",
|
||||||
|
"show_tray_icon": "Zobrazit ikonu v systémové liště",
|
||||||
|
"about": "O aplikaci",
|
||||||
|
"u_love_spotube": "Víme, že milujete Spotube",
|
||||||
|
"check_for_updates": "Zkontrolovat aktualizace",
|
||||||
|
"about_spotube": "O Spotube",
|
||||||
|
"blacklist": "Černá listina",
|
||||||
|
"please_sponsor": "Sponzorovat/darovat",
|
||||||
|
"spotube_description": "Spotube, rychlý, multiplatformní, bezplatný Spotify klient",
|
||||||
|
"version": "Verze",
|
||||||
|
"build_number": "Číslo sestavení",
|
||||||
|
"founder": "Zakladatel",
|
||||||
|
"repository": "Repozitář",
|
||||||
|
"bug_issues": "Chyby+Problémy",
|
||||||
|
"made_with": "Vytvořeno s ❤️ v Bangladéši🇧🇩",
|
||||||
|
"kingkor_roy_tirtho": "Kingkor Roy Tirtho",
|
||||||
|
"copyright": "© 2021-{current_year} Kingkor Roy Tirtho",
|
||||||
|
"license": "Licence",
|
||||||
|
"add_spotify_credentials": "Přidejte své přihlašovací údaje Spotify a začněte",
|
||||||
|
"credentials_will_not_be_shared_disclaimer": "Nebojte, žádné z vašich údajů nebudou shromažďovány ani s nikým sdíleny",
|
||||||
|
"know_how_to_login": "Nevíte, jak na to?",
|
||||||
|
"follow_step_by_step_guide": "Postupujte podle návodu",
|
||||||
|
"spotify_cookie": "Cookie Spotify {name}",
|
||||||
|
"cookie_name_cookie": "Cookie {name}",
|
||||||
|
"fill_in_all_fields": "Vyplňte prosím všechna pole",
|
||||||
|
"submit": "Odeslat",
|
||||||
|
"exit": "Ukončit",
|
||||||
|
"previous": "Předchozí",
|
||||||
|
"next": "Další",
|
||||||
|
"done": "Hotovo",
|
||||||
|
"step_1": "Krok 1",
|
||||||
|
"first_go_to": "Nejprve jděte na",
|
||||||
|
"login_if_not_logged_in": "a přihlašte se nebo se zaregistrujte, pokud nejste přihlášeni",
|
||||||
|
"step_2": "Krok 2",
|
||||||
|
"step_2_steps": "1. Jakmile jste přihlášeni, stiskněte F12 nebo pravé tlačítko myši > Prozkoumat, abyste otevřeli nástroje pro vývojáře prohlížeče.\n2. Poté přejděte na kartu \"Aplikace\" (Chrome, Edge, Brave atd.) nebo kartu \"Úložiště\" (Firefox, Palemoon atd.)\n3. Přejděte do sekce \"Cookies\" a pak do podsekce \"https://accounts.spotify.com\"",
|
||||||
|
"step_3": "Krok 3",
|
||||||
|
"step_3_steps": "Zkopírujte hodnotu cookie \"sp_dc\"",
|
||||||
|
"success_emoji": "Úspěch🥳",
|
||||||
|
"success_message": "Nyní jste úspěšně přihlášeni pomocí svého Spotify účtu. Dobrá práce, kamaráde!",
|
||||||
|
"step_4": "Krok 4",
|
||||||
|
"step_4_steps": "Vložte zkopírovanou hodnotu \"sp_dc\"",
|
||||||
|
"something_went_wrong": "Něco se pokazilo",
|
||||||
|
"piped_instance": "Instance serveru Piped",
|
||||||
|
"piped_description": "Instance serveru Piped, kterou použít pro hledání skladeb",
|
||||||
|
"piped_warning": "Některé z nich nemusí dobře fungovat. Používejte na vlastní riziko",
|
||||||
|
"generate_playlist": "Vygenerovat playlist",
|
||||||
|
"track_exists": "Skladba {track} již existuje",
|
||||||
|
"replace_downloaded_tracks": "Nahradit všechny stažené skladby",
|
||||||
|
"skip_download_tracks": "Přeskočit stahování všech stažených skladeb",
|
||||||
|
"do_you_want_to_replace": "Chcete nahradit existující skladbu??",
|
||||||
|
"replace": "Nahradit",
|
||||||
|
"skip": "Přeskočit",
|
||||||
|
"select_up_to_count_type": "Vyberte až {count} {type}",
|
||||||
|
"select_genres": "Vyberte žánry",
|
||||||
|
"add_genres": "Přidat žánry",
|
||||||
|
"country": "Země",
|
||||||
|
"number_of_tracks_generate": "Počet skladeb k vygenerování",
|
||||||
|
"acousticness": "Akustičnost",
|
||||||
|
"danceability": "Tanečnost",
|
||||||
|
"energy": "Energie",
|
||||||
|
"instrumentalness": "Instrumentálnost",
|
||||||
|
"liveness": "Živost",
|
||||||
|
"loudness": "Hlasitost",
|
||||||
|
"speechiness": "Mluvnost",
|
||||||
|
"valence": "Valence",
|
||||||
|
"popularity": "Popularita",
|
||||||
|
"key": "Klíč",
|
||||||
|
"duration": "Délka (s)",
|
||||||
|
"tempo": "Tempo (BPM)",
|
||||||
|
"mode": "Režim",
|
||||||
|
"time_signature": "Udání taktu",
|
||||||
|
"short": "Krátký",
|
||||||
|
"medium": "Střední",
|
||||||
|
"long": "Dlouhý",
|
||||||
|
"min": "Min",
|
||||||
|
"max": "Max",
|
||||||
|
"target": "Cíl",
|
||||||
|
"moderate": "Mírný",
|
||||||
|
"deselect_all": "Zrušit výběr",
|
||||||
|
"select_all": "Vybrat vše",
|
||||||
|
"are_you_sure": "Jste si jisti?",
|
||||||
|
"generating_playlist": "Generování vašeho vlastního playlistu...",
|
||||||
|
"selected_count_tracks": "Vybráno {count} skladeb",
|
||||||
|
"download_warning": "Pokud stáhnete všechny skladby najednou, pirátíte tím hudbu a škodíte kreativní společnosti hudby. Doufám, že jste si toho vědomi. Vždy se snažte respektovat a podporovat tvrdou práci umělců",
|
||||||
|
"download_ip_ban_warning": "Mimochodem, vaše IP může být na YouTube zablokována kvůli nadměrným požadavkům na stahování. Blokování IP znamená, že nemůžete používat YouTube (i když jste přihlášeni) alespoň 2-3 měsíce ze zařízení s touto IP. A Spotube nenese žádnou odpovědnost, pokud se to někdy stane",
|
||||||
|
"by_clicking_accept_terms": "Kliknutím na 'přijmout' souhlasíte s následujícími podmínkami:",
|
||||||
|
"download_agreement_1": "Vím, že pirátím hudbu. Jsem špatný",
|
||||||
|
"download_agreement_2": "Budu podporovat umělce, kdekoliv to bude možné, a dělám to jen proto, že nemám peníze na koupi jejich umění",
|
||||||
|
"download_agreement_3": "Jsem si naprosto vědom toho, že moje IP může být na YouTube zablokována a nenesu žádnou odpovědnost za nehody způsobené mým současným jednáním",
|
||||||
|
"decline": "Odmítnout",
|
||||||
|
"accept": "Přijmout",
|
||||||
|
"details": "Podrobnosti",
|
||||||
|
"youtube": "YouTube",
|
||||||
|
"channel": "Kanál",
|
||||||
|
"likes": "Líbí se",
|
||||||
|
"dislikes": "Nelíbí se",
|
||||||
|
"views": "Zobrazení",
|
||||||
|
"streamUrl": "URL streamu",
|
||||||
|
"stop": "Zastavit",
|
||||||
|
"sort_newest": "Seřadit od nejnovějších",
|
||||||
|
"sort_oldest": "Seřadit od nejstarších",
|
||||||
|
"sleep_timer": "Časovač spánku",
|
||||||
|
"mins": "{minutes} Minut",
|
||||||
|
"hours": "{hours} Hodin",
|
||||||
|
"hour": "{hours} Hodina",
|
||||||
|
"custom_hours": "Vlastní hodiny",
|
||||||
|
"logs": "Protokoly",
|
||||||
|
"developers": "Vývojáři",
|
||||||
|
"not_logged_in": "Nejste přihlášeni",
|
||||||
|
"search_mode": "Režim hledání",
|
||||||
|
"audio_source": "Zdroj zvuku",
|
||||||
|
"ok": "Ok",
|
||||||
|
"failed_to_encrypt": "Šifrování selhalo",
|
||||||
|
"encryption_failed_warning": "Spotube používá šifrování k bezpečnému ukládání vašich dat. Ale selhalo. Takže se vrátí k nezabezpečenému úložišti\nPokud používáte linux, ujistěte se, že máte nainstalovanou jakoukoli službu k ukládání bezpečnostních pověření (gnome-keyring, kde-wallet, keepassxc atd.)",
|
||||||
|
"querying_info": "Získávání informací...",
|
||||||
|
"piped_api_down": "Piped API je mimo provoz",
|
||||||
|
"piped_down_error_instructions": "Instance Piped {pipedInstance} je momentálně mimo provoz\n\nBuď změňte instanci nebo změňte 'Typ API' na oficiální YouTube API\n\nPo změně se ujistěte, že aplikaci restartujete",
|
||||||
|
"you_are_offline": "Momentálně jste offline",
|
||||||
|
"connection_restored": "Vaše internetové připojení bylo obnoveno",
|
||||||
|
"use_system_title_bar": "Použít systémové záhlaví okna",
|
||||||
|
"crunching_results": "Zpracovávání výsledků...",
|
||||||
|
"search_to_get_results": "Hledejte pro získání výsledků",
|
||||||
|
"use_amoled_mode": "Úplně černé téma",
|
||||||
|
"pitch_dark_theme": "AMOLED režim",
|
||||||
|
"normalize_audio": "Normalizovat audio",
|
||||||
|
"change_cover": "Změnit obal",
|
||||||
|
"add_cover": "Přidat obal",
|
||||||
|
"restore_defaults": "Obnovit výchozí",
|
||||||
|
"download_music_codec": "Kodek pro stahování",
|
||||||
|
"streaming_music_codec": "Kodek pro streamování",
|
||||||
|
"login_with_lastfm": "Přihlásit se pomocí Last.fm",
|
||||||
|
"connect": "Připojit",
|
||||||
|
"disconnect_lastfm": "Odpojit Last.fm",
|
||||||
|
"disconnect": "Odpojit",
|
||||||
|
"username": "Uživatelské jméno",
|
||||||
|
"password": "Heslo",
|
||||||
|
"login": "Přihlásit se",
|
||||||
|
"login_with_your_lastfm": "Přihlásit se pomocí vašeho Last.fm účtu",
|
||||||
|
"scrobble_to_lastfm": "Scrobble na Last.fm",
|
||||||
|
"go_to_album": "Přejít na album",
|
||||||
|
"discord_rich_presence": "Discord Rich Presence",
|
||||||
|
"browse_all": "Procházet vše",
|
||||||
|
"genres": "Žánry",
|
||||||
|
"explore_genres": "Prozkoumat žánry",
|
||||||
|
"friends": "Přátelé",
|
||||||
|
"no_lyrics_available": "Omlouváme se, není možné najít texty pro tuto skladbu",
|
||||||
|
"start_a_radio": "Vytvořit rádio",
|
||||||
|
"how_to_start_radio": "Jak chcete vytvořit rádio?",
|
||||||
|
"replace_queue_question": "Chcete nahradit aktuální frontu nebo k ní přidat?",
|
||||||
|
"endless_playback": "Nekonečné přehrávání",
|
||||||
|
"delete_playlist": "Smazat playlist",
|
||||||
|
"delete_playlist_confirmation": "Jste si jisti, že chcete smazat tento playlist?",
|
||||||
|
"local_tracks": "Místní skladby",
|
||||||
|
"song_link": "Odkaz na skladbu",
|
||||||
|
"skip_this_nonsense": "Přeskočit tenhle nesmysl",
|
||||||
|
"freedom_of_music": "“Svobodná hudba”",
|
||||||
|
"freedom_of_music_palm": "“Svobodná hudba ve vaší dlani”",
|
||||||
|
"get_started": "Začít",
|
||||||
|
"youtube_source_description": "Doporučeno a funguje nejlépe.",
|
||||||
|
"piped_source_description": "Nechcete být sledováni? Stejné jako YouTube, ale respektuje soukromí.",
|
||||||
|
"jiosaavn_source_description": "Nejlepší pro jihoasijský region.",
|
||||||
|
"highest_quality": "Nejvyšší kvalita: {quality}",
|
||||||
|
"select_audio_source": "Vyberte zdroj zvuku",
|
||||||
|
"endless_playback_description": "Automaticky přidávat nové skladby\nna konec fronty",
|
||||||
|
"choose_your_region": "Vyberte svůj region",
|
||||||
|
"choose_your_region_description": "To pomůže Spotube ukázat vám správný obsah\npro vaši lokalitu.",
|
||||||
|
"choose_your_language": "Vyberte svůj jazyk",
|
||||||
|
"help_project_grow": "Pomozte tomuto projektu růst",
|
||||||
|
"help_project_grow_description": "Spotube je open-source projekt. Můžete pomoci tomuto projektu růst tím, že přispějete do projektu, nahlásíte chyby nebo navrhnete nové funkce.",
|
||||||
|
"contribute_on_github": "Přispějte na GitHub",
|
||||||
|
"donate_on_open_collective": "Darujte na Open Collective",
|
||||||
|
"browse_anonymously": "Procházet anonymně",
|
||||||
|
"enable_connect": "Povolit ovládání",
|
||||||
|
"enable_connect_description": "Ovládejte Spotube z jiného zařízení",
|
||||||
|
"devices": "Zařízení",
|
||||||
|
"select": "Vybrat",
|
||||||
|
"connect_client_alert": "Zařízení je ovládáno z {client}",
|
||||||
|
"this_device": "Toto zařízení",
|
||||||
|
"remote": "Ovladač"
|
||||||
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube ist ein Open-Source-Projekt. Sie können diesem Projekt helfen, indem Sie zum Projekt beitragen, Fehler melden oder neue Funktionen vorschlagen.",
|
"help_project_grow_description": "Spotube ist ein Open-Source-Projekt. Sie können diesem Projekt helfen, indem Sie zum Projekt beitragen, Fehler melden oder neue Funktionen vorschlagen.",
|
||||||
"contribute_on_github": "Auf GitHub beitragen",
|
"contribute_on_github": "Auf GitHub beitragen",
|
||||||
"donate_on_open_collective": "Auf Open Collective spenden",
|
"donate_on_open_collective": "Auf Open Collective spenden",
|
||||||
"browse_anonymously": "Anonym durchsuchen"
|
"browse_anonymously": "Anonym durchsuchen",
|
||||||
|
"enable_connect": "Verbindung aktivieren",
|
||||||
|
"enable_connect_description": "Spotube von anderen Geräten steuern",
|
||||||
|
"devices": "Geräte",
|
||||||
|
"select": "Auswählen",
|
||||||
|
"connect_client_alert": "Du wirst von {client} gesteuert",
|
||||||
|
"this_device": "Dieses Gerät",
|
||||||
|
"remote": "Fernbedienung"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube es un proyecto de código abierto. Puedes ayudar a que este proyecto crezca contribuyendo al proyecto, informando errores o sugiriendo nuevas funciones.",
|
"help_project_grow_description": "Spotube es un proyecto de código abierto. Puedes ayudar a que este proyecto crezca contribuyendo al proyecto, informando errores o sugiriendo nuevas funciones.",
|
||||||
"contribute_on_github": "Contribuir en GitHub",
|
"contribute_on_github": "Contribuir en GitHub",
|
||||||
"donate_on_open_collective": "Donar en Open Collective",
|
"donate_on_open_collective": "Donar en Open Collective",
|
||||||
"browse_anonymously": "Navegar Anónimamente"
|
"browse_anonymously": "Navegar Anónimamente",
|
||||||
|
"enable_connect": "Habilitar conexión",
|
||||||
|
"enable_connect_description": "Controla Spotube desde otros dispositivos",
|
||||||
|
"devices": "Dispositivos",
|
||||||
|
"select": "Seleccionar",
|
||||||
|
"connect_client_alert": "Estás siendo controlado por {client}",
|
||||||
|
"this_device": "Este dispositivo",
|
||||||
|
"remote": "Remoto"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube یک پروژه متن باز است. شما میتوانید با به پروژه کمک کردن، گزارش دادن اشکالات یا پیشنهاد ویژگیهای جدید، به این پروژه کمک کنید.",
|
"help_project_grow_description": "Spotube یک پروژه متن باز است. شما میتوانید با به پروژه کمک کردن، گزارش دادن اشکالات یا پیشنهاد ویژگیهای جدید، به این پروژه کمک کنید.",
|
||||||
"contribute_on_github": "مشارکت در GitHub",
|
"contribute_on_github": "مشارکت در GitHub",
|
||||||
"donate_on_open_collective": "کمک مالی در Open Collective",
|
"donate_on_open_collective": "کمک مالی در Open Collective",
|
||||||
"browse_anonymously": "مرور به صورت ناشناس"
|
"browse_anonymously": "مرور به صورت ناشناس",
|
||||||
|
"enable_connect": "فعالسازی اتصال",
|
||||||
|
"enable_connect_description": "کنترل Spotube از دیگر دستگاهها",
|
||||||
|
"devices": "دستگاهها",
|
||||||
|
"select": "انتخاب",
|
||||||
|
"connect_client_alert": "شما توسط {client} کنترل میشوید",
|
||||||
|
"this_device": "این دستگاه",
|
||||||
|
"remote": "راهدور"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube est un projet open-source. Vous pouvez aider ce projet à grandir en contribuant au projet, en signalant des bugs ou en suggérant de nouvelles fonctionnalités.",
|
"help_project_grow_description": "Spotube est un projet open-source. Vous pouvez aider ce projet à grandir en contribuant au projet, en signalant des bugs ou en suggérant de nouvelles fonctionnalités.",
|
||||||
"contribute_on_github": "Contribuer sur GitHub",
|
"contribute_on_github": "Contribuer sur GitHub",
|
||||||
"donate_on_open_collective": "Faire un don sur Open Collective",
|
"donate_on_open_collective": "Faire un don sur Open Collective",
|
||||||
"browse_anonymously": "Naviguer anonymement"
|
"browse_anonymously": "Naviguer anonymement",
|
||||||
|
"enable_connect": "Activer la connexion",
|
||||||
|
"enable_connect_description": "Contrôlez Spotube depuis d'autres appareils",
|
||||||
|
"devices": "Appareils",
|
||||||
|
"select": "Sélectionner",
|
||||||
|
"connect_client_alert": "Vous êtes contrôlé par {client}",
|
||||||
|
"this_device": "Cet appareil",
|
||||||
|
"remote": "À distance"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube एक ओपन सोर्स परियोजना है। आप इस परियोजना को योगदान देकर, बग रिपोर्ट करके या नई विशेषताओं का सुझाव देकर इस परियोजना को बढ़ा सकते हैं।",
|
"help_project_grow_description": "Spotube एक ओपन सोर्स परियोजना है। आप इस परियोजना को योगदान देकर, बग रिपोर्ट करके या नई विशेषताओं का सुझाव देकर इस परियोजना को बढ़ा सकते हैं।",
|
||||||
"contribute_on_github": "GitHub पर योगदान करें",
|
"contribute_on_github": "GitHub पर योगदान करें",
|
||||||
"donate_on_open_collective": "ओपन कलेक्टिव पर दान करें",
|
"donate_on_open_collective": "ओपन कलेक्टिव पर दान करें",
|
||||||
"browse_anonymously": "बिना नाम के ब्राउज़ करें"
|
"browse_anonymously": "बिना नाम के ब्राउज़ करें",
|
||||||
|
"enable_connect": "कनेक्ट सक्षम करें",
|
||||||
|
"enable_connect_description": "अन्य उपकरणों से Spotube को नियंत्रित करें",
|
||||||
|
"devices": "उपकरण",
|
||||||
|
"select": "चयन करें",
|
||||||
|
"connect_client_alert": "आप {client} द्वारा नियंत्रित हो रहे हैं",
|
||||||
|
"this_device": "यह उपकरण",
|
||||||
|
"remote": "रिमोट"
|
||||||
}
|
}
|
||||||
@ -314,5 +314,12 @@
|
|||||||
"help_project_grow_description": "Spotube è un progetto open-source. Puoi aiutare questo progetto a crescere contribuendo al progetto, segnalando bug o suggerendo nuove funzionalità.",
|
"help_project_grow_description": "Spotube è un progetto open-source. Puoi aiutare questo progetto a crescere contribuendo al progetto, segnalando bug o suggerendo nuove funzionalità.",
|
||||||
"contribute_on_github": "Contribuisci su GitHub",
|
"contribute_on_github": "Contribuisci su GitHub",
|
||||||
"donate_on_open_collective": "Dona su Open Collective",
|
"donate_on_open_collective": "Dona su Open Collective",
|
||||||
"browse_anonymously": "Naviga in modo anonimo"
|
"browse_anonymously": "Naviga in modo anonimo",
|
||||||
|
"enable_connect": "Abilita connessione",
|
||||||
|
"enable_connect_description": "Controlla Spotube da altri dispositivi",
|
||||||
|
"devices": "Dispositivi",
|
||||||
|
"select": "Seleziona",
|
||||||
|
"connect_client_alert": "Stai venendo controllato da {client}",
|
||||||
|
"this_device": "Questo dispositivo",
|
||||||
|
"remote": "Remoto"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグを報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。",
|
"help_project_grow_description": "Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグを報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。",
|
||||||
"contribute_on_github": "GitHubで貢献する",
|
"contribute_on_github": "GitHubで貢献する",
|
||||||
"donate_on_open_collective": "Open Collectiveで寄付する",
|
"donate_on_open_collective": "Open Collectiveで寄付する",
|
||||||
"browse_anonymously": "匿名で閲覧する"
|
"browse_anonymously": "匿名で閲覧する",
|
||||||
|
"enable_connect": "接続を有効にする",
|
||||||
|
"enable_connect_description": "他のデバイスからSpotubeを制御する",
|
||||||
|
"devices": "デバイス",
|
||||||
|
"select": "選択する",
|
||||||
|
"connect_client_alert": "{client} によって操作されています",
|
||||||
|
"this_device": "このデバイス",
|
||||||
|
"remote": "リモート"
|
||||||
}
|
}
|
||||||
@ -314,5 +314,12 @@
|
|||||||
"help_project_grow_description": "Spotube는 오픈 소스 프로젝트입니다. 프로젝트에 기여하거나 버그를 보고하거나 새로운 기능을 제안하여이 프로젝트의 성장에 도움을 줄 수 있습니다.",
|
"help_project_grow_description": "Spotube는 오픈 소스 프로젝트입니다. 프로젝트에 기여하거나 버그를 보고하거나 새로운 기능을 제안하여이 프로젝트의 성장에 도움을 줄 수 있습니다.",
|
||||||
"contribute_on_github": "GitHub에서 기여하기",
|
"contribute_on_github": "GitHub에서 기여하기",
|
||||||
"donate_on_open_collective": "Open Collective에 기부하기",
|
"donate_on_open_collective": "Open Collective에 기부하기",
|
||||||
"browse_anonymously": "익명으로 둘러보기"
|
"browse_anonymously": "익명으로 둘러보기",
|
||||||
|
"enable_connect": "연결 활성화",
|
||||||
|
"enable_connect_description": "다른 장치에서 Spotube 제어",
|
||||||
|
"devices": "장치",
|
||||||
|
"select": "선택",
|
||||||
|
"connect_client_alert": "{client}님에 의해 제어되고 있습니다",
|
||||||
|
"this_device": "이 장치",
|
||||||
|
"remote": "원격"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube एक खुला स्रोतको परियोजना हो। तपाईं परियोजनामा योगदान गरेर, त्रुटिहरू सूचिकै, वा नयाँ सुविधाहरू सुझाव दिएर यस परियोजनामा वृद्धि गर्न सक्नुहुन्छ।",
|
"help_project_grow_description": "Spotube एक खुला स्रोतको परियोजना हो। तपाईं परियोजनामा योगदान गरेर, त्रुटिहरू सूचिकै, वा नयाँ सुविधाहरू सुझाव दिएर यस परियोजनामा वृद्धि गर्न सक्नुहुन्छ।",
|
||||||
"contribute_on_github": "GitHubमा योगदान गर्नुहोस्",
|
"contribute_on_github": "GitHubमा योगदान गर्नुहोस्",
|
||||||
"donate_on_open_collective": "खुला संगठनमा दान गर्नुहोस्",
|
"donate_on_open_collective": "खुला संगठनमा दान गर्नुहोस्",
|
||||||
"browse_anonymously": "अनामित रूपमा ब्राउज़ गर्नुहोस्"
|
"browse_anonymously": "अनामित रूपमा ब्राउज़ गर्नुहोस्",
|
||||||
|
"enable_connect": "कनेक्ट सक्रिय गर्नुहोस्",
|
||||||
|
"enable_connect_description": "अन्य उपकरणहरूबाट Spotube कन्ट्रोल गर्नुहोस्",
|
||||||
|
"devices": "उपकरणहरू",
|
||||||
|
"select": "चयन गर्नुहोस्",
|
||||||
|
"connect_client_alert": "तपाईंलाई {client} द्वारा नियन्त्रित गरिएको छ",
|
||||||
|
"this_device": "यो उपकरण",
|
||||||
|
"remote": "दूरसंचार"
|
||||||
}
|
}
|
||||||
@ -314,5 +314,12 @@
|
|||||||
"help_project_grow_description": "Spotube is een open-source project. U kunt dit project helpen groeien door bij te dragen aan het project, bugs te melden of nieuwe functies voor te stellen.",
|
"help_project_grow_description": "Spotube is een open-source project. U kunt dit project helpen groeien door bij te dragen aan het project, bugs te melden of nieuwe functies voor te stellen.",
|
||||||
"contribute_on_github": "Bijdragen op GitHub",
|
"contribute_on_github": "Bijdragen op GitHub",
|
||||||
"donate_on_open_collective": "Doneren op Open Collective",
|
"donate_on_open_collective": "Doneren op Open Collective",
|
||||||
"browse_anonymously": "Anoniem Bladeren"
|
"browse_anonymously": "Anoniem Bladeren",
|
||||||
|
"enable_connect": "Verbinding inschakelen",
|
||||||
|
"enable_connect_description": "Spotube bedienen vanaf andere apparaten",
|
||||||
|
"devices": "Apparaten",
|
||||||
|
"select": "Selecteren",
|
||||||
|
"connect_client_alert": "Je wordt gecontroleerd door {client}",
|
||||||
|
"this_device": "Dit apparaat",
|
||||||
|
"remote": "Afstandsbediening"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube to projekt open-source. Możesz pomóc temu projektowi rosnąć, przyczyniając się do projektu, zgłaszając błędy lub sugerując nowe funkcje.",
|
"help_project_grow_description": "Spotube to projekt open-source. Możesz pomóc temu projektowi rosnąć, przyczyniając się do projektu, zgłaszając błędy lub sugerując nowe funkcje.",
|
||||||
"contribute_on_github": "Przyczyniaj się na GitHubie",
|
"contribute_on_github": "Przyczyniaj się na GitHubie",
|
||||||
"donate_on_open_collective": "Dotuj na Open Collective",
|
"donate_on_open_collective": "Dotuj na Open Collective",
|
||||||
"browse_anonymously": "Przeglądaj Anonimowo"
|
"browse_anonymously": "Przeglądaj Anonimowo",
|
||||||
|
"enable_connect": "Włącz połączenie",
|
||||||
|
"enable_connect_description": "Kontroluj Spotube z innych urządzeń",
|
||||||
|
"devices": "Urządzenia",
|
||||||
|
"select": "Wybierz",
|
||||||
|
"connect_client_alert": "Jesteś sterowany przez {client}",
|
||||||
|
"this_device": "To urządzenie",
|
||||||
|
"remote": "Zdalny"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube é um projeto de código aberto. Você pode ajudar este projeto a crescer contribuindo para o projeto, relatando bugs ou sugerindo novos recursos.",
|
"help_project_grow_description": "Spotube é um projeto de código aberto. Você pode ajudar este projeto a crescer contribuindo para o projeto, relatando bugs ou sugerindo novos recursos.",
|
||||||
"contribute_on_github": "Contribuir no GitHub",
|
"contribute_on_github": "Contribuir no GitHub",
|
||||||
"donate_on_open_collective": "Doar no Open Collective",
|
"donate_on_open_collective": "Doar no Open Collective",
|
||||||
"browse_anonymously": "Navegar Anonimamente"
|
"browse_anonymously": "Navegar Anonimamente",
|
||||||
|
"enable_connect": "Ativar conexão",
|
||||||
|
"enable_connect_description": "Controle o Spotube a partir de outros dispositivos",
|
||||||
|
"devices": "Dispositivos",
|
||||||
|
"select": "Selecionar",
|
||||||
|
"connect_client_alert": "Você está sendo controlado por {client}",
|
||||||
|
"this_device": "Este dispositivo",
|
||||||
|
"remote": "Remoto"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube - это проект с открытым исходным кодом. Вы можете помочь этому проекту развиваться, внося вклад в проект, сообщая ошибках или предлагая новые функции.",
|
"help_project_grow_description": "Spotube - это проект с открытым исходным кодом. Вы можете помочь этому проекту развиваться, внося вклад в проект, сообщая ошибках или предлагая новые функции.",
|
||||||
"contribute_on_github": "Внести вклад на GitHub",
|
"contribute_on_github": "Внести вклад на GitHub",
|
||||||
"donate_on_open_collective": "Пожертвовать на Open Collective",
|
"donate_on_open_collective": "Пожертвовать на Open Collective",
|
||||||
"browse_anonymously": "Анонимно просматривать"
|
"browse_anonymously": "Анонимно просматривать",
|
||||||
|
"enable_connect": "Включить подключение",
|
||||||
|
"enable_connect_description": "Управление Spotube с других устройств",
|
||||||
|
"devices": "Устройства",
|
||||||
|
"select": "Выбрать",
|
||||||
|
"connect_client_alert": "Вас контролирует {client}",
|
||||||
|
"this_device": "Это устройство",
|
||||||
|
"remote": "Дистанционное управление"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,13 @@
|
|||||||
"help_project_grow_description": "Spotube เป็นโครงการโอเพนซอร์ส คุณสามารถช่วยให้โครงการนี้เติบโตได้โดยการมีส่วนร่วมในโครงการ รายงานข้อบกพร่อง หรือเสนอคุณสมบัติใหม่",
|
"help_project_grow_description": "Spotube เป็นโครงการโอเพนซอร์ส คุณสามารถช่วยให้โครงการนี้เติบโตได้โดยการมีส่วนร่วมในโครงการ รายงานข้อบกพร่อง หรือเสนอคุณสมบัติใหม่",
|
||||||
"contribute_on_github": "มีส่วนร่วมบน GitHub",
|
"contribute_on_github": "มีส่วนร่วมบน GitHub",
|
||||||
"donate_on_open_collective": "บริจาคบน Open Collective",
|
"donate_on_open_collective": "บริจาคบน Open Collective",
|
||||||
"browse_anonymously": "เรียกดูแบบไม่ระบุตัวตน"
|
"browse_anonymously": "เรียกดูแบบไม่ระบุตัวตน",
|
||||||
|
"choose_your_language": "เลือกภาษาของคุณ",
|
||||||
|
"enable_connect": "เปิดใช้งานการเชื่อมต่อ",
|
||||||
|
"enable_connect_description": "ควบคุม Spotube จากอุปกรณ์อื่น",
|
||||||
|
"devices": "อุปกรณ์",
|
||||||
|
"select": "เลือก",
|
||||||
|
"connect_client_alert": "คุณกำลังถูกควบคุมโดย {client}",
|
||||||
|
"this_device": "อุปกรณ์นี้",
|
||||||
|
"remote": "ระยะไกล"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube - це проект з відкритим кодом. Ви можете допомогти цьому проекту зростати, вносячи свій внесок у проект, повідомляючи про помилки або пропонуючи нові функції.",
|
"help_project_grow_description": "Spotube - це проект з відкритим кодом. Ви можете допомогти цьому проекту зростати, вносячи свій внесок у проект, повідомляючи про помилки або пропонуючи нові функції.",
|
||||||
"contribute_on_github": "Долучайтесь на GitHub",
|
"contribute_on_github": "Долучайтесь на GitHub",
|
||||||
"donate_on_open_collective": "Пожертвуйте на Open Collective",
|
"donate_on_open_collective": "Пожертвуйте на Open Collective",
|
||||||
"browse_anonymously": "Анонімно переглядати"
|
"browse_anonymously": "Анонімно переглядати",
|
||||||
|
"enable_connect": "Увімкнути підключення",
|
||||||
|
"enable_connect_description": "Керуйте Spotube з інших пристроїв",
|
||||||
|
"devices": "Пристрої",
|
||||||
|
"select": "Вибрати",
|
||||||
|
"connect_client_alert": "Вас керує {client}",
|
||||||
|
"this_device": "Цей пристрій",
|
||||||
|
"remote": "Віддалений"
|
||||||
}
|
}
|
||||||
@ -311,5 +311,14 @@
|
|||||||
"help_project_grow_description": "Spotube là một dự án mã nguồn mở. Bạn có thể giúp dự án này phát triển bằng cách đóng góp vào dự án, báo cáo lỗi hoặc đề xuất tính năng mới.",
|
"help_project_grow_description": "Spotube là một dự án mã nguồn mở. Bạn có thể giúp dự án này phát triển bằng cách đóng góp vào dự án, báo cáo lỗi hoặc đề xuất tính năng mới.",
|
||||||
"contribute_on_github": "Đóng góp trên GitHub",
|
"contribute_on_github": "Đóng góp trên GitHub",
|
||||||
"donate_on_open_collective": "Quyên góp trên Open Collective",
|
"donate_on_open_collective": "Quyên góp trên Open Collective",
|
||||||
"browse_anonymously": "Duyệt Anonymously"
|
"browse_anonymously": "Duyệt Anonymously",
|
||||||
|
"friends": "Bạn bè",
|
||||||
|
"no_lyrics_available": "Xin lỗi, không tìm thấy lời cho bài hát này",
|
||||||
|
"enable_connect": "Kích hoạt kết nối",
|
||||||
|
"enable_connect_description": "Điều khiển Spotube từ các thiết bị khác",
|
||||||
|
"devices": "Thiết bị",
|
||||||
|
"select": "Chọn",
|
||||||
|
"connect_client_alert": "Bạn đang được điều khiển bởi {client}",
|
||||||
|
"this_device": "Thiết bị này",
|
||||||
|
"remote": "Từ xa"
|
||||||
}
|
}
|
||||||
@ -313,5 +313,12 @@
|
|||||||
"help_project_grow_description": "Spotube是一个开源项目。您可以通过为项目做出贡献、报告错误或建议新功能来帮助该项目成长。",
|
"help_project_grow_description": "Spotube是一个开源项目。您可以通过为项目做出贡献、报告错误或建议新功能来帮助该项目成长。",
|
||||||
"contribute_on_github": "在GitHub上做出贡献",
|
"contribute_on_github": "在GitHub上做出贡献",
|
||||||
"donate_on_open_collective": "在Open Collective上捐款",
|
"donate_on_open_collective": "在Open Collective上捐款",
|
||||||
"browse_anonymously": "匿名浏览"
|
"browse_anonymously": "匿名浏览",
|
||||||
|
"enable_connect": "启用连接",
|
||||||
|
"enable_connect_description": "从其他设备控制Spotube",
|
||||||
|
"devices": "设备",
|
||||||
|
"select": "选择",
|
||||||
|
"connect_client_alert": "您正在被 {client} 控制",
|
||||||
|
"this_device": "此设备",
|
||||||
|
"remote": "远程"
|
||||||
}
|
}
|
||||||
@ -12,6 +12,7 @@
|
|||||||
/// doannc2212@github => Vietnamese
|
/// doannc2212@github => Vietnamese
|
||||||
/// sappho192@github => Korean
|
/// sappho192@github => Korean
|
||||||
/// watchakorn-18k@github => Thai
|
/// watchakorn-18k@github => Thai
|
||||||
|
/// Microsoft Copilot, Tutislav@github => Czech
|
||||||
|
|
||||||
library l10n;
|
library l10n;
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ class L10n {
|
|||||||
const Locale('ar', 'SA'),
|
const Locale('ar', 'SA'),
|
||||||
const Locale('bn', 'BD'),
|
const Locale('bn', 'BD'),
|
||||||
const Locale('ca', 'AD'),
|
const Locale('ca', 'AD'),
|
||||||
|
const Locale('cs', 'CZ'),
|
||||||
const Locale('de', 'GE'),
|
const Locale('de', 'GE'),
|
||||||
const Locale('es', 'ES'),
|
const Locale('es', 'ES'),
|
||||||
const Locale('fa', 'IR'),
|
const Locale('fa', 'IR'),
|
||||||
|
|||||||
@ -4,11 +4,11 @@ import 'package:device_preview/device_preview.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:local_notifier/local_notifier.dart';
|
||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
import 'package:metadata_god/metadata_god.dart';
|
import 'package:metadata_god/metadata_god.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@ -19,6 +19,7 @@ import 'package:spotube/hooks/configurators/use_close_behavior.dart';
|
|||||||
import 'package:spotube/hooks/configurators/use_deep_linking.dart';
|
import 'package:spotube/hooks/configurators/use_deep_linking.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart';
|
import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_get_storage_perms.dart';
|
import 'package:spotube/hooks/configurators/use_get_storage_perms.dart';
|
||||||
|
import 'package:spotube/provider/tray_manager/tray_manager.dart';
|
||||||
import 'package:spotube/l10n/l10n.dart';
|
import 'package:spotube/l10n/l10n.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/models/skip_segment.dart';
|
import 'package:spotube/models/skip_segment.dart';
|
||||||
@ -31,15 +32,17 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart
|
|||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/cli/cli.dart';
|
import 'package:spotube/services/cli/cli.dart';
|
||||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||||
|
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
||||||
import 'package:spotube/themes/theme.dart';
|
import 'package:spotube/themes/theme.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:system_theme/system_theme.dart';
|
import 'package:system_theme/system_theme.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_init_sys_tray.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
Future<void> main(List<String> rawArgs) async {
|
Future<void> main(List<String> rawArgs) async {
|
||||||
final arguments = await startCLI(rawArgs);
|
final arguments = await startCLI(rawArgs);
|
||||||
@ -55,12 +58,12 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
|
|
||||||
// force High Refresh Rate on some Android devices (like One Plus)
|
// force High Refresh Rate on some Android devices (like One Plus)
|
||||||
if (DesktopTools.platform.isAndroid) {
|
if (kIsAndroid) {
|
||||||
await FlutterDisplayMode.setHighRefreshRate();
|
await FlutterDisplayMode.setHighRefreshRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DesktopTools.platform.isDesktop) {
|
if (kIsDesktop) {
|
||||||
await DesktopTools.window.setPreventClose(true);
|
await windowManager.setPreventClose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await SystemTheme.accentColor.load();
|
await SystemTheme.accentColor.load();
|
||||||
@ -69,7 +72,7 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
MetadataGod.initialize();
|
MetadataGod.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DesktopTools.platform.isWindows || DesktopTools.platform.isLinux) {
|
if (kIsWindows || kIsLinux) {
|
||||||
DiscordRPC.initialize();
|
DiscordRPC.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,14 +104,10 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
path: hiveCacheDir,
|
path: hiveCacheDir,
|
||||||
);
|
);
|
||||||
|
|
||||||
await DesktopTools.ensureInitialized(
|
if (kIsDesktop) {
|
||||||
DesktopWindowOptions(
|
await localNotifier.setup(appName: "Spotube");
|
||||||
hideTitleBar: true,
|
await WindowManagerTools.initialize();
|
||||||
title: "Spotube",
|
}
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
minimumSize: const Size(300, 700),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Catcher2(
|
Catcher2(
|
||||||
enableLogger: arguments["verbose"],
|
enableLogger: arguments["verbose"],
|
||||||
@ -189,9 +188,9 @@ class SpotubeState extends ConsumerState<Spotube> {
|
|||||||
ref.listen(playbackServerProvider, (_, __) {});
|
ref.listen(playbackServerProvider, (_, __) {});
|
||||||
ref.listen(connectServerProvider, (_, __) {});
|
ref.listen(connectServerProvider, (_, __) {});
|
||||||
ref.listen(connectClientsProvider, (_, __) {});
|
ref.listen(connectClientsProvider, (_, __) {});
|
||||||
|
ref.listen(trayManagerProvider, (_, __) {});
|
||||||
|
|
||||||
useDisableBatteryOptimizations();
|
useDisableBatteryOptimizations();
|
||||||
useInitSysTray(ref);
|
|
||||||
useDeepLinking(ref);
|
useDeepLinking(ref);
|
||||||
useCloseBehavior(ref);
|
useCloseBehavior(ref);
|
||||||
useGetStoragePermissions(ref);
|
useGetStoragePermissions(ref);
|
||||||
@ -233,9 +232,7 @@ class SpotubeState extends ConsumerState<Spotube> {
|
|||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return DevicePreview.appBuilder(
|
return DevicePreview.appBuilder(
|
||||||
context,
|
context,
|
||||||
DesktopTools.platform.isDesktop && !DesktopTools.platform.isMacOS
|
kIsDesktop && !kIsMacOS ? DragToResizeArea(child: child!) : child,
|
||||||
? DragToResizeArea(child: child!)
|
|
||||||
: child,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ part of 'connect.dart';
|
|||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
WebSocketLoadEventData _$WebSocketLoadEventDataFromJson(
|
WebSocketLoadEventData _$WebSocketLoadEventDataFromJson(
|
||||||
Map<String, dynamic> json) {
|
Map<String, dynamic> json) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ part of 'home_feed.dart';
|
|||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
SpotifySectionPlaylist _$SpotifySectionPlaylistFromJson(
|
SpotifySectionPlaylist _$SpotifySectionPlaylistFromJson(
|
||||||
Map<String, dynamic> json) {
|
Map<String, dynamic> json) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ part of 'recommendation_seeds.dart';
|
|||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$GeneratePlaylistProviderInput {
|
mixin _$GeneratePlaylistProviderInput {
|
||||||
|
|||||||
@ -23,6 +23,7 @@ class ConnectPage extends HookConsumerWidget {
|
|||||||
appBar: PageWindowTitleBar(
|
appBar: PageWindowTitleBar(
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
title: Text(context.l10n.devices),
|
title: Text(context.l10n.devices),
|
||||||
|
titleSpacing: 0,
|
||||||
),
|
),
|
||||||
body: ListTileTheme(
|
body: ListTileTheme(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
|||||||
@ -54,6 +54,11 @@ class HomeFeedSectionPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SafeArea(
|
||||||
|
child: SizedBox(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import 'package:spotube/components/shared/waypoint.dart';
|
|||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class GenrePlaylistsPage extends HookConsumerWidget {
|
class GenrePlaylistsPage extends HookConsumerWidget {
|
||||||
final Category category;
|
final Category category;
|
||||||
@ -27,7 +27,7 @@ class GenrePlaylistsPage extends HookConsumerWidget {
|
|||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: DesktopTools.platform.isDesktop
|
appBar: kIsDesktop
|
||||||
? const PageWindowTitleBar(
|
? const PageWindowTitleBar(
|
||||||
leading: BackButton(color: Colors.white),
|
leading: BackButton(color: Colors.white),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
@ -53,12 +53,12 @@ class GenrePlaylistsPage extends HookConsumerWidget {
|
|||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
automaticallyImplyLeading: DesktopTools.platform.isMobile,
|
automaticallyImplyLeading: kIsMobile,
|
||||||
expandedHeight: mediaQuery.mdAndDown ? 200 : 150,
|
expandedHeight: mediaQuery.mdAndDown ? 200 : 150,
|
||||||
title: const Text(""),
|
title: const Text(""),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
centerTitle: DesktopTools.platform.isDesktop,
|
centerTitle: kIsDesktop,
|
||||||
title: Text(
|
title: Text(
|
||||||
category.name!,
|
category.name!,
|
||||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||||
|
|||||||
@ -26,6 +26,7 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
appBar: PageWindowTitleBar(
|
appBar: PageWindowTitleBar(
|
||||||
title: Text(context.l10n.explore_genres),
|
title: Text(context.l10n.explore_genres),
|
||||||
automaticallyImplyLeading: true,
|
automaticallyImplyLeading: true,
|
||||||
|
titleSpacing: 0,
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/components/connect/connect_device.dart';
|
import 'package:spotube/components/connect/connect_device.dart';
|
||||||
import 'package:spotube/components/home/sections/featured.dart';
|
import 'package:spotube/components/home/sections/featured.dart';
|
||||||
import 'package:spotube/components/home/sections/feed.dart';
|
import 'package:spotube/components/home/sections/feed.dart';
|
||||||
@ -14,6 +14,7 @@ import 'package:spotube/components/shared/image/universal_image.dart';
|
|||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
@ -29,19 +30,26 @@ class HomePage extends HookConsumerWidget {
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
appBar: kIsMobile || kIsMacOS ? null : const PageWindowTitleBar(),
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
slivers: [
|
slivers: [
|
||||||
if (mediaQuery.mdAndDown)
|
if (mediaQuery.mdAndDown)
|
||||||
PageWindowTitleBar.sliver(
|
SliverAppBar(
|
||||||
pinned: DesktopTools.platform.isDesktop,
|
floating: true,
|
||||||
|
title: Assets.spotubeLogoPng.image(height: 45),
|
||||||
actions: [
|
actions: [
|
||||||
const ConnectDeviceButton(),
|
const ConnectDeviceButton(),
|
||||||
const Gap(10),
|
const Gap(10),
|
||||||
Consumer(builder: (context, ref, _) {
|
Consumer(builder: (context, ref, _) {
|
||||||
|
final auth = ref.watch(authenticationProvider);
|
||||||
final me = ref.watch(meProvider);
|
final me = ref.watch(meProvider);
|
||||||
final meData = me.asData?.value;
|
final meData = me.asData?.value;
|
||||||
|
|
||||||
|
if (auth == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: CircleAvatar(
|
icon: CircleAvatar(
|
||||||
backgroundImage: UniversalImage.imageProvider(
|
backgroundImage: UniversalImage.imageProvider(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -18,6 +17,7 @@ import 'package:spotube/pages/lyrics/synced_lyrics.dart';
|
|||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class MiniLyricsPage extends HookConsumerWidget {
|
class MiniLyricsPage extends HookConsumerWidget {
|
||||||
final Size prevSize;
|
final Size prevSize;
|
||||||
@ -36,9 +36,11 @@ class MiniLyricsPage extends HookConsumerWidget {
|
|||||||
final showLyrics = useState(true);
|
final showLyrics = useState(true);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
if (kIsDesktop) {
|
||||||
wasMaximized.value = await DesktopTools.window.isMaximized();
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
});
|
wasMaximized.value = await windowManager.isMaximized();
|
||||||
|
});
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -112,11 +114,13 @@ class MiniLyricsPage extends HookConsumerWidget {
|
|||||||
areaActive.value = true;
|
areaActive.value = true;
|
||||||
hoverMode.value = false;
|
hoverMode.value = false;
|
||||||
|
|
||||||
await DesktopTools.window.setSize(
|
if (kIsDesktop) {
|
||||||
showLyrics.value
|
await windowManager.setSize(
|
||||||
? const Size(400, 500)
|
showLyrics.value
|
||||||
: const Size(400, 150),
|
? const Size(400, 500)
|
||||||
);
|
: const Size(400, 150),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -135,33 +139,34 @@ class MiniLyricsPage extends HookConsumerWidget {
|
|||||||
hoverMode.value = !hoverMode.value;
|
hoverMode.value = !hoverMode.value;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FutureBuilder(
|
if (kIsDesktop)
|
||||||
future: DesktopTools.window.isAlwaysOnTop(),
|
FutureBuilder(
|
||||||
builder: (context, snapshot) {
|
future: windowManager.isAlwaysOnTop(),
|
||||||
return IconButton(
|
builder: (context, snapshot) {
|
||||||
tooltip: context.l10n.always_on_top,
|
return IconButton(
|
||||||
icon: Icon(
|
tooltip: context.l10n.always_on_top,
|
||||||
snapshot.data == true
|
icon: Icon(
|
||||||
? SpotubeIcons.pinOn
|
snapshot.data == true
|
||||||
: SpotubeIcons.pinOff,
|
? SpotubeIcons.pinOn
|
||||||
),
|
: SpotubeIcons.pinOff,
|
||||||
style: ButtonStyle(
|
),
|
||||||
foregroundColor: snapshot.data == true
|
style: ButtonStyle(
|
||||||
? MaterialStateProperty.all(
|
foregroundColor: snapshot.data == true
|
||||||
theme.colorScheme.primary)
|
? MaterialStateProperty.all(
|
||||||
: null,
|
theme.colorScheme.primary)
|
||||||
),
|
: null,
|
||||||
onPressed: snapshot.data == null
|
),
|
||||||
? null
|
onPressed: snapshot.data == null
|
||||||
: () async {
|
? null
|
||||||
await DesktopTools.window.setAlwaysOnTop(
|
: () async {
|
||||||
snapshot.data == true ? false : true,
|
await windowManager.setAlwaysOnTop(
|
||||||
);
|
snapshot.data == true ? false : true,
|
||||||
update();
|
);
|
||||||
},
|
update();
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -243,19 +248,20 @@ class MiniLyricsPage extends HookConsumerWidget {
|
|||||||
tooltip: context.l10n.exit_mini_player,
|
tooltip: context.l10n.exit_mini_player,
|
||||||
icon: const Icon(SpotubeIcons.maximize),
|
icon: const Icon(SpotubeIcons.maximize),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
if (!kIsDesktop) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DesktopTools.window
|
await windowManager
|
||||||
.setMinimumSize(const Size(300, 700));
|
.setMinimumSize(const Size(300, 700));
|
||||||
await DesktopTools.window.setAlwaysOnTop(false);
|
await windowManager.setAlwaysOnTop(false);
|
||||||
if (wasMaximized.value) {
|
if (wasMaximized.value) {
|
||||||
await DesktopTools.window.maximize();
|
await windowManager.maximize();
|
||||||
} else {
|
} else {
|
||||||
await DesktopTools.window.setSize(prevSize);
|
await windowManager.setSize(prevSize);
|
||||||
}
|
}
|
||||||
await DesktopTools.window
|
await windowManager.setAlignment(Alignment.center);
|
||||||
.setAlignment(Alignment.center);
|
|
||||||
if (!kIsLinux) {
|
if (!kIsLinux) {
|
||||||
await DesktopTools.window.setHasShadow(true);
|
await windowManager.setHasShadow(true);
|
||||||
}
|
}
|
||||||
await Future.delayed(
|
await Future.delayed(
|
||||||
const Duration(milliseconds: 200));
|
const Duration(milliseconds: 200));
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -21,6 +20,7 @@ import 'package:spotube/provider/download_manager_provider.dart';
|
|||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/services/connectivity_adapter.dart';
|
import 'package:spotube/services/connectivity_adapter.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
const rootPaths = {
|
const rootPaths = {
|
||||||
"/": 0,
|
"/": 0,
|
||||||
@ -38,7 +38,6 @@ class RootApp extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final isMounted = useIsMounted();
|
|
||||||
final showingDialogCompleter = useRef(Completer()..complete());
|
final showingDialogCompleter = useRef(Completer()..complete());
|
||||||
final downloader = ref.watch(downloadManagerProvider);
|
final downloader = ref.watch(downloadManagerProvider);
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
@ -129,7 +128,7 @@ class RootApp extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
downloader.onFileExists = (track) async {
|
downloader.onFileExists = (track) async {
|
||||||
if (!isMounted()) return false;
|
if (!context.mounted) return false;
|
||||||
|
|
||||||
if (!showingDialogCompleter.value.isCompleted) {
|
if (!showingDialogCompleter.value.isCompleted) {
|
||||||
await showingDialogCompleter.value.future;
|
await showingDialogCompleter.value.future;
|
||||||
@ -207,7 +206,7 @@ class RootApp extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
drawerScrimColor: Colors.transparent,
|
drawerScrimColor: Colors.transparent,
|
||||||
endDrawer: DesktopTools.platform.isDesktop
|
endDrawer: kIsDesktop
|
||||||
? Container(
|
? Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 800),
|
constraints: const BoxConstraints(maxWidth: 800),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@ -113,7 +113,7 @@ class SearchTracksSection extends HookConsumerWidget {
|
|||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: searchTrack.isLoadingNextPage
|
onPressed: searchTrack.isLoadingNextPage
|
||||||
? null
|
? null
|
||||||
: () => searchTrackNotifier.fetchMore,
|
: searchTrackNotifier.fetchMore,
|
||||||
child: searchTrack.isLoadingNextPage
|
child: searchTrack.isLoadingNextPage
|
||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: Text(context.l10n.load_more),
|
: Text(context.l10n.load_more),
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
@ -8,6 +7,7 @@ import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
|||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class SettingsDesktopSection extends HookConsumerWidget {
|
class SettingsDesktopSection extends HookConsumerWidget {
|
||||||
const SettingsDesktopSection({super.key});
|
const SettingsDesktopSection({super.key});
|
||||||
@ -53,7 +53,7 @@ class SettingsDesktopSection extends HookConsumerWidget {
|
|||||||
value: preferences.systemTitleBar,
|
value: preferences.systemTitleBar,
|
||||||
onChanged: preferencesNotifier.setSystemTitleBar,
|
onChanged: preferencesNotifier.setSystemTitleBar,
|
||||||
),
|
),
|
||||||
if (!DesktopTools.platform.isMacOS)
|
if (!kIsMacOS)
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: const Icon(SpotubeIcons.discord),
|
secondary: const Icon(SpotubeIcons.discord),
|
||||||
title: Text(context.l10n.discord_rich_presence),
|
title: Text(context.l10n.discord_rich_presence),
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:file_selector/file_selector.dart';
|
import 'package:file_selector/file_selector.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class SettingsDownloadsSection extends HookConsumerWidget {
|
class SettingsDownloadsSection extends HookConsumerWidget {
|
||||||
const SettingsDownloadsSection({super.key});
|
const SettingsDownloadsSection({super.key});
|
||||||
@ -18,7 +18,7 @@ class SettingsDownloadsSection extends HookConsumerWidget {
|
|||||||
final preferences = ref.watch(userPreferencesProvider);
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
|
||||||
final pickDownloadLocation = useCallback(() async {
|
final pickDownloadLocation = useCallback(() async {
|
||||||
if (DesktopTools.platform.isMobile || DesktopTools.platform.isMacOS) {
|
if (kIsMobile || kIsMacOS) {
|
||||||
final dirStr = await FilePicker.platform.getDirectoryPath(
|
final dirStr = await FilePicker.platform.getDirectoryPath(
|
||||||
initialDirectory: preferences.downloadLocation,
|
initialDirectory: preferences.downloadLocation,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
@ -14,6 +13,7 @@ import 'package:spotube/pages/settings/sections/downloads.dart';
|
|||||||
import 'package:spotube/pages/settings/sections/language_region.dart';
|
import 'package:spotube/pages/settings/sections/language_region.dart';
|
||||||
import 'package:spotube/pages/settings/sections/playback.dart';
|
import 'package:spotube/pages/settings/sections/playback.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class SettingsPage extends HookConsumerWidget {
|
class SettingsPage extends HookConsumerWidget {
|
||||||
const SettingsPage({super.key});
|
const SettingsPage({super.key});
|
||||||
@ -45,8 +45,7 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
const SettingsAppearanceSection(),
|
const SettingsAppearanceSection(),
|
||||||
const SettingsPlaybackSection(),
|
const SettingsPlaybackSection(),
|
||||||
const SettingsDownloadsSection(),
|
const SettingsDownloadsSection(),
|
||||||
if (DesktopTools.platform.isDesktop)
|
if (kIsDesktop) const SettingsDesktopSection(),
|
||||||
const SettingsDesktopSection(),
|
|
||||||
if (!kIsWeb) const SettingsDevelopersSection(),
|
if (!kIsWeb) const SettingsDevelopersSection(),
|
||||||
const SettingsAboutSection(),
|
const SettingsAboutSection(),
|
||||||
Center(
|
Center(
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
|
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/env.dart';
|
import 'package:spotube/collections/env.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class Discord extends ChangeNotifier {
|
class Discord extends ChangeNotifier {
|
||||||
final DiscordRPC? discordRPC;
|
final DiscordRPC? discordRPC;
|
||||||
final bool isEnabled;
|
final bool isEnabled;
|
||||||
|
|
||||||
Discord(this.isEnabled)
|
Discord(this.isEnabled)
|
||||||
: discordRPC = (DesktopTools.platform.isWindows ||
|
: discordRPC = (kIsWindows || kIsLinux) && isEnabled
|
||||||
DesktopTools.platform.isLinux) &&
|
|
||||||
isEnabled
|
|
||||||
? DiscordRPC(applicationId: Env.discordAppId)
|
? DiscordRPC(applicationId: Env.discordAppId)
|
||||||
: null {
|
: null {
|
||||||
discordRPC?.start(autoRegister: true);
|
discordRPC?.start(autoRegister: true);
|
||||||
|
|||||||
79
lib/provider/tray_manager/tray_manager.dart
Normal file
79
lib/provider/tray_manager/tray_manager.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/provider/tray_manager/tray_menu.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
class SystemTrayManager with TrayListener {
|
||||||
|
final Ref ref;
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
|
SystemTrayManager(
|
||||||
|
this.ref, {
|
||||||
|
required this.enabled,
|
||||||
|
}) {
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
if (!kIsDesktop) return;
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
await trayManager.setIcon(
|
||||||
|
kIsWindows
|
||||||
|
? 'assets/spotube-logo.ico'
|
||||||
|
: kIsFlatpak
|
||||||
|
? 'com.github.KRTirtho.Spotube.png'
|
||||||
|
: 'assets/spotube-logo.png',
|
||||||
|
);
|
||||||
|
trayManager.addListener(this);
|
||||||
|
} else {
|
||||||
|
await trayManager.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
trayManager.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
onTrayIconMouseDown() {
|
||||||
|
if (kIsWindows) {
|
||||||
|
windowManager.show();
|
||||||
|
} else {
|
||||||
|
trayManager.popUpContextMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
onTrayIconRightMouseDown() {
|
||||||
|
if (!kIsWindows) {
|
||||||
|
windowManager.show();
|
||||||
|
} else {
|
||||||
|
trayManager.popUpContextMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final trayManagerProvider = Provider(
|
||||||
|
(ref) {
|
||||||
|
final enabled = ref.watch(
|
||||||
|
userPreferencesProvider.select((s) => s.showSystemTrayIcon),
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.listen(trayMenuProvider, (_, menu) {
|
||||||
|
if (!enabled || !kIsDesktop) return;
|
||||||
|
trayManager.setContextMenu(menu);
|
||||||
|
});
|
||||||
|
|
||||||
|
final manager = SystemTrayManager(
|
||||||
|
ref,
|
||||||
|
enabled: enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
ref.onDispose(manager.dispose);
|
||||||
|
|
||||||
|
return manager;
|
||||||
|
},
|
||||||
|
);
|
||||||
108
lib/provider/tray_manager/tray_menu.dart
Normal file
108
lib/provider/tray_manager/tray_menu.dart
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
import 'package:spotube/services/audio_player/loop_mode.dart';
|
||||||
|
import 'package:tray_manager/tray_manager.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
final audioPlayerLoopMode = StreamProvider<PlaybackLoopMode>((ref) {
|
||||||
|
return audioPlayer.loopModeStream;
|
||||||
|
});
|
||||||
|
|
||||||
|
final audioPlayerShuffleMode = StreamProvider<bool>((ref) {
|
||||||
|
return audioPlayer.shuffledStream;
|
||||||
|
});
|
||||||
|
final audioPlayerPlaying = StreamProvider<bool>((ref) {
|
||||||
|
return audioPlayer.playingStream;
|
||||||
|
});
|
||||||
|
|
||||||
|
final trayMenuProvider = Provider((ref) {
|
||||||
|
final playlistNotifier = ref.watch(proxyPlaylistProvider.notifier);
|
||||||
|
final isPlaybackPlaying =
|
||||||
|
ref.watch(proxyPlaylistProvider.select((s) => s.activeTrack != null));
|
||||||
|
final isLoopOne =
|
||||||
|
ref.watch(audioPlayerLoopMode).asData?.value == PlaybackLoopMode.one;
|
||||||
|
final isShuffled = ref.watch(audioPlayerShuffleMode).asData?.value ?? false;
|
||||||
|
final isPlaying = ref.watch(audioPlayerPlaying).asData?.value ?? false;
|
||||||
|
|
||||||
|
return Menu(
|
||||||
|
items: [
|
||||||
|
MenuItem(
|
||||||
|
label: "Show/Hide Window",
|
||||||
|
onClick: (menuItem) async {
|
||||||
|
if (await windowManager.isVisible()) {
|
||||||
|
await windowManager.hide();
|
||||||
|
} else {
|
||||||
|
await windowManager.focus();
|
||||||
|
await windowManager.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
|
MenuItem(
|
||||||
|
label: isPlaying ? "Pause" : "Play",
|
||||||
|
disabled: !isPlaybackPlaying,
|
||||||
|
onClick: (menuItem) async {
|
||||||
|
if (audioPlayer.isPlaying) {
|
||||||
|
await audioPlayer.pause();
|
||||||
|
} else {
|
||||||
|
await audioPlayer.resume();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: "Next",
|
||||||
|
disabled: !isPlaybackPlaying,
|
||||||
|
onClick: (menuItem) {
|
||||||
|
playlistNotifier.next();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: "Previous",
|
||||||
|
disabled: !isPlaybackPlaying,
|
||||||
|
onClick: (menuItem) {
|
||||||
|
playlistNotifier.previous();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem.submenu(
|
||||||
|
label: "Playback",
|
||||||
|
submenu: Menu(
|
||||||
|
items: [
|
||||||
|
MenuItem(
|
||||||
|
label: "Repeat",
|
||||||
|
checked: isLoopOne,
|
||||||
|
onClick: (menuItem) {
|
||||||
|
audioPlayer.setLoopMode(
|
||||||
|
isLoopOne ? PlaybackLoopMode.none : PlaybackLoopMode.one,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: "Shuffle",
|
||||||
|
checked: isShuffled,
|
||||||
|
onClick: (menuItem) {
|
||||||
|
audioPlayer.setShuffle(!isShuffled);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
|
MenuItem(
|
||||||
|
label: "Stop",
|
||||||
|
onClick: (menuItem) {
|
||||||
|
playlistNotifier.stop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MenuItem.separator(),
|
||||||
|
MenuItem(
|
||||||
|
label: "Quit",
|
||||||
|
onClick: (menuItem) {
|
||||||
|
exit(0);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
@ -15,6 +14,7 @@ import 'package:spotube/services/sourced_track/enums.dart';
|
|||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
|
class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
@ -103,8 +103,8 @@ class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
|
|||||||
|
|
||||||
void setSystemTitleBar(bool isSystemTitleBar) {
|
void setSystemTitleBar(bool isSystemTitleBar) {
|
||||||
state = state.copyWith(systemTitleBar: isSystemTitleBar);
|
state = state.copyWith(systemTitleBar: isSystemTitleBar);
|
||||||
if (DesktopTools.platform.isDesktop) {
|
if (kIsDesktop) {
|
||||||
DesktopTools.window.setTitleBarStyle(
|
windowManager.setTitleBarStyle(
|
||||||
isSystemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
isSystemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -151,8 +151,8 @@ class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DesktopTools.platform.isDesktop) {
|
if (kIsDesktop) {
|
||||||
await DesktopTools.window.setTitleBarStyle(
|
await windowManager.setTitleBarStyle(
|
||||||
state.systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
state.systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,10 +62,10 @@ class UserPreferences with _$UserPreferences {
|
|||||||
@Default(false) bool amoledDarkTheme,
|
@Default(false) bool amoledDarkTheme,
|
||||||
@Default(true) bool checkUpdate,
|
@Default(true) bool checkUpdate,
|
||||||
@Default(false) bool normalizeAudio,
|
@Default(false) bool normalizeAudio,
|
||||||
@Default(true) bool showSystemTrayIcon,
|
@Default(false) bool showSystemTrayIcon,
|
||||||
@Default(false) bool skipNonMusic,
|
@Default(false) bool skipNonMusic,
|
||||||
@Default(false) bool systemTitleBar,
|
@Default(false) bool systemTitleBar,
|
||||||
@Default(CloseBehavior.minimizeToTray) CloseBehavior closeBehavior,
|
@Default(CloseBehavior.close) CloseBehavior closeBehavior,
|
||||||
@Default(SpotubeColor(0xFF2196F3, name: "Blue"))
|
@Default(SpotubeColor(0xFF2196F3, name: "Blue"))
|
||||||
@JsonKey(
|
@JsonKey(
|
||||||
fromJson: UserPreferences._accentColorSchemeFromJson,
|
fromJson: UserPreferences._accentColorSchemeFromJson,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ part of 'user_preferences_state.dart';
|
|||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
UserPreferences _$UserPreferencesFromJson(Map<String, dynamic> json) {
|
UserPreferences _$UserPreferencesFromJson(Map<String, dynamic> json) {
|
||||||
return _UserPreferences.fromJson(json);
|
return _UserPreferences.fromJson(json);
|
||||||
@ -415,10 +415,10 @@ class _$UserPreferencesImpl implements _UserPreferences {
|
|||||||
this.amoledDarkTheme = false,
|
this.amoledDarkTheme = false,
|
||||||
this.checkUpdate = true,
|
this.checkUpdate = true,
|
||||||
this.normalizeAudio = false,
|
this.normalizeAudio = false,
|
||||||
this.showSystemTrayIcon = true,
|
this.showSystemTrayIcon = false,
|
||||||
this.skipNonMusic = false,
|
this.skipNonMusic = false,
|
||||||
this.systemTitleBar = false,
|
this.systemTitleBar = false,
|
||||||
this.closeBehavior = CloseBehavior.minimizeToTray,
|
this.closeBehavior = CloseBehavior.close,
|
||||||
@JsonKey(
|
@JsonKey(
|
||||||
fromJson: UserPreferences._accentColorSchemeFromJson,
|
fromJson: UserPreferences._accentColorSchemeFromJson,
|
||||||
toJson: UserPreferences._accentColorSchemeToJson,
|
toJson: UserPreferences._accentColorSchemeToJson,
|
||||||
|
|||||||
@ -16,12 +16,12 @@ _$UserPreferencesImpl _$$UserPreferencesImplFromJson(
|
|||||||
amoledDarkTheme: json['amoledDarkTheme'] as bool? ?? false,
|
amoledDarkTheme: json['amoledDarkTheme'] as bool? ?? false,
|
||||||
checkUpdate: json['checkUpdate'] as bool? ?? true,
|
checkUpdate: json['checkUpdate'] as bool? ?? true,
|
||||||
normalizeAudio: json['normalizeAudio'] as bool? ?? false,
|
normalizeAudio: json['normalizeAudio'] as bool? ?? false,
|
||||||
showSystemTrayIcon: json['showSystemTrayIcon'] as bool? ?? true,
|
showSystemTrayIcon: json['showSystemTrayIcon'] as bool? ?? false,
|
||||||
skipNonMusic: json['skipNonMusic'] as bool? ?? false,
|
skipNonMusic: json['skipNonMusic'] as bool? ?? false,
|
||||||
systemTitleBar: json['systemTitleBar'] as bool? ?? false,
|
systemTitleBar: json['systemTitleBar'] as bool? ?? false,
|
||||||
closeBehavior:
|
closeBehavior:
|
||||||
$enumDecodeNullable(_$CloseBehaviorEnumMap, json['closeBehavior']) ??
|
$enumDecodeNullable(_$CloseBehaviorEnumMap, json['closeBehavior']) ??
|
||||||
CloseBehavior.minimizeToTray,
|
CloseBehavior.close,
|
||||||
accentColorScheme: UserPreferences._accentColorSchemeReadValue(
|
accentColorScheme: UserPreferences._accentColorSchemeReadValue(
|
||||||
json, 'accentColorScheme') ==
|
json, 'accentColorScheme') ==
|
||||||
null
|
null
|
||||||
|
|||||||
@ -101,7 +101,7 @@ abstract class AudioPlayerInterface {
|
|||||||
return _mkPlayer.state.completed;
|
return _mkPlayer.state.completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> get isShuffled async {
|
bool get isShuffled {
|
||||||
return _mkPlayer.shuffled;
|
return _mkPlayer.shuffled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:catcher_2/catcher_2.dart';
|
import 'package:catcher_2/catcher_2.dart';
|
||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
import 'package:flutter_broadcasts/flutter_broadcasts.dart';
|
import 'package:flutter_broadcasts/flutter_broadcasts.dart';
|
||||||
@ -7,6 +6,7 @@ import 'package:package_info_plus/package_info_plus.dart';
|
|||||||
import 'package:audio_session/audio_session.dart';
|
import 'package:audio_session/audio_session.dart';
|
||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:spotube/services/audio_player/playback_state.dart';
|
import 'package:spotube/services/audio_player/playback_state.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
/// MediaKit [Player] by default doesn't have a state stream.
|
/// MediaKit [Player] by default doesn't have a state stream.
|
||||||
/// This class adds a state stream to the [Player] class.
|
/// This class adds a state stream to the [Player] class.
|
||||||
@ -54,7 +54,7 @@ class CustomPlayer extends Player {
|
|||||||
PackageInfo.fromPlatform().then((packageInfo) {
|
PackageInfo.fromPlatform().then((packageInfo) {
|
||||||
_packageName = packageInfo.packageName;
|
_packageName = packageInfo.packageName;
|
||||||
});
|
});
|
||||||
if (DesktopTools.platform.isAndroid) {
|
if (kIsAndroid) {
|
||||||
_androidAudioManager = AndroidAudioManager();
|
_androidAudioManager = AndroidAudioManager();
|
||||||
AudioSession.instance.then((s) async {
|
AudioSession.instance.then((s) async {
|
||||||
_androidAudioSessionId =
|
_androidAudioSessionId =
|
||||||
@ -71,7 +71,7 @@ class CustomPlayer extends Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> notifyAudioSessionUpdate(bool active) async {
|
Future<void> notifyAudioSessionUpdate(bool active) async {
|
||||||
if (DesktopTools.platform.isAndroid) {
|
if (kIsAndroid) {
|
||||||
sendBroadcast(
|
sendBroadcast(
|
||||||
BroadcastMessage(
|
BroadcastMessage(
|
||||||
name: active
|
name: active
|
||||||
@ -106,6 +106,10 @@ class CustomPlayer extends Player {
|
|||||||
_shuffled = shuffle;
|
_shuffled = shuffle;
|
||||||
await super.setShuffle(shuffle);
|
await super.setShuffle(shuffle);
|
||||||
_shuffleStream.add(shuffle);
|
_shuffleStream.add(shuffle);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
if (shuffle) {
|
||||||
|
await move(state.playlist.index, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
@ -8,6 +7,7 @@ import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
|||||||
import 'package:spotube/services/audio_services/mobile_audio_service.dart';
|
import 'package:spotube/services/audio_services/mobile_audio_service.dart';
|
||||||
import 'package:spotube/services/audio_services/windows_audio_service.dart';
|
import 'package:spotube/services/audio_services/windows_audio_service.dart';
|
||||||
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class AudioServices {
|
class AudioServices {
|
||||||
final MobileAudioService? mobile;
|
final MobileAudioService? mobile;
|
||||||
@ -19,9 +19,7 @@ class AudioServices {
|
|||||||
Ref ref,
|
Ref ref,
|
||||||
ProxyPlaylistNotifier playback,
|
ProxyPlaylistNotifier playback,
|
||||||
) async {
|
) async {
|
||||||
final mobile = DesktopTools.platform.isMobile ||
|
final mobile = kIsMobile || kIsMacOS || kIsLinux
|
||||||
DesktopTools.platform.isMacOS ||
|
|
||||||
DesktopTools.platform.isLinux
|
|
||||||
? await AudioService.init(
|
? await AudioService.init(
|
||||||
builder: () => MobileAudioService(playback),
|
builder: () => MobileAudioService(playback),
|
||||||
config: const AudioServiceConfig(
|
config: const AudioServiceConfig(
|
||||||
@ -31,9 +29,7 @@ class AudioServices {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
final smtc = DesktopTools.platform.isWindows
|
final smtc = kIsWindows ? WindowsAudioService(ref, playback) : null;
|
||||||
? WindowsAudioService(ref, playback)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return AudioServices(
|
return AudioServices(
|
||||||
mobile,
|
mobile,
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
||||||
|
|
||||||
abstract class KVStoreService {
|
abstract class KVStoreService {
|
||||||
static SharedPreferences? _sharedPreferences;
|
static SharedPreferences? _sharedPreferences;
|
||||||
@ -23,4 +26,21 @@ abstract class KVStoreService {
|
|||||||
|
|
||||||
static Future<void> setRecentSearches(List<String> value) async =>
|
static Future<void> setRecentSearches(List<String> value) async =>
|
||||||
await sharedPreferences.setStringList('recentSearches', value);
|
await sharedPreferences.setStringList('recentSearches', value);
|
||||||
|
|
||||||
|
static WindowSize? get windowSize {
|
||||||
|
final raw = sharedPreferences.getString('windowSize');
|
||||||
|
|
||||||
|
if (raw == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return WindowSize.fromJson(jsonDecode(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> setWindowSize(WindowSize value) async =>
|
||||||
|
await sharedPreferences.setString(
|
||||||
|
'windowSize',
|
||||||
|
jsonEncode(
|
||||||
|
value.toJson(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ part of 'song_link.dart';
|
|||||||
T _$identity<T>(T value) => value;
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
SongLink _$SongLinkFromJson(Map<String, dynamic> json) {
|
SongLink _$SongLinkFromJson(Map<String, dynamic> json) {
|
||||||
return _SongLink.fromJson(json);
|
return _SongLink.fromJson(json);
|
||||||
|
|||||||
@ -163,7 +163,7 @@ class PipedSourcedTrack extends SourcedTrack {
|
|||||||
final PipedSearchResult(items: searchResults) = await pipedClient.search(
|
final PipedSearchResult(items: searchResults) = await pipedClient.search(
|
||||||
query,
|
query,
|
||||||
preference.searchMode == SearchMode.youtube
|
preference.searchMode == SearchMode.youtube
|
||||||
? PipedFilter.videos
|
? PipedFilter.video
|
||||||
: PipedFilter.musicSongs,
|
: PipedFilter.musicSongs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
88
lib/services/wm_tools/wm_tools.dart
Normal file
88
lib/services/wm_tools/wm_tools.dart
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
class WindowSize {
|
||||||
|
final double height;
|
||||||
|
final double width;
|
||||||
|
final bool maximized;
|
||||||
|
|
||||||
|
WindowSize({
|
||||||
|
required this.height,
|
||||||
|
required this.width,
|
||||||
|
required this.maximized,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory WindowSize.fromJson(Map<String, dynamic> json) => WindowSize(
|
||||||
|
height: json["height"],
|
||||||
|
width: json["width"],
|
||||||
|
maximized: json["maximized"],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
"height": height,
|
||||||
|
"width": width,
|
||||||
|
"maximized": maximized,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class WindowManagerTools with WidgetsBindingObserver {
|
||||||
|
static WindowManagerTools? _instance;
|
||||||
|
static WindowManagerTools get instance => _instance!;
|
||||||
|
|
||||||
|
WindowManagerTools._();
|
||||||
|
|
||||||
|
static Future<void> initialize() async {
|
||||||
|
await windowManager.ensureInitialized();
|
||||||
|
_instance = WindowManagerTools._();
|
||||||
|
WidgetsBinding.instance.addObserver(instance);
|
||||||
|
|
||||||
|
await windowManager.waitUntilReadyToShow(
|
||||||
|
const WindowOptions(
|
||||||
|
title: "Spotube",
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
minimumSize: Size(300, 700),
|
||||||
|
titleBarStyle: TitleBarStyle.hidden,
|
||||||
|
),
|
||||||
|
() async {
|
||||||
|
final savedSize = KVStoreService.windowSize;
|
||||||
|
await windowManager.setResizable(true);
|
||||||
|
if (savedSize?.maximized == true &&
|
||||||
|
!(await windowManager.isMaximized())) {
|
||||||
|
await windowManager.maximize();
|
||||||
|
} else if (savedSize != null) {
|
||||||
|
await windowManager.setSize(Size(savedSize.width, savedSize.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
await windowManager.focus();
|
||||||
|
await windowManager.show();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Size? _prevSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeMetrics() async {
|
||||||
|
super.didChangeMetrics();
|
||||||
|
if (kIsMobile) return;
|
||||||
|
final size = await windowManager.getSize();
|
||||||
|
final windowSameDimension =
|
||||||
|
_prevSize?.width == size.width && _prevSize?.height == size.height;
|
||||||
|
|
||||||
|
if (windowSameDimension || _prevSize == null) {
|
||||||
|
_prevSize = size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final isMaximized = await windowManager.isMaximized();
|
||||||
|
await KVStoreService.setWindowSize(
|
||||||
|
WindowSize(
|
||||||
|
height: size.height,
|
||||||
|
width: size.width,
|
||||||
|
maximized: isMaximized,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_prevSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,7 +14,7 @@
|
|||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
#include <system_theme/system_theme_plugin.h>
|
#include <system_theme/system_theme_plugin.h>
|
||||||
#include <system_tray/system_tray_plugin.h>
|
#include <tray_manager/tray_manager_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
#include <window_size/window_size_plugin.h>
|
#include <window_size/window_size_plugin.h>
|
||||||
@ -44,9 +44,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) system_theme_registrar =
|
g_autoptr(FlPluginRegistrar) system_theme_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin");
|
||||||
system_theme_plugin_register_with_registrar(system_theme_registrar);
|
system_theme_plugin_register_with_registrar(system_theme_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) system_tray_registrar =
|
g_autoptr(FlPluginRegistrar) tray_manager_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "SystemTrayPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
|
||||||
system_tray_plugin_register_with_registrar(system_tray_registrar);
|
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
screen_retriever
|
screen_retriever
|
||||||
system_theme
|
system_theme
|
||||||
system_tray
|
tray_manager
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
window_manager
|
window_manager
|
||||||
window_size
|
window_size
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import screen_retriever
|
|||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite
|
import sqflite
|
||||||
import system_theme
|
import system_theme
|
||||||
import system_tray
|
import tray_manager
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import window_manager
|
import window_manager
|
||||||
import window_size
|
import window_size
|
||||||
@ -37,13 +37,13 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
|
||||||
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
||||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin"))
|
SystemThemePlugin.register(with: registry.registrar(forPlugin: "SystemThemePlugin"))
|
||||||
SystemTrayPlugin.register(with: registry.registrar(forPlugin: "SystemTrayPlugin"))
|
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
|
||||||
WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin"))
|
WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin"))
|
||||||
|
|||||||
@ -18,9 +18,6 @@ PODS:
|
|||||||
- flutter_secure_storage_macos (6.1.1):
|
- flutter_secure_storage_macos (6.1.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
- FMDB (2.7.5):
|
|
||||||
- FMDB/standard (= 2.7.5)
|
|
||||||
- FMDB/standard (2.7.5)
|
|
||||||
- local_notifier (0.1.0):
|
- local_notifier (0.1.0):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- media_kit_libs_macos_audio (1.0.4):
|
- media_kit_libs_macos_audio (1.0.4):
|
||||||
@ -39,12 +36,12 @@ PODS:
|
|||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqflite (0.0.2):
|
- sqflite (0.0.3):
|
||||||
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FMDB (>= 2.7.5)
|
|
||||||
- system_theme (0.0.1):
|
- system_theme (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- system_tray (0.0.1):
|
- tray_manager (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- url_launcher_macos (0.0.1):
|
- url_launcher_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -71,16 +68,15 @@ DEPENDENCIES:
|
|||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
|
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
|
||||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
|
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
|
||||||
- system_theme (from `Flutter/ephemeral/.symlinks/plugins/system_theme/macos`)
|
- system_theme (from `Flutter/ephemeral/.symlinks/plugins/system_theme/macos`)
|
||||||
- system_tray (from `Flutter/ephemeral/.symlinks/plugins/system_tray/macos`)
|
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
|
||||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
|
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
|
||||||
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
|
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- FMDB
|
|
||||||
- OrderedSet
|
- OrderedSet
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
@ -119,11 +115,11 @@ EXTERNAL SOURCES:
|
|||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
|
||||||
system_theme:
|
system_theme:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/system_theme/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/system_theme/macos
|
||||||
system_tray:
|
tray_manager:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/system_tray/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
window_manager:
|
window_manager:
|
||||||
@ -132,28 +128,27 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67
|
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
|
||||||
audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9
|
audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9
|
||||||
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
|
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
|
||||||
bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842
|
bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842
|
||||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
|
||||||
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
||||||
flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d
|
flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d
|
||||||
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
|
||||||
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
|
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
|
||||||
media_kit_libs_macos_audio: 3871782a4f3f84c77f04d7666c87800a781c24da
|
media_kit_libs_macos_audio: 3871782a4f3f84c77f04d7666c87800a781c24da
|
||||||
media_kit_native_event_loop: 7321675377cb9ae8596a29bddf3a3d2b5e8792c5
|
media_kit_native_event_loop: 7321675377cb9ae8596a29bddf3a3d2b5e8792c5
|
||||||
metadata_god: eceae399d0020475069a5cebc35943ce8562b5d7
|
metadata_god: eceae399d0020475069a5cebc35943ce8562b5d7
|
||||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||||
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
|
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||||
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
system_theme: c7b9f6659a5caa26c9bc2284da096781e9a6fcbc
|
system_theme: c7b9f6659a5caa26c9bc2284da096781e9a6fcbc
|
||||||
system_tray: e53c972838c69589ff2e77d6d3abfd71332f9e5d
|
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||||
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
||||||
|
|||||||
646
pubspec.lock
646
pubspec.lock
File diff suppressed because it is too large
Load Diff
96
pubspec.yaml
96
pubspec.yaml
@ -3,7 +3,7 @@ description: Open source Spotify client that doesn't require Premium nor uses El
|
|||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
|
|
||||||
version: 3.5.0+29
|
version: 3.6.0+30
|
||||||
|
|
||||||
homepage: https://spotube.krtirtho.dev
|
homepage: https://spotube.krtirtho.dev
|
||||||
repository: https://github.com/KRTirtho/spotube
|
repository: https://github.com/KRTirtho/spotube
|
||||||
@ -13,96 +13,89 @@ environment:
|
|||||||
flutter: ">=3.10.0"
|
flutter: ">=3.10.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.3.2
|
args: ^2.5.0
|
||||||
async: ^2.9.0
|
async: ^2.9.0
|
||||||
audio_service: ^0.18.9
|
audio_service: ^0.18.13
|
||||||
audio_session: ^0.1.18
|
audio_service_mpris: ^0.1.3
|
||||||
|
audio_session: ^0.1.19
|
||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
buttons_tabbar: ^1.3.6
|
buttons_tabbar: ^1.3.8
|
||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
catcher_2: 1.0.0
|
catcher_2: ^1.2.4
|
||||||
collection: ^1.15.0
|
collection: ^1.15.0
|
||||||
cupertino_icons: ^1.0.5
|
|
||||||
curved_navigation_bar: ^1.0.3
|
curved_navigation_bar: ^1.0.3
|
||||||
dbus: ^0.7.8
|
dbus: ^0.7.8
|
||||||
device_info_plus: ^9.1.2
|
device_info_plus: ^10.1.0
|
||||||
device_preview: ^1.1.0
|
device_preview: ^1.1.0
|
||||||
dio: ^5.4.1
|
dio: ^5.4.3+1
|
||||||
disable_battery_optimization: ^1.1.0+1
|
disable_battery_optimization: ^1.1.1
|
||||||
duration: ^3.0.12
|
duration: ^3.0.12
|
||||||
envied: ^0.3.0
|
envied: ^0.5.4+1
|
||||||
file_selector: ^1.0.1
|
file_picker: ^8.0.0+1
|
||||||
fluentui_system_icons: ^1.1.189
|
file_selector: ^1.0.3
|
||||||
|
fluentui_system_icons: ^1.1.234
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_cache_manager: ^3.3.0
|
flutter_cache_manager: ^3.3.0
|
||||||
flutter_desktop_tools:
|
|
||||||
git:
|
|
||||||
url: https://github.com/KRTirtho/flutter_desktop_tools.git
|
|
||||||
ref: 1f0bec3283626dcbd8ee2f54e238d096d8dea50e
|
|
||||||
flutter_displaymode: ^0.6.0
|
flutter_displaymode: ^0.6.0
|
||||||
flutter_feather_icons: ^2.0.0+1
|
flutter_feather_icons: ^2.0.0+1
|
||||||
flutter_hooks: ^0.20.5
|
flutter_hooks: ^0.20.5
|
||||||
flutter_inappwebview: ^6.0.0
|
flutter_inappwebview: ^6.0.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_native_splash: ^2.3.10
|
flutter_native_splash: ^2.4.0
|
||||||
flutter_riverpod: ^2.4.10
|
flutter_riverpod: ^2.5.1
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
flutter_svg: ^1.1.6
|
flutter_svg: ^1.1.6
|
||||||
form_validator: ^2.1.1
|
form_validator: ^2.1.1
|
||||||
fuzzywuzzy: ^1.1.6
|
fuzzywuzzy: ^1.1.6
|
||||||
go_router: 12.1.3 # Stuck on this https://github.com/flutter/flutter/issues/140869
|
go_router: 12.1.3 # Stuck on this https://github.com/flutter/flutter/issues/140869
|
||||||
google_fonts: ^6.1.0
|
google_fonts: ^6.2.1
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
hooks_riverpod: ^2.4.3
|
hooks_riverpod: ^2.5.1
|
||||||
html: ^0.15.1
|
html: ^0.15.1
|
||||||
http: ^1.2.0
|
http: ^1.2.0
|
||||||
image_picker: ^1.0.4
|
image_picker: ^1.1.0
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
introduction_screen: ^3.0.2
|
introduction_screen: ^3.1.14
|
||||||
json_annotation: ^4.8.1
|
json_annotation: ^4.8.1
|
||||||
logger: ^2.0.2
|
logger: ^2.0.2
|
||||||
media_kit: ^1.1.3
|
media_kit: ^1.1.10+1
|
||||||
media_kit_libs_audio: ^1.0.3
|
media_kit_libs_audio: ^1.0.4
|
||||||
metadata_god: ^0.5.2+1
|
metadata_god: ^0.5.2+1
|
||||||
mime: ^1.0.2
|
mime: ^1.0.2
|
||||||
package_info_plus: ^4.1.0
|
package_info_plus: ^6.0.0
|
||||||
palette_generator: ^0.3.3
|
palette_generator: ^0.3.3
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
path_provider: ^2.0.8
|
path_provider: ^2.1.3
|
||||||
permission_handler: ^11.0.1
|
permission_handler: ^11.3.1
|
||||||
piped_client:
|
piped_client: ^0.1.1
|
||||||
git:
|
|
||||||
url: https://github.com/KRTirtho/piped_client.git
|
|
||||||
popover: ^0.3.0
|
popover: ^0.3.0
|
||||||
scrobblenaut:
|
scrobblenaut:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/KRTirtho/scrobblenaut.git
|
url: https://github.com/KRTirtho/scrobblenaut.git
|
||||||
ref: dart-3-support
|
ref: dart-3-support
|
||||||
scroll_to_index: ^3.0.1
|
scroll_to_index: ^3.0.1
|
||||||
sidebarx: ^0.16.3
|
sidebarx: ^0.17.1
|
||||||
shared_preferences: ^2.2.2
|
shared_preferences: ^2.2.3
|
||||||
skeleton_text: ^3.0.1
|
skeleton_text: ^3.0.1
|
||||||
smtc_windows: ^0.1.1
|
smtc_windows: ^0.1.2
|
||||||
stroke_text: ^0.0.2
|
stroke_text: ^0.0.2
|
||||||
system_theme: ^2.1.0
|
system_theme: ^2.1.0
|
||||||
titlebar_buttons: ^1.0.0
|
titlebar_buttons: ^1.0.0
|
||||||
url_launcher: ^6.1.7
|
url_launcher: ^6.2.6
|
||||||
uuid: ^3.0.7
|
uuid: ^4.4.0
|
||||||
version: ^3.0.2
|
version: ^3.0.2
|
||||||
visibility_detector: ^0.4.0+2
|
visibility_detector: ^0.4.0+2
|
||||||
window_manager: ^0.3.1
|
window_manager: ^0.3.8
|
||||||
window_size:
|
window_size:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/google/flutter-desktop-embedding.git
|
url: https://github.com/google/flutter-desktop-embedding.git
|
||||||
ref: a738913c8ce2c9f47515382d40827e794a334274
|
ref: a738913c8ce2c9f47515382d40827e794a334274
|
||||||
path: plugins/window_size
|
path: plugins/window_size
|
||||||
youtube_explode_dart: ^2.0.1
|
youtube_explode_dart: ^2.2.0
|
||||||
simple_icons: ^7.10.0
|
simple_icons: ^10.1.3
|
||||||
audio_service_mpris: ^0.1.0
|
|
||||||
file_picker: ^6.0.0
|
|
||||||
jiosaavn: ^0.1.0
|
jiosaavn: ^0.1.0
|
||||||
draggable_scrollbar:
|
draggable_scrollbar:
|
||||||
git:
|
git:
|
||||||
@ -116,9 +109,9 @@ dependencies:
|
|||||||
url: https://github.com/Tommypop2/dart_discord_rpc.git
|
url: https://github.com/Tommypop2/dart_discord_rpc.git
|
||||||
html_unescape: ^2.0.0
|
html_unescape: ^2.0.0
|
||||||
wikipedia_api: ^0.1.0
|
wikipedia_api: ^0.1.0
|
||||||
skeletonizer: ^0.8.0
|
skeletonizer: ^1.1.1
|
||||||
app_links: ^3.5.0
|
app_links: ^4.0.1
|
||||||
win32_registry: ^1.1.2
|
win32_registry: ^1.1.3
|
||||||
flutter_sharing_intent: ^1.1.0
|
flutter_sharing_intent: ^1.1.0
|
||||||
flutter_broadcasts: ^0.4.0
|
flutter_broadcasts: ^0.4.0
|
||||||
freezed_annotation: ^2.4.1
|
freezed_annotation: ^2.4.1
|
||||||
@ -130,17 +123,18 @@ dependencies:
|
|||||||
shelf: ^1.4.1
|
shelf: ^1.4.1
|
||||||
shelf_router: ^1.1.4
|
shelf_router: ^1.1.4
|
||||||
shelf_web_socket: ^1.0.4
|
shelf_web_socket: ^1.0.4
|
||||||
web_socket_channel: ^2.4.4
|
web_socket_channel: ^2.4.5
|
||||||
lrc: ^1.0.2
|
lrc: ^1.0.2
|
||||||
pub_api_client: ^2.4.0
|
pub_api_client: ^2.4.0
|
||||||
pubspec_parse: ^1.2.2
|
pubspec_parse: ^1.2.2
|
||||||
timezone: ^0.9.2
|
timezone: ^0.9.2
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
|
local_notifier: ^0.1.6
|
||||||
|
tray_manager: ^0.2.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.9
|
build_runner: ^2.4.9
|
||||||
envied_generator: ^0.3.0+3
|
envied_generator: ^0.5.4+1
|
||||||
flutter_distributor: ^0.0.2
|
|
||||||
flutter_gen_runner: ^5.4.0
|
flutter_gen_runner: ^5.4.0
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
flutter_lints: ^3.0.1
|
flutter_lints: ^3.0.1
|
||||||
@ -150,12 +144,12 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
hive_generator: ^2.0.0
|
hive_generator: ^2.0.0
|
||||||
json_serializable: ^6.6.2
|
json_serializable: ^6.6.2
|
||||||
freezed: ^2.4.6
|
freezed: ^2.5.2
|
||||||
custom_lint: ^0.5.11
|
custom_lint: ^0.6.4
|
||||||
riverpod_lint: ^2.1.1
|
riverpod_lint: ^2.3.10
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
system_tray: 2.0.2
|
uuid: ^4.4.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
generate: true
|
generate: true
|
||||||
|
|||||||
@ -1,204 +1 @@
|
|||||||
{
|
{}
|
||||||
"ar": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"bn": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ca": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"de": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"es": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"fa": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"fr": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"hi": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"it": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ja": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ko": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ne": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"nl": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"pl": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"pt": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"ru": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"th": [
|
|
||||||
"choose_your_language",
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"uk": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"vi": [
|
|
||||||
"friends",
|
|
||||||
"no_lyrics_available",
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
],
|
|
||||||
|
|
||||||
"zh": [
|
|
||||||
"enable_connect",
|
|
||||||
"enable_connect_description",
|
|
||||||
"devices",
|
|
||||||
"select",
|
|
||||||
"connect_client_alert",
|
|
||||||
"this_device",
|
|
||||||
"remote"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -16,7 +16,7 @@
|
|||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
#include <system_theme/system_theme_plugin.h>
|
#include <system_theme/system_theme_plugin.h>
|
||||||
#include <system_tray/system_tray_plugin.h>
|
#include <tray_manager/tray_manager_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
#include <window_size/window_size_plugin.h>
|
#include <window_size/window_size_plugin.h>
|
||||||
@ -42,8 +42,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
|
||||||
SystemThemePluginRegisterWithRegistrar(
|
SystemThemePluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SystemThemePlugin"));
|
registry->GetRegistrarForPlugin("SystemThemePlugin"));
|
||||||
SystemTrayPluginRegisterWithRegistrar(
|
TrayManagerPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SystemTrayPlugin"));
|
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
WindowManagerPluginRegisterWithRegistrar(
|
WindowManagerPluginRegisterWithRegistrar(
|
||||||
|
|||||||
@ -13,7 +13,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
screen_retriever
|
screen_retriever
|
||||||
system_theme
|
system_theme
|
||||||
system_tray
|
tray_manager
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
window_manager
|
window_manager
|
||||||
window_size
|
window_size
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user