feat: new sidebar widget and translucent bottom player

This commit is contained in:
Kingkor Roy Tirtho 2023-03-10 20:51:44 +06:00
parent a90261ed19
commit 4ba1e70636
5 changed files with 230 additions and 95 deletions

View File

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -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_track_details.dart';
import 'package:spotube/components/player/player_controls.dart'; import 'package:spotube/components/player/player_controls.dart';
import 'package:spotube/hooks/use_breakpoints.dart'; import 'package:spotube/hooks/use_breakpoints.dart';
import 'package:spotube/hooks/use_brightness_value.dart';
import 'package:spotube/models/logger.dart'; import 'package:spotube/models/logger.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:spotube/provider/playlist_queue_provider.dart'; import 'package:spotube/provider/playlist_queue_provider.dart';
@ -37,6 +40,13 @@ class BottomPlayer extends HookConsumerWidget {
[playlist?.activeTrack.album?.images], [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 // returning an empty non spacious Container as the overlay will take
// place in the global overlay stack aka [_entries] // place in the global overlay stack aka [_entries]
if (layoutMode == LayoutMode.compact || if (layoutMode == LayoutMode.compact ||
@ -45,10 +55,11 @@ class BottomPlayer extends HookConsumerWidget {
return PlayerOverlay(albumArt: albumArt); return PlayerOverlay(albumArt: albumArt);
} }
return DecoratedBox( return ClipRect(
decoration: BoxDecoration( child: BackdropFilter(
color: Theme.of(context).colorScheme.background, filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
), child: DecoratedBox(
decoration: BoxDecoration(color: bgColor?.withOpacity(0.8)),
child: Material( child: Material(
type: MaterialType.transparency, type: MaterialType.transparency,
textStyle: Theme.of(context).textTheme.bodyMedium!, textStyle: Theme.of(context).textTheme.bodyMedium!,
@ -72,7 +83,8 @@ class BottomPlayer extends HookConsumerWidget {
height: 20, height: 20,
constraints: const BoxConstraints(maxWidth: 200), constraints: const BoxConstraints(maxWidth: 200),
child: HookBuilder(builder: (context) { child: HookBuilder(builder: (context) {
final volumeState = ref.watch(VolumeProvider.provider); final volumeState =
ref.watch(VolumeProvider.provider);
final volumeNotifier = final volumeNotifier =
ref.watch(VolumeProvider.provider.notifier); ref.watch(VolumeProvider.provider.notifier);
final volume = useState(volumeState); final volume = useState(volumeState);
@ -89,10 +101,12 @@ class BottomPlayer extends HookConsumerWidget {
if (event is PointerScrollEvent) { if (event is PointerScrollEvent) {
if (event.scrollDelta.dy > 0) { if (event.scrollDelta.dy > 0) {
final value = volume.value - .2; final value = volume.value - .2;
volumeNotifier.setVolume(value < 0 ? 0 : value); volumeNotifier
.setVolume(value < 0 ? 0 : value);
} else { } else {
final value = volume.value + .2; final value = volume.value + .2;
volumeNotifier.setVolume(value > 1 ? 1 : value); volumeNotifier
.setVolume(value > 1 ? 1 : value);
} }
} }
}, },
@ -115,6 +129,8 @@ class BottomPlayer extends HookConsumerWidget {
], ],
), ),
), ),
),
),
); );
} }
} }

View File

@ -2,12 +2,15 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sidebarx/sidebarx.dart';
import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/side_bar_tiles.dart'; import 'package:spotube/collections/side_bar_tiles.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/shared/image/universal_image.dart'; import 'package:spotube/components/shared/image/universal_image.dart';
import 'package:spotube/hooks/use_breakpoints.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/authentication_provider.dart';
import 'package:spotube/provider/downloader_provider.dart'; import 'package:spotube/provider/downloader_provider.dart';
@ -55,6 +58,34 @@ class Sidebar extends HookConsumerWidget {
final layoutMode = final layoutMode =
ref.watch(userPreferencesProvider.select((s) => s.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 || if (layoutMode == LayoutMode.compact ||
(breakpoints.isSm && layoutMode == LayoutMode.adaptive)) { (breakpoints.isSm && layoutMode == LayoutMode.adaptive)) {
return Scaffold(body: child); return Scaffold(body: child);
@ -62,41 +93,61 @@ class Sidebar extends HookConsumerWidget {
return Row( return Row(
children: [ children: [
NavigationRail( SidebarX(
selectedIndex: selectedIndex, controller: controller,
onDestinationSelected: onSelectedIndexChanged, items: sidebarTileList.map(
destinations: sidebarTileList.map(
(e) { (e) {
return NavigationRailDestination( return SidebarXItem(
icon: Badge( // iconWidget: Badge(
backgroundColor: Theme.of(context).primaryColor, // backgroundColor: Theme.of(context).primaryColor,
isLabelVisible: e.title == "Library" && downloadCount > 0, // isLabelVisible: e.title == "Library" && downloadCount > 0,
label: Text( // label: Text(
downloadCount.toString(), // downloadCount.toString(),
style: const TextStyle( // style: const TextStyle(
color: Colors.white, // color: Colors.white,
fontSize: 10, // fontSize: 10,
), // ),
), // ),
child: Icon(e.icon), // child: Icon(e.icon),
), // ),
label: Text( icon: e.icon,
e.title, label: e.title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
); );
}, },
).toList(), ).toList(),
extended: breakpoints > Breakpoints.md, headerBuilder: (_, __) => const SidebarHeader(),
leading: const SidebarHeader(), footerBuilder: (_, __) => const Padding(
trailing: const Expanded( padding: EdgeInsets.only(bottom: 5),
child: Align(
alignment: Alignment.bottomCenter,
child: SidebarFooter(), 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) Expanded(child: child)

View File

@ -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';
}

View File

@ -1393,6 +1393,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" 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: simple_circular_progress_bar:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -64,6 +64,7 @@ dependencies:
queue: ^3.1.0+1 queue: ^3.1.0+1
scroll_to_index: ^3.0.1 scroll_to_index: ^3.0.1
shared_preferences: ^2.0.11 shared_preferences: ^2.0.11
sidebarx: ^0.15.0
simple_circular_progress_bar: ^1.0.2 simple_circular_progress_bar: ^1.0.2
skeleton_text: ^3.0.0 skeleton_text: ^3.0.0
spotify: spotify: