refactor: alternative track sheet to use shadcn

This commit is contained in:
Kingkor Roy Tirtho 2025-01-03 23:10:26 +06:00
parent f96b5eae97
commit 30e03786bf
3 changed files with 378 additions and 343 deletions

View File

@ -98,7 +98,12 @@ class PlayerView extends HookConsumerWidget {
onPopInvoked: (didPop) async {
await panelController.close();
},
child: SurfaceCard(
borderWidth: 0,
surfaceOpacity: 0.9,
padding: EdgeInsets.zero,
child: Scaffold(
backgroundColor: Colors.transparent,
headers: [
SafeArea(
child: TitleBar(
@ -319,6 +324,7 @@ class PlayerView extends HookConsumerWidget {
),
),
),
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/modules/player/player_queue.dart';
import 'package:spotube/modules/player/sibling_tracks_sheet.dart';
import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart';
@ -118,9 +119,28 @@ class PlayerActions extends HookConsumerWidget {
tooltip: TooltipContainer(
child: Text(context.l10n.alternative_track_sources)),
child: IconButton.ghost(
enabled: playlist.activeTrack != null,
icon: const Icon(SpotubeIcons.alternativeRoute),
onPressed: playlist.activeTrack != null
? () {
onPressed: () {
final screenSize = MediaQuery.sizeOf(context);
if (screenSize.mdAndUp) {
showPopover(
alignment: Alignment.bottomCenter,
context: context,
builder: (context) {
return SurfaceCard(
padding: EdgeInsets.zero,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 600,
maxWidth: 500,
),
child: SiblingTracksSheet(floating: floatingQueue),
),
);
},
);
} else {
openDrawer(
context: context,
position: OverlayPosition.bottom,
@ -129,12 +149,24 @@ class PlayerActions extends HookConsumerWidget {
barrierColor: Colors.black.withValues(alpha: .2),
borderRadius: BorderRadius.circular(10),
transformBackdrop: false,
surfaceBlur: context.theme.surfaceBlur,
surfaceOpacity: context.theme.surfaceOpacity,
builder: (context) {
return SiblingTracksSheet(floating: floatingQueue);
return Card(
borderWidth: 0,
borderColor: Colors.transparent,
padding: EdgeInsets.zero,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: screenSize.height * .8,
),
child: SiblingTracksSheet(floating: floatingQueue),
),
);
},
);
}
: null,
},
),
),
if (!kIsWeb && !isLocalTrack)

View File

@ -1,16 +1,15 @@
import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' show ListTile, Material, MaterialType;
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/image/universal_image.dart';
import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart';
import 'package:spotube/extensions/artist_simple.dart';
import 'package:spotube/extensions/constrains.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/duration.dart';
import 'package:spotube/hooks/utils/use_debounce.dart';
@ -152,13 +151,6 @@ class SiblingTracksSheet extends HookConsumerWidget {
[activeTrack, isFetchingActiveTrack],
);
final borderRadius = floating
? BorderRadius.circular(10)
: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
);
useEffect(() {
if (activeTrack is SourcedTrack && activeTrack.siblings.isEmpty) {
activeTrackNotifier.populateSibling();
@ -170,9 +162,17 @@ class SiblingTracksSheet extends HookConsumerWidget {
(SourceInfo sourceInfo) {
final icon = sourceInfoToIconMap[sourceInfo.runtimeType];
return ListTile(
hoverColor: theme.colorScheme.primary.withOpacity(.1),
dense: true,
subtitleTextStyle: theme.typography.small.copyWith(
color: theme.colorScheme.mutedForeground,
),
titleTextStyle: theme.typography.normal,
leadingAndTrailingTextStyle: theme.typography.normal,
title: Text(sourceInfo.title),
horizontalTitleGap: 0,
leading: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.only(top: 8.0, right: 8.0),
child: UniversalImage(
path: sourceInfo.thumbnail,
height: 60,
@ -192,12 +192,13 @@ class SiblingTracksSheet extends HookConsumerWidget {
enabled: !isFetchingActiveTrack,
selected: !isFetchingActiveTrack &&
sourceInfo.id == (activeTrack as SourcedTrack).sourceInfo.id,
selectedTileColor: theme.popupMenuTheme.color,
selectedTileColor: theme.colorScheme.primary.withOpacity(.1),
selectedColor: theme.colorScheme.primary,
onTap: () {
if (!isFetchingActiveTrack &&
sourceInfo.id != (activeTrack as SourcedTrack).sourceInfo.id) {
activeTrackNotifier.swapSibling(sourceInfo);
Navigator.of(context).pop();
closeDrawer(context);
}
},
);
@ -205,54 +206,42 @@ class SiblingTracksSheet extends HookConsumerWidget {
[activeTrack, siblings],
);
final mediaQuery = MediaQuery.of(context);
final scale = context.theme.scaling;
return SafeArea(
child: ClipRRect(
borderRadius: borderRadius,
clipBehavior: Clip.hardEdge,
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 12.0,
sigmaY: 12.0,
),
child: AnimatedSize(
duration: const Duration(milliseconds: 300),
child: Container(
height: isSearching.value && mediaQuery.smAndDown
? mediaQuery.size.height - 50
: mediaQuery.size.height * .6,
decoration: BoxDecoration(
borderRadius: borderRadius,
color:
theme.colorScheme.surfaceContainerHighest.withOpacity(.5),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
centerTitle: true,
title: AnimatedSwitcher(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16),
child: Row(
spacing: 5,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: !isSearching.value
? Text(
context.l10n.alternative_track_sources,
style: theme.textTheme.headlineSmall,
style: theme.typography.bold,
)
: TextField(
: Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: 320 * scale,
maxHeight: 38 * scale,
),
child: TextField(
autofocus: true,
controller: searchController,
decoration: InputDecoration(
hintText: context.l10n.search,
hintStyle: theme.textTheme.headlineSmall,
border: InputBorder.none,
),
style: theme.textTheme.headlineSmall,
placeholder: Text(context.l10n.search),
style: theme.typography.bold,
),
),
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
actions: [
),
),
const Spacer(),
if (!isSearching.value)
IconButton(
IconButton.outline(
icon: const Icon(SpotubeIcons.search, size: 18),
onPressed: () {
isSearching.value = true;
@ -260,22 +249,31 @@ class SiblingTracksSheet extends HookConsumerWidget {
)
else ...[
if (preferences.audioSource == AudioSource.piped)
PopupMenuButton(
IconButton.outline(
icon: const Icon(SpotubeIcons.filter, size: 18),
onSelected: (SearchMode mode) {
searchMode.value = mode;
},
initialValue: searchMode.value,
itemBuilder: (context) => SearchMode.values
onPressed: () {
showPopover(
context: context,
alignment: Alignment.bottomRight,
builder: (context) {
return DropdownMenu(
children: SearchMode.values
.map(
(e) => PopupMenuItem(
value: e,
(e) => MenuButton(
onPressed: (context) {
searchMode.value = e;
},
enabled: searchMode.value != e,
child: Text(e.label),
),
)
.toList(),
);
},
);
},
),
IconButton(
IconButton.outline(
icon: const Icon(SpotubeIcons.close, size: 18),
onPressed: () {
isSearching.value = false;
@ -284,16 +282,19 @@ class SiblingTracksSheet extends HookConsumerWidget {
]
],
),
body: Padding(
padding: const EdgeInsets.all(8.0),
),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, animation) =>
FadeTransition(opacity: animation, child: child),
child: InterScrollbar(
controller: controller,
child: Material(
type: MaterialType.transparency,
child: switch (isSearching.value) {
false => ListView.builder(
padding: const EdgeInsets.all(8.0),
controller: controller,
itemCount: siblings.length,
itemBuilder: (context, index) =>
@ -311,14 +312,12 @@ class SiblingTracksSheet extends HookConsumerWidget {
child: CircularProgressIndicator());
}
return InterScrollbar(
controller: controller,
child: ListView.builder(
return ListView.builder(
padding: const EdgeInsets.all(8.0),
controller: controller,
itemCount: snapshot.data!.length,
itemBuilder: (context, index) =>
itemBuilder(snapshot.data![index]),
),
);
},
),
@ -327,9 +326,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
),
),
),
),
),
),
],
),
);
}