(fix) felhős projekthez csak bejelentkezés után lehet csatlakozni, név megjelenítése a kapcsolatok mellett a terepbejárás nézetben.
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:
@@ -24,7 +24,8 @@ class ContactEditView extends StatefulWidget {
|
||||
|
||||
class _ContactEditViewState extends State<ContactEditView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
late final Contact? _original;
|
||||
Contact? _original;
|
||||
String? _originalLocalUuid;
|
||||
|
||||
late final TextEditingController _name;
|
||||
late final TextEditingController _address;
|
||||
@@ -41,7 +42,13 @@ class _ContactEditViewState extends State<ContactEditView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_original = Get.arguments is Contact ? Get.arguments as Contact : null;
|
||||
final args = Get.arguments;
|
||||
if (args is ContactWithState) {
|
||||
_original = args.contact;
|
||||
_originalLocalUuid = args.isPending ? args.localUuid : null;
|
||||
} else if (args is Contact) {
|
||||
_original = args;
|
||||
}
|
||||
_name = TextEditingController(text: _original?.name ?? '');
|
||||
_address = TextEditingController(text: _original?.address ?? '');
|
||||
_phone = TextEditingController(text: _original?.phone ?? '');
|
||||
@@ -84,7 +91,8 @@ class _ContactEditViewState extends State<ContactEditView> {
|
||||
clearLocation: _lat == null,
|
||||
);
|
||||
|
||||
final queued = await ContactService.to.save(contact);
|
||||
final queued = await ContactService.to
|
||||
.save(contact, existingLocalUuid: _originalLocalUuid);
|
||||
Get.back(result: queued); // a lista frissítéshez visszakapja
|
||||
if (Get.isRegistered<ContactsController>()) {
|
||||
Get.find<ContactsController>().load(silent: true);
|
||||
|
||||
@@ -127,7 +127,7 @@ class ContactsView extends StatelessWidget {
|
||||
item: rows[i],
|
||||
onEdit: () async {
|
||||
final saved = await Get.to(() => const ContactEditView(),
|
||||
arguments: rows[i].contact);
|
||||
arguments: rows[i]);
|
||||
if (saved != null) c.load();
|
||||
},
|
||||
onDelete: () => _confirmDelete(c, rows[i]),
|
||||
|
||||
@@ -1241,6 +1241,11 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
|
||||
final contactMarkers = <Marker>[].obs;
|
||||
|
||||
/// Nyers lista a feliratozáshoz (a contactMarkers már kész Marker-eket
|
||||
/// tartalmaz, abból nem érhető el a név) — ugyanaz a szűrt kör, csak
|
||||
/// Contact-objektumként, hogy a felirat-réteg is el tudja olvasni.
|
||||
final contactsWithLocation = <ContactWithState>[].obs;
|
||||
|
||||
Future<void> saveContactAtPoint(LatLng point) async {
|
||||
activeEditTool.value = MapEditTool.none;
|
||||
if (!PermissionService.to.canContacts) return;
|
||||
@@ -1272,20 +1277,21 @@ class MapSurveyController extends GetxController implements StyleEditable {
|
||||
Future<void> _loadContactMarkers() async {
|
||||
if (!PermissionService.to.canContacts) {
|
||||
contactMarkers.clear();
|
||||
contactsWithLocation.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final projectId = ProjectService.to.activeProject.value?.uuid;
|
||||
if (projectId == null) {
|
||||
contactMarkers.clear();
|
||||
contactsWithLocation.clear();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final all = await ContactService.to.listMerged(projectId);
|
||||
contactMarkers.value = all
|
||||
.where((c) => c.contact.hasLocation)
|
||||
.map(_markerFromContact)
|
||||
.toList();
|
||||
final located = all.where((c) => c.contact.hasLocation).toList();
|
||||
contactsWithLocation.value = located;
|
||||
contactMarkers.value = located.map(_markerFromContact).toList();
|
||||
} catch (_) {
|
||||
// Offline/hiba: a meglévő jelölők maradnak, nem törli ki csendben.
|
||||
}
|
||||
|
||||
@@ -1351,7 +1351,8 @@ class AppDatabase {
|
||||
|
||||
Future<void> insertPendingContact(Map<String, dynamic> row) async {
|
||||
final db = await database;
|
||||
await db.insert('contacts_outbox', row);
|
||||
await db.insert('contacts_outbox', row,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace);
|
||||
}
|
||||
|
||||
/// [projectId] NÉLKÜL (a flush-hoz) MINDEN várólistás sort ad vissza —
|
||||
|
||||
@@ -280,6 +280,7 @@ class AppLogger extends GetxService {
|
||||
final userId = Supabase.instance.client.auth.currentUser?.id;
|
||||
|
||||
await Supabase.instance.client.from('terepi_seged_logs').insert({
|
||||
'type': type,
|
||||
'tag': tag,
|
||||
'message': message,
|
||||
'error_text': error?.toString(),
|
||||
|
||||
@@ -40,7 +40,17 @@ class ContactExportService {
|
||||
: v;
|
||||
}
|
||||
|
||||
final header = ['Nev', 'Cim', 'Telefon', 'Email', 'Megjegyzes'].join(sep);
|
||||
final header = [
|
||||
'Nev',
|
||||
'Cim',
|
||||
'Telefon',
|
||||
'Email',
|
||||
'Megjegyzes',
|
||||
'Szélesség',
|
||||
'Hosszúság',
|
||||
'EOV_Y',
|
||||
'EOV_X'
|
||||
].join(sep);
|
||||
|
||||
final sb = StringBuffer();
|
||||
sb.write('\uFEFF'); // UTF-8 BOM — az Excel enélkül elrontja az ékezeteket
|
||||
@@ -52,6 +62,9 @@ class ContactExportService {
|
||||
txt(c.phone),
|
||||
txt(c.email),
|
||||
txt(c.note),
|
||||
c.lat?.toString() ?? '',
|
||||
c.lon?.toString() ?? '',
|
||||
c.eovY?.toString() ?? '',
|
||||
].join(sep));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:terepi_seged/services/app_logger.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../models/contact.dart';
|
||||
@@ -81,16 +82,34 @@ class ContactService extends GetxService {
|
||||
|
||||
/// Új kapcsolat vagy szerver-oldali frissítés.
|
||||
/// Visszaadja, hogy a művelet OFFLINE pufferbe került-e (true = várólistán).
|
||||
Future<bool> save(Contact c) async {
|
||||
Future<bool> save(Contact c, {String? existingLocalUuid}) async {
|
||||
// Meglévő (szerver-oldali) rekord frissítése CSAK online — lásd korlát.
|
||||
final isUpdate = c.id != null;
|
||||
|
||||
if (await _isOnline) {
|
||||
try {
|
||||
await _client.from('terepi_seged_contacts').upsert(c.toWriteMap());
|
||||
// Ha ez korábban egy PENDING (helyi puffer-) sorból indult, és
|
||||
// most sikerült felmenni, a régi helyi sort törölni kell —
|
||||
// különben örökre "függőben" maradna, és a flush() is
|
||||
// duplikálná.
|
||||
if (existingLocalUuid != null) {
|
||||
await _db.deletePendingContact(existingLocalUuid);
|
||||
}
|
||||
return false; // felment
|
||||
} catch (e) {
|
||||
if (isUpdate) rethrow; // frissítést nem pufferelünk
|
||||
if (e is PostgrestException) {
|
||||
// Válasz érkezett a szervertől, de hibás (séma, RLS, megkötés) —
|
||||
// ez NEM hálózati probléma. Ha csendben pufferbe tennénk, ez a
|
||||
// sor SOSEM jutna fel, és senki nem venné észre — inkább
|
||||
// azonnal, láthatóan hibázzon.
|
||||
AppLogger.e('ContactService.save',
|
||||
'Supabase hiba új kapcsolat mentésekor: $e');
|
||||
rethrow;
|
||||
}
|
||||
// Valódi hálózati/kapcsolati hiba (pl. pillanatnyi kiesés) →
|
||||
// pufferbe, a flush() majd újrapróbálja.
|
||||
// Új rekord + online hiba (pl. pillanatnyi kiesés) → pufferbe.
|
||||
}
|
||||
} else if (isUpdate) {
|
||||
@@ -101,7 +120,7 @@ class ContactService extends GetxService {
|
||||
|
||||
// Offline (vagy online-hiba) új rekord → outbox.
|
||||
await _db.insertPendingContact({
|
||||
'local_uuid': _uuid.v4(),
|
||||
'local_uuid': existingLocalUuid ?? _uuid.v4(),
|
||||
'project_id': c.projectId,
|
||||
'name': c.name.trim(),
|
||||
'address': c.address.trim(),
|
||||
@@ -173,8 +192,12 @@ class ContactService extends GetxService {
|
||||
// Sikeres felküldés → a lokális példány törölhető.
|
||||
await _db.deletePendingContact(m['local_uuid'] as String);
|
||||
uploaded++;
|
||||
} catch (_) {
|
||||
// A sor marad a pufferben, a következő flush újrapróbálja.
|
||||
} catch (e) {
|
||||
// A sor marad a pufferben (legközelebb újrapróbáljuk) — de
|
||||
// naplózzuk, hogy ne maradjon örökre észrevétlen, ha a hiba nem
|
||||
// hálózati, hanem tartós (pl. séma-eltérés, RLS).
|
||||
AppLogger.e('ContactService.flush',
|
||||
'Kapcsolat feltöltési hiba (local_uuid=${m['local_uuid']}): $e');
|
||||
}
|
||||
}
|
||||
return uploaded;
|
||||
|
||||
@@ -6,6 +6,15 @@ import '../models/project.dart';
|
||||
import 'app_database.dart';
|
||||
import 'ts_sync_service.dart';
|
||||
|
||||
class ProjectRequiresLoginException implements Exception {
|
||||
final String message;
|
||||
ProjectRequiresLoginException(
|
||||
[this.message = 'Felhős projekt használatához be kell jelentkezni.']);
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
class ProjectService extends GetxService {
|
||||
static ProjectService get to => Get.find();
|
||||
|
||||
@@ -19,6 +28,20 @@ class ProjectService extends GetxService {
|
||||
super.onInit();
|
||||
await _loadProjects();
|
||||
await _restoreActiveProject();
|
||||
|
||||
Supabase.instance.client.auth.onAuthStateChange.listen((data) {
|
||||
final loggedOut = data.session == null;
|
||||
final active = activeProject.value;
|
||||
|
||||
if (loggedOut && active != null && !active.isLocalOnly) {
|
||||
final fallback = projects.firstWhereOrNull((p) => p.isLocalOnly);
|
||||
if (fallback != null) {
|
||||
setActiveProject(fallback);
|
||||
} else {
|
||||
activeProject.value = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadProjects() async {
|
||||
@@ -44,6 +67,11 @@ class ProjectService extends GetxService {
|
||||
}
|
||||
|
||||
Future<void> setActiveProject(Project project) async {
|
||||
if (!project.isLocalOnly &&
|
||||
Supabase.instance.client.auth.currentUser == null) {
|
||||
throw ProjectRequiresLoginException();
|
||||
}
|
||||
|
||||
activeProject.value = project;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('active_project_id', project.id!);
|
||||
@@ -164,13 +192,18 @@ class ProjectService extends GetxService {
|
||||
/// 3. azonnali szinkron, hogy a projekt eddigi adatai lejöjjenek.
|
||||
Future<Project> joinSharedProject(Map<String, dynamic> sharedRow) async {
|
||||
final client = Supabase.instance.client;
|
||||
final user = client.auth.currentUser;
|
||||
if (user == null) {
|
||||
throw ProjectRequiresLoginException(
|
||||
'Közös projekthez csatlakozáshoz be kell jelentkezni.');
|
||||
}
|
||||
final projectUuid = sharedRow['id'] as String;
|
||||
|
||||
// 1. Tagság (idempotens: ha már tag, nem hiba).
|
||||
await client.from('terepi_seged_project_members').upsert(
|
||||
{
|
||||
'project_id': projectUuid,
|
||||
'user_id': client.auth.currentUser!.id,
|
||||
'user_id': user.id,
|
||||
'role': 'editor',
|
||||
},
|
||||
ignoreDuplicates: true,
|
||||
@@ -193,11 +226,13 @@ class ProjectService extends GetxService {
|
||||
/// a lokális adat megmarad (archiválható külön).
|
||||
Future<void> leaveSharedProject(Project project) async {
|
||||
final client = Supabase.instance.client;
|
||||
final user = client.auth.currentUser;
|
||||
if (user == null) return; // nincs bejelentkezve — nincs mit tenni
|
||||
await client
|
||||
.from('terepi_seged_project_members')
|
||||
.delete()
|
||||
.eq('project_id', project.uuid)
|
||||
.eq('user_id', client.auth.currentUser!.id);
|
||||
.eq('user_id', user.id);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -65,6 +65,18 @@ class NoteItemLabelLayer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Kapcsolatok - névvel a személy ikon felett
|
||||
for (final c in controller.contactsWithLocation) {
|
||||
markers.add(Marker(
|
||||
point: LatLng(c.contact.lat!, c.contact.lon!),
|
||||
width: 130,
|
||||
height: 48,
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
_LabelBubble(label: c.contact.name, color: Colors.indigo)
|
||||
])));
|
||||
}
|
||||
|
||||
if (markers.isEmpty) return const SizedBox.shrink();
|
||||
return MarkerLayer(markers: markers);
|
||||
});
|
||||
|
||||
@@ -184,8 +184,15 @@ class _ProjectTileState extends State<_ProjectTile> {
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
await svc.setActiveProject(project);
|
||||
Get.back();
|
||||
try {
|
||||
await svc.setActiveProject(project);
|
||||
Get.back();
|
||||
} catch (e) {
|
||||
Get.snackbar('Nem lehet aktiválni', e.toString(),
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user