feat(inventory): external links on the variety detail
Add Wikipedia/forum-style URLs to a variety: - Repository: addExternalLink/removeExternalLink (soft delete); watchVariety loads links and re-emits on external_links changes; ExternalLinkItem model (title preferred over URL for display). - Cubit: addExternalLink/removeExternalLink. - Detail: a "Links" section with tappable link chips (open via url_launcher, http(s):// prepended if missing), delete per chip, and an "Add link" dialog (URL + optional title). Tests: repo add/remove + widget add/remove. 52 green; Linux runs.
This commit is contained in:
parent
02a9d5bf39
commit
b23f4f1c77
14 changed files with 315 additions and 2 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../data/species_repository.dart';
|
||||
import '../data/variety_repository.dart';
|
||||
|
|
@ -106,6 +107,30 @@ class _DetailView extends StatelessWidget {
|
|||
Text(detail.notes!),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
_SectionTitle(t.detail.links),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
for (final link in detail.links)
|
||||
InputChip(
|
||||
key: Key('link.${link.id}'),
|
||||
avatar: const Icon(Icons.link, size: 18),
|
||||
label: Text(link.display),
|
||||
deleteIcon: const Icon(Icons.close, size: 18),
|
||||
onDeleted: () => cubit.removeExternalLink(link.id),
|
||||
onPressed: () => _openUrl(link.url),
|
||||
),
|
||||
ActionChip(
|
||||
key: const Key('link.add'),
|
||||
avatar: const Icon(Icons.add, size: 18),
|
||||
label: Text(t.detail.addLink),
|
||||
onPressed: () => _showAddLinkDialog(context, cubit),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
|
@ -648,6 +673,66 @@ Future<void> _showAddNameDialog(
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _openUrl(String url) async {
|
||||
final normalized = url.contains('://') ? url : 'https://$url';
|
||||
final uri = Uri.tryParse(normalized);
|
||||
if (uri != null) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showAddLinkDialog(
|
||||
BuildContext context,
|
||||
VarietyDetailCubit cubit,
|
||||
) {
|
||||
final t = context.t;
|
||||
final urlController = TextEditingController();
|
||||
final titleController = TextEditingController();
|
||||
void submit(BuildContext dialogContext) {
|
||||
final url = urlController.text.trim();
|
||||
if (url.isNotEmpty) {
|
||||
cubit.addExternalLink(url, title: _nullIfBlank(titleController.text));
|
||||
}
|
||||
Navigator.of(dialogContext).pop();
|
||||
}
|
||||
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(t.detail.addLink),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
key: const Key('link.url'),
|
||||
controller: urlController,
|
||||
autofocus: true,
|
||||
keyboardType: TextInputType.url,
|
||||
decoration: InputDecoration(labelText: t.detail.linkUrl),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
key: const Key('link.title'),
|
||||
controller: titleController,
|
||||
decoration: InputDecoration(labelText: t.detail.linkTitle),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||
child: Text(t.common.cancel),
|
||||
),
|
||||
FilledButton(
|
||||
key: const Key('link.save'),
|
||||
onPressed: () => submit(dialogContext),
|
||||
child: Text(t.common.save),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String? _nullIfBlank(String value) {
|
||||
final trimmed = value.trim();
|
||||
return trimmed.isEmpty ? null : trimmed;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue