Files
sendico/frontend/pweb/lib/services/payments/csv_input.dart

96 lines
2.6 KiB
Dart

import 'dart:async';
import 'package:universal_html/html.dart' as html;
class PickedCsvFile {
final String name;
final String content;
const PickedCsvFile({required this.name, required this.content});
}
abstract class CsvInputService {
Future<PickedCsvFile?> pickCsv();
}
class WebCsvInputService implements CsvInputService {
@override
Future<PickedCsvFile?> pickCsv() async {
final input = html.FileUploadInputElement()
..accept = '.csv,text/csv'
..multiple = false
..style.display = 'none';
html.document.body?.append(input);
final file = await _pickFile(input);
if (file == null) return null;
final reader = html.FileReader();
final readCompleter = Completer<String>();
reader.onError.listen((_) {
readCompleter.completeError(StateError('Failed to read file'));
});
reader.onLoadEnd.listen((_) {
final result = reader.result;
if (result is String) {
readCompleter.complete(result);
} else {
readCompleter.completeError(StateError('Unsupported file payload'));
}
});
reader.readAsText(file);
final content = await readCompleter.future;
return PickedCsvFile(name: file.name, content: content);
}
Future<html.File?> _pickFile(html.FileUploadInputElement input) async {
final completer = Completer<html.File?>();
void completeWith(html.File? file) {
if (!completer.isCompleted) completer.complete(file);
}
StreamSubscription<html.Event>? changeSub;
StreamSubscription<html.Event>? inputSub;
StreamSubscription<html.Event>? focusSub;
Timer? timeout;
void cleanup() {
changeSub?.cancel();
inputSub?.cancel();
focusSub?.cancel();
timeout?.cancel();
input.remove();
}
void tryComplete() {
final file = input.files?.isNotEmpty == true ? input.files!.first : null;
if (file != null) {
completeWith(file);
}
}
changeSub = input.onChange.listen((_) => tryComplete());
inputSub = input.onInput.listen((_) => tryComplete());
focusSub = html.window.onFocus.listen((_) async {
if (completer.isCompleted) return;
for (var i = 0; i < 6; i++) {
tryComplete();
if (completer.isCompleted) return;
await Future<void>.delayed(const Duration(milliseconds: 120));
}
if (!completer.isCompleted && (input.value ?? '').isEmpty) {
completeWith(null);
}
});
timeout = Timer(const Duration(minutes: 2), () => completeWith(null));
completer.future.whenComplete(cleanup);
input.click();
return completer.future;
}
}