redesign app phase 1
This commit is contained in:
+200
-94
@@ -21,8 +21,9 @@ import '../components/PlayerScreen/playback_mode.dart';
|
||||
import '../components/PlayerScreen/add_to_playlist_button.dart';
|
||||
import '../components/PlayerScreen/sleep_timer_button.dart';
|
||||
|
||||
final _albumImageProvider =
|
||||
StateProvider.autoDispose<ImageProvider?>((_) => null);
|
||||
final _albumImageProvider = StateProvider.autoDispose<ImageProvider?>(
|
||||
(_) => null,
|
||||
);
|
||||
|
||||
class PlayerScreen extends StatelessWidget {
|
||||
const PlayerScreen({Key? key}) : super(key: key);
|
||||
@@ -58,10 +59,7 @@ class PlayerScreen extends StatelessWidget {
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
actions: const [
|
||||
SleepTimerButton(),
|
||||
AddToPlaylistButton(),
|
||||
],
|
||||
actions: const [SleepTimerButton(), AddToPlaylistButton()],
|
||||
),
|
||||
// Required for sleep timer input
|
||||
resizeToAvoidBottomInset: false,
|
||||
@@ -70,48 +68,42 @@ class PlayerScreen extends StatelessWidget {
|
||||
children: [
|
||||
if (FinampSettingsHelper.finampSettings.showCoverAsPlayerBackground)
|
||||
const _BlurredPlayerScreenBackground(),
|
||||
const SafeArea(
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _PlayerScreenAlbumImage(),
|
||||
SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isWide = constraints.maxWidth >= 720;
|
||||
final horizontalPadding = isWide ? 40.0 : 20.0;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
horizontalPadding,
|
||||
16,
|
||||
horizontalPadding,
|
||||
24,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SongName(),
|
||||
ProgressSlider(),
|
||||
PlayerButtons(),
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: PlaybackMode(),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: _PlayerScreenFavoriteButton(),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: QueueButton(),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: isWide
|
||||
? Row(
|
||||
children: const [
|
||||
Expanded(flex: 5, child: _PlayerArtworkSurface()),
|
||||
SizedBox(width: 32),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: _PlayerControlsSurface(),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const Column(
|
||||
children: [
|
||||
Expanded(flex: 6, child: _PlayerArtworkSurface()),
|
||||
SizedBox(height: 18),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: _PlayerControlsSurface(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -121,6 +113,114 @@ class PlayerScreen extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _PlayerArtworkSurface extends StatelessWidget {
|
||||
const _PlayerArtworkSurface({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 460),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(36),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.shadow.withOpacity(0.24),
|
||||
blurRadius: 36,
|
||||
offset: const Offset(0, 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(36),
|
||||
child: ColoredBox(
|
||||
color: colorScheme.surfaceVariant.withOpacity(0.34),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(28)),
|
||||
child: _PlayerScreenAlbumImage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlayerControlsSurface extends StatelessWidget {
|
||||
const _PlayerControlsSurface({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colorScheme = theme.colorScheme;
|
||||
final surfaceColor = Color.alphaBlend(
|
||||
colorScheme.primary.withOpacity(0.07),
|
||||
colorScheme.surface,
|
||||
);
|
||||
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: surfaceColor,
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withOpacity(0.45),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.shadow.withOpacity(0.12),
|
||||
blurRadius: 28,
|
||||
offset: const Offset(0, 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.fromLTRB(22, 24, 22, 18),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SongName(),
|
||||
SizedBox(height: 24),
|
||||
ProgressSlider(),
|
||||
SizedBox(height: 18),
|
||||
PlayerButtons(),
|
||||
SizedBox(height: 18),
|
||||
_PlayerSecondaryActions(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlayerSecondaryActions extends StatelessWidget {
|
||||
const _PlayerSecondaryActions({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Row(
|
||||
children: [
|
||||
Expanded(child: PlaybackMode()),
|
||||
_PlayerScreenFavoriteButton(),
|
||||
SizedBox(width: 8),
|
||||
QueueButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// This widget is just an AlbumImage in a StreamBuilder to get the song id.
|
||||
class _PlayerScreenAlbumImage extends ConsumerWidget {
|
||||
const _PlayerScreenAlbumImage({Key? key}) : super(key: key);
|
||||
@@ -130,41 +230,45 @@ class _PlayerScreenAlbumImage extends ConsumerWidget {
|
||||
final audioHandler = GetIt.instance<MusicPlayerBackgroundTask>();
|
||||
|
||||
return StreamBuilder<MediaItem?>(
|
||||
stream: audioHandler.mediaItem,
|
||||
builder: (context, snapshot) {
|
||||
final item = snapshot.data?.extras?["itemJson"] == null
|
||||
? null
|
||||
: BaseItemDto.fromJson(snapshot.data!.extras!["itemJson"]);
|
||||
stream: audioHandler.mediaItem,
|
||||
builder: (context, snapshot) {
|
||||
final item = snapshot.data?.extras?["itemJson"] == null
|
||||
? null
|
||||
: BaseItemDto.fromJson(snapshot.data!.extras!["itemJson"]);
|
||||
|
||||
return item == null
|
||||
? AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: AlbumImage.borderRadius,
|
||||
child: Container(color: Theme.of(context).cardColor),
|
||||
),
|
||||
)
|
||||
: AlbumImage(
|
||||
item: item,
|
||||
imageProviderCallback: (imageProvider) =>
|
||||
// We need a post frame callback because otherwise this
|
||||
// widget rebuilds on the same frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => ref
|
||||
.read(_albumImageProvider.notifier)
|
||||
.state = imageProvider),
|
||||
// Here we awkwardly get the next 3 queue items so that we
|
||||
// can precache them (so that the image is already loaded
|
||||
// when the next song comes on).
|
||||
itemsToPrecache: audioHandler.queue.value
|
||||
.sublist(min(
|
||||
(audioHandler.playbackState.value.queueIndex ?? 0) +
|
||||
1,
|
||||
audioHandler.queue.value.length))
|
||||
.take(3)
|
||||
.map((e) => BaseItemDto.fromJson(e.extras!["itemJson"]))
|
||||
.toList(),
|
||||
);
|
||||
});
|
||||
return item == null
|
||||
? AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: ClipRRect(
|
||||
borderRadius: AlbumImage.borderRadius,
|
||||
child: Container(color: Theme.of(context).cardColor),
|
||||
),
|
||||
)
|
||||
: AlbumImage(
|
||||
item: item,
|
||||
imageProviderCallback: (imageProvider) =>
|
||||
// We need a post frame callback because otherwise this
|
||||
// widget rebuilds on the same frame
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => ref.read(_albumImageProvider.notifier).state =
|
||||
imageProvider,
|
||||
),
|
||||
// Here we awkwardly get the next 3 queue items so that we
|
||||
// can precache them (so that the image is already loaded
|
||||
// when the next song comes on).
|
||||
itemsToPrecache: audioHandler.queue.value
|
||||
.sublist(
|
||||
min(
|
||||
(audioHandler.playbackState.value.queueIndex ?? 0) + 1,
|
||||
audioHandler.queue.value.length,
|
||||
),
|
||||
)
|
||||
.take(3)
|
||||
.map((e) => BaseItemDto.fromJson(e.extras!["itemJson"]))
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,10 +291,11 @@ class _BlurredPlayerScreenBackground extends ConsumerWidget {
|
||||
errorBuilder: (_, __, ___) => const SizedBox.shrink(),
|
||||
imageBuilder: (context, child) => ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.black.withOpacity(0.35)
|
||||
: Colors.white.withOpacity(0.75),
|
||||
BlendMode.srcOver),
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.black.withOpacity(0.35)
|
||||
: Colors.white.withOpacity(0.75),
|
||||
BlendMode.srcOver,
|
||||
),
|
||||
child: ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(
|
||||
sigmaX: 85,
|
||||
@@ -213,14 +318,15 @@ class _PlayerScreenFavoriteButton extends StatelessWidget {
|
||||
final audioHandler = GetIt.instance<MusicPlayerBackgroundTask>();
|
||||
|
||||
return StreamBuilder<MediaItem?>(
|
||||
stream: audioHandler.mediaItem,
|
||||
builder: (context, snapshot) {
|
||||
return FavoriteButton(
|
||||
item: snapshot.data?.extras?["itemJson"] == null
|
||||
? null
|
||||
: BaseItemDto.fromJson(snapshot.data!.extras!["itemJson"]),
|
||||
inPlayer: true,
|
||||
);
|
||||
});
|
||||
stream: audioHandler.mediaItem,
|
||||
builder: (context, snapshot) {
|
||||
return FavoriteButton(
|
||||
item: snapshot.data?.extras?["itemJson"] == null
|
||||
? null
|
||||
: BaseItemDto.fromJson(snapshot.data!.extras!["itemJson"]),
|
||||
inPlayer: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user