mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Merge pull request #1628 from KRTirtho/refactor/idk
refactor: separated page widgets, shared widgets and breaking down of large widgets
This commit is contained in:
commit
a35eece00c
@ -1,177 +0,0 @@
|
|||||||
version: 2.1
|
|
||||||
|
|
||||||
orbs:
|
|
||||||
gh: circleci/github-cli@2.2.0
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
flutter_linux_arm:
|
|
||||||
machine:
|
|
||||||
image: ubuntu-2204:current
|
|
||||||
resource_class: arm.medium
|
|
||||||
parameters:
|
|
||||||
version:
|
|
||||||
type: string
|
|
||||||
default: 3.1.1
|
|
||||||
channel:
|
|
||||||
type: enum
|
|
||||||
enum:
|
|
||||||
- release
|
|
||||||
- nightly
|
|
||||||
default: release
|
|
||||||
github_run_number:
|
|
||||||
type: string
|
|
||||||
default: "0"
|
|
||||||
dry_run:
|
|
||||||
type: boolean
|
|
||||||
default: true
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- gh/setup
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Get current date
|
|
||||||
command: |
|
|
||||||
echo "export CURRENT_DATE=$(date +%Y-%m-%d)" >> $BASH_ENV
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Install dependencies
|
|
||||||
command: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y tar clang cmake ninja-build pkg-config libgtk-3-dev make python3-pip python3-setuptools 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 zip rpm
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Install Flutter
|
|
||||||
command: |
|
|
||||||
git clone https://github.com/flutter/flutter.git
|
|
||||||
cd flutter && git checkout stable && cd ..
|
|
||||||
export PATH="$PATH:`pwd`/flutter/bin"
|
|
||||||
flutter precache
|
|
||||||
flutter doctor -v
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Install AppImageTool
|
|
||||||
command: |
|
|
||||||
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage"
|
|
||||||
chmod +x appimagetool
|
|
||||||
mv appimagetool flutter/bin
|
|
||||||
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: flutter
|
|
||||||
paths:
|
|
||||||
- .
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, nightly]
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Replace pubspec version and BUILD_VERSION Env (nightly)
|
|
||||||
command: |
|
|
||||||
curl -sS https://webi.sh/yq | sh
|
|
||||||
yq -i '.version |= sub("\+\d+", "+<< parameters.channel >>.")' pubspec.yaml
|
|
||||||
yq -i '.version += strenv(GITHUB_RUN_NUMBER)' pubspec.yaml
|
|
||||||
echo 'export BUILD_VERSION="<< parameters.version >>+<< parameters.channel >>.<< parameters.github_run_number >>"' >> $BASH_ENV
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, release]
|
|
||||||
steps:
|
|
||||||
- run: echo 'export BUILD_VERSION="<< parameters.version >>"' >> $BASH_ENV
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Generate .env file
|
|
||||||
command: |
|
|
||||||
echo "SPOTIFY_SECRETS=${SPOTIFY_SECRETS}" >> .env
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Replace Version in files
|
|
||||||
command: |
|
|
||||||
sed -i 's|%{{APPDATA_RELEASE}}%|<release version="${BUILD_VERSION}" date="${CURRENT_DATE}" />|' linux/com.github.KRTirtho.Spotube.appdata.xml
|
|
||||||
echo "build_arch: aarch64" >> linux/packaging/rpm/make_config.yaml
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Build secrets
|
|
||||||
command: |
|
|
||||||
export PATH="$PATH:`pwd`/flutter/bin"
|
|
||||||
flutter config --enable-linux-desktop
|
|
||||||
flutter pub get
|
|
||||||
dart run build_runner build --delete-conflicting-outputs --enable-experiment=records,patterns
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Build Flutter app
|
|
||||||
command: |
|
|
||||||
export PATH="$PATH:`pwd`/flutter/bin"
|
|
||||||
export PATH="$PATH":"$HOME/.pub-cache/bin"
|
|
||||||
dart pub global activate flutter_distributor
|
|
||||||
alias dpkg-deb="dpkg-deb --Zxz"
|
|
||||||
flutter_distributor package --platform=linux --targets=deb
|
|
||||||
flutter_distributor package --platform=linux --targets=appimage
|
|
||||||
flutter_distributor package --platform=linux --targets=rpm
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, nightly]
|
|
||||||
steps:
|
|
||||||
- run: make tar VERSION=nightly ARCH=arm64 PKG_ARCH=aarch64
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
equal: [<< parameters.channel >>, release]
|
|
||||||
steps:
|
|
||||||
- run: make tar VERSION=${BUILD_VERSION} ARCH=arm64 PKG_ARCH=aarch64
|
|
||||||
|
|
||||||
- run:
|
|
||||||
name: Move artifacts
|
|
||||||
command: |
|
|
||||||
mkdir bundle
|
|
||||||
mv build/spotube-linux-*-aarch64.tar.xz bundle/
|
|
||||||
mv dist/**/spotube-*-linux.deb bundle/Spotube-linux-aarch64.deb
|
|
||||||
mv dist/**/spotube-*-linux.rpm bundle/Spotube-linux-aarch64.rpm
|
|
||||||
mv dist/**/spotube-*-linux.AppImage bundle/Spotube-linux-aarch64.AppImage
|
|
||||||
zip -r Spotube-linux-aarch64.zip bundle
|
|
||||||
|
|
||||||
- store_artifacts:
|
|
||||||
path: Spotube-linux-aarch64.zip
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
and:
|
|
||||||
- equal: [<< parameters.dry_run >>, false]
|
|
||||||
- equal: [<< parameters.channel >>, release]
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Upload to release (release)
|
|
||||||
command: gh release upload v<< parameters.version >> bundle/* --clobber
|
|
||||||
|
|
||||||
- when:
|
|
||||||
condition:
|
|
||||||
and:
|
|
||||||
- equal: [<< parameters.dry_run >>, false]
|
|
||||||
- equal: [<< parameters.channel >>, nightly]
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Upload to release (nightly)
|
|
||||||
command: gh release upload nightly bundle/* --clobber
|
|
||||||
|
|
||||||
parameters:
|
|
||||||
GHA_Actor:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
GHA_Action:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
GHA_Event:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
GHA_Meta:
|
|
||||||
type: string
|
|
||||||
default: ""
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
build_flutter_for_arm_workflow:
|
|
||||||
when: << pipeline.parameters.GHA_Action >>
|
|
||||||
jobs:
|
|
||||||
- flutter_linux_arm:
|
|
||||||
context:
|
|
||||||
- org-global
|
|
||||||
- GITHUB_CREDS
|
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"flutterSdkVersion": "3.19.6",
|
"flutterSdkVersion": "3.22.1",
|
||||||
"flavors": {}
|
"flavors": {}
|
||||||
}
|
}
|
2
.github/workflows/pr-lint.yml
vendored
2
.github/workflows/pr-lint.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: '3.19.6'
|
FLUTTER_VERSION: 3.22.2
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
2
.github/workflows/spotube-release-binary.yml
vendored
2
.github/workflows/spotube-release-binary.yml
vendored
@ -20,7 +20,7 @@ on:
|
|||||||
description: Dry run without uploading to release
|
description: Dry run without uploading to release
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: 3.19.6
|
FLUTTER_VERSION: 3.22.1
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -17,6 +17,7 @@
|
|||||||
"songlink",
|
"songlink",
|
||||||
"speechiness",
|
"speechiness",
|
||||||
"Spotube",
|
"Spotube",
|
||||||
|
"titlebar",
|
||||||
"winget"
|
"winget"
|
||||||
],
|
],
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
@ -32,7 +32,7 @@ All types of contributions are encouraged and valued. See the [Table of Contents
|
|||||||
This project and everyone participating in it is governed by the
|
This project and everyone participating in it is governed by the
|
||||||
[Spotube Code of Conduct](https://github.com/KRTirtho/spotube/blob/master/CODE_OF_CONDUCT.md).
|
[Spotube Code of Conduct](https://github.com/KRTirtho/spotube/blob/master/CODE_OF_CONDUCT.md).
|
||||||
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||||
to <>.
|
to krtirtho@gmail.com.
|
||||||
|
|
||||||
## I Have a Question
|
## I Have a Question
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:spotube/collections/routes.dart';
|
import 'package:spotube/collections/routes.dart';
|
||||||
import 'package:spotube/components/player/player_controls.dart';
|
import 'package:spotube/modules/player/player_controls.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/pages/home/home.dart';
|
import 'package:spotube/pages/home/home.dart';
|
||||||
import 'package:spotube/pages/library/library.dart';
|
import 'package:spotube/pages/library/library.dart';
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:catcher_2/catcher_2.dart';
|
|
||||||
import 'package:flutter/foundation.dart' hide Category;
|
import 'package:flutter/foundation.dart' hide Category;
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -36,7 +35,7 @@ import 'package:spotube/pages/track/track.dart';
|
|||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
import 'package:spotube/components/shared/spotube_page_route.dart';
|
import 'package:spotube/components/spotube_page_route.dart';
|
||||||
import 'package:spotube/pages/artist/artist.dart';
|
import 'package:spotube/pages/artist/artist.dart';
|
||||||
import 'package:spotube/pages/library/library.dart';
|
import 'package:spotube/pages/library/library.dart';
|
||||||
import 'package:spotube/pages/desktop_login/login_tutorial.dart';
|
import 'package:spotube/pages/desktop_login/login_tutorial.dart';
|
||||||
@ -46,7 +45,7 @@ import 'package:spotube/pages/root/root_app.dart';
|
|||||||
import 'package:spotube/pages/settings/settings.dart';
|
import 'package:spotube/pages/settings/settings.dart';
|
||||||
import 'package:spotube/pages/mobile_login/mobile_login.dart';
|
import 'package:spotube/pages/mobile_login/mobile_login.dart';
|
||||||
|
|
||||||
final rootNavigatorKey = Catcher2.navigatorKey;
|
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
final shellRouteNavigatorKey = GlobalKey<NavigatorState>();
|
final shellRouteNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
final routerProvider = Provider((ref) {
|
final routerProvider = Provider((ref) {
|
||||||
return GoRouter(
|
return GoRouter(
|
||||||
|
@ -226,8 +226,11 @@ class _AdaptivePopSheetListItem<T> extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: IconTheme.merge(
|
||||||
|
data: const IconThemeData(opacity: 1),
|
||||||
child: IgnorePointer(child: item),
|
child: IgnorePointer(child: item),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
@ -4,8 +4,8 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
|
import 'package:spotube/modules/playlist/playlist_create_dialog.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/links/artist_link.dart';
|
import 'package:spotube/components/links/artist_link.dart';
|
||||||
import 'package:spotube/components/shared/links/hyper_link.dart';
|
import 'package:spotube/components/links/hyper_link.dart';
|
||||||
import 'package:spotube/components/shared/links/link_text.dart';
|
import 'package:spotube/components/links/link_text.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
@ -1,11 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/heart_button/use_track_toggle_like.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/scrobbler_provider.dart';
|
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class HeartButton extends HookConsumerWidget {
|
class HeartButton extends HookConsumerWidget {
|
||||||
@ -55,38 +54,6 @@ class HeartButton extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef UseTrackToggleLike = ({
|
|
||||||
bool isLiked,
|
|
||||||
Future<void> Function(Track track) toggleTrackLike,
|
|
||||||
});
|
|
||||||
|
|
||||||
UseTrackToggleLike useTrackToggleLike(Track track, WidgetRef ref) {
|
|
||||||
final savedTracks = ref.watch(likedTracksProvider);
|
|
||||||
final savedTracksNotifier = ref.watch(likedTracksProvider.notifier);
|
|
||||||
|
|
||||||
final isLiked = useMemoized(
|
|
||||||
() =>
|
|
||||||
savedTracks.asData?.value.any((element) => element.id == track.id) ??
|
|
||||||
false,
|
|
||||||
[savedTracks.asData?.value, track.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
final scrobblerNotifier = ref.read(scrobblerProvider.notifier);
|
|
||||||
|
|
||||||
return (
|
|
||||||
isLiked: isLiked,
|
|
||||||
toggleTrackLike: (track) async {
|
|
||||||
await savedTracksNotifier.toggleFavorite(track);
|
|
||||||
|
|
||||||
if (!isLiked) {
|
|
||||||
await scrobblerNotifier.love(track);
|
|
||||||
} else {
|
|
||||||
await scrobblerNotifier.unlove(track);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class TrackHeartButton extends HookConsumerWidget {
|
class TrackHeartButton extends HookConsumerWidget {
|
||||||
final Track track;
|
final Track track;
|
||||||
const TrackHeartButton({
|
const TrackHeartButton({
|
37
lib/components/heart_button/use_track_toggle_like.dart
Normal file
37
lib/components/heart_button/use_track_toggle_like.dart
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/provider/scrobbler_provider.dart';
|
||||||
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
|
typedef UseTrackToggleLike = ({
|
||||||
|
bool isLiked,
|
||||||
|
Future<void> Function(Track track) toggleTrackLike,
|
||||||
|
});
|
||||||
|
|
||||||
|
UseTrackToggleLike useTrackToggleLike(Track track, WidgetRef ref) {
|
||||||
|
final savedTracks = ref.watch(likedTracksProvider);
|
||||||
|
final savedTracksNotifier = ref.watch(likedTracksProvider.notifier);
|
||||||
|
|
||||||
|
final isLiked = useMemoized(
|
||||||
|
() =>
|
||||||
|
savedTracks.asData?.value.any((element) => element.id == track.id) ??
|
||||||
|
false,
|
||||||
|
[savedTracks.asData?.value, track.id],
|
||||||
|
);
|
||||||
|
|
||||||
|
final scrobblerNotifier = ref.read(scrobblerProvider.notifier);
|
||||||
|
|
||||||
|
return (
|
||||||
|
isLiked: isLiked,
|
||||||
|
toggleTrackLike: (track) async {
|
||||||
|
await savedTracksNotifier.toggleFavorite(track);
|
||||||
|
|
||||||
|
if (!isLiked) {
|
||||||
|
await scrobblerNotifier.love(track);
|
||||||
|
} else {
|
||||||
|
await scrobblerNotifier.unlove(track);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -5,9 +5,9 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/components/album/album_card.dart';
|
import 'package:spotube/modules/album/album_card.dart';
|
||||||
import 'package:spotube/components/artist/artist_card.dart';
|
import 'package:spotube/modules/artist/artist_card.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
import 'package:spotube/modules/playlist/playlist_card.dart';
|
||||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/links/anchor_button.dart';
|
import 'package:spotube/components/links/anchor_button.dart';
|
||||||
import 'package:spotube/pages/artist/artist.dart';
|
import 'package:spotube/pages/artist/artist.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:spotube/components/shared/links/anchor_button.dart';
|
import 'package:spotube/components/links/anchor_button.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class Hyperlink extends StatelessWidget {
|
class Hyperlink extends StatelessWidget {
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:spotube/components/shared/links/anchor_button.dart';
|
import 'package:spotube/components/links/anchor_button.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
|
|
||||||
class LinkText<T> extends StatelessWidget {
|
class LinkText<T> extends StatelessWidget {
|
@ -1,4 +1,4 @@
|
|||||||
part of './sliding_up_panel.dart';
|
part of 'sliding_up_panel.dart';
|
||||||
|
|
||||||
class PanelController extends ChangeNotifier {
|
class PanelController extends ChangeNotifier {
|
||||||
SlidingUpPanelState? _panelState;
|
SlidingUpPanelState? _panelState;
|
@ -1,4 +1,4 @@
|
|||||||
part of "./sliding_up_panel.dart";
|
part of "sliding_up_panel.dart";
|
||||||
|
|
||||||
/// if you want to prevent the panel from being dragged using the widget,
|
/// if you want to prevent the panel from being dragged using the widget,
|
||||||
/// wrap the widget with this
|
/// wrap the widget with this
|
@ -5,8 +5,8 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/hover_builder.dart';
|
import 'package:spotube/components/hover_builder.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/hooks/utils/use_brightness_value.dart';
|
import 'package:spotube/hooks/utils/use_brightness_value.dart';
|
@ -1,653 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
|
||||||
import 'package:spotube/utils/platform.dart';
|
|
||||||
import 'package:titlebar_buttons/titlebar_buttons.dart';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
|
||||||
import 'dart:io' show Platform;
|
|
||||||
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
class PageWindowTitleBar extends StatefulHookConsumerWidget
|
|
||||||
implements PreferredSizeWidget {
|
|
||||||
final Widget? leading;
|
|
||||||
final bool automaticallyImplyLeading;
|
|
||||||
final List<Widget>? actions;
|
|
||||||
final Color? backgroundColor;
|
|
||||||
final Color? foregroundColor;
|
|
||||||
final IconThemeData? actionsIconTheme;
|
|
||||||
final bool? centerTitle;
|
|
||||||
final double? titleSpacing;
|
|
||||||
final double toolbarOpacity;
|
|
||||||
final double? leadingWidth;
|
|
||||||
final TextStyle? toolbarTextStyle;
|
|
||||||
final TextStyle? titleTextStyle;
|
|
||||||
final double? titleWidth;
|
|
||||||
final Widget? title;
|
|
||||||
|
|
||||||
final bool _sliver;
|
|
||||||
|
|
||||||
const PageWindowTitleBar({
|
|
||||||
super.key,
|
|
||||||
this.actions,
|
|
||||||
this.title,
|
|
||||||
this.toolbarOpacity = 1,
|
|
||||||
this.backgroundColor,
|
|
||||||
this.actionsIconTheme,
|
|
||||||
this.automaticallyImplyLeading = false,
|
|
||||||
this.centerTitle,
|
|
||||||
this.foregroundColor,
|
|
||||||
this.leading,
|
|
||||||
this.leadingWidth,
|
|
||||||
this.titleSpacing,
|
|
||||||
this.titleTextStyle,
|
|
||||||
this.titleWidth,
|
|
||||||
this.toolbarTextStyle,
|
|
||||||
}) : _sliver = false,
|
|
||||||
pinned = false,
|
|
||||||
floating = false,
|
|
||||||
snap = false,
|
|
||||||
stretch = false;
|
|
||||||
|
|
||||||
final bool pinned;
|
|
||||||
final bool floating;
|
|
||||||
final bool snap;
|
|
||||||
final bool stretch;
|
|
||||||
|
|
||||||
const PageWindowTitleBar.sliver({
|
|
||||||
super.key,
|
|
||||||
this.actions,
|
|
||||||
this.title,
|
|
||||||
this.backgroundColor,
|
|
||||||
this.actionsIconTheme,
|
|
||||||
this.automaticallyImplyLeading = false,
|
|
||||||
this.centerTitle,
|
|
||||||
this.foregroundColor,
|
|
||||||
this.leading,
|
|
||||||
this.leadingWidth,
|
|
||||||
this.titleSpacing,
|
|
||||||
this.titleTextStyle,
|
|
||||||
this.titleWidth,
|
|
||||||
this.toolbarTextStyle,
|
|
||||||
this.pinned = false,
|
|
||||||
this.floating = false,
|
|
||||||
this.snap = false,
|
|
||||||
this.stretch = false,
|
|
||||||
}) : _sliver = true,
|
|
||||||
toolbarOpacity = 1;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ConsumerState<PageWindowTitleBar> createState() => _PageWindowTitleBarState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
|
|
||||||
void onDrag(details) {
|
|
||||||
final systemTitleBar =
|
|
||||||
ref.read(userPreferencesProvider.select((s) => s.systemTitleBar));
|
|
||||||
if (kIsDesktop && !systemTitleBar) {
|
|
||||||
windowManager.startDragging();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final mediaQuery = MediaQuery.of(context);
|
|
||||||
|
|
||||||
if (widget._sliver) {
|
|
||||||
return SliverLayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
final hasFullscreen =
|
|
||||||
mediaQuery.size.width == constraints.crossAxisExtent;
|
|
||||||
final hasLeadingOrCanPop =
|
|
||||||
widget.leading != null || Navigator.canPop(context);
|
|
||||||
|
|
||||||
return SliverPadding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
|
|
||||||
),
|
|
||||||
sliver: SliverAppBar(
|
|
||||||
leading: widget.leading,
|
|
||||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
|
||||||
actions: [
|
|
||||||
...?widget.actions,
|
|
||||||
WindowTitleBarButtons(foregroundColor: widget.foregroundColor),
|
|
||||||
],
|
|
||||||
backgroundColor: widget.backgroundColor,
|
|
||||||
foregroundColor: widget.foregroundColor,
|
|
||||||
actionsIconTheme: widget.actionsIconTheme,
|
|
||||||
centerTitle: widget.centerTitle,
|
|
||||||
titleSpacing: widget.titleSpacing,
|
|
||||||
leadingWidth: widget.leadingWidth,
|
|
||||||
toolbarTextStyle: widget.toolbarTextStyle,
|
|
||||||
titleTextStyle: widget.titleTextStyle,
|
|
||||||
title: SizedBox(
|
|
||||||
width: double.infinity, // workaround to force dragging
|
|
||||||
child: widget.title ?? const Text(""),
|
|
||||||
),
|
|
||||||
pinned: widget.pinned,
|
|
||||||
floating: widget.floating,
|
|
||||||
snap: widget.snap,
|
|
||||||
stretch: widget.stretch,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return LayoutBuilder(builder: (context, constrains) {
|
|
||||||
final hasFullscreen = mediaQuery.size.width == constrains.maxWidth;
|
|
||||||
final hasLeadingOrCanPop =
|
|
||||||
widget.leading != null || Navigator.canPop(context);
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onHorizontalDragStart: onDrag,
|
|
||||||
onVerticalDragStart: onDrag,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
|
|
||||||
),
|
|
||||||
child: AppBar(
|
|
||||||
leading: widget.leading,
|
|
||||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
|
||||||
actions: [
|
|
||||||
...?widget.actions,
|
|
||||||
WindowTitleBarButtons(foregroundColor: widget.foregroundColor),
|
|
||||||
],
|
|
||||||
backgroundColor: widget.backgroundColor,
|
|
||||||
foregroundColor: widget.foregroundColor,
|
|
||||||
actionsIconTheme: widget.actionsIconTheme,
|
|
||||||
centerTitle: widget.centerTitle,
|
|
||||||
titleSpacing: widget.titleSpacing,
|
|
||||||
toolbarOpacity: widget.toolbarOpacity,
|
|
||||||
leadingWidth: widget.leadingWidth,
|
|
||||||
toolbarTextStyle: widget.toolbarTextStyle,
|
|
||||||
titleTextStyle: widget.titleTextStyle,
|
|
||||||
title: SizedBox(
|
|
||||||
width: double.infinity, // workaround to force dragging
|
|
||||||
child: widget.title ?? const Text(""),
|
|
||||||
),
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
shadowColor: Colors.transparent,
|
|
||||||
forceMaterialTransparency: true,
|
|
||||||
elevation: 0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WindowTitleBarButtons extends HookConsumerWidget {
|
|
||||||
final Color? foregroundColor;
|
|
||||||
const WindowTitleBarButtons({
|
|
||||||
super.key,
|
|
||||||
this.foregroundColor,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, ref) {
|
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
|
||||||
final isMaximized = useState<bool?>(null);
|
|
||||||
const type = ThemeType.auto;
|
|
||||||
|
|
||||||
Future<void> onClose() async {
|
|
||||||
await windowManager.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() {
|
|
||||||
if (kIsDesktop) {
|
|
||||||
windowManager.isMaximized().then((value) {
|
|
||||||
isMaximized.value = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!kIsDesktop || kIsMacOS || preferences.systemTitleBar) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kIsWindows) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
final colors = WindowButtonColors(
|
|
||||||
normal: Colors.transparent,
|
|
||||||
iconNormal: foregroundColor ?? theme.colorScheme.onBackground,
|
|
||||||
mouseOver: theme.colorScheme.onBackground.withOpacity(0.1),
|
|
||||||
mouseDown: theme.colorScheme.onBackground.withOpacity(0.2),
|
|
||||||
iconMouseOver: theme.colorScheme.onBackground,
|
|
||||||
iconMouseDown: theme.colorScheme.onBackground,
|
|
||||||
);
|
|
||||||
|
|
||||||
final closeColors = WindowButtonColors(
|
|
||||||
normal: Colors.transparent,
|
|
||||||
iconNormal: foregroundColor ?? theme.colorScheme.onBackground,
|
|
||||||
mouseOver: Colors.red,
|
|
||||||
mouseDown: Colors.red[800]!,
|
|
||||||
iconMouseOver: Colors.white,
|
|
||||||
iconMouseDown: Colors.black,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 25),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
MinimizeWindowButton(
|
|
||||||
onPressed: windowManager.minimize,
|
|
||||||
colors: colors,
|
|
||||||
),
|
|
||||||
if (isMaximized.value != true)
|
|
||||||
MaximizeWindowButton(
|
|
||||||
colors: colors,
|
|
||||||
onPressed: () {
|
|
||||||
windowManager.maximize();
|
|
||||||
isMaximized.value = true;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
else
|
|
||||||
RestoreWindowButton(
|
|
||||||
colors: colors,
|
|
||||||
onPressed: () {
|
|
||||||
windowManager.unmaximize();
|
|
||||||
isMaximized.value = false;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
CloseWindowButton(
|
|
||||||
colors: closeColors,
|
|
||||||
onPressed: onClose,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 20, left: 10),
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
DecoratedMinimizeButton(
|
|
||||||
type: type,
|
|
||||||
onPressed: windowManager.minimize,
|
|
||||||
),
|
|
||||||
DecoratedMaximizeButton(
|
|
||||||
type: type,
|
|
||||||
onPressed: () async {
|
|
||||||
if (await windowManager.isMaximized()) {
|
|
||||||
await windowManager.unmaximize();
|
|
||||||
isMaximized.value = false;
|
|
||||||
} else {
|
|
||||||
await windowManager.maximize();
|
|
||||||
isMaximized.value = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
DecoratedCloseButton(
|
|
||||||
type: type,
|
|
||||||
onPressed: onClose,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef WindowButtonIconBuilder = Widget Function(
|
|
||||||
WindowButtonContext buttonContext);
|
|
||||||
typedef WindowButtonBuilder = Widget Function(
|
|
||||||
WindowButtonContext buttonContext, Widget icon);
|
|
||||||
|
|
||||||
class WindowButtonContext {
|
|
||||||
BuildContext context;
|
|
||||||
MouseState mouseState;
|
|
||||||
Color? backgroundColor;
|
|
||||||
Color iconColor;
|
|
||||||
WindowButtonContext(
|
|
||||||
{required this.context,
|
|
||||||
required this.mouseState,
|
|
||||||
this.backgroundColor,
|
|
||||||
required this.iconColor});
|
|
||||||
}
|
|
||||||
|
|
||||||
class WindowButtonColors {
|
|
||||||
late Color normal;
|
|
||||||
late Color mouseOver;
|
|
||||||
late Color mouseDown;
|
|
||||||
late Color iconNormal;
|
|
||||||
late Color iconMouseOver;
|
|
||||||
late Color iconMouseDown;
|
|
||||||
WindowButtonColors(
|
|
||||||
{Color? normal,
|
|
||||||
Color? mouseOver,
|
|
||||||
Color? mouseDown,
|
|
||||||
Color? iconNormal,
|
|
||||||
Color? iconMouseOver,
|
|
||||||
Color? iconMouseDown}) {
|
|
||||||
this.normal = normal ?? _defaultButtonColors.normal;
|
|
||||||
this.mouseOver = mouseOver ?? _defaultButtonColors.mouseOver;
|
|
||||||
this.mouseDown = mouseDown ?? _defaultButtonColors.mouseDown;
|
|
||||||
this.iconNormal = iconNormal ?? _defaultButtonColors.iconNormal;
|
|
||||||
this.iconMouseOver = iconMouseOver ?? _defaultButtonColors.iconMouseOver;
|
|
||||||
this.iconMouseDown = iconMouseDown ?? _defaultButtonColors.iconMouseDown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final _defaultButtonColors = WindowButtonColors(
|
|
||||||
normal: Colors.transparent,
|
|
||||||
iconNormal: const Color(0xFF805306),
|
|
||||||
mouseOver: const Color(0xFF404040),
|
|
||||||
mouseDown: const Color(0xFF202020),
|
|
||||||
iconMouseOver: const Color(0xFFFFFFFF),
|
|
||||||
iconMouseDown: const Color(0xFFF0F0F0),
|
|
||||||
);
|
|
||||||
|
|
||||||
class WindowButton extends StatelessWidget {
|
|
||||||
final WindowButtonBuilder? builder;
|
|
||||||
final WindowButtonIconBuilder? iconBuilder;
|
|
||||||
late final WindowButtonColors colors;
|
|
||||||
final bool animate;
|
|
||||||
final EdgeInsets? padding;
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
|
|
||||||
WindowButton(
|
|
||||||
{super.key,
|
|
||||||
WindowButtonColors? colors,
|
|
||||||
this.builder,
|
|
||||||
@required this.iconBuilder,
|
|
||||||
this.padding,
|
|
||||||
this.onPressed,
|
|
||||||
this.animate = false}) {
|
|
||||||
this.colors = colors ?? _defaultButtonColors;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color getBackgroundColor(MouseState mouseState) {
|
|
||||||
if (mouseState.isMouseDown) return colors.mouseDown;
|
|
||||||
if (mouseState.isMouseOver) return colors.mouseOver;
|
|
||||||
return colors.normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
Color getIconColor(MouseState mouseState) {
|
|
||||||
if (mouseState.isMouseDown) return colors.iconMouseDown;
|
|
||||||
if (mouseState.isMouseOver) return colors.iconMouseOver;
|
|
||||||
return colors.iconNormal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (kIsWeb) {
|
|
||||||
return Container();
|
|
||||||
} else {
|
|
||||||
// Don't show button on macOS
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return MouseStateBuilder(
|
|
||||||
builder: (context, mouseState) {
|
|
||||||
WindowButtonContext buttonContext = WindowButtonContext(
|
|
||||||
mouseState: mouseState,
|
|
||||||
context: context,
|
|
||||||
backgroundColor: getBackgroundColor(mouseState),
|
|
||||||
iconColor: getIconColor(mouseState));
|
|
||||||
|
|
||||||
var icon =
|
|
||||||
(iconBuilder != null) ? iconBuilder!(buttonContext) : Container();
|
|
||||||
|
|
||||||
var fadeOutColor =
|
|
||||||
getBackgroundColor(MouseState()..isMouseOver = true).withOpacity(0);
|
|
||||||
var padding = this.padding ?? const EdgeInsets.all(10);
|
|
||||||
var animationMs =
|
|
||||||
mouseState.isMouseOver ? (animate ? 100 : 0) : (animate ? 200 : 0);
|
|
||||||
Widget iconWithPadding = Padding(padding: padding, child: icon);
|
|
||||||
iconWithPadding = AnimatedContainer(
|
|
||||||
curve: Curves.easeOut,
|
|
||||||
duration: Duration(milliseconds: animationMs),
|
|
||||||
color: buttonContext.backgroundColor ?? fadeOutColor,
|
|
||||||
child: iconWithPadding);
|
|
||||||
var button =
|
|
||||||
(builder != null) ? builder!(buttonContext, icon) : iconWithPadding;
|
|
||||||
return SizedBox(
|
|
||||||
width: 45,
|
|
||||||
height: 32,
|
|
||||||
child: button,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onPressed: () {
|
|
||||||
if (onPressed != null) onPressed!();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MinimizeWindowButton extends WindowButton {
|
|
||||||
MinimizeWindowButton(
|
|
||||||
{super.key, super.colors, super.onPressed, bool? animate})
|
|
||||||
: super(
|
|
||||||
animate: animate ?? false,
|
|
||||||
iconBuilder: (buttonContext) =>
|
|
||||||
MinimizeIcon(color: buttonContext.iconColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MaximizeWindowButton extends WindowButton {
|
|
||||||
MaximizeWindowButton(
|
|
||||||
{super.key, super.colors, super.onPressed, bool? animate})
|
|
||||||
: super(
|
|
||||||
animate: animate ?? false,
|
|
||||||
iconBuilder: (buttonContext) =>
|
|
||||||
MaximizeIcon(color: buttonContext.iconColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class RestoreWindowButton extends WindowButton {
|
|
||||||
RestoreWindowButton({super.key, super.colors, super.onPressed, bool? animate})
|
|
||||||
: super(
|
|
||||||
animate: animate ?? false,
|
|
||||||
iconBuilder: (buttonContext) =>
|
|
||||||
RestoreIcon(color: buttonContext.iconColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final _defaultCloseButtonColors = WindowButtonColors(
|
|
||||||
mouseOver: const Color(0xFFD32F2F),
|
|
||||||
mouseDown: const Color(0xFFB71C1C),
|
|
||||||
iconNormal: const Color(0xFF805306),
|
|
||||||
iconMouseOver: const Color(0xFFFFFFFF));
|
|
||||||
|
|
||||||
class CloseWindowButton extends WindowButton {
|
|
||||||
CloseWindowButton(
|
|
||||||
{super.key, WindowButtonColors? colors, super.onPressed, bool? animate})
|
|
||||||
: super(
|
|
||||||
colors: colors ?? _defaultCloseButtonColors,
|
|
||||||
animate: animate ?? false,
|
|
||||||
iconBuilder: (buttonContext) =>
|
|
||||||
CloseIcon(color: buttonContext.iconColor),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switched to CustomPaint icons by https://github.com/esDotDev
|
|
||||||
|
|
||||||
/// Close
|
|
||||||
class CloseIcon extends StatelessWidget {
|
|
||||||
final Color color;
|
|
||||||
const CloseIcon({super.key, required this.color});
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Align(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Stack(children: [
|
|
||||||
// Use rotated containers instead of a painter because it renders slightly crisper than a painter for some reason.
|
|
||||||
Transform.rotate(
|
|
||||||
angle: pi * .25,
|
|
||||||
child:
|
|
||||||
Center(child: Container(width: 14, height: 1, color: color))),
|
|
||||||
Transform.rotate(
|
|
||||||
angle: pi * -.25,
|
|
||||||
child:
|
|
||||||
Center(child: Container(width: 14, height: 1, color: color))),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maximize
|
|
||||||
class MaximizeIcon extends StatelessWidget {
|
|
||||||
final Color color;
|
|
||||||
const MaximizeIcon({super.key, required this.color});
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MaximizePainter extends _IconPainter {
|
|
||||||
_MaximizePainter(super.color);
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
Paint p = getPaint(color);
|
|
||||||
canvas.drawRect(Rect.fromLTRB(0, 0, size.width - 1, size.height - 1), p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Restore
|
|
||||||
class RestoreIcon extends StatelessWidget {
|
|
||||||
final Color color;
|
|
||||||
const RestoreIcon({
|
|
||||||
super.key,
|
|
||||||
required this.color,
|
|
||||||
});
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RestorePainter extends _IconPainter {
|
|
||||||
_RestorePainter(super.color);
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
Paint p = getPaint(color);
|
|
||||||
canvas.drawRect(Rect.fromLTRB(0, 2, size.width - 2, size.height), p);
|
|
||||||
canvas.drawLine(const Offset(2, 2), const Offset(2, 0), p);
|
|
||||||
canvas.drawLine(const Offset(2, 0), Offset(size.width, 0), p);
|
|
||||||
canvas.drawLine(
|
|
||||||
Offset(size.width, 0), Offset(size.width, size.height - 2), p);
|
|
||||||
canvas.drawLine(Offset(size.width, size.height - 2),
|
|
||||||
Offset(size.width - 2, size.height - 2), p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Minimize
|
|
||||||
class MinimizeIcon extends StatelessWidget {
|
|
||||||
final Color color;
|
|
||||||
const MinimizeIcon({super.key, required this.color});
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MinimizePainter extends _IconPainter {
|
|
||||||
_MinimizePainter(super.color);
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
Paint p = getPaint(color);
|
|
||||||
canvas.drawLine(
|
|
||||||
Offset(0, size.height / 2), Offset(size.width, size.height / 2), p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helpers
|
|
||||||
abstract class _IconPainter extends CustomPainter {
|
|
||||||
_IconPainter(this.color);
|
|
||||||
final Color color;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AlignedPaint extends StatelessWidget {
|
|
||||||
const _AlignedPaint(this.painter);
|
|
||||||
final CustomPainter painter;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: CustomPaint(size: const Size(10, 10), painter: painter));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Paint getPaint(Color color, [bool isAntiAlias = false]) => Paint()
|
|
||||||
..color = color
|
|
||||||
..style = PaintingStyle.stroke
|
|
||||||
..isAntiAlias = isAntiAlias
|
|
||||||
..strokeWidth = 1;
|
|
||||||
|
|
||||||
typedef MouseStateBuilderCB = Widget Function(
|
|
||||||
BuildContext context, MouseState mouseState);
|
|
||||||
|
|
||||||
class MouseState {
|
|
||||||
bool isMouseOver = false;
|
|
||||||
bool isMouseDown = false;
|
|
||||||
MouseState();
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return "isMouseDown: $isMouseDown - isMouseOver: $isMouseOver";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
T? _ambiguate<T>(T? value) => value;
|
|
||||||
|
|
||||||
class MouseStateBuilder extends StatefulWidget {
|
|
||||||
final MouseStateBuilderCB builder;
|
|
||||||
final VoidCallback? onPressed;
|
|
||||||
const MouseStateBuilder({super.key, required this.builder, this.onPressed});
|
|
||||||
@override
|
|
||||||
// ignore: library_private_types_in_public_api
|
|
||||||
_MouseStateBuilderState createState() => _MouseStateBuilderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MouseStateBuilderState extends State<MouseStateBuilder> {
|
|
||||||
late MouseState _mouseState;
|
|
||||||
_MouseStateBuilderState() {
|
|
||||||
_mouseState = MouseState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return MouseRegion(
|
|
||||||
onEnter: (event) {
|
|
||||||
setState(() {
|
|
||||||
_mouseState.isMouseOver = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onExit: (event) {
|
|
||||||
setState(() {
|
|
||||||
_mouseState.isMouseOver = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: GestureDetector(
|
|
||||||
onTapDown: (_) {
|
|
||||||
setState(() {
|
|
||||||
_mouseState.isMouseDown = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onTapCancel: () {
|
|
||||||
setState(() {
|
|
||||||
_mouseState.isMouseDown = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_mouseState.isMouseDown = false;
|
|
||||||
_mouseState.isMouseOver = false;
|
|
||||||
});
|
|
||||||
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) {
|
|
||||||
if (widget.onPressed != null) {
|
|
||||||
widget.onPressed!();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onTapUp: (_) {},
|
|
||||||
child: widget.builder(context, _mouseState)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/library/user_local_tracks.dart';
|
import 'package:spotube/modules/library/user_local_tracks.dart';
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
|
import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
||||||
class SortTracksDropdown extends StatelessWidget {
|
class SortTracksDropdown extends StatelessWidget {
|
73
lib/components/titlebar/mouse_state.dart
Normal file
73
lib/components/titlebar/mouse_state.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
typedef MouseStateBuilderCB = Widget Function(
|
||||||
|
BuildContext context, MouseState mouseState);
|
||||||
|
|
||||||
|
class MouseState {
|
||||||
|
bool isMouseOver = false;
|
||||||
|
bool isMouseDown = false;
|
||||||
|
MouseState();
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "isMouseDown: $isMouseDown - isMouseOver: $isMouseOver";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T? _ambiguate<T>(T? value) => value;
|
||||||
|
|
||||||
|
class MouseStateBuilder extends StatefulWidget {
|
||||||
|
final MouseStateBuilderCB builder;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
const MouseStateBuilder({super.key, required this.builder, this.onPressed});
|
||||||
|
@override
|
||||||
|
// ignore: library_private_types_in_public_api
|
||||||
|
_MouseStateBuilderState createState() => _MouseStateBuilderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MouseStateBuilderState extends State<MouseStateBuilder> {
|
||||||
|
late MouseState _mouseState;
|
||||||
|
_MouseStateBuilderState() {
|
||||||
|
_mouseState = MouseState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (event) {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseOver = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onExit: (event) {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseOver = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onTapDown: (_) {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseDown = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTapCancel: () {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseDown = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseDown = false;
|
||||||
|
_mouseState.isMouseOver = false;
|
||||||
|
});
|
||||||
|
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) {
|
||||||
|
if (widget.onPressed != null) {
|
||||||
|
widget.onPressed!();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTapUp: (_) {},
|
||||||
|
child: widget.builder(context, _mouseState),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
179
lib/components/titlebar/titlebar.dart
Normal file
179
lib/components/titlebar/titlebar.dart
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/components/titlebar/titlebar_buttons.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
class PageWindowTitleBar extends StatefulHookConsumerWidget
|
||||||
|
implements PreferredSizeWidget {
|
||||||
|
final Widget? leading;
|
||||||
|
final bool automaticallyImplyLeading;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final Color? foregroundColor;
|
||||||
|
final IconThemeData? actionsIconTheme;
|
||||||
|
final bool? centerTitle;
|
||||||
|
final double? titleSpacing;
|
||||||
|
final double toolbarOpacity;
|
||||||
|
final double? leadingWidth;
|
||||||
|
final TextStyle? toolbarTextStyle;
|
||||||
|
final TextStyle? titleTextStyle;
|
||||||
|
final double? titleWidth;
|
||||||
|
final Widget? title;
|
||||||
|
|
||||||
|
final bool _sliver;
|
||||||
|
|
||||||
|
const PageWindowTitleBar({
|
||||||
|
super.key,
|
||||||
|
this.actions,
|
||||||
|
this.title,
|
||||||
|
this.toolbarOpacity = 1,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.actionsIconTheme,
|
||||||
|
this.automaticallyImplyLeading = false,
|
||||||
|
this.centerTitle,
|
||||||
|
this.foregroundColor,
|
||||||
|
this.leading,
|
||||||
|
this.leadingWidth,
|
||||||
|
this.titleSpacing,
|
||||||
|
this.titleTextStyle,
|
||||||
|
this.titleWidth,
|
||||||
|
this.toolbarTextStyle,
|
||||||
|
}) : _sliver = false,
|
||||||
|
pinned = false,
|
||||||
|
floating = false,
|
||||||
|
snap = false,
|
||||||
|
stretch = false;
|
||||||
|
|
||||||
|
final bool pinned;
|
||||||
|
final bool floating;
|
||||||
|
final bool snap;
|
||||||
|
final bool stretch;
|
||||||
|
|
||||||
|
const PageWindowTitleBar.sliver({
|
||||||
|
super.key,
|
||||||
|
this.actions,
|
||||||
|
this.title,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.actionsIconTheme,
|
||||||
|
this.automaticallyImplyLeading = false,
|
||||||
|
this.centerTitle,
|
||||||
|
this.foregroundColor,
|
||||||
|
this.leading,
|
||||||
|
this.leadingWidth,
|
||||||
|
this.titleSpacing,
|
||||||
|
this.titleTextStyle,
|
||||||
|
this.titleWidth,
|
||||||
|
this.toolbarTextStyle,
|
||||||
|
this.pinned = false,
|
||||||
|
this.floating = false,
|
||||||
|
this.snap = false,
|
||||||
|
this.stretch = false,
|
||||||
|
}) : _sliver = true,
|
||||||
|
toolbarOpacity = 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PageWindowTitleBar> createState() => _PageWindowTitleBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
|
||||||
|
void onDrag(details) {
|
||||||
|
final systemTitleBar =
|
||||||
|
ref.read(userPreferencesProvider.select((s) => s.systemTitleBar));
|
||||||
|
if (kIsDesktop && !systemTitleBar) {
|
||||||
|
windowManager.startDragging();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
|
if (widget._sliver) {
|
||||||
|
return SliverLayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final hasFullscreen =
|
||||||
|
mediaQuery.size.width == constraints.crossAxisExtent;
|
||||||
|
final hasLeadingOrCanPop =
|
||||||
|
widget.leading != null || Navigator.canPop(context);
|
||||||
|
|
||||||
|
return SliverPadding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
|
||||||
|
),
|
||||||
|
sliver: SliverAppBar(
|
||||||
|
leading: widget.leading,
|
||||||
|
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||||
|
actions: [
|
||||||
|
...?widget.actions,
|
||||||
|
WindowTitleBarButtons(foregroundColor: widget.foregroundColor),
|
||||||
|
],
|
||||||
|
backgroundColor: widget.backgroundColor,
|
||||||
|
foregroundColor: widget.foregroundColor,
|
||||||
|
actionsIconTheme: widget.actionsIconTheme,
|
||||||
|
centerTitle: widget.centerTitle,
|
||||||
|
titleSpacing: widget.titleSpacing,
|
||||||
|
leadingWidth: widget.leadingWidth,
|
||||||
|
toolbarTextStyle: widget.toolbarTextStyle,
|
||||||
|
titleTextStyle: widget.titleTextStyle,
|
||||||
|
title: SizedBox(
|
||||||
|
width: double.infinity, // workaround to force dragging
|
||||||
|
child: widget.title ?? const Text(""),
|
||||||
|
),
|
||||||
|
pinned: widget.pinned,
|
||||||
|
floating: widget.floating,
|
||||||
|
snap: widget.snap,
|
||||||
|
stretch: widget.stretch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(builder: (context, constrains) {
|
||||||
|
final hasFullscreen = mediaQuery.size.width == constrains.maxWidth;
|
||||||
|
final hasLeadingOrCanPop =
|
||||||
|
widget.leading != null || Navigator.canPop(context);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onHorizontalDragStart: onDrag,
|
||||||
|
onVerticalDragStart: onDrag,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
|
||||||
|
),
|
||||||
|
child: AppBar(
|
||||||
|
leading: widget.leading,
|
||||||
|
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||||
|
actions: [
|
||||||
|
...?widget.actions,
|
||||||
|
WindowTitleBarButtons(foregroundColor: widget.foregroundColor),
|
||||||
|
],
|
||||||
|
backgroundColor: widget.backgroundColor,
|
||||||
|
foregroundColor: widget.foregroundColor,
|
||||||
|
actionsIconTheme: widget.actionsIconTheme,
|
||||||
|
centerTitle: widget.centerTitle,
|
||||||
|
titleSpacing: widget.titleSpacing,
|
||||||
|
toolbarOpacity: widget.toolbarOpacity,
|
||||||
|
leadingWidth: widget.leadingWidth,
|
||||||
|
toolbarTextStyle: widget.toolbarTextStyle,
|
||||||
|
titleTextStyle: widget.titleTextStyle,
|
||||||
|
title: SizedBox(
|
||||||
|
width: double.infinity, // workaround to force dragging
|
||||||
|
child: widget.title ?? const Text(""),
|
||||||
|
),
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
forceMaterialTransparency: true,
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
124
lib/components/titlebar/titlebar_buttons.dart
Normal file
124
lib/components/titlebar/titlebar_buttons.dart
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/components/titlebar/titlebar_icon_buttons.dart';
|
||||||
|
import 'package:spotube/components/titlebar/window_button.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:titlebar_buttons/titlebar_buttons.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
class WindowTitleBarButtons extends HookConsumerWidget {
|
||||||
|
final Color? foregroundColor;
|
||||||
|
const WindowTitleBarButtons({
|
||||||
|
super.key,
|
||||||
|
this.foregroundColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
final isMaximized = useState<bool?>(null);
|
||||||
|
const type = ThemeType.auto;
|
||||||
|
|
||||||
|
Future<void> onClose() async {
|
||||||
|
await windowManager.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (kIsDesktop) {
|
||||||
|
windowManager.isMaximized().then((value) {
|
||||||
|
isMaximized.value = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!kIsDesktop || kIsMacOS || preferences.systemTitleBar) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kIsWindows) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final colors = WindowButtonColors(
|
||||||
|
normal: Colors.transparent,
|
||||||
|
iconNormal: foregroundColor ?? theme.colorScheme.onBackground,
|
||||||
|
mouseOver: theme.colorScheme.onBackground.withOpacity(0.1),
|
||||||
|
mouseDown: theme.colorScheme.onBackground.withOpacity(0.2),
|
||||||
|
iconMouseOver: theme.colorScheme.onBackground,
|
||||||
|
iconMouseDown: theme.colorScheme.onBackground,
|
||||||
|
);
|
||||||
|
|
||||||
|
final closeColors = WindowButtonColors(
|
||||||
|
normal: Colors.transparent,
|
||||||
|
iconNormal: foregroundColor ?? theme.colorScheme.onBackground,
|
||||||
|
mouseOver: Colors.red,
|
||||||
|
mouseDown: Colors.red[800]!,
|
||||||
|
iconMouseOver: Colors.white,
|
||||||
|
iconMouseDown: Colors.black,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 25),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MinimizeWindowButton(
|
||||||
|
onPressed: windowManager.minimize,
|
||||||
|
colors: colors,
|
||||||
|
),
|
||||||
|
if (isMaximized.value != true)
|
||||||
|
MaximizeWindowButton(
|
||||||
|
colors: colors,
|
||||||
|
onPressed: () {
|
||||||
|
windowManager.maximize();
|
||||||
|
isMaximized.value = true;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else
|
||||||
|
RestoreWindowButton(
|
||||||
|
colors: colors,
|
||||||
|
onPressed: () {
|
||||||
|
windowManager.unmaximize();
|
||||||
|
isMaximized.value = false;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CloseWindowButton(
|
||||||
|
colors: closeColors,
|
||||||
|
onPressed: onClose,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20, left: 10),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
DecoratedMinimizeButton(
|
||||||
|
type: type,
|
||||||
|
onPressed: windowManager.minimize,
|
||||||
|
),
|
||||||
|
DecoratedMaximizeButton(
|
||||||
|
type: type,
|
||||||
|
onPressed: () async {
|
||||||
|
if (await windowManager.isMaximized()) {
|
||||||
|
await windowManager.unmaximize();
|
||||||
|
isMaximized.value = false;
|
||||||
|
} else {
|
||||||
|
await windowManager.maximize();
|
||||||
|
isMaximized.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DecoratedCloseButton(
|
||||||
|
type: type,
|
||||||
|
onPressed: onClose,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
161
lib/components/titlebar/titlebar_icon_buttons.dart
Normal file
161
lib/components/titlebar/titlebar_icon_buttons.dart
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:spotube/components/titlebar/window_button.dart';
|
||||||
|
|
||||||
|
class MinimizeWindowButton extends WindowButton {
|
||||||
|
MinimizeWindowButton(
|
||||||
|
{super.key, super.colors, super.onPressed, bool? animate})
|
||||||
|
: super(
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
MinimizeIcon(color: buttonContext.iconColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MaximizeWindowButton extends WindowButton {
|
||||||
|
MaximizeWindowButton(
|
||||||
|
{super.key, super.colors, super.onPressed, bool? animate})
|
||||||
|
: super(
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
MaximizeIcon(color: buttonContext.iconColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestoreWindowButton extends WindowButton {
|
||||||
|
RestoreWindowButton({super.key, super.colors, super.onPressed, bool? animate})
|
||||||
|
: super(
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
RestoreIcon(color: buttonContext.iconColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _defaultCloseButtonColors = WindowButtonColors(
|
||||||
|
mouseOver: const Color(0xFFD32F2F),
|
||||||
|
mouseDown: const Color(0xFFB71C1C),
|
||||||
|
iconNormal: const Color(0xFF805306),
|
||||||
|
iconMouseOver: const Color(0xFFFFFFFF));
|
||||||
|
|
||||||
|
class CloseWindowButton extends WindowButton {
|
||||||
|
CloseWindowButton(
|
||||||
|
{super.key, WindowButtonColors? colors, super.onPressed, bool? animate})
|
||||||
|
: super(
|
||||||
|
colors: colors ?? _defaultCloseButtonColors,
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
CloseIcon(color: buttonContext.iconColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switched to CustomPaint icons by https://github.com/esDotDev
|
||||||
|
|
||||||
|
/// Close
|
||||||
|
class CloseIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const CloseIcon({super.key, required this.color});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Stack(children: [
|
||||||
|
// Use rotated containers instead of a painter because it renders slightly crisper than a painter for some reason.
|
||||||
|
Transform.rotate(
|
||||||
|
angle: pi * .25,
|
||||||
|
child:
|
||||||
|
Center(child: Container(width: 14, height: 1, color: color))),
|
||||||
|
Transform.rotate(
|
||||||
|
angle: pi * -.25,
|
||||||
|
child:
|
||||||
|
Center(child: Container(width: 14, height: 1, color: color))),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximize
|
||||||
|
class MaximizeIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const MaximizeIcon({super.key, required this.color});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MaximizePainter extends _IconPainter {
|
||||||
|
_MaximizePainter(super.color);
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
Paint p = getPaint(color);
|
||||||
|
canvas.drawRect(Rect.fromLTRB(0, 0, size.width - 1, size.height - 1), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore
|
||||||
|
class RestoreIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const RestoreIcon({
|
||||||
|
super.key,
|
||||||
|
required this.color,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestorePainter extends _IconPainter {
|
||||||
|
_RestorePainter(super.color);
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
Paint p = getPaint(color);
|
||||||
|
canvas.drawRect(Rect.fromLTRB(0, 2, size.width - 2, size.height), p);
|
||||||
|
canvas.drawLine(const Offset(2, 2), const Offset(2, 0), p);
|
||||||
|
canvas.drawLine(const Offset(2, 0), Offset(size.width, 0), p);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.width, 0), Offset(size.width, size.height - 2), p);
|
||||||
|
canvas.drawLine(Offset(size.width, size.height - 2),
|
||||||
|
Offset(size.width - 2, size.height - 2), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimize
|
||||||
|
class MinimizeIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const MinimizeIcon({super.key, required this.color});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MinimizePainter extends _IconPainter {
|
||||||
|
_MinimizePainter(super.color);
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
Paint p = getPaint(color);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(0, size.height / 2), Offset(size.width, size.height / 2), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helpers
|
||||||
|
abstract class _IconPainter extends CustomPainter {
|
||||||
|
_IconPainter(this.color);
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlignedPaint extends StatelessWidget {
|
||||||
|
const _AlignedPaint(this.painter);
|
||||||
|
final CustomPainter painter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: CustomPaint(size: const Size(10, 10), painter: painter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Paint getPaint(Color color, [bool isAntiAlias = false]) => Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..isAntiAlias = isAntiAlias
|
||||||
|
..strokeWidth = 1;
|
133
lib/components/titlebar/window_button.dart
Normal file
133
lib/components/titlebar/window_button.dart
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:spotube/components/titlebar/mouse_state.dart';
|
||||||
|
|
||||||
|
typedef WindowButtonIconBuilder = Widget Function(
|
||||||
|
WindowButtonContext buttonContext);
|
||||||
|
typedef WindowButtonBuilder = Widget Function(
|
||||||
|
WindowButtonContext buttonContext, Widget icon);
|
||||||
|
|
||||||
|
class WindowButtonContext {
|
||||||
|
BuildContext context;
|
||||||
|
MouseState mouseState;
|
||||||
|
Color? backgroundColor;
|
||||||
|
Color iconColor;
|
||||||
|
WindowButtonContext(
|
||||||
|
{required this.context,
|
||||||
|
required this.mouseState,
|
||||||
|
this.backgroundColor,
|
||||||
|
required this.iconColor});
|
||||||
|
}
|
||||||
|
|
||||||
|
class WindowButtonColors {
|
||||||
|
late Color normal;
|
||||||
|
late Color mouseOver;
|
||||||
|
late Color mouseDown;
|
||||||
|
late Color iconNormal;
|
||||||
|
late Color iconMouseOver;
|
||||||
|
late Color iconMouseDown;
|
||||||
|
WindowButtonColors(
|
||||||
|
{Color? normal,
|
||||||
|
Color? mouseOver,
|
||||||
|
Color? mouseDown,
|
||||||
|
Color? iconNormal,
|
||||||
|
Color? iconMouseOver,
|
||||||
|
Color? iconMouseDown}) {
|
||||||
|
this.normal = normal ?? _defaultButtonColors.normal;
|
||||||
|
this.mouseOver = mouseOver ?? _defaultButtonColors.mouseOver;
|
||||||
|
this.mouseDown = mouseDown ?? _defaultButtonColors.mouseDown;
|
||||||
|
this.iconNormal = iconNormal ?? _defaultButtonColors.iconNormal;
|
||||||
|
this.iconMouseOver = iconMouseOver ?? _defaultButtonColors.iconMouseOver;
|
||||||
|
this.iconMouseDown = iconMouseDown ?? _defaultButtonColors.iconMouseDown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final _defaultButtonColors = WindowButtonColors(
|
||||||
|
normal: Colors.transparent,
|
||||||
|
iconNormal: const Color(0xFF805306),
|
||||||
|
mouseOver: const Color(0xFF404040),
|
||||||
|
mouseDown: const Color(0xFF202020),
|
||||||
|
iconMouseOver: const Color(0xFFFFFFFF),
|
||||||
|
iconMouseDown: const Color(0xFFF0F0F0),
|
||||||
|
);
|
||||||
|
|
||||||
|
class WindowButton extends StatelessWidget {
|
||||||
|
final WindowButtonBuilder? builder;
|
||||||
|
final WindowButtonIconBuilder? iconBuilder;
|
||||||
|
late final WindowButtonColors colors;
|
||||||
|
final bool animate;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
WindowButton(
|
||||||
|
{super.key,
|
||||||
|
WindowButtonColors? colors,
|
||||||
|
this.builder,
|
||||||
|
@required this.iconBuilder,
|
||||||
|
this.padding,
|
||||||
|
this.onPressed,
|
||||||
|
this.animate = false}) {
|
||||||
|
this.colors = colors ?? _defaultButtonColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getBackgroundColor(MouseState mouseState) {
|
||||||
|
if (mouseState.isMouseDown) return colors.mouseDown;
|
||||||
|
if (mouseState.isMouseOver) return colors.mouseOver;
|
||||||
|
return colors.normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getIconColor(MouseState mouseState) {
|
||||||
|
if (mouseState.isMouseDown) return colors.iconMouseDown;
|
||||||
|
if (mouseState.isMouseOver) return colors.iconMouseOver;
|
||||||
|
return colors.iconNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return Container();
|
||||||
|
} else {
|
||||||
|
// Don't show button on macOS
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MouseStateBuilder(
|
||||||
|
builder: (context, mouseState) {
|
||||||
|
WindowButtonContext buttonContext = WindowButtonContext(
|
||||||
|
mouseState: mouseState,
|
||||||
|
context: context,
|
||||||
|
backgroundColor: getBackgroundColor(mouseState),
|
||||||
|
iconColor: getIconColor(mouseState));
|
||||||
|
|
||||||
|
var icon =
|
||||||
|
(iconBuilder != null) ? iconBuilder!(buttonContext) : Container();
|
||||||
|
|
||||||
|
var fadeOutColor =
|
||||||
|
getBackgroundColor(MouseState()..isMouseOver = true).withOpacity(0);
|
||||||
|
var padding = this.padding ?? const EdgeInsets.all(10);
|
||||||
|
var animationMs =
|
||||||
|
mouseState.isMouseOver ? (animate ? 100 : 0) : (animate ? 200 : 0);
|
||||||
|
Widget iconWithPadding = Padding(padding: padding, child: icon);
|
||||||
|
iconWithPadding = AnimatedContainer(
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
duration: Duration(milliseconds: animationMs),
|
||||||
|
color: buttonContext.backgroundColor ?? fadeOutColor,
|
||||||
|
child: iconWithPadding);
|
||||||
|
var button =
|
||||||
|
(builder != null) ? builder!(buttonContext, icon) : iconWithPadding;
|
||||||
|
return SizedBox(
|
||||||
|
width: 45,
|
||||||
|
height: 32,
|
||||||
|
child: button,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onPressed: () {
|
||||||
|
if (onPressed != null) onPressed!();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,13 +8,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
|
import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/playlist_add_track_dialog.dart';
|
import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/prompt_dialog.dart';
|
import 'package:spotube/components/dialogs/prompt_dialog.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/track_details_dialog.dart';
|
import 'package:spotube/components/dialogs/track_details_dialog.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/heart_button/use_track_toggle_like.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/components/shared/links/artist_link.dart';
|
import 'package:spotube/components/links/artist_link.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
@ -7,11 +7,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/hover_builder.dart';
|
import 'package:spotube/components/hover_builder.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/components/shared/links/artist_link.dart';
|
import 'package:spotube/components/links/artist_link.dart';
|
||||||
import 'package:spotube/components/shared/links/link_text.dart';
|
import 'package:spotube/components/links/link_text.dart';
|
||||||
import 'package:spotube/components/shared/track_tile/track_options.dart';
|
import 'package:spotube/components/track_tile/track_options.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/duration.dart';
|
import 'package:spotube/extensions/duration.dart';
|
@ -8,13 +8,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
import 'package:spotube/components/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
import 'package:spotube/components/expandable_search/expandable_search.dart';
|
||||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
import 'package:spotube/components/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body_headers.dart';
|
import 'package:spotube/components/tracks_view/sections/body/track_view_body_headers.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_playlist.dart';
|
import 'package:spotube/components/tracks_view/sections/body/use_is_user_playlist.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_provider.dart';
|
import 'package:spotube/components/tracks_view/track_view_provider.dart';
|
||||||
import 'package:spotube/models/connect/connect.dart';
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
import 'package:spotube/provider/connect/connect.dart';
|
import 'package:spotube/provider/connect/connect.dart';
|
||||||
import 'package:spotube/provider/history/history.dart';
|
import 'package:spotube/provider/history/history.dart';
|
@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
import 'package:spotube/components/expandable_search/expandable_search.dart';
|
||||||
import 'package:spotube/components/shared/sort_tracks_dropdown.dart';
|
import 'package:spotube/components/sort_tracks_dropdown.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_options.dart';
|
import 'package:spotube/components/tracks_view/sections/body/track_view_options.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_provider.dart';
|
import 'package:spotube/components/tracks_view/track_view_provider.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
|
@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
|
import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/confirm_download_dialog.dart';
|
import 'package:spotube/components/dialogs/confirm_download_dialog.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/playlist_add_track_dialog.dart';
|
import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_provider.dart';
|
import 'package:spotube/components/tracks_view/track_view_provider.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.dart';
|
||||||
import 'package:spotube/provider/history/history.dart';
|
import 'package:spotube/provider/history/history.dart';
|
@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/components/shared/playbutton_card.dart';
|
import 'package:spotube/components/playbutton_card.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/header/header_actions.dart';
|
import 'package:spotube/components/tracks_view/sections/header/header_actions.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/header/header_buttons.dart';
|
import 'package:spotube/components/tracks_view/sections/header/header_buttons.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/hooks/utils/use_palette_color.dart';
|
import 'package:spotube/hooks/utils/use_palette_color.dart';
|
@ -4,10 +4,10 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
|
import 'package:spotube/modules/playlist/playlist_create_dialog.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/heart_button/heart_button.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_playlist.dart';
|
import 'package:spotube/components/tracks_view/sections/body/use_is_user_playlist.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/history/history.dart';
|
import 'package:spotube/provider/history/history.dart';
|
@ -7,8 +7,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
import 'package:spotube/components/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/models/connect/connect.dart';
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
import 'package:spotube/provider/connect/connect.dart';
|
import 'package:spotube/provider/connect/connect.dart';
|
@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:sliver_tools/sliver_tools.dart';
|
import 'package:sliver_tools/sliver_tools.dart';
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/header/flexible_header.dart';
|
import 'package:spotube/components/tracks_view/sections/header/flexible_header.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body.dart';
|
import 'package:spotube/components/tracks_view/sections/body/track_view_body.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class TrackView extends HookConsumerWidget {
|
class TrackView extends HookConsumerWidget {
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/library/user_local_tracks.dart';
|
import 'package:spotube/modules/library/user_local_tracks.dart';
|
||||||
|
|
||||||
class TrackViewNotifier extends ChangeNotifier {
|
class TrackViewNotifier extends ChangeNotifier {
|
||||||
List<Track> tracks;
|
List<Track> tracks;
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:catcher_2/catcher_2.dart';
|
import 'package:spotube/services/logger/logger.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
@ -62,7 +62,7 @@ void useEndlessPlayback(WidgetRef ref) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Catcher2.reportCheckedError(e, stack);
|
AppLogger.reportError(e, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
|
|
||||||
final _paletteColorState = StateProvider<PaletteColor>(
|
final _paletteColorState = StateProvider<PaletteColor>(
|
||||||
(ref) {
|
(ref) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:catcher_2/catcher_2.dart';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
|
import 'package:dart_discord_rpc/dart_discord_rpc.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -17,19 +18,19 @@ import 'package:spotube/hooks/configurators/use_close_behavior.dart';
|
|||||||
import 'package:spotube/hooks/configurators/use_deep_linking.dart';
|
import 'package:spotube/hooks/configurators/use_deep_linking.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart';
|
import 'package:spotube/hooks/configurators/use_disable_battery_optimizations.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_get_storage_perms.dart';
|
import 'package:spotube/hooks/configurators/use_get_storage_perms.dart';
|
||||||
|
import 'package:spotube/provider/server/bonsoir.dart';
|
||||||
|
import 'package:spotube/provider/server/server.dart';
|
||||||
import 'package:spotube/provider/tray_manager/tray_manager.dart';
|
import 'package:spotube/provider/tray_manager/tray_manager.dart';
|
||||||
import 'package:spotube/l10n/l10n.dart';
|
import 'package:spotube/l10n/l10n.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
|
||||||
import 'package:spotube/models/skip_segment.dart';
|
import 'package:spotube/models/skip_segment.dart';
|
||||||
import 'package:spotube/models/source_match.dart';
|
import 'package:spotube/models/source_match.dart';
|
||||||
import 'package:spotube/provider/connect/clients.dart';
|
import 'package:spotube/provider/connect/clients.dart';
|
||||||
import 'package:spotube/provider/connect/server.dart';
|
|
||||||
import 'package:spotube/provider/palette_provider.dart';
|
import 'package:spotube/provider/palette_provider.dart';
|
||||||
import 'package:spotube/provider/server/server.dart';
|
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
import 'package:spotube/services/cli/cli.dart';
|
import 'package:spotube/services/cli/cli.dart';
|
||||||
import 'package:spotube/services/kv_store/kv_store.dart';
|
import 'package:spotube/services/kv_store/kv_store.dart';
|
||||||
|
import 'package:spotube/services/logger/logger.dart';
|
||||||
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
import 'package:spotube/services/wm_tools/wm_tools.dart';
|
||||||
import 'package:spotube/themes/theme.dart';
|
import 'package:spotube/themes/theme.dart';
|
||||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||||
@ -44,7 +45,9 @@ import 'package:window_manager/window_manager.dart';
|
|||||||
|
|
||||||
Future<void> main(List<String> rawArgs) async {
|
Future<void> main(List<String> rawArgs) async {
|
||||||
final arguments = await startCLI(rawArgs);
|
final arguments = await startCLI(rawArgs);
|
||||||
|
AppLogger.initialize(arguments["verbose"]);
|
||||||
|
|
||||||
|
AppLogger.runZoned(() async {
|
||||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
await registerWindowsScheme("spotify");
|
await registerWindowsScheme("spotify");
|
||||||
@ -107,35 +110,8 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
await WindowManagerTools.initialize();
|
await WindowManagerTools.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Catcher2(
|
runApp(const ProviderScope(child: Spotube()));
|
||||||
enableLogger: arguments["verbose"],
|
});
|
||||||
debugConfig: Catcher2Options(
|
|
||||||
SilentReportMode(),
|
|
||||||
[
|
|
||||||
ConsoleHandler(
|
|
||||||
enableDeviceParameters: false,
|
|
||||||
enableApplicationParameters: false,
|
|
||||||
),
|
|
||||||
if (!kIsWeb) FileHandler(await getLogsPath(), printLogs: false),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
releaseConfig: Catcher2Options(
|
|
||||||
SilentReportMode(),
|
|
||||||
[
|
|
||||||
if (arguments["verbose"] ?? false) ConsoleHandler(),
|
|
||||||
if (!kIsWeb)
|
|
||||||
FileHandler(
|
|
||||||
await getLogsPath(),
|
|
||||||
printLogs: false,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
runAppFunction: () {
|
|
||||||
runApp(
|
|
||||||
const ProviderScope(child: Spotube()),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Spotube extends HookConsumerWidget {
|
class Spotube extends HookConsumerWidget {
|
||||||
@ -154,8 +130,8 @@ class Spotube extends HookConsumerWidget {
|
|||||||
ref.watch(paletteProvider.select((s) => s?.dominantColor?.color));
|
ref.watch(paletteProvider.select((s) => s?.dominantColor?.color));
|
||||||
final router = ref.watch(routerProvider);
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
ref.listen(playbackServerProvider, (_, __) {});
|
ref.listen(serverProvider, (_, __) {});
|
||||||
ref.listen(connectServerProvider, (_, __) {});
|
ref.listen(bonsoirProvider, (_, __) {});
|
||||||
ref.listen(connectClientsProvider, (_, __) {});
|
ref.listen(connectClientsProvider, (_, __) {});
|
||||||
ref.listen(trayManagerProvider, (_, __) {});
|
ref.listen(trayManagerProvider, (_, __) {});
|
||||||
|
|
||||||
@ -166,6 +142,7 @@ class Spotube extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
FlutterNativeSplash.remove();
|
FlutterNativeSplash.remove();
|
||||||
|
|
||||||
return () {
|
return () {
|
||||||
/// For enabling hot reload for audio player
|
/// For enabling hot reload for audio player
|
||||||
if (!kDebugMode) return;
|
if (!kDebugMode) return;
|
||||||
|
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
import 'package:spotube/components/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/playbutton_card.dart';
|
import 'package:spotube/components/playbutton_card.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
@ -4,7 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart' hide Page;
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/pages/home/feed/feed_section.dart';
|
import 'package:spotube/pages/home/feed/feed_section.dart';
|
||||||
import 'package:spotube/provider/spotify/views/home.dart';
|
import 'package:spotube/provider/spotify/views/home.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
@ -5,7 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/components/home/sections/friends/friend_item.dart';
|
import 'package:spotube/modules/home/sections/friends/friend_item.dart';
|
||||||
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
import 'package:spotube/hooks/utils/use_breakpoint_value.dart';
|
||||||
import 'package:spotube/models/spotify_friends.dart';
|
import 'package:spotube/models/spotify_friends.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
@ -4,7 +4,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/models/spotify_friends.dart';
|
import 'package:spotube/models/spotify_friends.dart';
|
||||||
import 'package:spotube/pages/album/album.dart';
|
import 'package:spotube/pages/album/album.dart';
|
||||||
import 'package:spotube/pages/artist/artist.dart';
|
import 'package:spotube/pages/artist/artist.dart';
|
@ -10,7 +10,7 @@ import 'package:spotify/spotify.dart';
|
|||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/collections/gradients.dart';
|
import 'package:spotube/collections/gradients.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/pages/home/genres/genre_playlists.dart';
|
import 'package:spotube/pages/home/genres/genre_playlists.dart';
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
class HomeMadeForUserSection extends HookConsumerWidget {
|
class HomeMadeForUserSection extends HookConsumerWidget {
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart' hide Page;
|
import 'package:flutter/material.dart' hide Page;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||||
import 'package:spotube/provider/history/recent.dart';
|
import 'package:spotube/provider/history/recent.dart';
|
||||||
import 'package:spotube/provider/history/state.dart';
|
import 'package:spotube/provider/history/state.dart';
|
||||||
|
|
@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:spotube/components/library/playlist_generate/recommendation_attribute_dials.dart';
|
import 'package:spotube/modules/library/playlist_generate/recommendation_attribute_dials.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
@ -3,7 +3,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
|
|
||||||
class SimpleTrackTile extends HookWidget {
|
class SimpleTrackTile extends HookWidget {
|
@ -8,10 +8,10 @@ import 'package:skeletonizer/skeletonizer.dart';
|
|||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/album/album_card.dart';
|
import 'package:spotube/modules/album/album_card.dart';
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/waypoint.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
@ -8,10 +8,10 @@ import 'package:skeletonizer/skeletonizer.dart';
|
|||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/artist/artist_card.dart';
|
import 'package:spotube/modules/artist/artist_card.dart';
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/waypoint.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
@ -2,7 +2,7 @@ import 'package:auto_size_text/auto_size_text.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotube/components/library/user_downloads/download_item.dart';
|
import 'package:spotube/modules/library/user_downloads/download_item.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.dart';
|
||||||
|
|
@ -3,8 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/components/shared/links/artist_link.dart';
|
import 'package:spotube/components/links/artist_link.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.dart';
|
@ -6,7 +6,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/library/local_folder/local_folder_item.dart';
|
import 'package:spotube/modules/library/local_folder/local_folder_item.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
|
import 'package:spotube/provider/local_tracks/local_tracks_provider.dart';
|
@ -9,11 +9,11 @@ import 'package:skeletonizer/skeletonizer.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
|
import 'package:spotube/modules/playlist/playlist_create_dialog.dart';
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
import 'package:spotube/modules/playlist/playlist_card.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/waypoint.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart';
|
@ -6,16 +6,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/player/player_actions.dart';
|
import 'package:spotube/modules/player/player_actions.dart';
|
||||||
import 'package:spotube/components/player/player_controls.dart';
|
import 'package:spotube/modules/player/player_controls.dart';
|
||||||
import 'package:spotube/components/player/player_queue.dart';
|
import 'package:spotube/modules/player/player_queue.dart';
|
||||||
import 'package:spotube/components/player/volume_slider.dart';
|
import 'package:spotube/modules/player/volume_slider.dart';
|
||||||
import 'package:spotube/components/shared/animated_gradient.dart';
|
import 'package:spotube/components/animated_gradient.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/track_details_dialog.dart';
|
import 'package:spotube/components/dialogs/track_details_dialog.dart';
|
||||||
import 'package:spotube/components/shared/links/artist_link.dart';
|
import 'package:spotube/components/links/artist_link.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/titlebar/titlebar.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/components/shared/panels/sliding_up_panel.dart';
|
import 'package:spotube/components/panels/sliding_up_panel.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
@ -4,9 +4,9 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/player/sibling_tracks_sheet.dart';
|
import 'package:spotube/modules/player/sibling_tracks_sheet.dart';
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
|
import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/heart_button/heart_button.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/duration.dart';
|
import 'package:spotube/extensions/duration.dart';
|
@ -8,7 +8,7 @@ import 'package:spotube/collections/spotube_icons.dart';
|
|||||||
import 'package:spotube/collections/intents.dart';
|
import 'package:spotube/collections/intents.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/duration.dart';
|
import 'package:spotube/extensions/duration.dart';
|
||||||
import 'package:spotube/components/player/use_progress.dart';
|
import 'package:spotube/modules/player/use_progress.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
import 'package:spotube/models/logger.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
@ -4,13 +4,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
import 'package:spotube/components/player/player_track_details.dart';
|
import 'package:spotube/modules/player/player_track_details.dart';
|
||||||
import 'package:spotube/components/root/spotube_navigation_bar.dart';
|
import 'package:spotube/modules/root/spotube_navigation_bar.dart';
|
||||||
import 'package:spotube/components/shared/panels/sliding_up_panel.dart';
|
import 'package:spotube/components/panels/sliding_up_panel.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/collections/intents.dart';
|
import 'package:spotube/collections/intents.dart';
|
||||||
import 'package:spotube/components/player/use_progress.dart';
|
import 'package:spotube/modules/player/use_progress.dart';
|
||||||
import 'package:spotube/components/player/player.dart';
|
import 'package:spotube/modules/player/player.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
|
@ -11,9 +11,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/not_found.dart';
|
import 'package:spotube/components/fallbacks/not_found.dart';
|
||||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
import 'package:spotube/components/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
@ -3,9 +3,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
|
||||||
import 'package:spotube/collections/assets.gen.dart';
|
import 'package:spotube/collections/assets.gen.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/image/universal_image.dart';
|
||||||
import 'package:spotube/components/shared/links/artist_link.dart';
|
import 'package:spotube/components/links/artist_link.dart';
|
||||||
import 'package:spotube/components/shared/links/link_text.dart';
|
import 'package:spotube/components/links/link_text.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user