Skip to content

Commit 98dbdff

Browse files
committed
fix: import Subsurface buddies and divemasters as proper Buddy entities
Buddies from <buddy> and divemasters from <divemaster> are now created as Buddy entities with correct roles (buddy/diveGuide) instead of being stored as plain text fields. Sets diverId on inline-created buddies so they appear in the diver-scoped buddy list, and invalidates buddyListNotifierProvider after import for immediate UI refresh. Also fixes updateBuddy() not persisting diverId changes to the database.
1 parent 95f7bbd commit 98dbdff

7 files changed

Lines changed: 8511 additions & 14 deletions

File tree

docs/REMAINING_TASKS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,14 @@ All v1.5 tasks complete.
223223
| Feature | Notes |
224224
|---------|-------|
225225
| HTML export | Web-viewable logbook |
226-
| Subsurface XML import | Common format |
226+
| Subsurface import | Common formats |
227227

228228
**Tasks:**
229229

230230
- [ ] HTML export (static website with CSS, images, interactive map)
231231
- [ ] MySQL dump export (for migration to other systems)
232232
- [ ] Handle importing Subsurface XML natively
233+
- [ ] Handle importing from Subsurface database natively
233234

234235
### 13.2 Interoperability
235236

lib/features/buddies/data/repositories/buddy_repository.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ class BuddyRepository {
208208
_db.buddies,
209209
)..where((t) => t.id.equals(buddy.id))).write(
210210
BuddiesCompanion(
211+
diverId: Value(buddy.diverId),
211212
name: Value(buddy.name),
212213
email: Value(buddy.email),
213214
phone: Value(buddy.phone),

lib/features/dive_import/data/services/uddf_entity_importer.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,7 @@ class UddfEntityImporter {
11151115
inlineBuddies += await _linkBuddiesToDive(
11161116
diveData,
11171117
diveId,
1118+
diverId,
11181119
buddyIdMapping,
11191120
repos.buddyRepository,
11201121
);
@@ -1283,6 +1284,7 @@ class UddfEntityImporter {
12831284
Future<int> _linkBuddiesToDive(
12841285
Map<String, dynamic> diveData,
12851286
String diveId,
1287+
String diverId,
12861288
Map<String, String> buddyIdMapping,
12871289
BuddyRepository repository,
12881290
) async {
@@ -1306,10 +1308,27 @@ class UddfEntityImporter {
13061308
: <String>[];
13071309
for (final buddyName in unmatchedNames) {
13081310
final buddy = await repository.findOrCreateByName(buddyName);
1311+
if (buddy.diverId == null) {
1312+
await repository.updateBuddy(buddy.copyWith(diverId: diverId));
1313+
}
13091314
await repository.addBuddyToDive(diveId, buddy.id, BuddyRole.buddy);
13101315
inlineCount++;
13111316
}
13121317

1318+
// Handle inline dive guide / divemaster names
1319+
final unmatchedGuideValue = diveData['unmatchedDiveGuideNames'];
1320+
final unmatchedGuides = unmatchedGuideValue is List
1321+
? unmatchedGuideValue.whereType<String>().toList()
1322+
: <String>[];
1323+
for (final guideName in unmatchedGuides) {
1324+
final guide = await repository.findOrCreateByName(guideName);
1325+
if (guide.diverId == null) {
1326+
await repository.updateBuddy(guide.copyWith(diverId: diverId));
1327+
}
1328+
await repository.addBuddyToDive(diveId, guide.id, BuddyRole.diveGuide);
1329+
inlineCount++;
1330+
}
1331+
13131332
return inlineCount;
13141333
}
13151334

lib/features/universal_import/data/parsers/subsurface_xml_parser.dart

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,19 +250,22 @@ class SubsurfaceXmlParser implements ImportParser {
250250
: WaterType.fresh;
251251
}
252252

253-
// Buddy (strip leading/trailing commas and whitespace)
253+
// Buddy -> proper Buddy entities via unmatchedBuddyNames
254254
final buddyEl = dive.findElements('buddy').firstOrNull;
255255
if (buddyEl != null) {
256-
final raw = buddyEl.innerText.trim();
257-
final cleaned = raw.replaceAll(RegExp(r'^[,\s]+|[,\s]+$'), '').trim();
258-
if (cleaned.isNotEmpty) result['buddy'] = cleaned;
256+
final buddyNames = _splitNames(buddyEl.innerText);
257+
if (buddyNames.isNotEmpty) {
258+
result['unmatchedBuddyNames'] = buddyNames;
259+
}
259260
}
260261

261-
// Divemaster
262+
// Divemaster -> proper Buddy entities with diveGuide role
262263
final divemasterEl = dive.findElements('divemaster').firstOrNull;
263264
if (divemasterEl != null) {
264-
final raw = divemasterEl.innerText.trim();
265-
if (raw.isNotEmpty) result['diveMaster'] = raw;
265+
final dmNames = _splitNames(divemasterEl.innerText);
266+
if (dmNames.isNotEmpty) {
267+
result['unmatchedDiveGuideNames'] = dmNames;
268+
}
266269
}
267270

268271
// Composite notes: <notes> + "Suit: <suit>" + "SAC: <sac attr>"
@@ -538,6 +541,18 @@ class SubsurfaceXmlParser implements ImportParser {
538541
_ => null,
539542
};
540543

544+
/// Splits a comma-separated name string, trimming leading/trailing commas
545+
/// and whitespace from each name.
546+
///
547+
/// Handles Subsurface quirks like ', Kiyan Griffin' (leading comma).
548+
static List<String> _splitNames(String text) {
549+
return text
550+
.split(',')
551+
.map((n) => n.trim())
552+
.where((n) => n.isNotEmpty)
553+
.toList();
554+
}
555+
541556
/// Parses a double value from a string that may have a unit suffix.
542557
///
543558
/// Examples: '2.41 m' -> 2.41, '25.5 bar' -> 25.5, '21.0' -> 21.0

lib/features/universal_import/presentation/providers/universal_import_providers.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ class UniversalImportNotifier extends StateNotifier<UniversalImportState> {
622622
_ref.invalidate(sitesWithCountsProvider);
623623
_ref.invalidate(siteListNotifierProvider);
624624
_ref.invalidate(allBuddiesProvider);
625+
_ref.invalidate(buddyListNotifierProvider);
625626
_ref.invalidate(allEquipmentProvider);
626627
_ref.invalidate(activeEquipmentProvider);
627628
_ref.invalidate(retiredEquipmentProvider);

0 commit comments

Comments
 (0)