mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: make control page adaptive
This commit is contained in:
parent
22cc210f30
commit
7e887d54ed
@ -283,12 +283,17 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
trackSnapshot.isLoading ? 5 : filteredTracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (trackSnapshot.isLoading) {
|
||||
return TrackTile(track: FakeData.track, index: index);
|
||||
return TrackTile(
|
||||
playlist: playlist,
|
||||
track: FakeData.track,
|
||||
index: index,
|
||||
);
|
||||
}
|
||||
|
||||
final track = filteredTracks[index];
|
||||
return TrackTile(
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
track: track,
|
||||
userPlaylist: false,
|
||||
onTap: () async {
|
||||
@ -311,8 +316,11 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
enabled: true,
|
||||
child: ListView.builder(
|
||||
itemCount: 5,
|
||||
itemBuilder: (context, index) =>
|
||||
TrackTile(track: FakeData.track, index: index),
|
||||
itemBuilder: (context, index) => TrackTile(
|
||||
track: FakeData.track,
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -66,7 +66,6 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
topRight: Radius.circular(10),
|
||||
);
|
||||
final theme = Theme.of(context);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final headlineColor = theme.textTheme.headlineSmall?.color;
|
||||
|
||||
final filteredTracks = useMemoized(
|
||||
@ -105,6 +104,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
return const NotFound(vertical: true);
|
||||
}
|
||||
|
||||
return LayoutBuilder(builder: (context, constrains) {
|
||||
return ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
@ -147,7 +147,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (mediaQuery.mdAndUp || !isSearching.value) ...[
|
||||
if (constrains.mdAndUp || !isSearching.value) ...[
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
context.l10n.tracks_in_queue(tracks.length),
|
||||
@ -159,7 +159,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
if (mediaQuery.mdAndUp || isSearching.value)
|
||||
if (constrains.mdAndUp || isSearching.value)
|
||||
TextField(
|
||||
onChanged: (value) {
|
||||
searchText.value = value;
|
||||
@ -167,7 +167,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.search,
|
||||
isDense: true,
|
||||
prefixIcon: mediaQuery.smAndDown
|
||||
prefixIcon: constrains.smAndDown
|
||||
? IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_outlined,
|
||||
@ -184,8 +184,8 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
: const Icon(SpotubeIcons.filter),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 40,
|
||||
maxWidth: mediaQuery.smAndDown
|
||||
? mediaQuery.size.width - 40
|
||||
maxWidth: constrains.smAndDown
|
||||
? constrains.maxWidth - 40
|
||||
: 300,
|
||||
),
|
||||
),
|
||||
@ -197,13 +197,14 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
isSearching.value = !isSearching.value;
|
||||
},
|
||||
),
|
||||
if (mediaQuery.mdAndUp || !isSearching.value) ...[
|
||||
if (constrains.mdAndUp || !isSearching.value) ...[
|
||||
const SizedBox(width: 10),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.scaffoldBackgroundColor.withOpacity(0.5),
|
||||
foregroundColor: theme.textTheme.headlineSmall?.color,
|
||||
foregroundColor:
|
||||
theme.textTheme.headlineSmall?.color,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
@ -250,6 +251,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
child: TrackTile(
|
||||
index: i,
|
||||
track: track,
|
||||
playlist: playlist,
|
||||
onTap: () async {
|
||||
if (playlist.activeTrack?.id == track.id) {
|
||||
return;
|
||||
@ -282,6 +284,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: TrackTile(
|
||||
index: i,
|
||||
playlist: playlist,
|
||||
track: track,
|
||||
onTap: () async {
|
||||
if (playlist.activeTrack?.id == track.id) {
|
||||
@ -301,5 +304,6 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ import 'package:spotube/extensions/duration.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/provider/blacklist_provider.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart';
|
||||
|
||||
class TrackTile extends HookConsumerWidget {
|
||||
/// [index] will not be shown if null
|
||||
@ -30,6 +30,7 @@ class TrackTile extends HookConsumerWidget {
|
||||
final VoidCallback? onLongPress;
|
||||
final bool userPlaylist;
|
||||
final String? playlistId;
|
||||
final ProxyPlaylist playlist;
|
||||
|
||||
final List<Widget>? leadingActions;
|
||||
|
||||
@ -38,6 +39,7 @@ class TrackTile extends HookConsumerWidget {
|
||||
this.index,
|
||||
required this.track,
|
||||
this.selected = false,
|
||||
required this.playlist,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onChanged,
|
||||
@ -48,7 +50,6 @@ class TrackTile extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final blacklist = ref.watch(BlackListNotifier.provider);
|
||||
@ -65,10 +66,10 @@ class TrackTile extends HookConsumerWidget {
|
||||
|
||||
final showOptionCbRef = useRef<ValueChanged<RelativeRect>?>(null);
|
||||
|
||||
final isPlaying = track.id == playlist.activeTrack?.id;
|
||||
|
||||
final isLoading = useState(false);
|
||||
|
||||
final isPlaying = playlist.activeTrack?.id == track.id;
|
||||
|
||||
final isSelected = isPlaying || isLoading.value;
|
||||
|
||||
return LayoutBuilder(builder: (context, constrains) {
|
||||
|
||||
@ -89,6 +89,7 @@ class TrackViewBodySection extends HookConsumerWidget {
|
||||
loadingBuilder: (context) => Skeletonizer(
|
||||
enabled: true,
|
||||
child: TrackTile(
|
||||
playlist: playlist,
|
||||
track: FakeData.track,
|
||||
index: 0,
|
||||
),
|
||||
@ -98,13 +99,18 @@ class TrackViewBodySection extends HookConsumerWidget {
|
||||
child: Column(
|
||||
children: List.generate(
|
||||
10,
|
||||
(index) => TrackTile(track: FakeData.track, index: index),
|
||||
(index) => TrackTile(
|
||||
track: FakeData.track,
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final track = tracks[index];
|
||||
return TrackTile(
|
||||
playlist: playlist,
|
||||
track: track,
|
||||
index: index,
|
||||
selected: trackViewState.selectedTrackIds.contains(track.id!),
|
||||
|
||||
@ -107,6 +107,7 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
||||
final track = topTracks.elementAt(index);
|
||||
return TrackTile(
|
||||
index: index,
|
||||
playlist: playlist,
|
||||
track: track,
|
||||
onTap: () async {
|
||||
playPlaylist(
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
@ -9,6 +8,7 @@ import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
import 'package:spotube/components/shared/links/anchor_button.dart';
|
||||
import 'package:spotube/components/shared/links/artist_link.dart';
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/duration.dart';
|
||||
import 'package:spotube/extensions/image.dart';
|
||||
@ -49,26 +49,59 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
minimumSize: const Size(28, 28),
|
||||
);
|
||||
|
||||
final playerQueue = Consumer(builder: (context, ref, _) {
|
||||
final playlist = ref.watch(queueProvider);
|
||||
return PlayerQueue(
|
||||
playlist: playlist,
|
||||
floating: true,
|
||||
onJump: (track) async {
|
||||
final index = playlist.tracks.toList().indexOf(track);
|
||||
connectNotifier.jumpTo(index);
|
||||
},
|
||||
onRemove: (track) async {
|
||||
await connectNotifier.removeTrack(track);
|
||||
},
|
||||
onStop: () async => connectNotifier.stop(),
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
await connectNotifier.reorder(
|
||||
(oldIndex: oldIndex, newIndex: newIndex),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
ref.listen(connectClientsProvider, (prev, next) {
|
||||
if (next.asData?.value.resolvedService == null) {
|
||||
context.pop();
|
||||
}
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: PageWindowTitleBar(
|
||||
title: Text(resolvedService!.name),
|
||||
automaticallyImplyLeading: true,
|
||||
),
|
||||
body: CustomScrollView(
|
||||
body: LayoutBuilder(builder: (context, constrains) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 10,
|
||||
).copyWith(top: 0),
|
||||
constraints:
|
||||
const BoxConstraints(maxHeight: 400, maxWidth: 400),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: UniversalImage(
|
||||
path: (playlist.activeTrack?.album?.images).asUrlString(
|
||||
path: (playlist.activeTrack?.album?.images)
|
||||
.asUrlString(
|
||||
placeholder: ImagePlaceholder.albumArt,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
@ -76,7 +109,6 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverGap(10),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
sliver: SliverMainAxisGroup(
|
||||
@ -126,7 +158,8 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(position.toHumanReadableString()),
|
||||
Text(duration.toHumanReadableString()),
|
||||
@ -174,7 +207,9 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
playing ? SpotubeIcons.pause : SpotubeIcons.play,
|
||||
playing
|
||||
? SpotubeIcons.pause
|
||||
: SpotubeIcons.play,
|
||||
),
|
||||
style: resumePauseStyle,
|
||||
onPressed: playlist.activeTrack == null
|
||||
@ -214,9 +249,12 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
: () async {
|
||||
connectNotifier.setLoopMode(
|
||||
switch (loopMode) {
|
||||
PlaybackLoopMode.all => PlaybackLoopMode.one,
|
||||
PlaybackLoopMode.one => PlaybackLoopMode.none,
|
||||
PlaybackLoopMode.none => PlaybackLoopMode.all,
|
||||
PlaybackLoopMode.all =>
|
||||
PlaybackLoopMode.one,
|
||||
PlaybackLoopMode.one =>
|
||||
PlaybackLoopMode.none,
|
||||
PlaybackLoopMode.none =>
|
||||
PlaybackLoopMode.all,
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -225,6 +263,7 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
const SliverGap(30),
|
||||
if (constrains.mdAndDown)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
sliver: SliverToBoxAdapter(
|
||||
@ -235,27 +274,7 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Consumer(builder: (context, ref, _) {
|
||||
final playlist = ref.watch(queueProvider);
|
||||
return PlayerQueue(
|
||||
playlist: playlist,
|
||||
floating: true,
|
||||
onJump: (track) async {
|
||||
final index =
|
||||
playlist.tracks.toList().indexOf(track);
|
||||
connectNotifier.jumpTo(index);
|
||||
},
|
||||
onRemove: (track) async {
|
||||
await connectNotifier.removeTrack(track);
|
||||
},
|
||||
onStop: () async => connectNotifier.stop(),
|
||||
onReorder: (oldIndex, newIndex) async {
|
||||
await connectNotifier.reorder(
|
||||
(oldIndex: oldIndex, newIndex: newIndex),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
return playerQueue;
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -264,6 +283,17 @@ class ConnectControlPage extends HookConsumerWidget {
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (constrains.lgAndUp) ...[
|
||||
const VerticalDivider(thickness: 1),
|
||||
Expanded(
|
||||
child: playerQueue,
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,7 +53,8 @@ class HomePage extends HookConsumerWidget {
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
const Gap(10),
|
||||
],
|
||||
),
|
||||
const HomeGenresSection(),
|
||||
|
||||
@ -46,6 +46,7 @@ class SearchTracksSection extends HookConsumerWidget {
|
||||
return TrackTile(
|
||||
index: i,
|
||||
track: track,
|
||||
playlist: playlist,
|
||||
onTap: () async {
|
||||
final isTrackPlaying = playlist.activeTrack?.id == track.id;
|
||||
if (!isTrackPlaying && context.mounted) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user