Felvételek képének megjelenítése a Mérés menüpontbam
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 6s
This commit is contained in:
@@ -21,6 +21,7 @@ import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
|
|||||||
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
||||||
import 'package:terepi_seged/services/layer_import_service.dart';
|
import 'package:terepi_seged/services/layer_import_service.dart';
|
||||||
import 'package:terepi_seged/services/layer_sync_service.dart';
|
import 'package:terepi_seged/services/layer_sync_service.dart';
|
||||||
|
import 'package:terepi_seged/services/measured_data_service.dart';
|
||||||
import 'package:terepi_seged/services/note_audio_service.dart';
|
import 'package:terepi_seged/services/note_audio_service.dart';
|
||||||
import 'package:terepi_seged/services/note_photo_service.dart';
|
import 'package:terepi_seged/services/note_photo_service.dart';
|
||||||
import 'package:terepi_seged/services/ntrip_service.dart';
|
import 'package:terepi_seged/services/ntrip_service.dart';
|
||||||
@@ -117,6 +118,7 @@ Future<void> main() async {
|
|||||||
Get.put(VehicleIdentityService());
|
Get.put(VehicleIdentityService());
|
||||||
Get.put(FieldPropertyService());
|
Get.put(FieldPropertyService());
|
||||||
Get.put(ParcelGeometryService());
|
Get.put(ParcelGeometryService());
|
||||||
|
Get.put(MeasuredDataService());
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/// Egy mérési felvétel (lövés) a PC-szoftver által feltöltött
|
||||||
|
/// `terepi_seged_measure_records` táblából.
|
||||||
|
///
|
||||||
|
/// SZÁNDÉKOSAN nincs helyi tükör — ez a PC-szoftverből folyamatosan,
|
||||||
|
/// élőben érkező adat, mindig friss, sosem gyorsítótárazott (lásd
|
||||||
|
/// MeasureDataService.watchRecords).
|
||||||
|
class MeasuredRecord {
|
||||||
|
final int id;
|
||||||
|
final String projectId;
|
||||||
|
final double? sourceLineNumber;
|
||||||
|
final double? sourcePointNumber;
|
||||||
|
final DateTime measuredAt;
|
||||||
|
final double? eovX;
|
||||||
|
final double? eovY;
|
||||||
|
final int? gpsTimeInMicroseconds;
|
||||||
|
final int? minReceiverPointNumber;
|
||||||
|
final int? minReceiverPointFduNumber;
|
||||||
|
final int? maxReceiverPointNumber;
|
||||||
|
final int? maxReceiverPointFduNumber;
|
||||||
|
final String? imagePath;
|
||||||
|
final String? imageUrl;
|
||||||
|
final int? ffid;
|
||||||
|
final int? recordType;
|
||||||
|
final int? recordSerialNumberOfSourcePoint;
|
||||||
|
|
||||||
|
const MeasuredRecord({
|
||||||
|
required this.id,
|
||||||
|
required this.projectId,
|
||||||
|
this.sourceLineNumber,
|
||||||
|
this.sourcePointNumber,
|
||||||
|
required this.measuredAt,
|
||||||
|
this.eovX,
|
||||||
|
this.eovY,
|
||||||
|
this.gpsTimeInMicroseconds,
|
||||||
|
this.minReceiverPointNumber,
|
||||||
|
this.minReceiverPointFduNumber,
|
||||||
|
this.maxReceiverPointNumber,
|
||||||
|
this.maxReceiverPointFduNumber,
|
||||||
|
this.imagePath,
|
||||||
|
this.imageUrl,
|
||||||
|
this.ffid,
|
||||||
|
this.recordType,
|
||||||
|
this.recordSerialNumberOfSourcePoint,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory MeasuredRecord.fromMap(Map<String, dynamic> m) => MeasuredRecord(
|
||||||
|
id: m['id'] as int,
|
||||||
|
projectId: m['project_id'] as String,
|
||||||
|
sourceLineNumber: (m['source_line_number'] as num?)?.toDouble(),
|
||||||
|
sourcePointNumber: (m['source_point_number'] as num?)?.toDouble(),
|
||||||
|
measuredAt: DateTime.parse(m['measured_at'] as String),
|
||||||
|
eovX: (m['eov_x'] as num?)?.toDouble(),
|
||||||
|
eovY: (m['eov_y'] as num?)?.toDouble(),
|
||||||
|
gpsTimeInMicroseconds: (m['gps_time_in_microseconds'] as num?)?.toInt(),
|
||||||
|
minReceiverPointNumber:
|
||||||
|
(m['min_receiver_point_number'] as num?)?.toInt(),
|
||||||
|
minReceiverPointFduNumber:
|
||||||
|
(m['min_receiver_point_fdu_number'] as num?)?.toInt(),
|
||||||
|
maxReceiverPointNumber:
|
||||||
|
(m['max_receiver_point_number'] as num?)?.toInt(),
|
||||||
|
maxReceiverPointFduNumber:
|
||||||
|
(m['max_receiver_point_fdu_number'] as num?)?.toInt(),
|
||||||
|
imagePath: m['image_path'] as String?,
|
||||||
|
imageUrl: m['image_url'] as String?,
|
||||||
|
ffid: (m['ffid'] as num?)?.toInt(),
|
||||||
|
recordType: (m['record_type'] as num?)?.toInt(),
|
||||||
|
recordSerialNumberOfSourcePoint:
|
||||||
|
(m['record_serial_number_of_source_point'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
bool get hasImage =>
|
||||||
|
(imageUrl != null && imageUrl!.isNotEmpty) ||
|
||||||
|
(imagePath != null && imagePath!.isNotEmpty);
|
||||||
|
|
||||||
|
String? get geophoneRange =>
|
||||||
|
(minReceiverPointNumber != null && maxReceiverPointNumber != null)
|
||||||
|
? '$minReceiverPointNumber–$maxReceiverPointNumber'
|
||||||
|
: null;
|
||||||
|
}
|
||||||
@@ -1,56 +1,113 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:firebase_storage/firebase_storage.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:terepi_seged/models/seismic_records.dart';
|
import 'package:terepi_seged/models/measured_record.dart';
|
||||||
import 'package:terepi_seged/pages/measured_data/presentation/views/seismogram_viewer_dialog.dart';
|
import 'package:terepi_seged/pages/measured_data/presentation/views/seismogram_viewer_dialog.dart';
|
||||||
|
import 'package:terepi_seged/services/measured_data_service.dart';
|
||||||
|
import 'package:terepi_seged/services/project_service.dart';
|
||||||
|
|
||||||
class MeasuredDataController extends GetxController {
|
class MeasuredDataController extends GetxController {
|
||||||
final CollectionReference _seismicRecords =
|
static MeasuredDataController get to => Get.find();
|
||||||
FirebaseFirestore.instance.collection('seismicRecords');
|
|
||||||
|
|
||||||
// late Query<Map<String, dynamic>> records = _seismicRecords.orderBy('field');
|
final records = <MeasuredRecord>[].obs;
|
||||||
late Stream<QuerySnapshot> seismicRecordSnapshot;
|
final isLoading = true.obs;
|
||||||
|
final searchText = ''.obs;
|
||||||
|
final lineFilter = Rxn<double>();
|
||||||
|
|
||||||
@override
|
StreamSubscription<List<MeasuredRecord>>? _sub;
|
||||||
void onReady() async {
|
|
||||||
super.onReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
_subscribe();
|
||||||
|
ever(ProjectService.to.activeProject, (_) => _subscribe());
|
||||||
|
}
|
||||||
|
|
||||||
seismicRecordSnapshot = _seismicRecords
|
void _subscribe() {
|
||||||
// .orderBy('sourcePoint', descending: true)
|
_sub?.cancel();
|
||||||
.where('gpsDateTime', isGreaterThan: DateTime(2024, 2, 25, 0, 0, 0))
|
lineFilter.value = null;
|
||||||
.orderBy('gpsDateTime', descending: true)
|
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||||
.snapshots();
|
if (projectId == null) {
|
||||||
|
records.clear();
|
||||||
|
isLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isLoading.value = true;
|
||||||
|
_sub = MeasuredDataService.to.watchRecords(projectId).listen(
|
||||||
|
(data) {
|
||||||
|
records.value = data;
|
||||||
|
isLoading.value = false;
|
||||||
|
},
|
||||||
|
onError: (e) {
|
||||||
|
isLoading.value = false;
|
||||||
|
Get.snackbar('Hiba', 'Nem sikerült betölteni a mérési adatokat: $e',
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Keresés/szűrés — a már memóriában lévő, élő listán, nem újabb
|
||||||
|
// lekérdezéssel (a teljes projekt-adat úgyis a stream-ben van). ──
|
||||||
|
|
||||||
|
List<MeasuredRecord> get filteredRecords {
|
||||||
|
var list = records.toList();
|
||||||
|
if (lineFilter.value != null) {
|
||||||
|
list = list.where((r) => r.sourceLineNumber == lineFilter.value).toList();
|
||||||
|
}
|
||||||
|
final q = searchText.value.trim();
|
||||||
|
if (q.isNotEmpty) {
|
||||||
|
list = list.where((r) {
|
||||||
|
return (r.ffid?.toString().contains(q) ?? false) ||
|
||||||
|
(r.sourcePointNumber?.toString().contains(q) ?? false) ||
|
||||||
|
(r.sourceLineNumber?.toString().contains(q) ?? false);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> get availableLines {
|
||||||
|
final set =
|
||||||
|
records.map((r) => r.sourceLineNumber).whereType<double>().toSet();
|
||||||
|
final list = set.toList()..sort();
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSearchChanged(String v) => searchText.value = v;
|
||||||
|
|
||||||
|
void setLineFilter(double? v) => lineFilter.value = v;
|
||||||
|
|
||||||
|
// ── Kép megtekintése ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
Future<void> showSeismicImage(MeasuredRecord record) async {
|
||||||
|
if (!record.hasImage) {
|
||||||
|
Get.snackbar('Nincs kép', 'Ehhez a felvételhez nincs rögzített kép.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||||
|
barrierDismissible: false);
|
||||||
|
try {
|
||||||
|
final Uint8List? bytes = await MeasuredDataService.to.loadImage(record);
|
||||||
|
Get.back(); // töltő-dialógus bezárása
|
||||||
|
if (bytes == null) {
|
||||||
|
Get.snackbar('Hiba', 'A kép nem tölthető be.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.to(() => SeismogramViewerDialog(record: record, image: bytes),
|
||||||
|
transition: Transition.downToUp);
|
||||||
|
} catch (e) {
|
||||||
|
Get.back();
|
||||||
|
Get.snackbar('Hiba', 'Kép letöltése sikertelen: $e',
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() async {
|
void onClose() {
|
||||||
|
_sub?.cancel();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
|
|
||||||
// seismicRecordSnapshot.
|
|
||||||
}
|
|
||||||
|
|
||||||
void showSeismicImage(int ffid) async {
|
|
||||||
late Uint8List image;
|
|
||||||
Reference reference =
|
|
||||||
FirebaseStorage.instance.ref().child('bp-4/${ffid}.jpg');
|
|
||||||
|
|
||||||
await reference
|
|
||||||
.getData()
|
|
||||||
.then((value) => {image = value!})
|
|
||||||
.catchError((error) => {});
|
|
||||||
|
|
||||||
Get.to(
|
|
||||||
() => SeismogramViewerDialog(
|
|
||||||
imageUrl: '',
|
|
||||||
image: image,
|
|
||||||
),
|
|
||||||
transition: Transition.downToUp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +1,123 @@
|
|||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/src/widgets/framework.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:terepi_seged/models/measured_record.dart';
|
||||||
import 'package:terepi_seged/pages/measured_data/presentation/controllers/measured_data_controller.dart';
|
import 'package:terepi_seged/pages/measured_data/presentation/controllers/measured_data_controller.dart';
|
||||||
import 'package:terepi_seged/pages/measured_data/presentation/views/seismogram_viewer_dialog.dart';
|
import 'package:terepi_seged/services/project_service.dart';
|
||||||
|
|
||||||
class MeasuredDataView extends GetView<MeasuredDataController> {
|
class MeasuredDataView extends GetView<MeasuredDataController> {
|
||||||
const MeasuredDataView({Key? key}) : super(key: key);
|
const MeasuredDataView({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
appBar: AppBar(
|
||||||
extendBody: true,
|
title: Obx(() => Column(
|
||||||
appBar: AppBar(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
title: const Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
const Text('Mérési adatok'),
|
||||||
Text('Mérési adatok'),
|
Text(
|
||||||
Text(
|
ProjectService.to.activeProject.value?.name ?? '',
|
||||||
"Budapest",
|
style: const TextStyle(fontSize: 12.0),
|
||||||
style: TextStyle(fontSize: 12.0),
|
),
|
||||||
)
|
],
|
||||||
],
|
)),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.search),
|
||||||
|
hintText: 'FFID / forráspont / vonal…',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
|
onChanged: controller.onSearchChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Obx(() => DropdownButtonFormField<double?>(
|
||||||
|
value: controller.lineFilter.value,
|
||||||
|
isExpanded: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Vonal', isDense: true),
|
||||||
|
items: [
|
||||||
|
const DropdownMenuItem(
|
||||||
|
value: null, child: Text('Mind')),
|
||||||
|
...controller.availableLines.map((l) =>
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: l, child: Text(_fmtNum(l)))),
|
||||||
|
],
|
||||||
|
onChanged: controller.setLineFilter,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
Expanded(
|
||||||
Padding(
|
child: Obx(() {
|
||||||
padding: const EdgeInsets.only(right: 20.0),
|
if (controller.isLoading.value) {
|
||||||
child: GestureDetector(
|
return const Center(child: CircularProgressIndicator());
|
||||||
onTap: () => Get.to(() => {}, transition: Transition.downToUp),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.more_vert,
|
|
||||||
size: 26.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Column(children: [
|
|
||||||
Text("Mérési adatok"),
|
|
||||||
StreamBuilder<QuerySnapshot>(
|
|
||||||
stream: controller.seismicRecordSnapshot,
|
|
||||||
builder:
|
|
||||||
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return const Text('Hiba ...');
|
|
||||||
}
|
}
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
final list = controller.filteredRecords;
|
||||||
return const Text('Adatbetöltés ...');
|
if (list.isEmpty) {
|
||||||
|
return const Center(child: Text('Nincs mérési adat.'));
|
||||||
}
|
}
|
||||||
return Expanded(
|
return ListView.builder(
|
||||||
child: ListView(
|
itemCount: list.length,
|
||||||
// shrinkWrap: true,
|
itemBuilder: (context, i) => _RecordTile(record: list[i]),
|
||||||
children: snapshot.data!.docs
|
|
||||||
.map((DocumentSnapshot document) {
|
|
||||||
Map<String, dynamic> data =
|
|
||||||
document.data()! as Map<String, dynamic>;
|
|
||||||
return ListTile(
|
|
||||||
title: Text(data['sourcePoint'].toString()),
|
|
||||||
subtitle: Text(
|
|
||||||
'${data['sourceLine'].toString()} - ${(data['gpsDateTime'] as Timestamp).toDate()}'),
|
|
||||||
trailing: Wrap(spacing: -16, children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
controller.showSeismicImage(data['ffid']);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.image_outlined)),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon:
|
|
||||||
const Icon(Icons.arrow_forward_ios_sharp))
|
|
||||||
]));
|
|
||||||
})
|
|
||||||
.toList()
|
|
||||||
.cast(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
}),
|
||||||
)
|
),
|
||||||
]));
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _fmtNum(double v) =>
|
||||||
|
v == v.roundToDouble() ? v.toInt().toString() : v.toString();
|
||||||
|
|
||||||
|
String _fmtDate(DateTime dt) {
|
||||||
|
final local = dt.toLocal();
|
||||||
|
String two(int n) => n.toString().padLeft(2, '0');
|
||||||
|
return '${local.year}.${two(local.month)}.${two(local.day)} '
|
||||||
|
'${two(local.hour)}:${two(local.minute)}:${two(local.second)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecordTile extends StatelessWidget {
|
||||||
|
final MeasuredRecord record;
|
||||||
|
const _RecordTile({required this.record});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final hasImage = record.hasImage;
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
'FFID ${record.ffid ?? "—"} · VP '
|
||||||
|
'${record.sourcePointNumber != null ? _fmtNum(record.sourcePointNumber!) : "—"}',
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
'Vonal ${record.sourceLineNumber != null ? _fmtNum(record.sourceLineNumber!) : "—"}'
|
||||||
|
'${record.geophoneRange != null ? " · Geofon ${record.geophoneRange}" : ""}'
|
||||||
|
'\n${_fmtDate(record.measuredAt)}',
|
||||||
|
),
|
||||||
|
isThreeLine: true,
|
||||||
|
trailing: Icon(
|
||||||
|
hasImage ? Icons.image_outlined : Icons.image_not_supported_outlined,
|
||||||
|
color: hasImage ? null : Colors.grey.shade400,
|
||||||
|
),
|
||||||
|
onTap: hasImage
|
||||||
|
? () => MeasuredDataController.to.showSeismicImage(record)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
|
||||||
import 'package:terepi_seged/pages/measured_data/presentation/controllers/measured_data_controller.dart';
|
|
||||||
import 'package:widget_zoom/widget_zoom.dart';
|
import 'package:widget_zoom/widget_zoom.dart';
|
||||||
|
|
||||||
|
import '../../../../models/measured_record.dart';
|
||||||
|
|
||||||
class SeismogramViewerDialog extends StatelessWidget {
|
class SeismogramViewerDialog extends StatelessWidget {
|
||||||
final controller = Get.find<MeasuredDataController>();
|
final MeasuredRecord record;
|
||||||
final String imageUrl;
|
|
||||||
final Uint8List image;
|
final Uint8List image;
|
||||||
// final double sourcePoint;
|
|
||||||
SeismogramViewerDialog(
|
const SeismogramViewerDialog({
|
||||||
{required this.imageUrl, required this.image, super.key});
|
required this.record,
|
||||||
|
required this.image,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -24,43 +26,47 @@ class SeismogramViewerDialog extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(onPressed: Get.back, icon: const Icon(Icons.close)),
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.close)),
|
|
||||||
TextButton(
|
TextButton(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
overlayColor:
|
overlayColor:
|
||||||
MaterialStateProperty.all(Colors.transparent)),
|
MaterialStateProperty.all(Colors.transparent)),
|
||||||
onPressed: () {
|
onPressed: Get.back,
|
||||||
Get.back();
|
child: const Text(
|
||||||
},
|
'Bezár',
|
||||||
child: const Text(
|
style: TextStyle(color: Colors.blue, fontSize: 14.0),
|
||||||
'Bezár',
|
),
|
||||||
style: TextStyle(color: Colors.blue, fontSize: 14.0),
|
),
|
||||||
))
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 15.0),
|
padding: const EdgeInsets.symmetric(horizontal: 15.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Felvétel megtekintése ()',
|
// Az eredeti verzióban ez a cím mindig üresen jelent meg
|
||||||
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
|
// ("Felvétel megtekintése ()") — itt tényleges adattal.
|
||||||
|
'Felvétel megtekintése — FFID ${record.ffid ?? "—"}',
|
||||||
|
style:
|
||||||
|
const TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (record.geophoneRange != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15.0),
|
||||||
|
child: Text('Geofon-tartomány: ${record.geophoneRange}',
|
||||||
|
style: TextStyle(fontSize: 13, color: Colors.grey.shade600)),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 10.0),
|
padding: const EdgeInsets.only(top: 10.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: WidgetZoom(
|
child: WidgetZoom(
|
||||||
zoomWidget: Image(image: MemoryImage(image)),
|
zoomWidget: Image(image: MemoryImage(image)),
|
||||||
heroAnimationTag: 'tag',
|
heroAnimationTag: 'seismogram_${record.id}',
|
||||||
maxScaleFullscreen: 8,
|
maxScaleFullscreen: 8,
|
||||||
maxScaleEmbeddedView: 8,
|
maxScaleEmbeddedView: 8,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
|
import '../models/measured_record.dart';
|
||||||
|
|
||||||
|
/// Mérési felvételek (lövések) élő lekérdezése a PC-szoftver által
|
||||||
|
/// feltöltött táblából. SZÁNDÉKOSAN nincs helyi tükör/gyorsítótár — ez
|
||||||
|
/// folyamatosan érkező, élő adat, mindig a jelenlegi állapotot mutatja.
|
||||||
|
class MeasuredDataService extends GetxService {
|
||||||
|
static MeasuredDataService get to => Get.find();
|
||||||
|
|
||||||
|
SupabaseClient get _client => Supabase.instance.client;
|
||||||
|
|
||||||
|
/// TODO: erősítsd meg a valós bucket-nevet, ha eltér ettől.
|
||||||
|
static const _bucket = 'terepi_seged-measure-images';
|
||||||
|
|
||||||
|
/// Élő, folyamatosan frissülő stream — a Supabase Realtime-on keresztül,
|
||||||
|
/// pontosan úgy, ahogy korábban a Firestore .snapshots() működött. Új
|
||||||
|
/// felvétel a PC-szoftverből AZONNAL megjelenik, frissítés nélkül.
|
||||||
|
Stream<List<MeasuredRecord>> watchRecords(String projectId) {
|
||||||
|
return _client
|
||||||
|
.from('terepi_seged_measure_records')
|
||||||
|
.stream(primaryKey: ['id'])
|
||||||
|
.eq('project_id', projectId)
|
||||||
|
.order('measured_at', ascending: false)
|
||||||
|
.map((rows) => rows.map((r) => MeasuredRecord.fromMap(r)).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A felvételhez tartozó kép betöltése — elsőbbséget adva az image_url-nek
|
||||||
|
/// (ha a PC-szoftver már kész, letölthető URL-t is küld), egyébként a
|
||||||
|
/// Storage-ból, image_path alapján.
|
||||||
|
Future<Uint8List?> loadImage(MeasuredRecord record) async {
|
||||||
|
if (record.imageUrl != null && record.imageUrl!.isNotEmpty) {
|
||||||
|
final resp = await http.get(Uri.parse(record.imageUrl!));
|
||||||
|
if (resp.statusCode == 200) return resp.bodyBytes;
|
||||||
|
throw Exception('Kép letöltése sikertelen (${resp.statusCode})');
|
||||||
|
}
|
||||||
|
if (record.imagePath != null && record.imagePath!.isNotEmpty) {
|
||||||
|
return _client.storage.from(_bucket).download(record.imagePath!);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -158,7 +158,7 @@ class AppDrawer extends StatelessWidget {
|
|||||||
title: const Text('Mérés'),
|
title: const Text('Mérés'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
// Get.to(() => const NtripSettingsView());
|
Get.toNamed(Routes.MEASUREDDATA);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|||||||
Reference in New Issue
Block a user