redesign app phase 3
This commit is contained in:
+208
-147
@@ -265,91 +265,87 @@ class _MusicScreenState extends State<MusicScreen>
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: getFloatingActionButton(),
|
||||
),
|
||||
body: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primaryContainer
|
||||
.withOpacity(0.45),
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surface,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
children: [
|
||||
if (!isSearching)
|
||||
_MusicHeroHeader(
|
||||
title: _finampUserHelper
|
||||
.currentUser?.currentView?.name ??
|
||||
AppLocalizations.of(context)!.music,
|
||||
activeTab: tabs
|
||||
.elementAt(_tabController!.index)
|
||||
.toLocalisedString(context),
|
||||
isFavourite: finampSettings.isFavourite,
|
||||
isOffline: finampSettings.isOffline,
|
||||
onShuffle: () async {
|
||||
try {
|
||||
await _audioServiceHelper.shuffleAll(
|
||||
FinampSettingsHelper
|
||||
.finampSettings.isFavourite,
|
||||
);
|
||||
} catch (e) {
|
||||
errorSnackbar(e, context);
|
||||
}
|
||||
},
|
||||
body: Stack(
|
||||
children: [
|
||||
const _MusicBackdrop(),
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
children: [
|
||||
if (!isSearching)
|
||||
_MusicHeroHeader(
|
||||
title: _finampUserHelper
|
||||
.currentUser?.currentView?.name ??
|
||||
AppLocalizations.of(context)!.music,
|
||||
activeTab: tabs
|
||||
.elementAt(_tabController!.index)
|
||||
.toLocalisedString(context),
|
||||
isFavourite: finampSettings.isFavourite,
|
||||
isOffline: finampSettings.isOffline,
|
||||
onShuffle: () async {
|
||||
try {
|
||||
await _audioServiceHelper.shuffleAll(
|
||||
FinampSettingsHelper
|
||||
.finampSettings.isFavourite,
|
||||
);
|
||||
} catch (e) {
|
||||
errorSnackbar(e, context);
|
||||
}
|
||||
},
|
||||
),
|
||||
_ModernMusicTabBar(
|
||||
controller: _tabController!,
|
||||
tabs: tabs.toList(),
|
||||
),
|
||||
_ModernMusicTabBar(
|
||||
controller: _tabController!,
|
||||
tabs: tabs.toList(),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(12, 8, 12, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(28),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.shadow
|
||||
.withOpacity(0.08),
|
||||
blurRadius: 24,
|
||||
offset: const Offset(0, -6),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 14, 0),
|
||||
child: Material(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withOpacity(0.74),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: Radius.circular(34),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outlineVariant
|
||||
.withOpacity(0.42),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: tabs
|
||||
.map((tabType) => MusicScreenTabView(
|
||||
tabContentType: tabType,
|
||||
searchTerm: searchQuery,
|
||||
isFavourite: finampSettings.isFavourite,
|
||||
sortBy: finampSettings
|
||||
.getTabSortBy(tabType),
|
||||
sortOrder: finampSettings
|
||||
.getSortOrder(tabType),
|
||||
view: _finampUserHelper
|
||||
.currentUser?.currentView,
|
||||
))
|
||||
.toList(),
|
||||
elevation: 16,
|
||||
shadowColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.shadow
|
||||
.withOpacity(0.24),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: tabs
|
||||
.map((tabType) => MusicScreenTabView(
|
||||
tabContentType: tabType,
|
||||
searchTerm: searchQuery,
|
||||
isFavourite:
|
||||
finampSettings.isFavourite,
|
||||
sortBy: finampSettings
|
||||
.getTabSortBy(tabType),
|
||||
sortOrder: finampSettings
|
||||
.getSortOrder(tabType),
|
||||
view: _finampUserHelper
|
||||
.currentUser?.currentView,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -360,6 +356,78 @@ class _MusicScreenState extends State<MusicScreen>
|
||||
}
|
||||
}
|
||||
|
||||
class _MusicBackdrop extends StatelessWidget {
|
||||
const _MusicBackdrop();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
const Color(0xFF081D38),
|
||||
colorScheme.background,
|
||||
const Color(0xFF020611),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: -110,
|
||||
right: -80,
|
||||
child: _GlowOrb(
|
||||
color: colorScheme.primary.withOpacity(0.28),
|
||||
size: 280,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 90,
|
||||
left: -120,
|
||||
child: _GlowOrb(
|
||||
color: colorScheme.tertiary.withOpacity(0.20),
|
||||
size: 260,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: -140,
|
||||
right: -130,
|
||||
child: _GlowOrb(
|
||||
color: colorScheme.secondary.withOpacity(0.16),
|
||||
size: 320,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GlowOrb extends StatelessWidget {
|
||||
const _GlowOrb({required this.color, required this.size});
|
||||
|
||||
final Color color;
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [color, Colors.transparent],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MusicHeroHeader extends StatelessWidget {
|
||||
const _MusicHeroHeader({
|
||||
required this.title,
|
||||
@@ -381,73 +449,64 @@ class _MusicHeroHeader extends StatelessWidget {
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 86, 20, 12),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.primary.withOpacity(0.95),
|
||||
colorScheme.tertiary.withOpacity(0.78),
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: colorScheme.primary.withOpacity(0.28),
|
||||
blurRadius: 34,
|
||||
offset: const Offset(0, 18),
|
||||
padding: const EdgeInsets.fromLTRB(24, 88, 24, 24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isOffline ? 'OFFLINE MODE' : 'FINAMP MUSIC',
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 2.2,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.displaySmall?.copyWith(
|
||||
color: colorScheme.onBackground,
|
||||
fontWeight: FontWeight.w900,
|
||||
height: 0.98,
|
||||
letterSpacing: -1.4,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Browse your Jellyfin collection with artwork-first shelves.',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
isOffline ? 'Offline library' : 'Your Jellyfin music',
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
color: colorScheme.onPrimary.withOpacity(0.76),
|
||||
letterSpacing: 0.6,
|
||||
FilledButton.icon(
|
||||
onPressed: onShuffle,
|
||||
icon: const Icon(Icons.shuffle_rounded),
|
||||
label: const Text('Shuffle'),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: colorScheme.primary,
|
||||
foregroundColor: colorScheme.onPrimary,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 18,
|
||||
vertical: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.headlineMedium?.copyWith(
|
||||
color: colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.w800,
|
||||
height: 1.04,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
onPressed: onShuffle,
|
||||
icon: const Icon(Icons.shuffle_rounded),
|
||||
label: const Text('Shuffle'),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: colorScheme.onPrimary,
|
||||
foregroundColor: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
_HeroChip(
|
||||
icon: isFavourite
|
||||
? Icons.favorite_rounded
|
||||
: Icons.library_music_rounded,
|
||||
label: isFavourite ? 'Favourites' : activeTab,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 12),
|
||||
_HeroChip(
|
||||
icon:
|
||||
isFavourite ? Icons.favorite_rounded : Icons.album_rounded,
|
||||
label: isFavourite ? 'Favourites' : activeTab,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -466,19 +525,19 @@ class _HeroChip extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.onPrimary.withOpacity(0.14),
|
||||
color: colorScheme.surfaceVariant.withOpacity(0.58),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(color: colorScheme.onPrimary.withOpacity(0.16)),
|
||||
border: Border.all(color: colorScheme.outlineVariant.withOpacity(0.55)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: colorScheme.onPrimary, size: 18),
|
||||
Icon(icon, color: colorScheme.secondary, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: colorScheme.onPrimary,
|
||||
color: colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
@@ -502,10 +561,10 @@ class _ModernMusicTabBar extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface.withOpacity(0.7),
|
||||
color: colorScheme.surfaceVariant.withOpacity(0.50),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border:
|
||||
Border.all(color: colorScheme.outlineVariant.withOpacity(0.4)),
|
||||
Border.all(color: colorScheme.outlineVariant.withOpacity(0.55)),
|
||||
),
|
||||
child: TabBar(
|
||||
controller: controller,
|
||||
@@ -518,7 +577,9 @@ class _ModernMusicTabBar extends StatelessWidget {
|
||||
indicatorSize: TabBarIndicatorSize.tab,
|
||||
indicator: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
color: colorScheme.primary,
|
||||
gradient: LinearGradient(
|
||||
colors: [colorScheme.primary, colorScheme.secondary],
|
||||
),
|
||||
),
|
||||
labelColor: colorScheme.onPrimary,
|
||||
unselectedLabelColor: colorScheme.onSurfaceVariant,
|
||||
|
||||
Reference in New Issue
Block a user