Perfction on new Playlist & Album View

TrackCollectionView shows ShimmerTrackTile (loading) seperately
This commit is contained in:
Kingkor Roy Tirtho 2022-06-15 17:51:37 +06:00
parent 71d6fc5a4a
commit 41102b3bb8
17 changed files with 270 additions and 294 deletions

View File

@ -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(

View File

@ -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(

View File

@ -44,7 +44,7 @@ class ShimmerArtistProfile extends HookWidget {
),
),
const SizedBox(width: 10),
const Flexible(child: ShimmerTrackTile()),
const Flexible(child: ShimmerTrackTile(noSliver: true)),
],
);
}

View File

@ -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,15 +18,7 @@ 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(
final single = Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
@ -78,7 +75,20 @@ class ShimmerTrackTile extends StatelessWidget {
],
),
);
},
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,
),
);
}

View File

@ -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
),
useCustomStatusBarColor(
paletteColor.color,
GoRouter.of(context).location == "/player",
);
return;
}, [paletteColor.color]);
useEffect(() {
return () {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
statusBarColor: backgroundColor, // status bar color
statusBarIconBrightness: backgroundColor.computeLuminance() > 0.179
? Brightness.dark
: Brightness.light,
),
);
};
}, []);
return SafeArea(
child: Scaffold(

View File

@ -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(

View File

@ -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,

View File

@ -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();

View File

@ -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,20 +68,23 @@ 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: Container(
color: backgroundColor,
child: Row(
children: [
if (leading != null) leading!,
Expanded(child: Center(child: center)),
],
),
),
);
}
return WindowTitleBarBox(
@ -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)
],
),

View File

@ -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,25 +110,16 @@ 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
: null,
appBar: (kIsDesktop)
? PageWindowTitleBar(
backgroundColor: color?.color,
foregroundColor: color?.titleTextColor,
leading: Row(
children: [
BackButton(
color: tracksSnapshot.asData?.value != null
? color?.titleTextColor
: null,
children: [BackButton(color: color?.titleTextColor)],
),
)
],
),
),
body: tracksSnapshot.when(
data: (tracks) {
return CustomScrollView(
: null,
body: CustomScrollView(
controller: controller,
slivers: [
SliverAppBar(
@ -125,19 +127,19 @@ class TrackCollectionView extends HookConsumerWidget {
floating: false,
pinned: true,
expandedHeight: 400,
automaticallyImplyLeading: false,
automaticallyImplyLeading: kIsMobile,
iconTheme: IconThemeData(color: color?.titleTextColor),
primary: true,
backgroundColor: color?.color,
title: collapsed.value
? Text(
title,
style:
Theme.of(context).textTheme.headline4?.copyWith(
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(
@ -218,7 +220,9 @@ class TrackCollectionView extends HookConsumerWidget {
);
}),
),
TracksTableView(
tracksSnapshot.when(
data: (tracks) {
return TracksTableView(
tracks is! List<Track>
? tracks
.map((track) => simpleTrackToTrack(track, album!))
@ -227,14 +231,14 @@ class TrackCollectionView extends HookConsumerWidget {
onTrackPlayButtonPressed: onPlay,
playlistId: id,
userPlaylist: isOwned,
),
],
);
},
error: (error, _) => Text("Error $error"),
error: (error, _) =>
SliverToBoxAdapter(child: Text("Error $error")),
loading: () => const ShimmerTrackTile(),
),
),
],
)),
);
}
}

View File

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

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

View File

@ -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),

View File

@ -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();

View File

@ -67,8 +67,8 @@ GoRouter createGoRouter() => GoRouter(
GoRoute(
path: "/player",
pageBuilder: (context, state) {
return SpotubePage(
child: const PlayerView(),
return const SpotubePage(
child: PlayerView(),
);
},
)

View File

@ -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
View File

@ -0,0 +1,5 @@
import 'dart:io';
final kIsDesktop = Platform.isLinux || Platform.isWindows || Platform.isMacOS;
final kIsMobile = Platform.isAndroid || Platform.isIOS;