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

This commit is contained in:
Zakaria
2026-05-19 16:41:18 -04:00
parent c36fc55102
commit 9eb6b5237c
20 changed files with 645 additions and 275 deletions
@@ -7,6 +7,7 @@ import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:get_it/get_it.dart';
import '../../models/finamp_models.dart';
import '../../services/download_support.dart';
import '../error_snackbar.dart';
import '../album_image.dart';
@@ -25,6 +26,10 @@ class _CurrentDownloadsListState extends State<CurrentDownloadsList> {
void initState() {
super.initState();
if (!isDownloaderSupported) {
return;
}
IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
_port.listen((dynamic data) {
@@ -36,7 +41,9 @@ class _CurrentDownloadsListState extends State<CurrentDownloadsList> {
@override
void dispose() {
IsolateNameServer.removePortNameMapping('downloader_send_port');
if (isDownloaderSupported) {
IsolateNameServer.removePortNameMapping('downloader_send_port');
}
super.dispose();
}
@@ -6,6 +6,7 @@ import 'package:finamp/l10n/app_localizations.dart';
import 'package:get_it/get_it.dart';
import '../../services/download_update_stream.dart';
import '../../services/download_support.dart';
import '../error_snackbar.dart';
const double downloadsOverviewCardLoadingHeight = 120;
@@ -39,7 +40,9 @@ class _DownloadsOverviewState extends State<DownloadsOverview> {
@override
void initState() {
super.initState();
_downloadsOverviewFuture = FlutterDownloader.loadTasks();
_downloadsOverviewFuture = isDownloaderSupported
? FlutterDownloader.loadTasks()
: Future.value([]);
// Like in DownloadedIndicator, we use our own listener instead of a
// StreamBuilder to ensure that we capture all events.
+40 -15
View File
@@ -25,14 +25,34 @@ class AlbumItemCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
// In AlbumItem, the OpenContainer handles padding.
margin: EdgeInsets.zero,
final colorScheme = Theme.of(context).colorScheme;
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(26),
boxShadow: [
BoxShadow(
color: colorScheme.shadow.withOpacity(0.32),
blurRadius: 22,
offset: const Offset(0, 12),
),
],
),
child: ClipRRect(
borderRadius: AlbumImage.borderRadius,
borderRadius: BorderRadius.circular(26),
child: Stack(
children: [
AlbumImage(item: item),
Positioned.fill(child: AlbumImage(item: item)),
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(
color: Colors.white.withOpacity(0.10),
),
borderRadius: BorderRadius.circular(26),
),
),
),
addSettingsListener
? // We need this ValueListenableBuilder to react to changes to
// showTextOnGridView. When shown in a MusicScreen, this widget
@@ -88,19 +108,21 @@ class _AlbumItemCardText extends StatelessWidget {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
// We fade from half transparent black to transparent so that text is visible on bright images
Colors.black.withOpacity(0.5),
Colors.black.withOpacity(0.86),
Colors.black.withOpacity(0.24),
Colors.transparent,
],
stops: const [0.0, 0.58, 1.0],
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.fromLTRB(14, 46, 14, 14),
child: Align(
alignment: Alignment.bottomLeft,
child: Column(
@@ -110,19 +132,22 @@ class _AlbumItemCardText extends StatelessWidget {
Text(
item.name ?? "Unknown Name",
overflow: TextOverflow.ellipsis,
maxLines: 3,
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(color: Colors.white),
maxLines: 2,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
color: Colors.white,
fontWeight: FontWeight.w800,
height: 1.05,
),
),
if (subtitle != null)
Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Colors.white.withOpacity(0.7)),
.copyWith(color: Colors.white.withOpacity(0.72)),
)
],
),
@@ -131,4 +156,4 @@ class _AlbumItemCardText extends StatelessWidget {
),
);
}
}
}
@@ -25,24 +25,61 @@ class AlbumItemListTile extends StatelessWidget {
Widget build(BuildContext context) {
final jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();
final subtitle = generateSubtitle(item, parentType, context);
final colorScheme = Theme.of(context).colorScheme;
return ListTile(
// This widget is used on the add to playlist screen, so we allow a custom
// onTap to be passed as an argument.
onTap: onTap,
leading: AlbumImage(item: item),
title: Text(
item.name ?? AppLocalizations.of(context)!.unknownName,
overflow: TextOverflow.ellipsis,
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
child: Material(
color: colorScheme.surfaceVariant.withOpacity(0.42),
borderRadius: BorderRadius.circular(22),
clipBehavior: Clip.antiAlias,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
border: Border.all(
color: colorScheme.outlineVariant.withOpacity(0.24),
),
),
child: ListTile(
// This widget is used on the add to playlist screen, so we allow a
// custom onTap to be passed as an argument.
onTap: onTap,
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
leading: SizedBox.square(
dimension: 62,
child: ClipRRect(
borderRadius: BorderRadius.circular(18),
child: AlbumImage(item: item),
),
),
title: Text(
item.name ?? AppLocalizations.of(context)!.unknownName,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w800,
color: colorScheme.onSurface,
),
),
subtitle: subtitle == null
? null
: Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: colorScheme.onSurfaceVariant),
),
trailing: jellyfinApiHelper.selectedMixAlbumIds.contains(item.id)
? Icon(Icons.explore_rounded, color: colorScheme.primary)
: Icon(
Icons.chevron_right_rounded,
color: colorScheme.onSurfaceVariant,
),
),
),
),
subtitle: subtitle == null ? null : Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: jellyfinApiHelper.selectedMixAlbumIds.contains(item.id)
? const Icon(Icons.explore)
: null,
);
}
}
+26 -20
View File
@@ -3,29 +3,35 @@ import 'package:flutter/material.dart';
import 'package:finamp/l10n/app_localizations.dart';
/// Snackbar with error icon for displaying errors
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> errorSnackbar(
dynamic error, BuildContext context) {
return ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.anErrorHasOccured),
action: SnackBarAction(
label: MaterialLocalizations.of(context).moreButtonTooltip,
onPressed: () => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context)!.error),
content: Text(_errorText(error, context)),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
)
],
void errorSnackbar(dynamic error, BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!context.mounted) {
return;
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.anErrorHasOccured),
action: SnackBarAction(
label: MaterialLocalizations.of(context).moreButtonTooltip,
onPressed: () => showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(AppLocalizations.of(context)!.error),
content: Text(_errorText(error, context)),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child:
Text(MaterialLocalizations.of(context).closeButtonLabel),
)
],
),
),
),
),
),
);
);
});
}
String _errorText(dynamic error, BuildContext context) {