mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 06:52:18 +02:00
Options group, quick-select button, switch field, selection field and section card become public StatelessWidgets in metadata_editor_fields.dart with unchanged build bodies; State fields they read (color scheme, fetching flag) are now constructor parameters.
367 lines
11 KiB
Dart
367 lines
11 KiB
Dart
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:spotiflac_android/theme/mornye_icons.dart';
|
|
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
|
import 'package:spotiflac_android/widgets/app_content_card.dart';
|
|
import 'package:spotiflac_android/widgets/app_switch.dart';
|
|
import 'package:spotiflac_android/widgets/metadata_text_field.dart';
|
|
import 'package:spotiflac_android/widgets/settings_group.dart';
|
|
|
|
/// Grouped option list used by the metadata editor pickers.
|
|
class MetadataEditorOptions extends StatelessWidget {
|
|
final List<Widget> children;
|
|
|
|
const MetadataEditorOptions({super.key, required this.children});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (!context.isMornye) return SettingsGroup(children: children);
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Material(
|
|
color: MornyeTheme.controlFill(context),
|
|
borderRadius: BorderRadius.circular(28),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Column(mainAxisSize: MainAxisSize.min, children: children),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MetadataEditorQuickSelectButton extends StatelessWidget {
|
|
final String label;
|
|
final VoidCallback onTap;
|
|
final ColorScheme cs;
|
|
final bool fetching;
|
|
|
|
const MetadataEditorQuickSelectButton({
|
|
super.key,
|
|
required this.label,
|
|
required this.onTap,
|
|
required this.cs,
|
|
required this.fetching,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (context.isMornye) {
|
|
return CupertinoButton(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
|
onPressed: fetching ? null : onTap,
|
|
child: Text(
|
|
label,
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.bodySmall?.copyWith(color: cs.primary),
|
|
),
|
|
);
|
|
}
|
|
return InkWell(
|
|
onTap: fetching ? null : onTap,
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: cs.outline.withValues(alpha: 0.5)),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.labelSmall?.copyWith(color: cs.primary),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MetadataEditorSwitchField extends StatelessWidget {
|
|
final ColorScheme colorScheme;
|
|
final String label;
|
|
final String subtitle;
|
|
final bool value;
|
|
final ValueChanged<bool> onChanged;
|
|
|
|
const MetadataEditorSwitchField({
|
|
super.key,
|
|
required this.colorScheme,
|
|
required this.label,
|
|
required this.subtitle,
|
|
required this.value,
|
|
required this.onChanged,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = colorScheme;
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 14),
|
|
child: Column(
|
|
children: [
|
|
Material(
|
|
color: metadataEditorFieldFill(context, cs),
|
|
borderRadius: BorderRadius.circular(14),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: AppSwitchListTile(
|
|
adaptive: true,
|
|
value: value,
|
|
onChanged: onChanged,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: context.isMornye ? 4 : 16,
|
|
),
|
|
title: Text(label, style: Theme.of(context).textTheme.bodyLarge),
|
|
subtitle: Text(
|
|
subtitle,
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.bodySmall?.copyWith(color: cs.onSurfaceVariant),
|
|
),
|
|
),
|
|
),
|
|
if (context.isMornye) const Divider(height: 0.5),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MetadataEditorSelectionField extends StatelessWidget {
|
|
final ColorScheme colorScheme;
|
|
final String label;
|
|
final String value;
|
|
final VoidCallback onTap;
|
|
final bool enabled;
|
|
|
|
const MetadataEditorSelectionField({
|
|
super.key,
|
|
required this.colorScheme,
|
|
required this.label,
|
|
required this.value,
|
|
required this.onTap,
|
|
this.enabled = true,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = colorScheme;
|
|
if (context.isMornye) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 14),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 4, bottom: 6),
|
|
child: Text(
|
|
label,
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.bodySmall?.copyWith(color: cs.onSurfaceVariant),
|
|
),
|
|
),
|
|
CupertinoButton(
|
|
color: metadataEditorFieldFill(context, cs),
|
|
disabledColor: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(16),
|
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 14),
|
|
onPressed: enabled ? onTap : null,
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
color: enabled ? cs.onSurface : cs.onSurfaceVariant,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Icon(
|
|
CupertinoIcons.chevron_up_chevron_down,
|
|
size: 16,
|
|
color: cs.primary,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Divider(height: 0.5),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
final radius = BorderRadius.circular(14);
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 14),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 4, bottom: 6),
|
|
child: Text(
|
|
label,
|
|
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
|
color: enabled ? cs.onSurfaceVariant : cs.outline,
|
|
fontWeight: FontWeight.w600,
|
|
letterSpacing: 0.1,
|
|
),
|
|
),
|
|
),
|
|
Material(
|
|
color: metadataEditorFieldFill(context, cs),
|
|
borderRadius: radius,
|
|
clipBehavior: Clip.antiAlias,
|
|
child: InkWell(
|
|
onTap: enabled ? onTap : null,
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 14, 12, 14),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
|
color: enabled ? cs.onSurface : cs.outline,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Icon(
|
|
Icons.expand_more_rounded,
|
|
color: enabled ? cs.onSurfaceVariant : cs.outline,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Titled section card. When [onHeaderTap] is set the header is a full-width
|
|
/// tappable row (ripple clipped to the card) with an auto chevron.
|
|
class MetadataEditorSectionCard extends StatelessWidget {
|
|
final ColorScheme colorScheme;
|
|
final IconData icon;
|
|
final String title;
|
|
final List<Widget> children;
|
|
final VoidCallback? onHeaderTap;
|
|
final bool expanded;
|
|
|
|
const MetadataEditorSectionCard({
|
|
super.key,
|
|
required this.colorScheme,
|
|
required this.icon,
|
|
required this.title,
|
|
required this.children,
|
|
this.onHeaderTap,
|
|
this.expanded = true,
|
|
});
|
|
|
|
RoundedRectangleBorder _sectionCardShape(ColorScheme cs) {
|
|
return RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
side: BorderSide(color: cs.outlineVariant.withValues(alpha: 0.5)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = colorScheme;
|
|
final collapsible = onHeaderTap != null;
|
|
const inset = 16.0;
|
|
|
|
final headerRow = Row(
|
|
children: [
|
|
Icon(context.adaptiveIcon(icon), size: 20, color: cs.primary),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: cs.onSurface,
|
|
),
|
|
),
|
|
),
|
|
if (collapsible)
|
|
AnimatedRotation(
|
|
turns: expanded ? 0.5 : 0,
|
|
duration: const Duration(milliseconds: 200),
|
|
curve: Curves.easeOutCubic,
|
|
child: Icon(
|
|
context.isMornye
|
|
? CupertinoIcons.chevron_down
|
|
: Icons.expand_more,
|
|
size: 22,
|
|
color: cs.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
|
|
final Widget header = collapsible
|
|
? InkWell(
|
|
onTap: onHeaderTap,
|
|
child: Padding(
|
|
padding: EdgeInsets.fromLTRB(inset, 16, inset, 16),
|
|
child: headerRow,
|
|
),
|
|
)
|
|
: Padding(
|
|
padding: EdgeInsets.fromLTRB(inset, 16, inset, 8),
|
|
child: headerRow,
|
|
);
|
|
|
|
final content = AnimatedSize(
|
|
duration: const Duration(milliseconds: 220),
|
|
curve: Curves.easeOutCubic,
|
|
alignment: Alignment.topCenter,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
header,
|
|
if (children.isNotEmpty)
|
|
Padding(
|
|
padding: EdgeInsets.fromLTRB(inset, 0, inset, 12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: children,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 12),
|
|
child: context.isMornye
|
|
? Material(
|
|
color: MornyeTheme.controlFill(context),
|
|
borderRadius: BorderRadius.circular(28),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: DividerTheme(
|
|
data: DividerTheme.of(
|
|
context,
|
|
).copyWith(color: MornyeTheme.metadataDividerColor(context)),
|
|
child: content,
|
|
),
|
|
)
|
|
: AppContentCard(
|
|
elevation: 0,
|
|
margin: EdgeInsets.zero,
|
|
color: settingsGroupColor(context),
|
|
shape: _sectionCardShape(cs),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: content,
|
|
),
|
|
);
|
|
}
|
|
}
|