mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 06:52:18 +02:00
fix(headers): fit artwork and unify toolbar controls
This commit is contained in:
1 parent
1bae2e6b16
commit
d28e3f65b1
7 files changed
+82
-90
No files matched your search
@@ -988,17 +988,13 @@ class _ArtistLoadingScaffold extends StatelessWidget {
|
||||
leadingWidth: kToolbarHeight + edgeInset,
|
||||
leading: Padding(
|
||||
padding: EdgeInsets.only(left: edgeInset),
|
||||
child: IconButton(
|
||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
// Centered so the tight leading slot cannot stretch it.
|
||||
child: Center(
|
||||
child: HeaderCircleButton(
|
||||
icon: Icons.arrow_back,
|
||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -635,75 +635,44 @@ class _LibraryTracksFolderScreenState
|
||||
: null,
|
||||
appBarActions: [
|
||||
if (!isSelectionMode && entries.isNotEmpty)
|
||||
IconButton(
|
||||
HeaderCircleButton(
|
||||
icon: Icons.ios_share,
|
||||
iconSize: 20,
|
||||
tooltip: context.l10n.collectionExportM3u,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.ios_share, color: Colors.white, size: 20),
|
||||
),
|
||||
onPressed: () => _exportAsM3u8(context, title, entries),
|
||||
),
|
||||
if (isPlaylistMode && !isSelectionMode) ...[
|
||||
IconButton(
|
||||
const SizedBox(width: 8),
|
||||
HeaderCircleButton(
|
||||
icon: Icons.edit_outlined,
|
||||
iconSize: 20,
|
||||
tooltip: context.l10n.collectionRenamePlaylist,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.edit_outlined,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
final id = widget.playlistId ?? playlist?.id;
|
||||
if (id == null || id.isEmpty) return;
|
||||
_showRenamePlaylistDialog(context, id, playlist?.name ?? title);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
const SizedBox(width: 8),
|
||||
HeaderCircleButton(
|
||||
icon: Icons.camera_alt_outlined,
|
||||
iconSize: 20,
|
||||
tooltip: context.l10n.collectionPlaylistChangeCover,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.camera_alt_outlined,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
onPressed: () => _showCoverOptionsSheet(context, hasCustomCover),
|
||||
),
|
||||
],
|
||||
],
|
||||
leading: IconButton(
|
||||
tooltip: isSelectionMode
|
||||
? MaterialLocalizations.of(context).closeButtonTooltip
|
||||
: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isSelectionMode ? Icons.close : Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
// Centered so the tight leading slot cannot stretch it.
|
||||
leading: Center(
|
||||
child: HeaderCircleButton(
|
||||
icon: isSelectionMode ? Icons.close : Icons.arrow_back,
|
||||
tooltip: isSelectionMode
|
||||
? MaterialLocalizations.of(context).closeButtonTooltip
|
||||
: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
onPressed: isSelectionMode
|
||||
? exitSelectionMode
|
||||
: () => Navigator.pop(context),
|
||||
),
|
||||
onPressed: isSelectionMode
|
||||
? exitSelectionMode
|
||||
: () => Navigator.pop(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -188,9 +188,14 @@ extension _TrackMetadataCards on _TrackMetadataScreenState {
|
||||
|
||||
// Centered square cover over a blurred backdrop (same layout as the album
|
||||
// header) so the Hero flight from a list thumbnail keeps its square shape.
|
||||
final coverSize = (MediaQuery.sizeOf(context).width * 0.5)
|
||||
.clamp(150.0, 210.0)
|
||||
.toDouble();
|
||||
// Landscape headers are short: the cover also yields to the height left
|
||||
// below the toolbar after the title block, so nothing rises into it.
|
||||
final toolbarBottom = MediaQuery.paddingOf(context).top + kToolbarHeight;
|
||||
const textBlockHeight = 190.0;
|
||||
final coverSize = math.min(
|
||||
(MediaQuery.sizeOf(context).width * 0.5).clamp(150.0, 210.0),
|
||||
math.max(96.0, expandedHeight - toolbarBottom - textBlockHeight),
|
||||
);
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show ImageFilter;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/theme/app_motion.dart';
|
||||
@@ -745,31 +746,22 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen>
|
||||
leadingWidth: kToolbarHeight + edgeInset,
|
||||
leading: Padding(
|
||||
padding: EdgeInsets.only(left: edgeInset),
|
||||
child: IconButton(
|
||||
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.arrow_back, color: Colors.white),
|
||||
// Centered so the tight leading slot cannot stretch it.
|
||||
child: Center(
|
||||
child: HeaderCircleButton(
|
||||
icon: Icons.arrow_back,
|
||||
tooltip: MaterialLocalizations.of(
|
||||
context,
|
||||
).backButtonTooltip,
|
||||
onPressed: _popWithMetadataResult,
|
||||
),
|
||||
onPressed: _popWithMetadataResult,
|
||||
),
|
||||
),
|
||||
actionsPadding: EdgeInsets.only(right: edgeInset),
|
||||
actions: [
|
||||
IconButton(
|
||||
HeaderCircleButton(
|
||||
icon: Icons.more_vert,
|
||||
tooltip: MaterialLocalizations.of(context).showMenuTooltip,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.4),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.more_vert, color: Colors.white),
|
||||
),
|
||||
onPressed: () => _showOptionsMenu(context, ref, colorScheme),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -128,7 +129,13 @@ class AlbumDetailHeader extends StatelessWidget {
|
||||
}
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final edgeInset = detailHeaderEdgeInset(context);
|
||||
final artworkSize = MediaQuery.sizeOf(context).width.clamp(0.0, 440.0);
|
||||
final screenSize = MediaQuery.sizeOf(context);
|
||||
// Landscape tablets are short: the artwork also yields to the height so
|
||||
// the title and actions stay on the first screen.
|
||||
final artworkSize = math.min(
|
||||
screenSize.width.clamp(0.0, 440.0),
|
||||
math.max(240.0, screenSize.height * 0.55),
|
||||
);
|
||||
final details = _buildMornyeDetails(context);
|
||||
Widget toolbar({bool collapsed = true}) => SliverAppBar(
|
||||
pinned: true,
|
||||
@@ -201,6 +208,8 @@ class AlbumDetailHeader extends StatelessWidget {
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Stack(
|
||||
// Centers the metadata when it is narrower than the screen.
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: squareArtwork
|
||||
@@ -349,9 +358,26 @@ class AlbumDetailHeader extends StatelessWidget {
|
||||
if (coverBuilder != null) ...[
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final coverSize = (constraints.maxWidth * 0.5)
|
||||
.clamp(150.0, 210.0)
|
||||
.toDouble();
|
||||
// Landscape headers are short: the cover also yields to
|
||||
// the height left below the toolbar once the title,
|
||||
// details and actions are placed, so it never rises
|
||||
// into the status bar or toolbar buttons.
|
||||
final toolbarBottom =
|
||||
MediaQuery.paddingOf(context).top + kToolbarHeight;
|
||||
final detailsHeight =
|
||||
40 +
|
||||
20 +
|
||||
58 +
|
||||
(subtitle == null ? 0 : 28) +
|
||||
(meta == null ? 0 : 34) +
|
||||
(actions == null ? 0 : 64);
|
||||
final coverSize = math.min(
|
||||
(constraints.maxWidth * 0.5).clamp(150.0, 210.0),
|
||||
math.max(
|
||||
96.0,
|
||||
expandedHeight - toolbarBottom - detailsHeight,
|
||||
),
|
||||
);
|
||||
// Material uses the expressive extra-large corner.
|
||||
final coverRadius = context.isMornye
|
||||
? tokens.radiusControl
|
||||
|
||||
@@ -14,6 +14,8 @@ import 'package:spotiflac_android/screens/track_metadata_screen.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/widgets/app_choice_chip.dart';
|
||||
|
||||
import 'support/app_icon_finder.dart';
|
||||
|
||||
class _Extensions extends ExtensionNotifier {
|
||||
@override
|
||||
ExtensionState build() => const ExtensionState(
|
||||
@@ -163,7 +165,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byIcon(Icons.more_vert));
|
||||
await tester.tap(findAppIcon(Icons.more_vert));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Edit audio file'), findsNothing);
|
||||
expect(find.text('Similar songs in Library'), findsNothing);
|
||||
|
||||
@@ -10,6 +10,8 @@ import 'package:spotiflac_android/providers/download_history_provider.dart';
|
||||
import 'package:spotiflac_android/screens/track_metadata_screen.dart';
|
||||
import 'package:spotiflac_android/widgets/app_alert_dialog.dart';
|
||||
|
||||
import 'support/app_icon_finder.dart';
|
||||
|
||||
class _History extends DownloadHistoryNotifier {
|
||||
_History(this._item);
|
||||
final DownloadHistoryItem _item;
|
||||
@@ -140,7 +142,7 @@ void main() {
|
||||
expect(find.text(text), hasTags ? findsOneWidget : findsNothing);
|
||||
}
|
||||
Future<void> openRemoval() async {
|
||||
await tester.tap(find.byIcon(Icons.more_vert));
|
||||
await tester.tap(findAppIcon(Icons.more_vert));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Edit audio file'), findsNothing);
|
||||
expect(find.text('Similar songs in Library'), findsNothing);
|
||||
|
||||
Reference in new issue
Block a user