Ocideck/lib/widgets/editors/audio_attachment_editor.dart
Brenno de Winter 68725341a7 Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
  file per group (preferring the most-used, then the oldest), merges
  the tags/descriptions and captions of the copies, repoints slides in
  open decks and in .md presentations on disk, and deletes the copies
  after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
  easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
  reference the image (marked "not open"), next to the open decks.

Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
  keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
  the whole grid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00

91 lines
2.9 KiB
Dart

import 'package:flutter/material.dart';
import '../../models/slide.dart';
import '../../services/image_service.dart';
import '../../l10n/app_localizations.dart';
import '_editor_field.dart';
class AudioAttachmentEditor extends StatelessWidget {
final Slide slide;
final ImageService imageService;
final ValueChanged<Slide> onUpdate;
const AudioAttachmentEditor({
super.key,
required this.slide,
required this.imageService,
required this.onUpdate,
});
Future<void> _pickAudio() async {
final path = await imageService.pickAudio();
if (path != null) onUpdate(slide.copyWith(audioPath: path));
}
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SectionLabel('Audio bij deze slide'),
Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFCBD5E1)),
borderRadius: BorderRadius.circular(6),
color: Colors.white,
),
child: Text(
slide.audioPath.isEmpty
? l10n.d('Geen audiobestand gekozen')
: slide.audioPath,
style: TextStyle(
fontSize: 12,
color: slide.audioPath.isEmpty
? const Color(0xFF64748B)
: const Color(0xFF334155),
),
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(width: 8),
ElevatedButton.icon(
onPressed: _pickAudio,
icon: const Icon(Icons.audio_file_outlined, size: 16),
label: Text(l10n.d('Kiezen')),
),
if (slide.audioPath.isNotEmpty)
IconButton(
onPressed: () => onUpdate(
slide.copyWith(audioPath: '', audioAutoplay: false),
),
icon: const Icon(Icons.clear, size: 18),
tooltip: l10n.d('Audio verwijderen'),
),
],
),
Material(
color: Colors.transparent,
child: CheckboxListTile(
value: slide.audioAutoplay,
onChanged: slide.audioPath.isEmpty
? null
: (value) =>
onUpdate(slide.copyWith(audioAutoplay: value ?? false)),
title: Text(l10n.d('Audio automatisch afspelen')),
dense: true,
contentPadding: EdgeInsets.zero,
controlAffinity: ListTileControlAffinity.leading,
),
),
],
);
}
}