Skip to content

Commit

Permalink
feat: new sidebar widget and translucent bottom player
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Mar 10, 2023
1 parent a90261e commit 4ba1e70
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 95 deletions.
146 changes: 81 additions & 65 deletions lib/components/root/bottom_player.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:ui';

import 'package:flutter/gestures.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
Expand All @@ -8,6 +10,7 @@ import 'package:spotube/components/player/player_overlay.dart';
import 'package:spotube/components/player/player_track_details.dart';
import 'package:spotube/components/player/player_controls.dart';
import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/hooks/use_brightness_value.dart';
import 'package:spotube/models/logger.dart';
import 'package:flutter/material.dart';
import 'package:spotube/provider/playlist_queue_provider.dart';
Expand Down Expand Up @@ -37,6 +40,13 @@ class BottomPlayer extends HookConsumerWidget {
[playlist?.activeTrack.album?.images],
);

final bg = Theme.of(context).colorScheme.surfaceVariant;

final bgColor = useBrightnessValue(
Color.lerp(bg, Colors.white, 0.7),
Color.lerp(bg, Colors.black, 0.45)!,
);

// returning an empty non spacious Container as the overlay will take
// place in the global overlay stack aka [_entries]
if (layoutMode == LayoutMode.compact ||
Expand All @@ -45,74 +55,80 @@ class BottomPlayer extends HookConsumerWidget {
return PlayerOverlay(albumArt: albumArt);
}

return DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
),
child: Material(
type: MaterialType.transparency,
textStyle: Theme.of(context).textTheme.bodyMedium!,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(child: PlayerTrackDetails(albumArt: albumArt)),
// controls
Flexible(
flex: 3,
child: PlayerControls(),
),
// add to saved tracks
Expanded(
flex: 1,
child: Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
children: [
Container(
height: 20,
constraints: const BoxConstraints(maxWidth: 200),
child: HookBuilder(builder: (context) {
final volumeState = ref.watch(VolumeProvider.provider);
final volumeNotifier =
ref.watch(VolumeProvider.provider.notifier);
final volume = useState(volumeState);

useEffect(() {
if (volume.value != volumeState) {
volume.value = volumeState;
}
return null;
}, [volumeState]);
return ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
child: DecoratedBox(
decoration: BoxDecoration(color: bgColor?.withOpacity(0.8)),
child: Material(
type: MaterialType.transparency,
textStyle: Theme.of(context).textTheme.bodyMedium!,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(child: PlayerTrackDetails(albumArt: albumArt)),
// controls
Flexible(
flex: 3,
child: PlayerControls(),
),
// add to saved tracks
Expanded(
flex: 1,
child: Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
children: [
Container(
height: 20,
constraints: const BoxConstraints(maxWidth: 200),
child: HookBuilder(builder: (context) {
final volumeState =
ref.watch(VolumeProvider.provider);
final volumeNotifier =
ref.watch(VolumeProvider.provider.notifier);
final volume = useState(volumeState);

return Listener(
onPointerSignal: (event) async {
if (event is PointerScrollEvent) {
if (event.scrollDelta.dy > 0) {
final value = volume.value - .2;
volumeNotifier.setVolume(value < 0 ? 0 : value);
} else {
final value = volume.value + .2;
volumeNotifier.setVolume(value > 1 ? 1 : value);
useEffect(() {
if (volume.value != volumeState) {
volume.value = volumeState;
}
}
},
child: Slider(
min: 0,
max: 1,
value: volume.value,
onChanged: (v) {
volume.value = v;
},
onChangeEnd: volumeNotifier.setVolume,
),
);
}),
return null;
}, [volumeState]);

return Listener(
onPointerSignal: (event) async {
if (event is PointerScrollEvent) {
if (event.scrollDelta.dy > 0) {
final value = volume.value - .2;
volumeNotifier
.setVolume(value < 0 ? 0 : value);
} else {
final value = volume.value + .2;
volumeNotifier
.setVolume(value > 1 ? 1 : value);
}
}
},
child: Slider(
min: 0,
max: 1,
value: volume.value,
onChanged: (v) {
volume.value = v;
},
onChangeEnd: volumeNotifier.setVolume,
),
);
}),
),
PlayerActions()
],
),
PlayerActions()
],
),
)
],
)
],
),
),
),
),
);
Expand Down
111 changes: 81 additions & 30 deletions lib/components/root/sidebar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter/material.dart';
import 'package:sidebarx/sidebarx.dart';

import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/side_bar_tiles.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/hooks/use_brightness_value.dart';
import 'package:spotube/hooks/use_sidebarx_controller.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/downloader_provider.dart';

Expand Down Expand Up @@ -55,48 +58,96 @@ class Sidebar extends HookConsumerWidget {
final layoutMode =
ref.watch(userPreferencesProvider.select((s) => s.layoutMode));

final controller = useSidebarXController(
selectedIndex: selectedIndex,
extended: breakpoints > Breakpoints.md,
);

final bg = Theme.of(context).colorScheme.surfaceVariant;

final bgColor = useBrightnessValue(
Color.lerp(bg, Colors.white, 0.7),
Color.lerp(bg, Colors.black, 0.45)!,
);

useEffect(() {
controller.addListener(() {
onSelectedIndexChanged(controller.selectedIndex);
});
return null;
}, [controller]);

useEffect(() {
if (breakpoints > Breakpoints.md && !controller.extended) {
controller.setExtended(true);
} else if (breakpoints <= Breakpoints.md && controller.extended) {
controller.setExtended(false);
}
return null;
}, [breakpoints, controller]);

if (layoutMode == LayoutMode.compact ||
(breakpoints.isSm && layoutMode == LayoutMode.adaptive)) {
return Scaffold(body: child);
}

return Row(
children: [
NavigationRail(
selectedIndex: selectedIndex,
onDestinationSelected: onSelectedIndexChanged,
destinations: sidebarTileList.map(
SidebarX(
controller: controller,
items: sidebarTileList.map(
(e) {
return NavigationRailDestination(
icon: Badge(
backgroundColor: Theme.of(context).primaryColor,
isLabelVisible: e.title == "Library" && downloadCount > 0,
label: Text(
downloadCount.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 10,
),
),
child: Icon(e.icon),
),
label: Text(
e.title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
return SidebarXItem(
// iconWidget: Badge(
// backgroundColor: Theme.of(context).primaryColor,
// isLabelVisible: e.title == "Library" && downloadCount > 0,
// label: Text(
// downloadCount.toString(),
// style: const TextStyle(
// color: Colors.white,
// fontSize: 10,
// ),
// ),
// child: Icon(e.icon),
// ),
icon: e.icon,
label: e.title,
);
},
).toList(),
extended: breakpoints > Breakpoints.md,
leading: const SidebarHeader(),
trailing: const Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: SidebarFooter(),
headerBuilder: (_, __) => const SidebarHeader(),
footerBuilder: (_, __) => const Padding(
padding: EdgeInsets.only(bottom: 5),
child: SidebarFooter(),
),
showToggleButton: false,
theme: SidebarXTheme(
width: 60,
margin: const EdgeInsets.all(10).copyWith(bottom: 122),
),
extendedTheme: SidebarXTheme(
width: 250,
margin: const EdgeInsets.all(10).copyWith(bottom: 122, left: 0),
padding: const EdgeInsets.symmetric(horizontal: 6),
decoration: BoxDecoration(
color: bgColor?.withOpacity(0.8),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
)),
selectedItemDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
),
selectedIconTheme: IconThemeData(
color: Theme.of(context).colorScheme.primary,
),
selectedTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.w600,
),
itemTextPadding: const EdgeInsets.only(left: 10),
selectedItemTextPadding: const EdgeInsets.only(left: 10),
),
),
Expanded(child: child)
Expand Down
59 changes: 59 additions & 0 deletions lib/hooks/use_sidebarx_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:sidebarx/sidebarx.dart';

/// Creates [SidebarXController] that will be disposed automatically.
///
/// See also:
/// - [SidebarXController]
SidebarXController useSidebarXController({
required int selectedIndex,
bool? extended,
List<Object?>? keys,
}) {
return use(
_SidebarXControllerHook(
selectedIndex: selectedIndex,
extended: extended,
keys: keys,
),
);
}

class _SidebarXControllerHook extends Hook<SidebarXController> {
const _SidebarXControllerHook({
required this.selectedIndex,
this.extended,
List<Object?>? keys,
}) : super(keys: keys);

final int selectedIndex;
final bool? extended;

@override
HookState<SidebarXController, Hook<SidebarXController>> createState() =>
_SidebarXControllerHookState();
}

class _SidebarXControllerHookState
extends HookState<SidebarXController, _SidebarXControllerHook> {
late final SidebarXController controller;

@override
void initHook() {
super.initHook();
controller = SidebarXController(
selectedIndex: hook.selectedIndex,
extended: hook.extended,
);
}

@override
SidebarXController build(BuildContext context) => controller;

@override
void dispose() => controller.dispose();

@override
String get debugLabel => 'useSidebarXController';
}
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.3"
sidebarx:
dependency: "direct main"
description:
name: sidebarx
sha256: "26a8392ceddb659c8f2c688beba6c04bcbf520b4d5decb143c5fd7253653081f"
url: "https://pub.dev"
source: hosted
version: "0.15.0"
simple_circular_progress_bar:
dependency: "direct main"
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies:
queue: ^3.1.0+1
scroll_to_index: ^3.0.1
shared_preferences: ^2.0.11
sidebarx: ^0.15.0
simple_circular_progress_bar: ^1.0.2
skeleton_text: ^3.0.0
spotify:
Expand Down

0 comments on commit 4ba1e70

Please sign in to comment.