mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
Perfction on new Playlist & Album View
TrackCollectionView shows ShimmerTrackTile (loading) seperately
This commit is contained in:
parent
71d6fc5a4a
commit
41102b3bb8
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/Shared/HeartButton.dart';
|
||||
@ -59,6 +60,7 @@ class AlbumView extends HookConsumerWidget {
|
||||
titleImage: albumArt,
|
||||
tracksSnapshot: tracksSnapshot,
|
||||
album: album,
|
||||
routePath: "/album/${album.id}",
|
||||
onPlay: ([track]) {
|
||||
if (tracksSnapshot.asData?.value != null) {
|
||||
playPlaylist(
|
||||
|
@ -23,6 +23,7 @@ import 'package:spotube/hooks/usePaginatedFutureProvider.dart';
|
||||
import 'package:spotube/hooks/useUpdateChecker.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:spotube/provider/SpotifyRequests.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
List<String> spotifyScopes = [
|
||||
"playlist-modify-public",
|
||||
@ -73,7 +74,7 @@ class Home extends HookConsumerWidget {
|
||||
child: MoveWindow(),
|
||||
),
|
||||
Expanded(child: MoveWindow()),
|
||||
if (!Platform.isMacOS && !Platform.isAndroid && !Platform.isIOS)
|
||||
if (!Platform.isMacOS && !kIsMobile)
|
||||
const TitleBarActionButtons(),
|
||||
],
|
||||
))
|
||||
@ -98,7 +99,7 @@ class Home extends HookConsumerWidget {
|
||||
child: Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
Platform.isAndroid || Platform.isIOS
|
||||
kIsMobile
|
||||
? titleBarContents
|
||||
: WindowTitleBarBox(child: titleBarContents),
|
||||
Expanded(
|
||||
|
@ -44,7 +44,7 @@ class ShimmerArtistProfile extends HookWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Flexible(child: ShimmerTrackTile()),
|
||||
const Flexible(child: ShimmerTrackTile(noSliver: true)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,12 @@ import 'package:skeleton_text/skeleton_text.dart';
|
||||
import 'package:spotube/extensions/ShimmerColorTheme.dart';
|
||||
|
||||
class ShimmerTrackTile extends StatelessWidget {
|
||||
const ShimmerTrackTile({Key? key}) : super(key: key);
|
||||
final bool noSliver;
|
||||
|
||||
const ShimmerTrackTile({
|
||||
Key? key,
|
||||
this.noSliver = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -13,25 +18,35 @@ class ShimmerTrackTile extends StatelessWidget {
|
||||
.extension<ShimmerColorTheme>()!
|
||||
.shimmerBackgroundColor!;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 30),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 5,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
final single = Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
children: [
|
||||
SkeletonAnimation(
|
||||
shimmerColor: shimmerColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
shimmerDuration: 1000,
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SkeletonAnimation(
|
||||
shimmerColor: shimmerColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
shimmerDuration: 1000,
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
@ -39,46 +54,41 @@ class ShimmerTrackTile extends StatelessWidget {
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SkeletonAnimation(
|
||||
shimmerColor: shimmerColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
shimmerDuration: 1000,
|
||||
child: Container(
|
||||
height: 15,
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
),
|
||||
),
|
||||
SkeletonAnimation(
|
||||
shimmerColor: shimmerColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
shimmerDuration: 1000,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .8),
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
),
|
||||
),
|
||||
],
|
||||
SkeletonAnimation(
|
||||
shimmerColor: shimmerColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
shimmerDuration: 1000,
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * .8),
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: shimmerBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
margin: const EdgeInsets.only(top: 10),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (noSliver) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: 5,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, _) => single,
|
||||
);
|
||||
}
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) => single,
|
||||
childCount: 5,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -12,6 +11,7 @@ import 'package:spotube/components/Shared/SpotubeMarqueeText.dart';
|
||||
import 'package:spotube/helpers/artists-to-clickable-artists.dart';
|
||||
import 'package:spotube/helpers/image-to-url-string.dart';
|
||||
import 'package:spotube/hooks/useBreakpoints.dart';
|
||||
import 'package:spotube/hooks/useCustomStatusBarColor.dart';
|
||||
import 'package:spotube/hooks/usePaletteColor.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
|
||||
@ -46,29 +46,10 @@ class PlayerView extends HookConsumerWidget {
|
||||
|
||||
final PaletteColor paletteColor = usePaletteColor(context, albumArt, ref);
|
||||
|
||||
final backgroundColor = Theme.of(context).backgroundColor;
|
||||
|
||||
useEffect(() {
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: paletteColor.color, // status bar color
|
||||
),
|
||||
);
|
||||
return;
|
||||
}, [paletteColor.color]);
|
||||
|
||||
useEffect(() {
|
||||
return () {
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: backgroundColor, // status bar color
|
||||
statusBarIconBrightness: backgroundColor.computeLuminance() > 0.179
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
useCustomStatusBarColor(
|
||||
paletteColor.color,
|
||||
GoRouter.of(context).location == "/player",
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/components/Shared/HeartButton.dart';
|
||||
import 'package:spotube/components/Shared/TrackCollectionView.dart';
|
||||
@ -80,6 +81,7 @@ class PlaylistView extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
showShare: playlist.id != "user-liked-tracks",
|
||||
routePath: "/playlist/${playlist.id}",
|
||||
onShare: () {
|
||||
final data = "https://open.spotify.com/playlist/${playlist.id}";
|
||||
Clipboard.setData(
|
||||
|
@ -13,6 +13,7 @@ import 'package:spotube/models/SpotifyMarkets.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class Settings extends HookConsumerWidget {
|
||||
@ -56,7 +57,7 @@ class Settings extends HookConsumerWidget {
|
||||
constraints: const BoxConstraints(maxWidth: 1366),
|
||||
child: ListView(
|
||||
children: [
|
||||
if (!Platform.isAndroid && !Platform.isIOS) ...[
|
||||
if (!kIsMobile) ...[
|
||||
SettingsHotKeyTile(
|
||||
title: "Next track global shortcut",
|
||||
currentHotKey: preferences.nextTrackHotKey,
|
||||
|
@ -9,6 +9,7 @@ import 'package:spotube/helpers/getLyrics.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||
import 'package:path/path.dart' as path;
|
||||
@ -30,7 +31,7 @@ class DownloadTrackButton extends HookConsumerWidget {
|
||||
|
||||
final _downloadTrack = useCallback(() async {
|
||||
if (track == null) return;
|
||||
if ((Platform.isAndroid || Platform.isIOS) &&
|
||||
if ((kIsMobile) &&
|
||||
!await Permission.storage.isGranted &&
|
||||
!await Permission.storage.isPermanentlyDenied) {
|
||||
final status = await Permission.storage.request();
|
||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class TitleBarActionButtons extends StatelessWidget {
|
||||
final Color? color;
|
||||
@ -67,19 +68,22 @@ class PageWindowTitleBar extends StatelessWidget
|
||||
}) : super(key: key);
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(
|
||||
!Platform.isIOS && !Platform.isAndroid ? appWindow.titleBarHeight : 35,
|
||||
(kIsDesktop ? appWindow.titleBarHeight : 35),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
if (kIsMobile) {
|
||||
return PreferredSize(
|
||||
preferredSize: const Size.fromHeight(300),
|
||||
child: Row(
|
||||
children: [
|
||||
if (leading != null) leading!,
|
||||
Expanded(child: Center(child: center)),
|
||||
],
|
||||
child: Container(
|
||||
color: backgroundColor,
|
||||
child: Row(
|
||||
children: [
|
||||
if (leading != null) leading!,
|
||||
Expanded(child: Center(child: center)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -94,7 +98,7 @@ class PageWindowTitleBar extends StatelessWidget
|
||||
),
|
||||
if (leading != null) leading!,
|
||||
Expanded(child: MoveWindow(child: Center(child: center))),
|
||||
if (!Platform.isMacOS && !Platform.isIOS && !Platform.isAndroid)
|
||||
if (!Platform.isMacOS && !kIsMobile)
|
||||
TitleBarActionButtons(color: foregroundColor)
|
||||
],
|
||||
),
|
||||
|
@ -1,14 +1,17 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/components/LoaderShimmers/ShimmerTrackTile.dart';
|
||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||
import 'package:spotube/components/Shared/TracksTableView.dart';
|
||||
import 'package:spotube/helpers/simple-track-to-track.dart';
|
||||
import 'package:spotube/hooks/useCustomStatusBarColor.dart';
|
||||
import 'package:spotube/hooks/usePaletteColor.dart';
|
||||
import 'package:spotube/models/Logger.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
class TrackCollectionView extends HookConsumerWidget {
|
||||
final logger = getLogger(TrackCollectionView);
|
||||
@ -25,6 +28,8 @@ class TrackCollectionView extends HookConsumerWidget {
|
||||
|
||||
final bool showShare;
|
||||
final bool isOwned;
|
||||
|
||||
final String routePath;
|
||||
TrackCollectionView({
|
||||
required this.title,
|
||||
required this.id,
|
||||
@ -33,6 +38,7 @@ class TrackCollectionView extends HookConsumerWidget {
|
||||
required this.isPlaying,
|
||||
required this.onPlay,
|
||||
required this.onShare,
|
||||
required this.routePath,
|
||||
this.heartBtn,
|
||||
this.album,
|
||||
this.description,
|
||||
@ -83,6 +89,11 @@ class TrackCollectionView extends HookConsumerWidget {
|
||||
|
||||
final collapsed = useState(false);
|
||||
|
||||
useCustomStatusBarColor(
|
||||
color?.color ?? Theme.of(context).backgroundColor,
|
||||
GoRouter.of(context).location == routePath,
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
listener() {
|
||||
if (controller.position.pixels >= 400 && !collapsed.value) {
|
||||
@ -99,142 +110,135 @@ class TrackCollectionView extends HookConsumerWidget {
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PageWindowTitleBar(
|
||||
backgroundColor:
|
||||
tracksSnapshot.asData?.value != null ? color?.color : null,
|
||||
foregroundColor: tracksSnapshot.asData?.value != null
|
||||
? color?.titleTextColor
|
||||
appBar: (kIsDesktop)
|
||||
? PageWindowTitleBar(
|
||||
backgroundColor: color?.color,
|
||||
foregroundColor: color?.titleTextColor,
|
||||
leading: Row(
|
||||
children: [BackButton(color: color?.titleTextColor)],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
leading: Row(
|
||||
children: [
|
||||
BackButton(
|
||||
color: tracksSnapshot.asData?.value != null
|
||||
? color?.titleTextColor
|
||||
: null,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
body: tracksSnapshot.when(
|
||||
data: (tracks) {
|
||||
return CustomScrollView(
|
||||
controller: controller,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
actions: collapsed.value ? buttons : null,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
expandedHeight: 400,
|
||||
automaticallyImplyLeading: false,
|
||||
primary: true,
|
||||
title: collapsed.value
|
||||
? Text(
|
||||
title,
|
||||
style:
|
||||
Theme.of(context).textTheme.headline4?.copyWith(
|
||||
color: color?.titleTextColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
backgroundColor: color?.color.withOpacity(0.8),
|
||||
flexibleSpace: LayoutBuilder(builder: (context, constrains) {
|
||||
return FlexibleSpaceBar(
|
||||
background: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
color?.color ?? Colors.transparent,
|
||||
Theme.of(context).canvasColor,
|
||||
],
|
||||
begin: const FractionalOffset(0, 0),
|
||||
end: const FractionalOffset(0, 1),
|
||||
tileMode: TileMode.clamp,
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
body: CustomScrollView(
|
||||
controller: controller,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
actions: collapsed.value ? buttons : null,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
expandedHeight: 400,
|
||||
automaticallyImplyLeading: kIsMobile,
|
||||
iconTheme: IconThemeData(color: color?.titleTextColor),
|
||||
primary: true,
|
||||
backgroundColor: color?.color,
|
||||
title: collapsed.value
|
||||
? Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headline4?.copyWith(
|
||||
color: color?.titleTextColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 20,
|
||||
runSpacing: 20,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
alignment: WrapAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
constraints:
|
||||
const BoxConstraints(maxHeight: 200),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: titleImage,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
flexibleSpace: LayoutBuilder(builder: (context, constrains) {
|
||||
return FlexibleSpaceBar(
|
||||
background: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
color?.color ?? Colors.transparent,
|
||||
Theme.of(context).canvasColor,
|
||||
],
|
||||
begin: const FractionalOffset(0, 0),
|
||||
end: const FractionalOffset(0, 1),
|
||||
tileMode: TileMode.clamp,
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 20,
|
||||
runSpacing: 20,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
alignment: WrapAlignment.center,
|
||||
runAlignment: WrapAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
constraints:
|
||||
const BoxConstraints(maxHeight: 200),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: titleImage,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline4
|
||||
?.copyWith(
|
||||
color: color?.titleTextColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (description != null)
|
||||
Text(
|
||||
title,
|
||||
description!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headline4
|
||||
.bodyLarge
|
||||
?.copyWith(
|
||||
color: color?.titleTextColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color?.bodyTextColor,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
if (description != null)
|
||||
Text(
|
||||
description!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(
|
||||
color: color?.bodyTextColor,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: buttons,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: buttons,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
TracksTableView(
|
||||
tracks is! List<Track>
|
||||
? tracks
|
||||
.map((track) => simpleTrackToTrack(track, album!))
|
||||
.toList()
|
||||
: tracks,
|
||||
onTrackPlayButtonPressed: onPlay,
|
||||
playlistId: id,
|
||||
userPlaylist: isOwned,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (error, _) => Text("Error $error"),
|
||||
loading: () => const ShimmerTrackTile(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
tracksSnapshot.when(
|
||||
data: (tracks) {
|
||||
return TracksTableView(
|
||||
tracks is! List<Track>
|
||||
? tracks
|
||||
.map((track) => simpleTrackToTrack(track, album!))
|
||||
.toList()
|
||||
: tracks,
|
||||
onTrackPlayButtonPressed: onPlay,
|
||||
playlistId: id,
|
||||
userPlaylist: isOwned,
|
||||
);
|
||||
},
|
||||
error: (error, _) =>
|
||||
SliverToBoxAdapter(child: Text("Error $error")),
|
||||
loading: () => const ShimmerTrackTile(),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -97,77 +97,5 @@ class TracksTableView extends HookConsumerWidget {
|
||||
}).toList()
|
||||
]),
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Scrollbar(
|
||||
child: ListView(
|
||||
children: [
|
||||
if (heading != null) heading!,
|
||||
Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"#",
|
||||
textAlign: TextAlign.center,
|
||||
style: tableHeadStyle,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Title",
|
||||
style: tableHeadStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// used alignment of this table-head
|
||||
if (breakpoint.isMoreThan(Breakpoints.md)) ...[
|
||||
const SizedBox(width: 100),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Album",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: tableHeadStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
if (!breakpoint.isSm) ...[
|
||||
const SizedBox(width: 10),
|
||||
Text("Time", style: tableHeadStyle),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
const SizedBox(width: 40),
|
||||
],
|
||||
),
|
||||
...tracks.asMap().entries.map((track) {
|
||||
String? thumbnailUrl = imageToUrlString(
|
||||
track.value.album?.images,
|
||||
index: (track.value.album?.images?.length ?? 1) - 1,
|
||||
);
|
||||
String duration =
|
||||
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
||||
return TrackTile(
|
||||
playback,
|
||||
playlistId: playlistId,
|
||||
track: track,
|
||||
duration: duration,
|
||||
thumbnailUrl: thumbnailUrl,
|
||||
userPlaylist: userPlaylist,
|
||||
onTrackPlayButtonPressed: onTrackPlayButtonPressed,
|
||||
);
|
||||
}).toList()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
38
lib/hooks/useCustomStatusBarColor.dart
Normal file
38
lib/hooks/useCustomStatusBarColor.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
void useCustomStatusBarColor(Color color, bool isCurrentRoute) {
|
||||
final context = useContext();
|
||||
final backgroundColor = Theme.of(context).backgroundColor;
|
||||
resetStatusbar() => SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: backgroundColor, // status bar color
|
||||
statusBarIconBrightness: backgroundColor.computeLuminance() > 0.179
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
);
|
||||
|
||||
final statusBarColor = SystemChrome.latestStyle?.statusBarColor;
|
||||
|
||||
useEffect(() {
|
||||
if (isCurrentRoute && statusBarColor != color) {
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
SystemUiOverlayStyle(
|
||||
statusBarColor: color, // status bar color
|
||||
statusBarIconBrightness: color.computeLuminance() > 0.179
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
);
|
||||
} else if (!isCurrentRoute && statusBarColor == color) {
|
||||
resetStatusbar();
|
||||
}
|
||||
return;
|
||||
}, [color, isCurrentRoute, statusBarColor]);
|
||||
|
||||
useEffect(() {
|
||||
return resetStatusbar;
|
||||
}, []);
|
||||
}
|
@ -7,6 +7,7 @@ import 'package:spotube/hooks/playback.dart';
|
||||
import 'package:spotube/models/GlobalKeyActions.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
useHotKeys(WidgetRef ref) {
|
||||
final playback = ref.watch(playbackProvider);
|
||||
@ -20,7 +21,7 @@ useHotKeys(WidgetRef ref) {
|
||||
final _playOrPause = useTogglePlayPause(playback);
|
||||
|
||||
useEffect(() {
|
||||
if (Platform.isIOS || Platform.isAndroid) return null;
|
||||
if (kIsMobile) return null;
|
||||
_hotKeys = [
|
||||
GlobalKeyActions(
|
||||
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
||||
|
@ -16,13 +16,9 @@ import 'package:spotube/provider/YouTube.dart';
|
||||
import 'package:spotube/themes/dark-theme.dart';
|
||||
import 'package:spotube/themes/light-theme.dart';
|
||||
import 'package:spotube/utils/AudioPlayerHandler.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
|
||||
void main() async {
|
||||
// await JustAudioBackground.init(
|
||||
// androidNotificationChannelId: 'oss.krtirtho.Spotube',
|
||||
// androidNotificationChannelName: 'Spotube',
|
||||
// androidNotificationOngoing: true,
|
||||
// );
|
||||
AudioPlayerHandler audioPlayerHandler = await AudioService.init(
|
||||
builder: () => AudioPlayerHandler(),
|
||||
config: const AudioServiceConfig(
|
||||
@ -31,12 +27,11 @@ void main() async {
|
||||
androidNotificationOngoing: true,
|
||||
),
|
||||
);
|
||||
if (!Platform.isAndroid && !Platform.isIOS) {
|
||||
if (kIsDesktop) {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await hotKeyManager.unregisterAll();
|
||||
doWhenWindowReady(() {
|
||||
appWindow.minSize =
|
||||
Size(Platform.isAndroid || Platform.isIOS ? 280 : 359, 700);
|
||||
appWindow.minSize = const Size(359, 700);
|
||||
appWindow.alignment = Alignment.center;
|
||||
appWindow.title = "Spotube";
|
||||
appWindow.maximize();
|
||||
@ -75,6 +70,7 @@ class Spotube extends HookConsumerWidget {
|
||||
.watch(userPreferencesProvider.select((s) => s.backgroundColorScheme));
|
||||
final player = ref.watch(audioPlayerProvider);
|
||||
final youtube = ref.watch(youtubeProvider);
|
||||
|
||||
useEffect(() {
|
||||
return () {
|
||||
player.dispose();
|
||||
|
@ -67,8 +67,8 @@ GoRouter createGoRouter() => GoRouter(
|
||||
GoRoute(
|
||||
path: "/player",
|
||||
pageBuilder: (context, state) {
|
||||
return SpotubePage(
|
||||
child: const PlayerView(),
|
||||
return const SpotubePage(
|
||||
child: PlayerView(),
|
||||
);
|
||||
},
|
||||
)
|
||||
|
@ -72,7 +72,9 @@ class Auth extends PersistedChangeNotifier {
|
||||
_clientSecret = map["clientSecret"];
|
||||
_accessToken = map["accessToken"];
|
||||
_refreshToken = map["refreshToken"];
|
||||
_expiration = DateTime.tryParse(map["expiration"]);
|
||||
_expiration = map["expiration"] != null
|
||||
? DateTime.tryParse(map["expiration"])
|
||||
: _expiration;
|
||||
}
|
||||
|
||||
@override
|
||||
|
5
lib/utils/platform.dart
Normal file
5
lib/utils/platform.dart
Normal file
@ -0,0 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
final kIsDesktop = Platform.isLinux || Platform.isWindows || Platform.isMacOS;
|
||||
|
||||
final kIsMobile = Platform.isAndroid || Platform.isIOS;
|
Loading…
Reference in New Issue
Block a user