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
|
|
@ -126,6 +126,22 @@ class VarietyPhoto extends Equatable {
|
|||
List<Object?> get props => [id, bytes];
|
||||
}
|
||||
|
||||
/// An external URL attached to a variety (Wikipedia, a forum thread…).
|
||||
class ExternalLinkItem extends Equatable {
|
||||
const ExternalLinkItem({required this.id, required this.url, this.title});
|
||||
|
||||
final String id;
|
||||
final String url;
|
||||
final String? title;
|
||||
|
||||
/// What to show: the title if present, otherwise the URL.
|
||||
String get display =>
|
||||
(title != null && title!.trim().isNotEmpty) ? title! : url;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id, url, title];
|
||||
}
|
||||
|
||||
/// The full detail of one variety (identity + its lots, names and photos).
|
||||
class VarietyDetail extends Equatable {
|
||||
const VarietyDetail({
|
||||
|
|
@ -138,6 +154,7 @@ class VarietyDetail extends Equatable {
|
|||
this.lots = const [],
|
||||
this.vernacularNames = const [],
|
||||
this.photos = const [],
|
||||
this.links = const [],
|
||||
});
|
||||
|
||||
final String id;
|
||||
|
|
@ -149,6 +166,7 @@ class VarietyDetail extends Equatable {
|
|||
final List<VarietyLot> lots;
|
||||
final List<VernacularName> vernacularNames;
|
||||
final List<VarietyPhoto> photos;
|
||||
final List<ExternalLinkItem> links;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
|
|
@ -161,6 +179,7 @@ class VarietyDetail extends Equatable {
|
|||
lots,
|
||||
vernacularNames,
|
||||
photos,
|
||||
links,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -347,6 +366,9 @@ class VarietyRepository {
|
|||
(_db.select(
|
||||
_db.attachments,
|
||||
)..where((a) => a.parentId.equals(id))).watch().map((_) {}),
|
||||
(_db.select(
|
||||
_db.externalLinks,
|
||||
)..where((e) => e.parentId.equals(id))).watch().map((_) {}),
|
||||
// Coarse: any germination-test change re-emits (catalog of tests is tiny).
|
||||
_db.select(_db.germinationTests).watch().map((_) {}),
|
||||
]);
|
||||
|
|
@ -412,6 +434,17 @@ class VarietyRepository {
|
|||
..orderBy([(a) => OrderingTerm(expression: a.createdAt)]))
|
||||
.get();
|
||||
|
||||
final links =
|
||||
await (_db.select(_db.externalLinks)
|
||||
..where(
|
||||
(e) =>
|
||||
e.parentId.equals(id) &
|
||||
e.parentType.equalsValue(ParentType.variety) &
|
||||
e.isDeleted.equals(false),
|
||||
)
|
||||
..orderBy([(e) => OrderingTerm(expression: e.createdAt)]))
|
||||
.get();
|
||||
|
||||
return VarietyDetail(
|
||||
id: v.id,
|
||||
label: v.label,
|
||||
|
|
@ -434,6 +467,9 @@ class VarietyRepository {
|
|||
for (final p in photos)
|
||||
if (p.bytes != null) VarietyPhoto(id: p.id, bytes: p.bytes!),
|
||||
],
|
||||
links: links
|
||||
.map((e) => ExternalLinkItem(id: e.id, url: e.url, title: e.title))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -630,6 +666,45 @@ class VarietyRepository {
|
|||
);
|
||||
}
|
||||
|
||||
/// Adds an external link (URL, optional title) to a variety. Returns its id.
|
||||
Future<String> addExternalLink(
|
||||
String varietyId,
|
||||
String url, {
|
||||
String? title,
|
||||
}) async {
|
||||
final (created, updated) = _stamp();
|
||||
final id = idGen.newId();
|
||||
await _db
|
||||
.into(_db.externalLinks)
|
||||
.insert(
|
||||
ExternalLinksCompanion.insert(
|
||||
id: id,
|
||||
createdAt: created,
|
||||
updatedAt: updated,
|
||||
lastAuthor: nodeId,
|
||||
parentType: ParentType.variety,
|
||||
parentId: varietyId,
|
||||
url: url,
|
||||
title: Value(title),
|
||||
),
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
/// Soft-deletes an external link (tombstone).
|
||||
Future<void> removeExternalLink(String linkId) async {
|
||||
final (_, updated) = _stamp();
|
||||
await (_db.update(
|
||||
_db.externalLinks,
|
||||
)..where((e) => e.id.equals(linkId))).write(
|
||||
ExternalLinksCompanion(
|
||||
isDeleted: const Value(true),
|
||||
updatedAt: Value(updated),
|
||||
lastAuthor: Value(nodeId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Soft-deletes a variety (tombstone); it disappears from the inventory but
|
||||
/// the row survives for correct CRDT merges later.
|
||||
Future<void> softDeleteVariety(String id) async {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue