redesign app phase 2
Build / Build for Android (push) Has been cancelled
Build / Build for iOS (push) Has been cancelled

This commit is contained in:
Zakaria
2026-05-18 16:17:32 -04:00
parent d0ca84d8d2
commit c36fc55102
54 changed files with 2371 additions and 276 deletions
+279 -30
View File
@@ -52,6 +52,7 @@ class _MusicScreenState extends State<MusicScreen>
.where((element) => element.value)
.elementAt(_tabController!.index)
.key;
setState(() {});
if (_tabController != null &&
(tabKey == TabContentType.songs ||
tabKey == TabContentType.artists ||
@@ -186,7 +187,11 @@ class _MusicScreenState extends State<MusicScreen>
return true;
},
child: Scaffold(
extendBody: true,
appBar: AppBar(
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor: Colors.transparent,
title: isSearching
? TextField(
controller: textEditingController,
@@ -200,25 +205,19 @@ class _MusicScreenState extends State<MusicScreen>
.searchFieldLabel,
),
)
: Text(_finampUserHelper.currentUser?.currentView?.name ??
AppLocalizations.of(context)!.music),
bottom: TabBar(
controller: _tabController,
tabs: tabs
.map((tabType) => Tab(
text: tabType
.toLocalisedString(context)
.toUpperCase(),
))
.toList(),
isScrollable: true,
tabAlignment: TabAlignment.start,
),
: null,
leading: isSearching
? BackButton(
onPressed: () => _stopSearching(),
)
: null,
: Builder(
builder: (context) => IconButton.filledTonal(
icon: const Icon(Icons.menu_rounded),
onPressed: () => Scaffold.of(context).openDrawer(),
tooltip: MaterialLocalizations.of(context)
.openAppDrawerTooltip,
),
),
actions: isSearching
? [
IconButton(
@@ -242,8 +241,8 @@ class _MusicScreenState extends State<MusicScreen>
),
IconButton(
icon: finampSettings.isFavourite
? const Icon(Icons.favorite)
: const Icon(Icons.favorite_outline),
? const Icon(Icons.favorite_rounded)
: const Icon(Icons.favorite_outline_rounded),
onPressed: finampSettings.isOffline
? null
: () => FinampSettingsHelper.setIsFavourite(
@@ -251,7 +250,7 @@ class _MusicScreenState extends State<MusicScreen>
tooltip: AppLocalizations.of(context)!.favourites,
),
IconButton(
icon: const Icon(Icons.search),
icon: const Icon(Icons.search_rounded),
onPressed: () => setState(() {
isSearching = true;
}),
@@ -266,18 +265,91 @@ class _MusicScreenState extends State<MusicScreen>
padding: const EdgeInsets.only(right: 8.0),
child: getFloatingActionButton(),
),
body: TabBarView(
controller: _tabController,
children: tabs
.map((tabType) => MusicScreenTabView(
tabContentType: tabType,
searchTerm: searchQuery,
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,
sortBy: finampSettings.getTabSortBy(tabType),
sortOrder: finampSettings.getSortOrder(tabType),
view: _finampUserHelper.currentUser?.currentView,
))
.toList(),
isOffline: finampSettings.isOffline,
onShuffle: () async {
try {
await _audioServiceHelper.shuffleAll(
FinampSettingsHelper
.finampSettings.isFavourite,
);
} catch (e) {
errorSnackbar(e, context);
}
},
),
_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),
),
],
),
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(),
),
),
),
],
),
),
),
),
);
@@ -286,4 +358,181 @@ class _MusicScreenState extends State<MusicScreen>
},
);
}
}
}
class _MusicHeroHeader extends StatelessWidget {
const _MusicHeroHeader({
required this.title,
required this.activeTab,
required this.isFavourite,
required this.isOffline,
required this.onShuffle,
});
final String title;
final String activeTab;
final bool isFavourite;
final bool isOffline;
final VoidCallback onShuffle;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
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),
),
],
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isOffline ? 'Offline library' : 'Your Jellyfin music',
style: theme.textTheme.labelLarge?.copyWith(
color: colorScheme.onPrimary.withOpacity(0.76),
letterSpacing: 0.6,
),
),
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,
),
],
),
],
),
),
),
);
}
}
class _HeroChip extends StatelessWidget {
const _HeroChip({required this.icon, required this.label});
final IconData icon;
final String label;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: colorScheme.onPrimary.withOpacity(0.14),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: colorScheme.onPrimary.withOpacity(0.16)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: colorScheme.onPrimary, size: 18),
const SizedBox(width: 8),
Text(
label,
style: TextStyle(
color: colorScheme.onPrimary,
fontWeight: FontWeight.w700,
),
),
],
),
);
}
}
class _ModernMusicTabBar extends StatelessWidget {
const _ModernMusicTabBar({required this.controller, required this.tabs});
final TabController controller;
final List<TabContentType> tabs;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: DecoratedBox(
decoration: BoxDecoration(
color: colorScheme.surface.withOpacity(0.7),
borderRadius: BorderRadius.circular(999),
border:
Border.all(color: colorScheme.outlineVariant.withOpacity(0.4)),
),
child: TabBar(
controller: controller,
tabs: tabs
.map((tabType) => Tab(text: tabType.toLocalisedString(context)))
.toList(),
isScrollable: true,
tabAlignment: TabAlignment.start,
dividerColor: Colors.transparent,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(999),
color: colorScheme.primary,
),
labelColor: colorScheme.onPrimary,
unselectedLabelColor: colorScheme.onSurfaceVariant,
labelStyle: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w800,
),
unselectedLabelStyle:
Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
),
padding: const EdgeInsets.all(4),
labelPadding: const EdgeInsets.symmetric(horizontal: 18),
),
),
);
}
}