refactor: use Appbar titlebar throughout the app

This commit is contained in:
Kingkor Roy Tirtho 2024-12-21 17:02:13 +06:00
parent fcefce4b1b
commit f80ea32de4
33 changed files with 149 additions and 232 deletions

View File

@ -1,89 +1,56 @@
import 'package:flutter/material.dart' hide AppBar; import 'package:flutter/material.dart' hide AppBar;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' show AppBar; import 'package:shadcn_flutter/shadcn_flutter.dart'
show AppBar, WidgetExtension;
import 'package:spotube/components/titlebar/titlebar_buttons.dart'; import 'package:spotube/components/titlebar/titlebar_buttons.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/platform.dart';
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
class PageWindowTitleBar extends StatefulHookConsumerWidget class TitleBar extends HookConsumerWidget implements PreferredSizeWidget {
implements PreferredSizeWidget {
final Widget? leading;
final bool automaticallyImplyLeading; final bool automaticallyImplyLeading;
final List<Widget>? actions; final List<Widget> trailing;
final List<Widget> leading;
final Widget? child;
final Widget? title;
final Widget? header; // small widget placed on top of title
final Widget? subtitle; // small widget placed below title
final bool
trailingExpanded; // expand the trailing instead of the main content
final AlignmentGeometry alignment;
final Color? backgroundColor; final Color? backgroundColor;
final Color? foregroundColor; final Color? foregroundColor;
final IconThemeData? actionsIconTheme; final double? leadingGap;
final bool? centerTitle; final double? trailingGap;
final double? titleSpacing; final EdgeInsetsGeometry? padding;
final double toolbarOpacity; final double? height;
final double? leadingWidth; final bool useSafeArea;
final TextStyle? toolbarTextStyle; final double? surfaceBlur;
final TextStyle? titleTextStyle; final double? surfaceOpacity;
final double? titleWidth;
final Widget? title;
final bool _sliver; const TitleBar({
const PageWindowTitleBar({
super.key, super.key,
this.actions, this.automaticallyImplyLeading = true,
this.trailing = const [],
this.leading = const [],
this.title, this.title,
this.toolbarOpacity = 1, this.header,
this.subtitle,
this.child,
this.trailingExpanded = false,
this.alignment = Alignment.center,
this.padding,
this.backgroundColor, this.backgroundColor,
this.actionsIconTheme,
this.automaticallyImplyLeading = false,
this.centerTitle,
this.foregroundColor, this.foregroundColor,
this.leading, this.leadingGap,
this.leadingWidth, this.trailingGap,
this.titleSpacing, this.height,
this.titleTextStyle, this.surfaceBlur,
this.titleWidth, this.surfaceOpacity,
this.toolbarTextStyle, this.useSafeArea = true,
}) : _sliver = false, });
pinned = false,
floating = false,
snap = false,
stretch = false;
final bool pinned; void onDrag(WidgetRef ref) {
final bool floating;
final bool snap;
final bool stretch;
const PageWindowTitleBar.sliver({
super.key,
this.actions,
this.title,
this.backgroundColor,
this.actionsIconTheme,
this.automaticallyImplyLeading = false,
this.centerTitle,
this.foregroundColor,
this.leading,
this.leadingWidth,
this.titleSpacing,
this.titleTextStyle,
this.titleWidth,
this.toolbarTextStyle,
this.pinned = false,
this.floating = false,
this.snap = false,
this.stretch = false,
}) : _sliver = true,
toolbarOpacity = 1;
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
@override
ConsumerState<PageWindowTitleBar> createState() => _PageWindowTitleBarState();
}
class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
void onDrag(details) {
final systemTitleBar = final systemTitleBar =
ref.read(userPreferencesProvider.select((s) => s.systemTitleBar)); ref.read(userPreferencesProvider.select((s) => s.systemTitleBar));
if (kIsDesktop && !systemTitleBar) { if (kIsDesktop && !systemTitleBar) {
@ -92,86 +59,53 @@ class _PageWindowTitleBarState extends ConsumerState<PageWindowTitleBar> {
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, ref) {
final mediaQuery = MediaQuery.of(context); final hasLeadingOrCanPop = leading.isNotEmpty || Navigator.canPop(context);
if (widget._sliver) { return SizedBox(
return SliverLayoutBuilder( height: height ?? 56,
child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final hasFullscreen = final hasFullscreen =
mediaQuery.size.width == constraints.crossAxisExtent; MediaQuery.sizeOf(context).width == constraints.maxWidth;
final hasLeadingOrCanPop =
widget.leading != null || Navigator.canPop(context);
return SliverPadding(
padding: EdgeInsets.only(
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
),
sliver: SliverAppBar(
leading: widget.leading,
automaticallyImplyLeading: widget.automaticallyImplyLeading,
actions: [
...?widget.actions,
WindowTitleBarButtons(foregroundColor: widget.foregroundColor),
],
backgroundColor: widget.backgroundColor,
foregroundColor: widget.foregroundColor,
actionsIconTheme: widget.actionsIconTheme,
centerTitle: widget.centerTitle,
titleSpacing: widget.titleSpacing,
leadingWidth: widget.leadingWidth,
toolbarTextStyle: widget.toolbarTextStyle,
titleTextStyle: widget.titleTextStyle,
title: SizedBox(
width: double.infinity, // workaround to force dragging
child: widget.title ?? const Text(""),
),
pinned: widget.pinned,
floating: widget.floating,
snap: widget.snap,
stretch: widget.stretch,
),
);
},
);
}
return LayoutBuilder(builder: (context, constrains) {
final hasFullscreen = mediaQuery.size.width == constrains.maxWidth;
final hasLeadingOrCanPop =
widget.leading != null || Navigator.canPop(context);
return GestureDetector( return GestureDetector(
onHorizontalDragStart: onDrag, onHorizontalDragStart: (_) => onDrag(ref),
onVerticalDragStart: onDrag, onVerticalDragStart: (_) => onDrag(ref),
child: Padding(
padding: EdgeInsets.only(
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0,
),
child: AppBar( child: AppBar(
leading: [ leading: leading.isEmpty &&
if (widget.leading != null) widget.leading!, automaticallyImplyLeading &&
if (widget.leading == null && Navigator.canPop(context)
widget.automaticallyImplyLeading && ? [
Navigator.canPop(context))
const BackButton(), const BackButton(),
], ]
: leading,
trailing: [ trailing: [
...?widget.actions, ...trailing,
WindowTitleBarButtons(foregroundColor: widget.foregroundColor), WindowTitleBarButtons(foregroundColor: foregroundColor),
], ],
backgroundColor: widget.backgroundColor, title: title,
title: SizedBox( header: header,
width: double.infinity, // workaround to force dragging subtitle: subtitle,
child: widget.title ?? const Text(""), trailingExpanded: trailingExpanded,
), alignment: alignment,
alignment: widget.centerTitle == true padding: padding,
? Alignment.center backgroundColor: backgroundColor,
: Alignment.centerLeft, leadingGap: leadingGap,
leadingGap: widget.leadingWidth, trailingGap: trailingGap,
), height: height,
surfaceBlur: surfaceBlur,
surfaceOpacity: surfaceOpacity,
useSafeArea: useSafeArea,
child: child,
).withPadding(
left: kIsMacOS && hasFullscreen && hasLeadingOrCanPop ? 65 : 0),
);
},
), ),
); );
});
} }
@override
Size get preferredSize => Size.fromHeight(height ?? 56.0);
} }

View File

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; 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:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotify/spotify.dart' hide Offset; import 'package:spotify/spotify.dart' hide Offset;
import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/assets.gen.dart';
@ -344,6 +345,7 @@ class TrackOptions extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.album), leading: const Icon(SpotubeIcons.album),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(context.l10n.go_to_album), Text(context.l10n.go_to_album),
Text( Text(

View File

@ -20,14 +20,14 @@ class TrackView extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: kIsDesktop appBar: kIsDesktop
? const PageWindowTitleBar( ? const TitleBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Colors.white, leading: [
leadingWidth: 400, Align(
leading: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: BackButton(color: Colors.white), child: BackButton(color: Colors.white),
), )
],
) )
: null, : null,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,

View File

@ -138,15 +138,15 @@ class PlayerView extends HookConsumerWidget {
child: ForceDraggableWidget( child: ForceDraggableWidget(
child: Padding( child: Padding(
padding: EdgeInsets.only(top: topPadding), padding: EdgeInsets.only(top: topPadding),
child: PageWindowTitleBar( child: TitleBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: titleTextColor, leading: [
toolbarOpacity: 1, IconButton(
leading: IconButton(
icon: const Icon(SpotubeIcons.angleDown, size: 18), icon: const Icon(SpotubeIcons.angleDown, size: 18),
onPressed: panelController.close, onPressed: panelController.close,
), )
actions: [ ],
trailing: [
if (currentTrack is YoutubeSourcedTrack) if (currentTrack is YoutubeSourcedTrack)
TextButton.icon( TextButton.icon(
icon: Assets.logos.songlinkTransparent.image( icon: Assets.logos.songlinkTransparent.image(

View File

@ -30,8 +30,8 @@ class ArtistPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: const PageWindowTitleBar( appBar: const TitleBar(
leading: BackButton(), leading: [BackButton()],
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
), ),
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,

View File

@ -23,10 +23,9 @@ class ConnectPage extends HookConsumerWidget {
final discoveredDevices = connectClients.asData?.value.services; final discoveredDevices = connectClients.asData?.value.services;
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
title: Text(context.l10n.devices), title: Text(context.l10n.devices),
titleSpacing: 0,
), ),
body: ListTileTheme( body: ListTileTheme(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(

View File

@ -88,7 +88,7 @@ class ConnectControlPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(resolvedService!.name), title: Text(resolvedService!.name),
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
), ),

View File

@ -43,9 +43,9 @@ class GettingStarting extends HookConsumerWidget {
return Theme( return Theme(
data: themeData, data: themeData,
child: Scaffold( child: Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
actions: [ trailing: [
ListenableBuilder( ListenableBuilder(
listenable: pageController, listenable: pageController,
builder: (context, _) { builder: (context, _) {

View File

@ -23,11 +23,9 @@ class HomeFeedSectionPage extends HookConsumerWidget {
return Skeletonizer( return Skeletonizer(
enabled: homeFeedSection.isLoading, enabled: homeFeedSection.isLoading,
child: Scaffold( child: Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(section.title ?? ""), title: Text(section.title ?? ""),
centerTitle: false,
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
titleSpacing: 0,
), ),
body: CustomScrollView( body: CustomScrollView(
slivers: [ slivers: [

View File

@ -40,8 +40,8 @@ class GenrePlaylistsPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: kIsDesktop appBar: kIsDesktop
? const PageWindowTitleBar( ? const TitleBar(
leading: BackButton(color: Colors.white), leading: [BackButton(color: Colors.white)],
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Colors.white, foregroundColor: Colors.white,
) )

View File

@ -25,10 +25,9 @@ class GenrePage extends HookConsumerWidget {
final mediaQuery = MediaQuery.of(context); final mediaQuery = MediaQuery.of(context);
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(context.l10n.explore_genres), title: Text(context.l10n.explore_genres),
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
titleSpacing: 0,
), ),
body: SafeArea( body: SafeArea(
top: false, top: false,

View File

@ -34,7 +34,7 @@ class HomePage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: kIsMobile || kIsMacOS ? null : const PageWindowTitleBar(), appBar: kIsMobile || kIsMacOS ? null : const TitleBar(),
body: CustomScrollView( body: CustomScrollView(
controller: controller, controller: controller,
slivers: [ slivers: [

View File

@ -27,7 +27,7 @@ class LastFMLoginPage extends HookConsumerWidget {
final isLoading = useState(false); final isLoading = useState(false);
return Scaffold( return Scaffold(
appBar: const PageWindowTitleBar(leading: BackButton()), appBar: const TitleBar(leading: [BackButton()]),
body: Center( body: Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400), constraints: const BoxConstraints(maxWidth: 400),

View File

@ -37,8 +37,9 @@ class LibraryPage extends HookConsumerWidget {
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
headers: [ headers: [
PageWindowTitleBar( TitleBar(
leading: TabList( leading: [
TabList(
index: index.value, index: index.value,
children: [ children: [
for (final child in children) for (final child in children)
@ -49,7 +50,8 @@ class LibraryPage extends HookConsumerWidget {
}, },
), ),
], ],
), )
],
) )
], ],
child: IndexedStack( child: IndexedStack(
@ -60,11 +62,6 @@ class LibraryPage extends HookConsumerWidget {
UserDownloads(), UserDownloads(),
UserArtists(), UserArtists(),
UserAlbums(), UserAlbums(),
// Text("UserPlaylists()"),
// Text("UserLocalTracks()"),
// Text("UserDownloads()"),
// Text("UserArtists()"),
// Text("UserAlbums()"),
], ],
), ),
), ),

View File

@ -93,9 +93,8 @@ class LocalLibraryPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
leading: const BackButton(), leading: const [BackButton()],
centerTitle: true,
title: Column( title: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -120,7 +119,7 @@ class LocalLibraryPage extends HookConsumerWidget {
], ],
), ),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
actions: [ trailing: [
if (isCache) ...[ if (isCache) ...[
IconButton( IconButton(
iconSize: 16, iconSize: 16,

View File

@ -231,10 +231,9 @@ class PlaylistGeneratorPage extends HookConsumerWidget {
final controller = useScrollController(); final controller = useScrollController();
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
leading: const BackButton(), leading: const [BackButton()],
title: Text(context.l10n.generate_playlist), title: Text(context.l10n.generate_playlist),
centerTitle: true,
), ),
body: Scrollbar( body: Scrollbar(
controller: controller, controller: controller,

View File

@ -48,7 +48,7 @@ class PlaylistGenerateResultPage extends HookConsumerWidget {
(generatedPlaylist.asData?.value.length ?? 0); (generatedPlaylist.asData?.value.length ?? 0);
return Scaffold( return Scaffold(
appBar: const PageWindowTitleBar(leading: BackButton()), appBar: const TitleBar(leading: [BackButton()]),
body: generatedPlaylist.isLoading body: generatedPlaylist.isLoading
? Center( ? Center(
child: Column( child: Column(

View File

@ -146,7 +146,7 @@ class LyricsPage extends HookConsumerWidget {
child: Scaffold( child: Scaffold(
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
appBar: !kIsMacOS appBar: !kIsMacOS
? PageWindowTitleBar( ? TitleBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
title: tabbar, title: tabbar,
) )

View File

@ -24,8 +24,8 @@ class WebViewLogin extends HookConsumerWidget {
} }
return Scaffold( return Scaffold(
appBar: const PageWindowTitleBar( appBar: const TitleBar(
leading: BackButton(color: Colors.white), leading: [BackButton(color: Colors.white)],
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
), ),
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,

View File

@ -42,11 +42,9 @@ class ProfilePage extends HookConsumerWidget {
return SafeArea( return SafeArea(
child: Scaffold( child: Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(context.l10n.profile), title: Text(context.l10n.profile),
titleSpacing: 0,
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
centerTitle: false,
), ),
body: Skeletonizer( body: Skeletonizer(
enabled: me.isLoading, enabled: me.isLoading,

View File

@ -88,7 +88,7 @@ class SearchPage extends HookConsumerWidget {
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: kIsDesktop && !kIsMacOS appBar: kIsDesktop && !kIsMacOS
? const PageWindowTitleBar(automaticallyImplyLeading: true) ? const TitleBar(automaticallyImplyLeading: true)
: null, : null,
body: auth.asData?.value == null body: auth.asData?.value == null
? const AnonymousFallback() ? const AnonymousFallback()

View File

@ -29,8 +29,8 @@ class AboutSpotube extends HookConsumerWidget {
const colon = Text(":"); const colon = Text(":");
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
leading: const BackButton(), leading: const [BackButton()],
title: Text(context.l10n.about_spotube), title: Text(context.l10n.about_spotube),
), ),
body: SingleChildScrollView( body: SingleChildScrollView(

View File

@ -44,10 +44,9 @@ class BlackListPage extends HookConsumerWidget {
); );
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(context.l10n.blacklist), title: Text(context.l10n.blacklist),
centerTitle: true, leading: const [BackButton()],
leading: const BackButton(),
), ),
body: Column( body: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -21,10 +21,10 @@ class LogsPage extends HookConsumerWidget {
final logsQuery = ref.watch(logsProvider); final logsQuery = ref.watch(logsProvider);
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(context.l10n.logs), title: Text(context.l10n.logs),
leading: const BackButton(), leading: const [BackButton()],
actions: [ trailing: [
IconButton( IconButton(
icon: const Icon(SpotubeIcons.clipboard), icon: const Icon(SpotubeIcons.clipboard),
iconSize: 16, iconSize: 16,

View File

@ -28,9 +28,8 @@ class SettingsPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(context.l10n.settings), title: Text(context.l10n.settings),
centerTitle: true,
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
), ),
body: Scrollbar( body: Scrollbar(

View File

@ -25,9 +25,8 @@ class StatsAlbumsPage extends HookConsumerWidget {
final albumsData = topAlbums.asData?.value.items ?? []; final albumsData = topAlbums.asData?.value.items ?? [];
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
centerTitle: false,
title: Text(context.l10n.albums), title: Text(context.l10n.albums),
), ),
body: Skeletonizer( body: Skeletonizer(

View File

@ -28,9 +28,8 @@ class StatsArtistsPage extends HookConsumerWidget {
() => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]); () => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]);
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
centerTitle: false,
title: Text(context.l10n.artists), title: Text(context.l10n.artists),
), ),
body: Skeletonizer( body: Skeletonizer(

View File

@ -41,9 +41,8 @@ class StatsStreamFeesPage extends HookConsumerWidget {
); );
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
centerTitle: false,
title: Text(context.l10n.streaming_fees_hypothetical), title: Text(context.l10n.streaming_fees_hypothetical),
), ),
body: CustomScrollView( body: CustomScrollView(

View File

@ -28,9 +28,8 @@ class StatsMinutesPage extends HookConsumerWidget {
final tracksData = topTracks.asData?.value.items ?? []; final tracksData = topTracks.asData?.value.items ?? [];
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(context.l10n.minutes_listened), title: Text(context.l10n.minutes_listened),
centerTitle: false,
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
), ),
body: Skeletonizer( body: Skeletonizer(

View File

@ -26,9 +26,8 @@ class StatsPlaylistsPage extends HookConsumerWidget {
final playlistsData = topPlaylists.asData?.value.items ?? []; final playlistsData = topPlaylists.asData?.value.items ?? [];
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
centerTitle: false,
title: Text(context.l10n.playlists), title: Text(context.l10n.playlists),
), ),
body: Skeletonizer( body: Skeletonizer(

View File

@ -16,7 +16,7 @@ class StatsPage extends HookConsumerWidget {
return SafeArea( return SafeArea(
bottom: false, bottom: false,
child: Scaffold( child: Scaffold(
appBar: kIsMacOS || kIsMobile ? null : const PageWindowTitleBar(), appBar: kIsMacOS || kIsMobile ? null : const TitleBar(),
body: CustomScrollView( body: CustomScrollView(
slivers: [ slivers: [
if (kIsMacOS) const SliverGap(20), if (kIsMacOS) const SliverGap(20),

View File

@ -28,9 +28,8 @@ class StatsStreamsPage extends HookConsumerWidget {
final tracksData = topTracks.asData?.value.items ?? []; final tracksData = topTracks.asData?.value.items ?? [];
return Scaffold( return Scaffold(
appBar: PageWindowTitleBar( appBar: TitleBar(
title: Text(context.l10n.streamed_songs), title: Text(context.l10n.streamed_songs),
centerTitle: false,
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
), ),
body: Skeletonizer( body: Skeletonizer(

View File

@ -53,7 +53,7 @@ class TrackPage extends HookConsumerWidget {
} }
return Scaffold( return Scaffold(
appBar: const PageWindowTitleBar( appBar: const TitleBar(
automaticallyImplyLeading: true, automaticallyImplyLeading: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
), ),