Merge branch 'dev' into feat/stats

This commit is contained in:
Kingkor Roy Tirtho 2024-04-27 22:27:15 +06:00
commit db209ac449
92 changed files with 1736 additions and 1225 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
build
dist
.dart_tool
.idea

View File

@ -1,4 +1,4 @@
{
"flutterSdkVersion": "3.19.1",
"flutterSdkVersion": "3.19.5",
"flavors": {}
}

32
.github/Dockerfile vendored Normal file
View 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

View File

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

View File

@ -4,7 +4,7 @@ on:
inputs:
version:
description: Version to release (x.x.x)
default: 3.4.1
default: 3.6.0
required: true
channel:
type: choice
@ -26,7 +26,7 @@ on:
default: true
env:
FLUTTER_VERSION: '3.19.1'
FLUTTER_VERSION: '3.19.5'
jobs:
windows:
@ -70,7 +70,7 @@ jobs:
run: |
flutter config --enable-windows-desktop
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
run: |
@ -156,7 +156,7 @@ jobs:
run: |
flutter config --enable-linux-desktop
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
run: |
@ -206,6 +206,66 @@ jobs:
with:
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:
runs-on: ubuntu-latest
@ -245,7 +305,7 @@ jobs:
- name: Generate Secrets
run: |
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
run: |
@ -260,7 +320,7 @@ jobs:
- name: Build Playstore AppBundle
run: |
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
xmlstarlet ed -d '//meta-data[@android:name="com.google.android.gms.car.application"]' $MANIFEST > $MANIFEST.tmp
mv $MANIFEST.tmp $MANIFEST
@ -283,7 +343,6 @@ jobs:
limit-access-to-actor: true
macos:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
@ -317,7 +376,7 @@ jobs:
run: |
dart pub global activate flutter_distributor
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
run: |
@ -381,7 +440,7 @@ jobs:
- name: Generate Secrets
run: |
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
run: |
@ -408,6 +467,7 @@ jobs:
needs:
- windows
- linux
- linux_arm
- android
- macos
- iOS

View File

@ -24,5 +24,6 @@
"explorer.fileNesting.patterns": {
"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",
"*.dart": "${capture}.g.dart,${capture}.freezed.dart",
}
}

View File

@ -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.
## [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)

View File

@ -97,12 +97,7 @@ This handy table lists all the methods you can use to install Spotube:
</tr>
<tr>
<td>AppImage</td>
<td>
<a href="https://github.com/KRTirtho/spotube/releases/latest/download/Spotube-linux-x86_64.AppImage">
<img width="220" alt="Download AppImage" src="https://user-images.githubusercontent.com/61944859/169455015-13385466-8901-48fe-ba90-b62d58b0be64.png">
</a>
<p><b>Note:</b> <a href="https://github.com/TheAssassin/AppImageLauncher">AppimageLauncher</a> is required!</p>
</td>
<td>AppImage's lacking stability led to it's temporal removal. More information at https://github.com/KRTirtho/spotube/issues/1082</td>
</tr>
<tr>
<td>Debian/Ubuntu</td>
@ -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. [JioSaavn](https://www.jiosaavn.com) - JioSaavn is an Indian online music streaming service and a digital distributor of Bollywood, English and other regional Indian music across the world. Since it was founded in 2007 as Saavn, the company has acquired rights to over 5 crore (50 million) music tracks in 15 languages
1. [SongLink](https://song.link) - SongLink is a free smart link service that helps you share music with your audience. It's a one-stop-shop for creating smart links for music, podcasts, and other audio content
1. [LRCLib](https://lrclib.net/) - A public synced lyric API
1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users
1. [Flatpak](https://flatpak.org) - Flatpak is a utility for software deployment and package management for Linux
@ -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. [envied](https://github.com/petercinibulk/envied) - Explicitly reads environment variables into a dart file from a .env file for more security and faster start up times.
1. [file_selector](https://pub.dev/packages/file_selector) - Flutter plugin for opening and saving files, or selecting directories, using native file selection UI.
1. [fl_query](https://fl-query.krtirtho.dev) - Asynchronous data caching, refetching & invalidation library for Flutter
1. [fl_query_hooks](https://fl-query.krtirtho.dev) - Elite flutter_hooks compatible library for fl_query, the Asynchronous data caching, refetching & invalidation library for Flutter
1. [fl_query_devtools](https://fl-query.krtirtho.dev) - Devtools support for Fl-Query
1. [fluentui_system_icons](https://github.com/microsoft/fluentui-system-icons/tree/main) - Fluent UI System Icons are a collection of familiar, friendly and modern icons from Microsoft.
1. [flutter_cache_manager](https://github.com/Baseflow/flutter_cache_manager/tree/develop/flutter_cache_manager) - Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
1. [flutter_displaymode](https://github.com/ajinasokan/flutter_displaymode) - A Flutter plugin to set display mode (resolution, refresh rate) on Android platform. Allows to enable high refresh rate on supported devices.
@ -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. [image_picker](https://pub.dev/packages/image_picker) - Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.
1. [intl](https://pub.dev/packages/intl) - Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
1. [introduction_screen](https://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. [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.
@ -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. [skeletonizer](https://github.com/Milad-Akarie/skeletonizer) - Converts already built widgets into skeleton loaders with no extra effort.
1. [app_links](https://github.com/llfbandit/app_links) - Android App Links, Deep Links, iOs Universal Links and Custom URL schemes handler for Flutter (desktop included).
1. [win32_registry](https://win32.pub) - A package that provides a friendly Dart API for accessing the Windows Registry.
1. [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_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. [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. [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_launcher_icons](https://github.com/fluttercommunity/flutter_launcher_icons) - A package which simplifies the task of updating your Flutter app's launcher icon.
1. [flutter_lints](https://pub.dev/packages/flutter_lints) - Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.
1. [hive_generator](https://github.com/hivedb/hive/tree/master/hive_generator) - Extension for Hive. Automatically generates TypeAdapters to store any class.
1. [json_serializable](https://pub.dev/packages/json_serializable) - Automatically generate code for converting to and from JSON by annotating Dart classes.
1. [pub_api_client](https://github.com/leoafarias/pub_api_client) - An API Client for Pub to interact with public package information.
1. [pubspec_parse](https://pub.dev/packages/pubspec_parse) - Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.
1. [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. [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.

View File

@ -1,5 +1,5 @@
import 'package:envied/envied.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:spotube/utils/platform.dart';
part 'env.g.dart';
@ -26,7 +26,7 @@ abstract class Env {
static final String _enableUpdateChecker = _Env._enableUpdateChecker;
static bool get enableUpdateChecker =>
DesktopTools.platform.isFlatpak || _enableUpdateChecker == "1";
kIsFlatpak || _enableUpdateChecker == "1";
static String discordAppId = "1176718791388975124";
}

View File

@ -1,9 +1,10 @@
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';
Future<void> registerWindowsScheme(String scheme) async {
if (!DesktopTools.platform.isWindows) return;
if (!kIsWindows) return;
String appPath = Platform.resolvedExecutable;
String protocolRegKey = 'Software\\Classes\\$scheme';

View File

@ -157,10 +157,10 @@ abstract class LanguageLocals {
// name: "Croatian",
// nativeName: "hrvatski",
// ),
// "cs": const ISOLanguageName(
// name: "Czech",
// nativeName: "česky, čeština",
// ),
"cs": const ISOLanguageName(
name: "Czech",
nativeName: "česky, čeština",
),
// "da": const ISOLanguageName(
// name: "Danish",
// nativeName: "dansk",

View File

@ -52,52 +52,58 @@ class ConnectDeviceButton extends HookConsumerWidget {
alignment: Alignment.centerRight,
fit: StackFit.loose,
children: [
Center(
child: InkWell(
onTap: () {
ServiceUtils.push(context, "/connect");
},
borderRadius: BorderRadius.circular(50),
child: Ink(
decoration: BoxDecoration(
Material(
type: MaterialType.transparency,
child: Center(
child: ClipRect(
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () {
ServiceUtils.push(context, "/connect");
},
borderRadius: BorderRadius.circular(50),
color: colorScheme.primaryContainer,
),
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (connectClients.asData?.value.resolvedService !=
null) ...[
Container(
width: 7,
height: 7,
decoration: BoxDecoration(
color: Colors.greenAccent,
borderRadius: BorderRadius.circular(50),
),
),
const Gap(5),
],
Text(context.l10n.devices),
if (connectClients.asData?.value.services.isNotEmpty ==
true)
Text(
" (${connectClients.asData?.value.services.length})",
style: TextStyle(
color:
colorScheme.onPrimaryContainer.withOpacity(0.5),
),
),
const Gap(35),
],
child: Ink(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: colorScheme.primaryContainer,
),
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (connectClients.asData?.value.resolvedService !=
null) ...[
Container(
width: 7,
height: 7,
decoration: BoxDecoration(
color: Colors.greenAccent,
borderRadius: BorderRadius.circular(50),
),
),
const Gap(5),
],
Text(context.l10n.devices),
if (connectClients.asData?.value.services.isNotEmpty ==
true)
Text(
" (${connectClients.asData?.value.services.length})",
style: TextStyle(
color: colorScheme.onPrimaryContainer
.withOpacity(0.5),
),
),
const Gap(35),
],
),
),
),
),
),
),
Positioned(
right: 0,
right: -3,
child: IconButton.filled(
icon: const Icon(SpotubeIcons.speaker),
style: IconButton.styleFrom(

View File

@ -16,7 +16,6 @@ class TokenLoginForm extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final authenticationNotifier = ref.watch(authenticationProvider.notifier);
final directCodeController = useTextEditingController();
final mounted = useIsMounted();
final isLoading = useState(false);
@ -57,7 +56,7 @@ class TokenLoginForm extends HookConsumerWidget {
await AuthenticationCredentials.fromCookie(
cookieHeader),
);
if (mounted()) {
if (context.mounted) {
onDone?.call();
}
} finally {

View File

@ -1,12 +1,14 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/components/home/sections/friends/friend_item.dart';
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
import 'package:spotube/models/spotify_friends.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart';
class HomePageFriendsSection extends HookConsumerWidget {
@ -14,6 +16,7 @@ class HomePageFriendsSection extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final auth = ref.watch(authenticationProvider);
final friendsQuery = ref.watch(friendsProvider);
final friends =
friendsQuery.asData?.value.friends ?? FakeData.friends.friends;
@ -27,32 +30,36 @@ class HomePageFriendsSection extends HookConsumerWidget {
xxl: 7,
);
final friendGroup = friends.fold<List<List<SpotifyFriendActivity>>>(
[],
(previousValue, element) {
if (previousValue.isEmpty) {
final friendGroup = useMemoized(
() => friends.fold<List<List<SpotifyFriendActivity>>>(
[],
(previousValue, element) {
if (previousValue.isEmpty) {
return [
[element]
];
}
final lastGroup = previousValue.last;
if (lastGroup.length < groupCount) {
return [
...previousValue.sublist(0, previousValue.length - 1),
[...lastGroup, element]
];
}
return [
...previousValue,
[element]
];
}
final lastGroup = previousValue.last;
if (lastGroup.length < groupCount) {
return [
...previousValue.sublist(0, previousValue.length - 1),
[...lastGroup, element]
];
}
return [
...previousValue,
[element]
];
},
},
),
[friends, groupCount],
);
if (friendsQuery.isLoading ||
friendsQuery.asData?.value.friends.isEmpty == true) {
friendsQuery.asData?.value.friends.isEmpty == true ||
auth == null) {
return const SliverToBoxAdapter(
child: SizedBox.shrink(),
);

View File

@ -2,17 +2,17 @@ import 'package:flutter/material.dart' hide Image;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:collection/collection.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/album/album_card.dart';
import 'package:spotube/components/shared/fallbacks/not_found.dart';
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.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/provider/authentication_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart';
@ -50,71 +50,65 @@ class UserAlbums extends HookConsumerWidget {
return const AnonymousFallback();
}
final theme = Theme.of(context);
return RefreshIndicator(
onRefresh: () async {
ref.invalidate(favoriteAlbumsProvider);
},
child: SafeArea(
child: Scaffold(
appBar: PreferredSize(
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(
return SafeArea(
child: Scaffold(
body: RefreshIndicator(
onRefresh: () async {
ref.invalidate(favoriteAlbumsProvider);
},
child: InterScrollbar(
controller: controller,
child: CustomScrollView(
controller: controller,
child: SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
controller: controller,
child: Skeletonizer(
enabled: albumsQuery.isLoading,
child: Center(
child: Wrap(
runSpacing: 20,
alignment: WrapAlignment.center,
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),
),
)
],
slivers: [
SliverAppBar(
floating: true,
flexibleSpace: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: SearchBar(
onChanged: (value) => searchText.value = value,
leading: const Icon(SpotubeIcons.filter),
hintText: context.l10n.filter_albums,
),
),
),
),
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,
);
},
);
}),
),
],
),
),
),

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:collection/collection.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:spotube/collections/fake.dart';
@ -9,8 +10,9 @@ import 'package:spotube/collections/fake.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
import 'package:spotube/components/artist/artist_card.dart';
import 'package:spotube/components/shared/fallbacks/not_found.dart';
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart';
@ -20,10 +22,10 @@ class UserArtists extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final theme = Theme.of(context);
final auth = ref.watch(authenticationProvider);
final artistQuery = ref.watch(followedArtistsProvider);
final artistQueryNotifier = ref.watch(followedArtistsProvider.notifier);
final searchText = useState('');
@ -50,77 +52,73 @@ class UserArtists extends HookConsumerWidget {
return const AnonymousFallback();
}
return Scaffold(
appBar: PreferredSize(
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_artist,
return SafeArea(
child: Scaffold(
body: RefreshIndicator(
onRefresh: () async {
ref.invalidate(followedArtistsProvider);
},
child: InterScrollbar(
controller: controller,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
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(),
),
),
),
),
),
),
),
),
);
}
}

View File

@ -176,7 +176,7 @@ class UserLocalTracks extends HookConsumerWidget {
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const SizedBox(width: 10),
const SizedBox(width: 5),
FilledButton(
onPressed: trackSnapshot.asData?.value != null
? () async {
@ -212,7 +212,7 @@ class UserLocalTracks extends HookConsumerWidget {
sortBy.value = value;
},
),
const SizedBox(width: 10),
const SizedBox(width: 5),
FilledButton(
child: const Icon(SpotubeIcons.refresh),
onPressed: () {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart' hide Image;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fuzzywuzzy/fuzzywuzzy.dart';
import 'package:collection/collection.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:skeletonizer/skeletonizer.dart';
@ -18,6 +19,7 @@ import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/platform.dart';
class UserPlaylists extends HookConsumerWidget {
const UserPlaylists({super.key});
@ -86,39 +88,37 @@ class UserPlaylists extends HookConsumerWidget {
child: CustomScrollView(
controller: controller,
slivers: [
SliverToBoxAdapter(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(10),
child: SearchBar(
onChanged: (value) => searchText.value = value,
hintText: context.l10n.filter_playlists,
leading: const Icon(SpotubeIcons.filter),
SliverAppBar(
floating: true,
flexibleSpace: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SearchBar(
onChanged: (value) => searchText.value = value,
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");
},
),
),
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 Gap(10),
],
),
),
),
const SliverToBoxAdapter(
child: SizedBox(height: 10),
),
const SliverGap(10),
SliverLayoutBuilder(builder: (context, constrains) {
return SliverGrid.builder(
itemCount: playlists.isEmpty ? 6 : playlists.length + 1,

View File

@ -1,6 +1,5 @@
import 'dart:ui';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.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/volume_provider.dart';
import 'package:spotube/utils/platform.dart';
import 'package:window_manager/window_manager.dart';
class BottomPlayer extends HookConsumerWidget {
BottomPlayer({super.key});
@ -95,19 +95,19 @@ class BottomPlayer extends HookConsumerWidget {
tooltip: context.l10n.mini_player,
icon: const Icon(SpotubeIcons.miniPlayer),
onPressed: () async {
final prevSize =
await DesktopTools.window.getSize();
await DesktopTools.window.setMinimumSize(
if (!kIsDesktop) return;
final prevSize = await windowManager.getSize();
await windowManager.setMinimumSize(
const Size(300, 300),
);
await DesktopTools.window.setAlwaysOnTop(true);
await windowManager.setAlwaysOnTop(true);
if (!kIsLinux) {
await DesktopTools.window.setHasShadow(false);
await windowManager.setHasShadow(false);
}
await DesktopTools.window
await windowManager
.setAlignment(Alignment.topRight);
await DesktopTools.window
.setSize(const Size(400, 500));
await windowManager.setSize(const Size(400, 500));
await Future.delayed(
const Duration(milliseconds: 100),
() async {

View File

@ -1,7 +1,7 @@
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:spotube/utils/platform.dart';
class InterScrollbar extends HookWidget {
final Widget child;
@ -15,7 +15,7 @@ class InterScrollbar extends HookWidget {
@override
Widget build(BuildContext context) {
if (DesktopTools.platform.isDesktop) return child;
if (kIsDesktop) return child;
return DraggableScrollbar.semicircle(
controller: controller,

View File

@ -7,7 +7,8 @@ import 'package:titlebar_buttons/titlebar_buttons.dart';
import 'dart:math';
import 'package:flutter/foundation.dart' show kIsWeb;
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
implements PreferredSizeWidget {
@ -89,7 +90,7 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
final systemTitleBar =
ref.read(userPreferencesProvider.select((s) => s.systemTitleBar));
if (kIsDesktop && !systemTitleBar) {
DesktopTools.window.startDragging();
windowManager.startDragging();
}
}
@ -107,11 +108,7 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
return SliverPadding(
padding: EdgeInsets.only(
left: DesktopTools.platform.isMacOS &&
hasFullscreen &&
hasLeadingOrCanPop
? 65
: 0,
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
),
sliver: SliverAppBar(
leading: widget.leading,
@ -149,11 +146,7 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
onVerticalDragStart: onDrag,
child: Padding(
padding: EdgeInsets.only(
left: DesktopTools.platform.isMacOS &&
hasFullscreen &&
hasLeadingOrCanPop
? 65
: 0,
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
),
child: AppBar(
leading: widget.leading,
@ -193,12 +186,12 @@ class WindowTitleBarButtons extends HookConsumerWidget {
const type = ThemeType.auto;
Future<void> onClose() async {
await DesktopTools.window.close();
await windowManager.close();
}
useEffect(() {
if (kIsDesktop) {
DesktopTools.window.isMaximized().then((value) {
windowManager.isMaximized().then((value) {
isMaximized.value = value;
});
}
@ -235,14 +228,14 @@ class WindowTitleBarButtons extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MinimizeWindowButton(
onPressed: DesktopTools.window.minimize,
onPressed: windowManager.minimize,
colors: colors,
),
if (isMaximized.value != true)
MaximizeWindowButton(
colors: colors,
onPressed: () {
DesktopTools.window.maximize();
windowManager.maximize();
isMaximized.value = true;
},
)
@ -250,7 +243,7 @@ class WindowTitleBarButtons extends HookConsumerWidget {
RestoreWindowButton(
colors: colors,
onPressed: () {
DesktopTools.window.unmaximize();
windowManager.unmaximize();
isMaximized.value = false;
},
),
@ -270,16 +263,16 @@ class WindowTitleBarButtons extends HookConsumerWidget {
children: [
DecoratedMinimizeButton(
type: type,
onPressed: DesktopTools.window.minimize,
onPressed: windowManager.minimize,
),
DecoratedMaximizeButton(
type: type,
onPressed: () async {
if (await DesktopTools.window.isMaximized()) {
await DesktopTools.window.unmaximize();
if (await windowManager.isMaximized()) {
await windowManager.unmaximize();
isMaximized.value = false;
} else {
await DesktopTools.window.maximize();
await windowManager.maximize();
isMaximized.value = true;
}
},

View File

@ -1,7 +1,7 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/assets.gen.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:spotube/extensions/constrains.dart';
import 'package:spotube/hooks/utils/use_palette_color.dart';
import 'package:spotube/utils/platform.dart';
class TrackViewFlexHeader extends HookConsumerWidget {
const TrackViewFlexHeader({super.key});
@ -53,7 +54,7 @@ class TrackViewFlexHeader extends HookConsumerWidget {
floating: false,
pinned: true,
expandedHeight: 450,
automaticallyImplyLeading: DesktopTools.platform.isMobile,
automaticallyImplyLeading: kIsMobile,
backgroundColor: palette.color,
title: isExpanded ? null : Text(props.title, style: headingStyle),
flexibleSpace: FlexibleSpaceBar(

View File

@ -1,5 +1,5 @@
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: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/body/track_view_body.dart';
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
import 'package:spotube/utils/platform.dart';
class TrackView extends HookConsumerWidget {
const TrackView({super.key});
@ -18,7 +19,7 @@ class TrackView extends HookConsumerWidget {
final controller = useScrollController();
return Scaffold(
appBar: DesktopTools.platform.isDesktop
appBar: kIsDesktop
? const PageWindowTitleBar(
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,

View File

@ -20,8 +20,6 @@ class Waypoint extends HookWidget {
@override
Widget build(BuildContext context) {
final isMounted = useIsMounted();
useEffect(() {
if (isGrid) {
return null;
@ -32,19 +30,19 @@ class Waypoint extends HookWidget {
// scrollController fetches the next paginated data when the current
// 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();
}
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (controller.hasClients && isMounted()) {
if (controller.hasClients && context.mounted) {
listener();
controller.addListener(listener);
}
});
return () => controller.removeListener(listener);
}, [controller, onTouchEdge, isMounted]);
}, [controller, onTouchEdge]);
if (isGrid) {
return VisibilityDetector(

View File

@ -1,29 +1,31 @@
import 'dart:io';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/hooks/configurators/use_window_listener.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
// ignore: depend_on_referenced_packages
import 'package:local_notifier/local_notifier.dart';
import 'package:spotube/utils/platform.dart';
import 'package:window_manager/window_manager.dart';
final closeNotification = DesktopTools.createNotification(
title: 'Spotube',
message: 'Running in background. Minimized to System Tray',
actions: [
LocalNotificationAction(text: 'Close The App'),
],
)?..onClickAction = (value) {
exit(0);
};
final closeNotification = !kIsDesktop
? null
: (LocalNotification(
title: 'Spotube',
body: 'Running in background. Minimized to System Tray',
actions: [
LocalNotificationAction(text: 'Close The App'),
],
)..onClickAction = (value) {
exit(0);
});
void useCloseBehavior(WidgetRef ref) {
useWindowListener(
onWindowClose: () async {
final preferences = ref.read(userPreferencesProvider);
if (preferences.closeBehavior == CloseBehavior.minimizeToTray) {
await DesktopTools.window.hide();
await windowManager.hide();
closeNotification?.show();
} else {
exit(0);

View File

@ -7,7 +7,7 @@ import 'package:spotube/collections/routes.dart';
import 'package:spotube/provider/spotify_provider.dart';
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
import 'package:flutter_sharing_intent/model/sharing_file.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:spotube/utils/platform.dart';
final appLinks = AppLinks();
final linkStream = appLinks.allStringLinkStream.asBroadcastStream();
@ -53,7 +53,7 @@ void useDeepLinking(WidgetRef ref) {
StreamSubscription? mediaStream;
if (DesktopTools.platform.isMobile) {
if (kIsMobile) {
FlutterSharingIntent.instance.getInitialSharing().then(uriListener);
mediaStream =

View File

@ -1,12 +1,12 @@
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/services/kv_store/kv_store.dart';
import 'package:spotube/utils/platform.dart';
void useDisableBatteryOptimizations() {
useAsyncEffect(() async {
if (!DesktopTools.platform.isAndroid ||
KVStoreService.askedForBatteryOptimization) return;
if (!kIsAndroid || KVStoreService.askedForBatteryOptimization) return;
await DisableBatteryOptimization.showDisableBatteryOptimizationSettings();

View File

@ -1,17 +1,18 @@
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:hooks_riverpod/hooks_riverpod.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:spotube/components/library/user_local_tracks.dart';
import 'package:spotube/hooks/utils/use_async_effect.dart';
import 'package:spotube/utils/platform.dart';
void useGetStoragePermissions(WidgetRef ref) {
final isMounted = useIsMounted();
final context = useContext();
useAsyncEffect(
() async {
if (!DesktopTools.platform.isMobile) return;
if (!kIsMobile) return;
final androidInfo = await DeviceInfoPlugin().androidInfo;
@ -25,11 +26,11 @@ void useGetStoragePermissions(WidgetRef ref) {
if (hasNoStoragePerm) {
await Permission.storage.request();
if (isMounted()) ref.invalidate(localTracksProvider);
if (context.mounted) ref.invalidate(localTracksProvider);
}
if (hasNoAudioPerm) {
await Permission.audio.request();
if (isMounted()) ref.invalidate(localTracksProvider);
if (context.mounted) ref.invalidate(localTracksProvider);
}
},
null,

View File

@ -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]);
}

View File

@ -62,7 +62,7 @@ void useUpdateChecker(WidgetRef ref) {
barrierColor: Colors.black26,
builder: (context) {
const url =
"https://spotube.krtirtho.dev/other-downloads/stable-downloads";
"https://spotube.krtirtho.dev/downloads";
return AlertDialog(
title: const Text("Spotube has an update"),
actions: [

View File

@ -1,6 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.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 {
final VoidCallback? _onWindowClose;
@ -154,6 +156,8 @@ void useWindowListener({
VoidCallback? onWindowEvent,
}) {
useEffect(() {
if (!kIsDesktop) return null;
final listener = CallbackWindowListener(
onWindowClose: onWindowClose,
onWindowFocus: onWindowFocus,
@ -172,9 +176,9 @@ void useWindowListener({
onWindowUndocked: onWindowUndocked,
onWindowEvent: onWindowEvent,
);
DesktopTools.window.addListener(listener);
windowManager.addListener(listener);
return () {
DesktopTools.window.removeListener(listener);
windowManager.removeListener(listener);
};
}, [
onWindowClose,

View File

@ -14,7 +14,6 @@ PaletteColor usePaletteColor(String imageUrl, WidgetRef ref) {
final context = useContext();
final theme = Theme.of(context);
final paletteColor = ref.watch(_paletteColorState);
final mounted = useIsMounted();
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
@ -25,7 +24,7 @@ PaletteColor usePaletteColor(String imageUrl, WidgetRef ref) {
width: 50,
),
);
if (!mounted()) return;
if (!context.mounted) return;
final color = theme.brightness == Brightness.light
? palette.lightMutedColor ?? palette.lightVibrantColor
: palette.darkMutedColor ?? palette.darkVibrantColor;
@ -41,7 +40,7 @@ PaletteColor usePaletteColor(String imageUrl, WidgetRef ref) {
PaletteGenerator usePaletteGenerator(String imageUrl) {
final palette = useState(PaletteGenerator.fromColors([]));
final mounted = useIsMounted();
final context = useContext();
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
@ -52,7 +51,7 @@ PaletteGenerator usePaletteGenerator(String imageUrl) {
width: 50,
),
);
if (!mounted()) return;
if (!context.mounted) return;
palette.value = newPalette;
});

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotube هو مشروع مفتوح المصدر. يمكنك مساعدة هذا المشروع في النمو عن طريق المساهمة في المشروع، أو الإبلاغ عن الأخطاء، أو اقتراح ميزات جديدة.",
"contribute_on_github": "المساهمة على GitHub",
"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": "بعيد"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "স্পটুব একটি ওপেন সোর্স প্রকল্প। আপনি প্রকল্পে অবদান রাখেন, বাগ রিপোর্ট করেন, বা নতুন বৈশিষ্ট্যগুলি সুপারিশ করেন।",
"contribute_on_github": "গিটহাবে অবদান রাখুন",
"donate_on_open_collective": "ওপেন কলেক্টিভে অনুদান করুন",
"browse_anonymously": "অজানে ব্রাউজ করুন"
"browse_anonymously": "অজানে ব্রাউজ করুন",
"enable_connect": "সংযোগ সক্রিয় করুন",
"enable_connect_description": "অন্যান্য ডিভাইস থেকে Spotube নিয়ন্ত্রণ করুন",
"devices": "ডিভাইস",
"select": "নির্বাচন করুন",
"connect_client_alert": "আপনি {client} দ্বারা নিয়ন্ত্রিত হচ্ছেন",
"this_device": "এই ডিভাইস",
"remote": "রিমোট"
}

View File

@ -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.",
"contribute_on_github": "Contribueix a GitHub",
"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
View 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č"
}

View File

@ -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.",
"contribute_on_github": "Auf GitHub beitragen",
"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"
}

View File

@ -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.",
"contribute_on_github": "Contribuir en GitHub",
"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"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotube یک پروژه متن باز است. شما می‌توانید با به پروژه کمک کردن، گزارش دادن اشکالات یا پیشنهاد ویژگی‌های جدید، به این پروژه کمک کنید.",
"contribute_on_github": "مشارکت در GitHub",
"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": "راه‌دور"
}

View File

@ -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.",
"contribute_on_github": "Contribuer sur GitHub",
"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"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotube एक ओपन सोर्स परियोजना है। आप इस परियोजना को योगदान देकर, बग रिपोर्ट करके या नई विशेषताओं का सुझाव देकर इस परियोजना को बढ़ा सकते हैं।",
"contribute_on_github": "GitHub पर योगदान करें",
"donate_on_open_collective": "ओपन कलेक्टिव पर दान करें",
"browse_anonymously": "बिना नाम के ब्राउज़ करें"
"browse_anonymously": "बिना नाम के ब्राउज़ करें",
"enable_connect": "कनेक्ट सक्षम करें",
"enable_connect_description": "अन्य उपकरणों से Spotube को नियंत्रित करें",
"devices": "उपकरण",
"select": "चयन करें",
"connect_client_alert": "आप {client} द्वारा नियंत्रित हो रहे हैं",
"this_device": "यह उपकरण",
"remote": "रिमोट"
}

View File

@ -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à.",
"contribute_on_github": "Contribuisci su GitHub",
"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"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグを報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。",
"contribute_on_github": "GitHubで貢献する",
"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": "リモート"
}

View File

@ -314,5 +314,12 @@
"help_project_grow_description": "Spotube는 오픈 소스 프로젝트입니다. 프로젝트에 기여하거나 버그를 보고하거나 새로운 기능을 제안하여이 프로젝트의 성장에 도움을 줄 수 있습니다.",
"contribute_on_github": "GitHub에서 기여하기",
"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": "원격"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotube एक खुला स्रोतको परियोजना हो। तपाईं परियोजनामा योगदान गरेर, त्रुटिहरू सूचिकै, वा नयाँ सुविधाहरू सुझाव दिएर यस परियोजनामा वृद्धि गर्न सक्नुहुन्छ।",
"contribute_on_github": "GitHubमा योगदान गर्नुहोस्",
"donate_on_open_collective": "खुला संगठनमा दान गर्नुहोस्",
"browse_anonymously": "अनामित रूपमा ब्राउज़ गर्नुहोस्"
"browse_anonymously": "अनामित रूपमा ब्राउज़ गर्नुहोस्",
"enable_connect": "कनेक्ट सक्रिय गर्नुहोस्",
"enable_connect_description": "अन्य उपकरणहरूबाट Spotube कन्ट्रोल गर्नुहोस्",
"devices": "उपकरणहरू",
"select": "चयन गर्नुहोस्",
"connect_client_alert": "तपाईंलाई {client} द्वारा नियन्त्रित गरिएको छ",
"this_device": "यो उपकरण",
"remote": "दूरसंचार"
}

View File

@ -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.",
"contribute_on_github": "Bijdragen op GitHub",
"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"
}

View File

@ -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.",
"contribute_on_github": "Przyczyniaj się na GitHubie",
"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"
}

View File

@ -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.",
"contribute_on_github": "Contribuir no GitHub",
"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"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotube - это проект с открытым исходным кодом. Вы можете помочь этому проекту развиваться, внося вклад в проект, сообщая ошибках или предлагая новые функции.",
"contribute_on_github": "Внести вклад на GitHub",
"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": "Дистанционное управление"
}

View File

@ -313,5 +313,13 @@
"help_project_grow_description": "Spotube เป็นโครงการโอเพนซอร์ส คุณสามารถช่วยให้โครงการนี้เติบโตได้โดยการมีส่วนร่วมในโครงการ รายงานข้อบกพร่อง หรือเสนอคุณสมบัติใหม่",
"contribute_on_github": "มีส่วนร่วมบน GitHub",
"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": "ระยะไกล"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotube - це проект з відкритим кодом. Ви можете допомогти цьому проекту зростати, вносячи свій внесок у проект, повідомляючи про помилки або пропонуючи нові функції.",
"contribute_on_github": "Долучайтесь на GitHub",
"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": "Віддалений"
}

View File

@ -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.",
"contribute_on_github": "Đóng góp trên GitHub",
"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"
}

View File

@ -313,5 +313,12 @@
"help_project_grow_description": "Spotube是一个开源项目。您可以通过为项目做出贡献、报告错误或建议新功能来帮助该项目成长。",
"contribute_on_github": "在GitHub上做出贡献",
"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": "远程"
}

View File

@ -12,6 +12,7 @@
/// doannc2212@github => Vietnamese
/// sappho192@github => Korean
/// watchakorn-18k@github => Thai
/// Microsoft Copilot, Tutislav@github => Czech
library l10n;
@ -23,6 +24,7 @@ class L10n {
const Locale('ar', 'SA'),
const Locale('bn', 'BD'),
const Locale('ca', 'AD'),
const Locale('cs', 'CZ'),
const Locale('de', 'GE'),
const Locale('es', 'ES'),
const Locale('fa', 'IR'),

View File

@ -4,11 +4,11 @@ import 'package:device_preview/device_preview.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.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_localizations/flutter_localizations.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:local_notifier/local_notifier.dart';
import 'package:media_kit/media_kit.dart';
import 'package:metadata_god/metadata_god.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_disable_battery_optimizations.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/models/logger.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/cli/cli.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/utils/persisted_state_notifier.dart';
import 'package:spotube/utils/platform.dart';
import 'package:system_theme/system_theme.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_native_splash/flutter_native_splash.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:window_manager/window_manager.dart';
Future<void> main(List<String> rawArgs) async {
final arguments = await startCLI(rawArgs);
@ -55,12 +58,12 @@ Future<void> main(List<String> rawArgs) async {
MediaKit.ensureInitialized();
// force High Refresh Rate on some Android devices (like One Plus)
if (DesktopTools.platform.isAndroid) {
if (kIsAndroid) {
await FlutterDisplayMode.setHighRefreshRate();
}
if (DesktopTools.platform.isDesktop) {
await DesktopTools.window.setPreventClose(true);
if (kIsDesktop) {
await windowManager.setPreventClose(true);
}
await SystemTheme.accentColor.load();
@ -69,7 +72,7 @@ Future<void> main(List<String> rawArgs) async {
MetadataGod.initialize();
}
if (DesktopTools.platform.isWindows || DesktopTools.platform.isLinux) {
if (kIsWindows || kIsLinux) {
DiscordRPC.initialize();
}
@ -101,14 +104,10 @@ Future<void> main(List<String> rawArgs) async {
path: hiveCacheDir,
);
await DesktopTools.ensureInitialized(
DesktopWindowOptions(
hideTitleBar: true,
title: "Spotube",
backgroundColor: Colors.transparent,
minimumSize: const Size(300, 700),
),
);
if (kIsDesktop) {
await localNotifier.setup(appName: "Spotube");
await WindowManagerTools.initialize();
}
Catcher2(
enableLogger: arguments["verbose"],
@ -189,9 +188,9 @@ class SpotubeState extends ConsumerState<Spotube> {
ref.listen(playbackServerProvider, (_, __) {});
ref.listen(connectServerProvider, (_, __) {});
ref.listen(connectClientsProvider, (_, __) {});
ref.listen(trayManagerProvider, (_, __) {});
useDisableBatteryOptimizations();
useInitSysTray(ref);
useDeepLinking(ref);
useCloseBehavior(ref);
useGetStoragePermissions(ref);
@ -233,9 +232,7 @@ class SpotubeState extends ConsumerState<Spotube> {
builder: (context, child) {
return DevicePreview.appBuilder(
context,
DesktopTools.platform.isDesktop && !DesktopTools.platform.isMacOS
? DragToResizeArea(child: child!)
: child,
kIsDesktop && !kIsMacOS ? DragToResizeArea(child: child!) : child,
);
},
themeMode: themeMode,

View File

@ -12,7 +12,7 @@ part of 'connect.dart';
T _$identity<T>(T value) => value;
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(
Map<String, dynamic> json) {

View File

@ -12,7 +12,7 @@ part of 'home_feed.dart';
T _$identity<T>(T value) => value;
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(
Map<String, dynamic> json) {

View File

@ -12,7 +12,7 @@ part of 'recommendation_seeds.dart';
T _$identity<T>(T value) => value;
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
mixin _$GeneratePlaylistProviderInput {

View File

@ -23,6 +23,7 @@ class ConnectPage extends HookConsumerWidget {
appBar: PageWindowTitleBar(
automaticallyImplyLeading: true,
title: Text(context.l10n.devices),
titleSpacing: 0,
),
body: ListTileTheme(
shape: RoundedRectangleBorder(

View File

@ -54,6 +54,11 @@ class HomeFeedSectionPage extends HookConsumerWidget {
);
},
),
const SliverToBoxAdapter(
child: SafeArea(
child: SizedBox(),
),
),
],
),
),

View File

@ -12,7 +12,7 @@ import 'package:spotube/components/shared/waypoint.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/provider/spotify/spotify.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 {
final Category category;
@ -27,7 +27,7 @@ class GenrePlaylistsPage extends HookConsumerWidget {
final scrollController = useScrollController();
return Scaffold(
appBar: DesktopTools.platform.isDesktop
appBar: kIsDesktop
? const PageWindowTitleBar(
leading: BackButton(color: Colors.white),
backgroundColor: Colors.transparent,
@ -53,12 +53,12 @@ class GenrePlaylistsPage extends HookConsumerWidget {
controller: scrollController,
slivers: [
SliverAppBar(
automaticallyImplyLeading: DesktopTools.platform.isMobile,
automaticallyImplyLeading: kIsMobile,
expandedHeight: mediaQuery.mdAndDown ? 200 : 150,
title: const Text(""),
backgroundColor: Colors.transparent,
flexibleSpace: FlexibleSpaceBar(
centerTitle: DesktopTools.platform.isDesktop,
centerTitle: kIsDesktop,
title: Text(
category.name!,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(

View File

@ -26,6 +26,7 @@ class GenrePage extends HookConsumerWidget {
appBar: PageWindowTitleBar(
title: Text(context.l10n.explore_genres),
automaticallyImplyLeading: true,
titleSpacing: 0,
),
body: SafeArea(
top: false,

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/components/connect/connect_device.dart';
import 'package:spotube/components/home/sections/featured.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/extensions/constrains.dart';
import 'package:spotube/extensions/image.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/spotify/spotify.dart';
import 'package:spotube/utils/platform.dart';
import 'package:spotube/utils/service_utils.dart';
@ -29,19 +30,26 @@ class HomePage extends HookConsumerWidget {
return SafeArea(
bottom: false,
child: Scaffold(
appBar: kIsMobile || kIsMacOS ? null : const PageWindowTitleBar(),
body: CustomScrollView(
controller: controller,
slivers: [
if (mediaQuery.mdAndDown)
PageWindowTitleBar.sliver(
pinned: DesktopTools.platform.isDesktop,
SliverAppBar(
floating: true,
title: Assets.spotubeLogoPng.image(height: 45),
actions: [
const ConnectDeviceButton(),
const Gap(10),
Consumer(builder: (context, ref, _) {
final auth = ref.watch(authenticationProvider);
final me = ref.watch(meProvider);
final meData = me.asData?.value;
if (auth == null) {
return const SizedBox();
}
return IconButton(
icon: CircleAvatar(
backgroundImage: UniversalImage.imageProvider(

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.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/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/utils/platform.dart';
import 'package:window_manager/window_manager.dart';
class MiniLyricsPage extends HookConsumerWidget {
final Size prevSize;
@ -36,9 +36,11 @@ class MiniLyricsPage extends HookConsumerWidget {
final showLyrics = useState(true);
useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) async {
wasMaximized.value = await DesktopTools.window.isMaximized();
});
if (kIsDesktop) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
wasMaximized.value = await windowManager.isMaximized();
});
}
return null;
}, []);
@ -112,11 +114,13 @@ class MiniLyricsPage extends HookConsumerWidget {
areaActive.value = true;
hoverMode.value = false;
await DesktopTools.window.setSize(
showLyrics.value
? const Size(400, 500)
: const Size(400, 150),
);
if (kIsDesktop) {
await windowManager.setSize(
showLyrics.value
? const Size(400, 500)
: const Size(400, 150),
);
}
},
),
IconButton(
@ -135,33 +139,34 @@ class MiniLyricsPage extends HookConsumerWidget {
hoverMode.value = !hoverMode.value;
},
),
FutureBuilder(
future: DesktopTools.window.isAlwaysOnTop(),
builder: (context, snapshot) {
return IconButton(
tooltip: context.l10n.always_on_top,
icon: Icon(
snapshot.data == true
? SpotubeIcons.pinOn
: SpotubeIcons.pinOff,
),
style: ButtonStyle(
foregroundColor: snapshot.data == true
? MaterialStateProperty.all(
theme.colorScheme.primary)
: null,
),
onPressed: snapshot.data == null
? null
: () async {
await DesktopTools.window.setAlwaysOnTop(
snapshot.data == true ? false : true,
);
update();
},
);
},
),
if (kIsDesktop)
FutureBuilder(
future: windowManager.isAlwaysOnTop(),
builder: (context, snapshot) {
return IconButton(
tooltip: context.l10n.always_on_top,
icon: Icon(
snapshot.data == true
? SpotubeIcons.pinOn
: SpotubeIcons.pinOff,
),
style: ButtonStyle(
foregroundColor: snapshot.data == true
? MaterialStateProperty.all(
theme.colorScheme.primary)
: null,
),
onPressed: snapshot.data == null
? null
: () async {
await windowManager.setAlwaysOnTop(
snapshot.data == true ? false : true,
);
update();
},
);
},
),
],
),
),
@ -243,19 +248,20 @@ class MiniLyricsPage extends HookConsumerWidget {
tooltip: context.l10n.exit_mini_player,
icon: const Icon(SpotubeIcons.maximize),
onPressed: () async {
if (!kIsDesktop) return;
try {
await DesktopTools.window
await windowManager
.setMinimumSize(const Size(300, 700));
await DesktopTools.window.setAlwaysOnTop(false);
await windowManager.setAlwaysOnTop(false);
if (wasMaximized.value) {
await DesktopTools.window.maximize();
await windowManager.maximize();
} else {
await DesktopTools.window.setSize(prevSize);
await windowManager.setSize(prevSize);
}
await DesktopTools.window
.setAlignment(Alignment.center);
await windowManager.setAlignment(Alignment.center);
if (!kIsLinux) {
await DesktopTools.window.setHasShadow(true);
await windowManager.setHasShadow(true);
}
await Future.delayed(
const Duration(milliseconds: 200));

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.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/services/connectivity_adapter.dart';
import 'package:spotube/utils/persisted_state_notifier.dart';
import 'package:spotube/utils/platform.dart';
const rootPaths = {
"/": 0,
@ -38,7 +38,6 @@ class RootApp extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final isMounted = useIsMounted();
final showingDialogCompleter = useRef(Completer()..complete());
final downloader = ref.watch(downloadManagerProvider);
final scaffoldMessenger = ScaffoldMessenger.of(context);
@ -129,7 +128,7 @@ class RootApp extends HookConsumerWidget {
useEffect(() {
downloader.onFileExists = (track) async {
if (!isMounted()) return false;
if (!context.mounted) return false;
if (!showingDialogCompleter.value.isCompleted) {
await showingDialogCompleter.value.future;
@ -207,7 +206,7 @@ class RootApp extends HookConsumerWidget {
),
extendBody: true,
drawerScrimColor: Colors.transparent,
endDrawer: DesktopTools.platform.isDesktop
endDrawer: kIsDesktop
? Container(
constraints: const BoxConstraints(maxWidth: 800),
decoration: BoxDecoration(

View File

@ -113,7 +113,7 @@ class SearchTracksSection extends HookConsumerWidget {
child: TextButton(
onPressed: searchTrack.isLoadingNextPage
? null
: () => searchTrackNotifier.fetchMore,
: searchTrackNotifier.fetchMore,
child: searchTrack.isLoadingNextPage
? const CircularProgressIndicator()
: Text(context.l10n.load_more),

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_state.dart';
import 'package:spotube/utils/platform.dart';
class SettingsDesktopSection extends HookConsumerWidget {
const SettingsDesktopSection({super.key});
@ -53,7 +53,7 @@ class SettingsDesktopSection extends HookConsumerWidget {
value: preferences.systemTitleBar,
onChanged: preferencesNotifier.setSystemTitleBar,
),
if (!DesktopTools.platform.isMacOS)
if (!kIsMacOS)
SwitchListTile(
secondary: const Icon(SpotubeIcons.discord),
title: Text(context.l10n.discord_rich_presence),

View File

@ -1,13 +1,13 @@
import 'package:file_picker/file_picker.dart';
import 'package:file_selector/file_selector.dart';
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/spotube_icons.dart';
import 'package:spotube/components/settings/section_card_with_heading.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart';
class SettingsDownloadsSection extends HookConsumerWidget {
const SettingsDownloadsSection({super.key});
@ -18,7 +18,7 @@ class SettingsDownloadsSection extends HookConsumerWidget {
final preferences = ref.watch(userPreferencesProvider);
final pickDownloadLocation = useCallback(() async {
if (DesktopTools.platform.isMobile || DesktopTools.platform.isMacOS) {
if (kIsMobile || kIsMacOS) {
final dirStr = await FilePicker.platform.getDirectoryPath(
initialDirectory: preferences.downloadLocation,
);

View File

@ -1,6 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotube/components/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/playback.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart';
class SettingsPage extends HookConsumerWidget {
const SettingsPage({super.key});
@ -45,8 +45,7 @@ class SettingsPage extends HookConsumerWidget {
const SettingsAppearanceSection(),
const SettingsPlaybackSection(),
const SettingsDownloadsSection(),
if (DesktopTools.platform.isDesktop)
const SettingsDesktopSection(),
if (kIsDesktop) const SettingsDesktopSection(),
if (!kIsWeb) const SettingsDevelopersSection(),
const SettingsAboutSection(),
Center(

View File

@ -1,21 +1,19 @@
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/collections/env.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart';
class Discord extends ChangeNotifier {
final DiscordRPC? discordRPC;
final bool isEnabled;
Discord(this.isEnabled)
: discordRPC = (DesktopTools.platform.isWindows ||
DesktopTools.platform.isLinux) &&
isEnabled
: discordRPC = (kIsWindows || kIsLinux) && isEnabled
? DiscordRPC(applicationId: Env.discordAppId)
: null {
discordRPC?.start(autoRegister: true);

View 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;
},
);

View 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);
},
),
],
);
});

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.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/platform.dart';
import 'package:path/path.dart' as path;
import 'package:window_manager/window_manager.dart';
class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
final Ref ref;
@ -103,8 +103,8 @@ class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
void setSystemTitleBar(bool isSystemTitleBar) {
state = state.copyWith(systemTitleBar: isSystemTitleBar);
if (DesktopTools.platform.isDesktop) {
DesktopTools.window.setTitleBarStyle(
if (kIsDesktop) {
windowManager.setTitleBarStyle(
isSystemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
);
}
@ -151,8 +151,8 @@ class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
);
}
if (DesktopTools.platform.isDesktop) {
await DesktopTools.window.setTitleBarStyle(
if (kIsDesktop) {
await windowManager.setTitleBarStyle(
state.systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
);
}

View File

@ -62,10 +62,10 @@ class UserPreferences with _$UserPreferences {
@Default(false) bool amoledDarkTheme,
@Default(true) bool checkUpdate,
@Default(false) bool normalizeAudio,
@Default(true) bool showSystemTrayIcon,
@Default(false) bool showSystemTrayIcon,
@Default(false) bool skipNonMusic,
@Default(false) bool systemTitleBar,
@Default(CloseBehavior.minimizeToTray) CloseBehavior closeBehavior,
@Default(CloseBehavior.close) CloseBehavior closeBehavior,
@Default(SpotubeColor(0xFF2196F3, name: "Blue"))
@JsonKey(
fromJson: UserPreferences._accentColorSchemeFromJson,

View File

@ -12,7 +12,7 @@ part of 'user_preferences_state.dart';
T _$identity<T>(T value) => value;
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) {
return _UserPreferences.fromJson(json);
@ -415,10 +415,10 @@ class _$UserPreferencesImpl implements _UserPreferences {
this.amoledDarkTheme = false,
this.checkUpdate = true,
this.normalizeAudio = false,
this.showSystemTrayIcon = true,
this.showSystemTrayIcon = false,
this.skipNonMusic = false,
this.systemTitleBar = false,
this.closeBehavior = CloseBehavior.minimizeToTray,
this.closeBehavior = CloseBehavior.close,
@JsonKey(
fromJson: UserPreferences._accentColorSchemeFromJson,
toJson: UserPreferences._accentColorSchemeToJson,

View File

@ -16,12 +16,12 @@ _$UserPreferencesImpl _$$UserPreferencesImplFromJson(
amoledDarkTheme: json['amoledDarkTheme'] as bool? ?? false,
checkUpdate: json['checkUpdate'] as bool? ?? true,
normalizeAudio: json['normalizeAudio'] as bool? ?? false,
showSystemTrayIcon: json['showSystemTrayIcon'] as bool? ?? true,
showSystemTrayIcon: json['showSystemTrayIcon'] as bool? ?? false,
skipNonMusic: json['skipNonMusic'] as bool? ?? false,
systemTitleBar: json['systemTitleBar'] as bool? ?? false,
closeBehavior:
$enumDecodeNullable(_$CloseBehaviorEnumMap, json['closeBehavior']) ??
CloseBehavior.minimizeToTray,
CloseBehavior.close,
accentColorScheme: UserPreferences._accentColorSchemeReadValue(
json, 'accentColorScheme') ==
null

View File

@ -101,7 +101,7 @@ abstract class AudioPlayerInterface {
return _mkPlayer.state.completed;
}
Future<bool> get isShuffled async {
bool get isShuffled {
return _mkPlayer.shuffled;
}

View File

@ -1,5 +1,4 @@
import 'dart:async';
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
import 'package:catcher_2/catcher_2.dart';
import 'package:media_kit/media_kit.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';
// ignore: implementation_imports
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.
/// This class adds a state stream to the [Player] class.
@ -54,7 +54,7 @@ class CustomPlayer extends Player {
PackageInfo.fromPlatform().then((packageInfo) {
_packageName = packageInfo.packageName;
});
if (DesktopTools.platform.isAndroid) {
if (kIsAndroid) {
_androidAudioManager = AndroidAudioManager();
AudioSession.instance.then((s) async {
_androidAudioSessionId =
@ -71,7 +71,7 @@ class CustomPlayer extends Player {
}
Future<void> notifyAudioSessionUpdate(bool active) async {
if (DesktopTools.platform.isAndroid) {
if (kIsAndroid) {
sendBroadcast(
BroadcastMessage(
name: active
@ -106,6 +106,10 @@ class CustomPlayer extends Player {
_shuffled = shuffle;
await super.setShuffle(shuffle);
_shuffleStream.add(shuffle);
await Future.delayed(const Duration(milliseconds: 100));
if (shuffle) {
await move(state.playlist.index, 0);
}
}
@override

View File

@ -1,5 +1,4 @@
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:spotify/spotify.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/windows_audio_service.dart';
import 'package:spotube/services/sourced_track/sourced_track.dart';
import 'package:spotube/utils/platform.dart';
class AudioServices {
final MobileAudioService? mobile;
@ -19,9 +19,7 @@ class AudioServices {
Ref ref,
ProxyPlaylistNotifier playback,
) async {
final mobile = DesktopTools.platform.isMobile ||
DesktopTools.platform.isMacOS ||
DesktopTools.platform.isLinux
final mobile = kIsMobile || kIsMacOS || kIsLinux
? await AudioService.init(
builder: () => MobileAudioService(playback),
config: const AudioServiceConfig(
@ -31,9 +29,7 @@ class AudioServices {
),
)
: null;
final smtc = DesktopTools.platform.isWindows
? WindowsAudioService(ref, playback)
: null;
final smtc = kIsWindows ? WindowsAudioService(ref, playback) : null;
return AudioServices(
mobile,

View File

@ -1,4 +1,7 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotube/services/wm_tools/wm_tools.dart';
abstract class KVStoreService {
static SharedPreferences? _sharedPreferences;
@ -23,4 +26,21 @@ abstract class KVStoreService {
static Future<void> setRecentSearches(List<String> value) async =>
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(),
),
);
}

View File

@ -12,7 +12,7 @@ part of 'song_link.dart';
T _$identity<T>(T value) => value;
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) {
return _SongLink.fromJson(json);

View File

@ -163,7 +163,7 @@ class PipedSourcedTrack extends SourcedTrack {
final PipedSearchResult(items: searchResults) = await pipedClient.search(
query,
preference.searchMode == SearchMode.youtube
? PipedFilter.videos
? PipedFilter.video
: PipedFilter.musicSongs,
);

View 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;
}
}

View File

@ -14,7 +14,7 @@
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <screen_retriever/screen_retriever_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 <window_manager/window_manager_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 =
fl_plugin_registry_get_registrar_for_plugin(registry, "SystemThemePlugin");
system_theme_plugin_register_with_registrar(system_theme_registrar);
g_autoptr(FlPluginRegistrar) system_tray_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SystemTrayPlugin");
system_tray_plugin_register_with_registrar(system_tray_registrar);
g_autoptr(FlPluginRegistrar) tray_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "TrayManagerPlugin");
tray_manager_plugin_register_with_registrar(tray_manager_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@ -11,7 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
media_kit_libs_linux
screen_retriever
system_theme
system_tray
tray_manager
url_launcher_linux
window_manager
window_size

View File

@ -21,7 +21,7 @@ import screen_retriever
import shared_preferences_foundation
import sqflite
import system_theme
import system_tray
import tray_manager
import url_launcher_macos
import window_manager
import window_size
@ -37,13 +37,13 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
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"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
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"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin"))

View File

@ -18,9 +18,6 @@ PODS:
- flutter_secure_storage_macos (6.1.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- local_notifier (0.1.0):
- FlutterMacOS
- media_kit_libs_macos_audio (1.0.4):
@ -39,12 +36,12 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite (0.0.2):
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
- FMDB (>= 2.7.5)
- system_theme (0.0.1):
- FlutterMacOS
- system_tray (0.0.1):
- tray_manager (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
@ -71,16 +68,15 @@ DEPENDENCIES:
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- 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`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
SPEC REPOS:
trunk:
- FMDB
- OrderedSet
EXTERNAL SOURCES:
@ -119,11 +115,11 @@ EXTERNAL SOURCES:
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
system_theme:
:path: Flutter/ephemeral/.symlinks/plugins/system_theme/macos
system_tray:
:path: Flutter/ephemeral/.symlinks/plugins/system_tray/macos
tray_manager:
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
window_manager:
@ -132,28 +128,27 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_size/macos
SPEC CHECKSUMS:
app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
audio_service: b88ff778e0e3915efd4cd1a5ad6f0beef0c950a9
audio_session: dea1f41890dbf1718f04a56f1d6150fd50039b72
bonsoir_darwin: e3b8526c42ca46a885142df84229131dfabea842
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
media_kit_libs_macos_audio: 3871782a4f3f84c77f04d7666c87800a781c24da
media_kit_native_event_loop: 7321675377cb9ae8596a29bddf3a3d2b5e8792c5
metadata_god: eceae399d0020475069a5cebc35943ce8562b5d7
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
system_theme: c7b9f6659a5caa26c9bc2284da096781e9a6fcbc
system_tray: e53c972838c69589ff2e77d6d3abfd71332f9e5d
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ description: Open source Spotify client that doesn't require Premium nor uses El
publish_to: "none"
version: 3.5.0+29
version: 3.6.0+30
homepage: https://spotube.krtirtho.dev
repository: https://github.com/KRTirtho/spotube
@ -13,96 +13,89 @@ environment:
flutter: ">=3.10.0"
dependencies:
args: ^2.3.2
args: ^2.5.0
async: ^2.9.0
audio_service: ^0.18.9
audio_session: ^0.1.18
audio_service: ^0.18.13
audio_service_mpris: ^0.1.3
audio_session: ^0.1.19
auto_size_text: ^3.0.0
buttons_tabbar: ^1.3.6
buttons_tabbar: ^1.3.8
cached_network_image: ^3.3.1
catcher_2: 1.0.0
catcher_2: ^1.2.4
collection: ^1.15.0
cupertino_icons: ^1.0.5
curved_navigation_bar: ^1.0.3
dbus: ^0.7.8
device_info_plus: ^9.1.2
device_info_plus: ^10.1.0
device_preview: ^1.1.0
dio: ^5.4.1
disable_battery_optimization: ^1.1.0+1
dio: ^5.4.3+1
disable_battery_optimization: ^1.1.1
duration: ^3.0.12
envied: ^0.3.0
file_selector: ^1.0.1
fluentui_system_icons: ^1.1.189
envied: ^0.5.4+1
file_picker: ^8.0.0+1
file_selector: ^1.0.3
fluentui_system_icons: ^1.1.234
flutter:
sdk: flutter
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_feather_icons: ^2.0.0+1
flutter_hooks: ^0.20.5
flutter_inappwebview: ^6.0.0
flutter_localizations:
sdk: flutter
flutter_native_splash: ^2.3.10
flutter_riverpod: ^2.4.10
flutter_native_splash: ^2.4.0
flutter_riverpod: ^2.5.1
flutter_secure_storage: ^9.0.0
flutter_svg: ^1.1.6
form_validator: ^2.1.1
fuzzywuzzy: ^1.1.6
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_flutter: ^1.1.0
hooks_riverpod: ^2.4.3
hooks_riverpod: ^2.5.1
html: ^0.15.1
http: ^1.2.0
image_picker: ^1.0.4
image_picker: ^1.1.0
intl: ^0.18.0
introduction_screen: ^3.0.2
introduction_screen: ^3.1.14
json_annotation: ^4.8.1
logger: ^2.0.2
media_kit: ^1.1.3
media_kit_libs_audio: ^1.0.3
media_kit: ^1.1.10+1
media_kit_libs_audio: ^1.0.4
metadata_god: ^0.5.2+1
mime: ^1.0.2
package_info_plus: ^4.1.0
package_info_plus: ^6.0.0
palette_generator: ^0.3.3
path: ^1.8.0
path_provider: ^2.0.8
permission_handler: ^11.0.1
piped_client:
git:
url: https://github.com/KRTirtho/piped_client.git
path_provider: ^2.1.3
permission_handler: ^11.3.1
piped_client: ^0.1.1
popover: ^0.3.0
scrobblenaut:
git:
url: https://github.com/KRTirtho/scrobblenaut.git
ref: dart-3-support
scroll_to_index: ^3.0.1
sidebarx: ^0.16.3
shared_preferences: ^2.2.2
sidebarx: ^0.17.1
shared_preferences: ^2.2.3
skeleton_text: ^3.0.1
smtc_windows: ^0.1.1
smtc_windows: ^0.1.2
stroke_text: ^0.0.2
system_theme: ^2.1.0
titlebar_buttons: ^1.0.0
url_launcher: ^6.1.7
uuid: ^3.0.7
url_launcher: ^6.2.6
uuid: ^4.4.0
version: ^3.0.2
visibility_detector: ^0.4.0+2
window_manager: ^0.3.1
window_manager: ^0.3.8
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding.git
ref: a738913c8ce2c9f47515382d40827e794a334274
path: plugins/window_size
youtube_explode_dart: ^2.0.1
simple_icons: ^7.10.0
audio_service_mpris: ^0.1.0
file_picker: ^6.0.0
youtube_explode_dart: ^2.2.0
simple_icons: ^10.1.3
jiosaavn: ^0.1.0
draggable_scrollbar:
git:
@ -116,9 +109,9 @@ dependencies:
url: https://github.com/Tommypop2/dart_discord_rpc.git
html_unescape: ^2.0.0
wikipedia_api: ^0.1.0
skeletonizer: ^0.8.0
app_links: ^3.5.0
win32_registry: ^1.1.2
skeletonizer: ^1.1.1
app_links: ^4.0.1
win32_registry: ^1.1.3
flutter_sharing_intent: ^1.1.0
flutter_broadcasts: ^0.4.0
freezed_annotation: ^2.4.1
@ -130,17 +123,18 @@ dependencies:
shelf: ^1.4.1
shelf_router: ^1.1.4
shelf_web_socket: ^1.0.4
web_socket_channel: ^2.4.4
web_socket_channel: ^2.4.5
lrc: ^1.0.2
pub_api_client: ^2.4.0
pubspec_parse: ^1.2.2
timezone: ^0.9.2
crypto: ^3.0.3
local_notifier: ^0.1.6
tray_manager: ^0.2.2
dev_dependencies:
build_runner: ^2.4.9
envied_generator: ^0.3.0+3
flutter_distributor: ^0.0.2
envied_generator: ^0.5.4+1
flutter_gen_runner: ^5.4.0
flutter_launcher_icons: ^0.13.1
flutter_lints: ^3.0.1
@ -150,12 +144,12 @@ dev_dependencies:
sdk: flutter
hive_generator: ^2.0.0
json_serializable: ^6.6.2
freezed: ^2.4.6
custom_lint: ^0.5.11
riverpod_lint: ^2.1.1
freezed: ^2.5.2
custom_lint: ^0.6.4
riverpod_lint: ^2.3.10
dependency_overrides:
system_tray: 2.0.2
uuid: ^4.4.0
flutter:
generate: true

View File

@ -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"
]
}
{}

View File

@ -16,7 +16,7 @@
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <screen_retriever/screen_retriever_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 <window_manager/window_manager_plugin.h>
#include <window_size/window_size_plugin.h>
@ -42,8 +42,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
SystemThemePluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SystemThemePlugin"));
SystemTrayPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SystemTrayPlugin"));
TrayManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowManagerPluginRegisterWithRegistrar(

View File

@ -13,7 +13,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
permission_handler_windows
screen_retriever
system_theme
system_tray
tray_manager
url_launcher_windows
window_manager
window_size