Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 171 additions & 23 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:dart_ping_ios/dart_ping_ios.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:network_info_plus/network_info_plus.dart';
Expand Down Expand Up @@ -129,6 +130,9 @@ class _MyHomePageState extends State<MyHomePage> {
bool scanningNetwork = false;
String networkScanProgress = "";

// Drag and drop
bool _isDragging = false;

// CLI
List<String> cliHistory = [""];
List<String> cliCommandHistory = [""];
Expand Down Expand Up @@ -283,13 +287,90 @@ class _MyHomePageState extends State<MyHomePage> {
}

Future<void> _onOpenDMP() async {
final result = await _fileService.openDMPFile();
if (result.success && result.hasData) {
transferBuffer = result.data!;
setState(() {
dmpLoaded = transferBuffer.isNotEmpty;
});
await _analyzeTransferBuffer();
final result = await _fileService.openDMPFiles();
if (result.success && result.hasResults) {
await _processMultipleFiles(result);
}
}

Future<void> _processMultipleFiles(MultiFileResult result) async {
int processedCount = 0;
final successfulFiles = result.successfulFiles;
final failedFiles = result.failedFiles;

for (final fileResult in successfulFiles) {
if (fileResult.hasData) {
transferBuffer = fileResult.data!;
final dataResult = await _dataService.processTransferBuffer(transferBuffer, unitType);

if (dataResult.success) {
setState(() {
sections.mergeSections(dataResult.sections);
dmpLoaded = true;
});
processedCount++;

if (dataResult.brokenSegmentDetected) {
await _showBrokenSegmentWarning();
}
}
}
}

// Show summary message
String message = "Processed $processedCount files successfully";
if (failedFiles.isNotEmpty) {
message += "\n${failedFiles.length} files failed to load:";
for (final failed in failedFiles) {
message += "\n• ${failed.fileName}: ${failed.error}";
}
}

if (mounted) {
if (failedFiles.isNotEmpty) {
_showErrorMessage(message);
} else {
_showMessage(message);
}
}
}

// Handle dropped files
Future<void> _onFilesDropped(List<String> filePaths) async {
final dmpFiles = filePaths.where((path) => path.toLowerCase().endsWith('.dmp')).toList();

if (dmpFiles.isEmpty) {
_showErrorMessage("No DMP files found in dropped files");
return;
}

try {
final fileResults = <FileProcessingResult>[];

for (final filePath in dmpFiles) {
try {
final file = File(filePath);
final fileName = file.path.split(Platform.pathSeparator).last;
final transferBuffer = await _fileService.parseDMPFile(file);

fileResults.add(FileProcessingResult.success(
fileName: fileName,
data: transferBuffer,
));
} catch (e) {
final fileName = filePath.split(Platform.pathSeparator).last;
fileResults.add(FileProcessingResult.error(
fileName: fileName,
error: "Failed to parse: $e",
));
}
}

final result = MultiFileResult.success(fileResults);
await _processMultipleFiles(result);

} catch (e) {
_showErrorMessage("Error processing dropped files: $e");
}
}

Expand All @@ -298,7 +379,7 @@ class _MyHomePageState extends State<MyHomePage> {

if (result.success) {
setState(() {
sections.sections = result.sections;
sections.mergeSections(result.sections);
});

if (result.brokenSegmentDetected) {
Expand Down Expand Up @@ -822,21 +903,88 @@ class _MyHomePageState extends State<MyHomePage> {
),
],
),
body: (!connected && !dmpLoaded)
? WelcomeScreen(
scanningNetwork: scanningNetwork,
networkDeviceFound: networkDeviceFound,
networkScanProgress: networkScanProgress,
ipController: ipController,
ipMNemo: ipMNemo,
onRefreshMnemo: _onRefreshMnemo,
onOpenDMP: _onOpenDMP,
onNetworkScan: _onNetworkScan,
onNetworkScanStop: _onNetworkScanStop,
onNetworkDMP: _onNetworkDMP,
onIPChanged: _onIPChanged,
)
: _buildMainInterface(),
body: _buildDragTarget(
(!connected && !dmpLoaded)
? WelcomeScreen(
scanningNetwork: scanningNetwork,
networkDeviceFound: networkDeviceFound,
networkScanProgress: networkScanProgress,
ipController: ipController,
ipMNemo: ipMNemo,
onRefreshMnemo: _onRefreshMnemo,
onOpenDMP: _onOpenDMP,
onNetworkScan: _onNetworkScan,
onNetworkScanStop: _onNetworkScanStop,
onNetworkDMP: _onNetworkDMP,
onIPChanged: _onIPChanged,
)
: _buildMainInterface(),
),
);
}

Widget _buildDragTarget(Widget child) {
// For desktop platforms, wrap with drag target
if (Platform.isAndroid || Platform.isIOS) {
return child;
}

return DropTarget(
onDragDone: (details) async {
setState(() {
_isDragging = false;
});

final files = details.files;
final dmpFiles = files
.where((file) => file.path.toLowerCase().endsWith('.dmp'))
.map((file) => file.path)
.toList();

if (dmpFiles.isNotEmpty) {
await _onFilesDropped(dmpFiles);
}
},
onDragEntered: (details) {
setState(() {
_isDragging = true;
});
},
onDragExited: (details) {
setState(() {
_isDragging = false;
});
},
child: Stack(
children: [
child,
if (_isDragging)
Container(
color: Colors.blue.withValues(alpha: 0.3),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.file_upload,
size: 64,
color: Colors.white,
),
SizedBox(height: 16),
Text(
'Drop DMP files here to load them',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
),
],
),
);
}

Expand Down
116 changes: 116 additions & 0 deletions lib/models/section_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,122 @@ class SectionList {
}
}

/// Merge new sections with existing sections, resolving conflicts
///
/// Merge behavior:
/// - Case 1: New section doesn't exist → Add it to existing sections
/// - Case 2: Section exists with same properties (shot count, start/end depths) → Skip (no duplicate)
/// - Case 3: Section exists with same name but different properties → Add with incremented name
void mergeSections(List<Section> newSections) {
for (final newSection in newSections) {
_mergeSection(newSection);
}
}

/// Merge a single section, handling conflicts
void _mergeSection(Section newSection) {
final existingSection = _findConflictingSection(newSection);

if (existingSection == null) {
// Case 1: Section does not exist - add it
sections.add(newSection);
} else {
if (_areSectionsIdentical(existingSection, newSection)) {
// Case 2: Section exists with same properties (shot count, depth range) - skip it
return;
} else {
// Case 3: Section exists with same name but different properties - add with new name
final uniqueName = _generateUniqueName(newSection.name);
newSection.name = uniqueName;
sections.add(newSection);
}
}
}

/// Find existing section that conflicts with the new section
Section? _findConflictingSection(Section newSection) {
for (final section in sections) {
if (section.name == newSection.name) {
return section;
}
}
return null;
}

/// Check if two sections are identical based on shot count and depth range
/// Sections are considered identical if they have:
/// - Same number of shots
/// - Same starting depth
/// - Same ending depth
bool _areSectionsIdentical(Section section1, Section section2) {
if (section1.shots.length != section2.shots.length) {
return false;
}

if (section1.depthStart != section2.depthStart) {
return false;
}

if (section1.depthEnd != section2.depthEnd) {
return false;
}

return true;
}

/// Generate a unique section name by incrementing suffix
String _generateUniqueName(String baseName) {
if (baseName.length < 3) {
baseName = baseName.padRight(3, '0');
}

String newName = _incrementName(baseName);

while (_nameExists(newName)) {
newName = _incrementName(newName);
}

return newName;
}

/// Check if a name already exists
bool _nameExists(String name) {
for (final section in sections) {
if (section.name == name) {
return true;
}
}
return false;
}

/// Increment section name following format (AA1->AA2, AA9->AB0, AZ9->BA0)
String _incrementName(String sectionName) {
if (sectionName.length != 3) {
return sectionName;
}

final chars = sectionName.split('');
final letter1 = chars[0];
final letter2 = chars[1];
final number = int.tryParse(chars[2]) ?? 0;

if (number < 9) {
return '$letter1$letter2${number + 1}';
} else {
if (letter2 != 'Z') {
final nextLetter2 = String.fromCharCode(letter2.codeUnitAt(0) + 1);
return '$letter1${nextLetter2}0';
} else {
if (letter1 != 'Z') {
final nextLetter1 = String.fromCharCode(letter1.codeUnitAt(0) + 1);
return '${nextLetter1}A0';
} else {
return 'AA0';
}
}
}
}

/// Legacy getters/setters for compatibility with existing code
List<Section> getSections() => sections;
void setSections(List<Section> newSections) => sections = newSections;
Expand Down
Loading
Loading