Kml parser utf-8 támogatás, induláskor minimum app verzió ellenőrzése
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
//
|
//
|
||||||
// KMZ = ZIP archív, benne doc.kml
|
// KMZ = ZIP archív, benne doc.kml
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:archive/archive.dart';
|
import 'package:archive/archive.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -17,6 +18,55 @@ import 'package:xml/xml.dart';
|
|||||||
import '../models/imported_layer.dart';
|
import '../models/imported_layer.dart';
|
||||||
|
|
||||||
class KmlParser {
|
class KmlParser {
|
||||||
|
/// KML szöveg dekódolása a fájlban DEKLARÁLT kódolással (vagy UTF-8,
|
||||||
|
/// ha nincs deklaráció — ez az XML alapértelmezése).
|
||||||
|
///
|
||||||
|
/// * UTF-8 deklaráció (vagy annak hiánya) → UTF-8 dekódolás; ha ez
|
||||||
|
/// mégis érvénytelen bájtsorozatra futna (rosszul címkézett fájl),
|
||||||
|
/// Latin-1-re esünk vissza — az minden bájt-értékre ad valamilyen
|
||||||
|
/// karaktert, nem dob kivételt.
|
||||||
|
/// * Explicit ISO-8859-1 / Windows-1252 deklaráció → Latin-1.
|
||||||
|
/// * Egyéb, Dart alapkönyvtára által NEM ismert kódolás (pl.
|
||||||
|
/// ISO-8859-2, Windows-1250 — ezek adnák helyesen az ő/ű betűket
|
||||||
|
/// egy esetleges nem-UTF-8 exportnál) → egyértelmű hibaüzenet,
|
||||||
|
/// ahelyett hogy csendben félrement szöveget adnánk.
|
||||||
|
static String decodeText(List<int> bytes) {
|
||||||
|
final declared = _detectDeclaredEncoding(bytes);
|
||||||
|
|
||||||
|
if (declared == null ||
|
||||||
|
declared.contains('utf-8') ||
|
||||||
|
declared.contains('utf8')) {
|
||||||
|
try {
|
||||||
|
return utf8.decode(bytes);
|
||||||
|
} catch (_) {
|
||||||
|
return latin1.decode(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declared.contains('8859-1') ||
|
||||||
|
declared.contains('latin1') ||
|
||||||
|
declared.contains('windows-1252') ||
|
||||||
|
declared.contains('cp1252')) {
|
||||||
|
return latin1.decode(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declared == 'ascii' || declared == 'us-ascii') {
|
||||||
|
return ascii.decode(bytes, allowInvalid: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FormatException(
|
||||||
|
'Ismeretlen vagy nem támogatott KML kódolás a fájlban: '
|
||||||
|
'"$declared". Exportáld a fájlt UTF-8 kódolással.');
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? _detectDeclaredEncoding(List<int> bytes) {
|
||||||
|
final headLen = bytes.length < 200 ? bytes.length : 200;
|
||||||
|
final head = ascii.decode(bytes.sublist(0, headLen), allowInvalid: true);
|
||||||
|
final match =
|
||||||
|
RegExp(r'''encoding\s*=\s*["']([^"']+)["']''').firstMatch(head);
|
||||||
|
return match?.group(1)?.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
// Alapértelmezett stílusok — felülírhatók konstruktorban
|
// Alapértelmezett stílusok — felülírhatók konstruktorban
|
||||||
final Color defaultPolylineColor;
|
final Color defaultPolylineColor;
|
||||||
final double defaultPolylineStroke;
|
final double defaultPolylineStroke;
|
||||||
@@ -46,7 +96,8 @@ class KmlParser {
|
|||||||
orElse: () => throw Exception('Nem található .kml fájl a KMZ-ben'),
|
orElse: () => throw Exception('Nem található .kml fájl a KMZ-ben'),
|
||||||
);
|
);
|
||||||
|
|
||||||
final kmlString = String.fromCharCodes(kmlFile.content as List<int>);
|
//final kmlString = String.fromCharCodes(kmlFile.content as List<int>);
|
||||||
|
final kmlString = utf8.decode(kmlFile.content as List<int>);
|
||||||
return _parse(kmlString, fileName, LayerImportSourceType.kmz,
|
return _parse(kmlString, fileName, LayerImportSourceType.kmz,
|
||||||
id: id, isVisible: isVisible);
|
id: id, isVisible: isVisible);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
|
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
|
||||||
|
import 'package:terepi_seged/pages/update_required/presentation/views/update_required_view.dart';
|
||||||
import 'package:terepi_seged/routes/app_pages.dart';
|
import 'package:terepi_seged/routes/app_pages.dart';
|
||||||
import 'package:terepi_seged/services/app_database.dart';
|
import 'package:terepi_seged/services/app_database.dart';
|
||||||
import 'package:terepi_seged/services/app_logger.dart';
|
import 'package:terepi_seged/services/app_logger.dart';
|
||||||
@@ -29,6 +30,7 @@ import 'package:terepi_seged/services/stakeout_sync_service.dart';
|
|||||||
import 'package:terepi_seged/services/tilt_service.dart';
|
import 'package:terepi_seged/services/tilt_service.dart';
|
||||||
import 'package:terepi_seged/services/track_sync_service.dart';
|
import 'package:terepi_seged/services/track_sync_service.dart';
|
||||||
import 'package:terepi_seged/services/ts_sync_service.dart';
|
import 'package:terepi_seged/services/ts_sync_service.dart';
|
||||||
|
import 'package:terepi_seged/services/version_gate_service.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@@ -62,6 +64,15 @@ Future<void> main() async {
|
|||||||
url: dotenv.env['SUPABASE_URL']!,
|
url: dotenv.env['SUPABASE_URL']!,
|
||||||
anonKey: dotenv.env['SUPABASE_ANON_KEY']!);
|
anonKey: dotenv.env['SUPABASE_ANON_KEY']!);
|
||||||
|
|
||||||
|
final versionGate = await checkVersionGate();
|
||||||
|
if (versionGate.blocked) {
|
||||||
|
runApp(MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
home: UpdateRequiredView(result: versionGate),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Get.put(AuthService());
|
Get.put(AuthService());
|
||||||
Get.put(DeviceIdentityService(), permanent: true);
|
Get.put(DeviceIdentityService(), permanent: true);
|
||||||
await AppDatabase.instance.database;
|
await AppDatabase.instance.database;
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:terepi_seged/services/version_gate_service.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:app_settings/app_settings.dart';
|
||||||
|
|
||||||
|
/// Blokkoló "frissítés szükséges" képernyő — ez az EGYETLEN dolog, amit
|
||||||
|
/// az app mutat, ha a build a minimum alatt van. A rendes appot (GetX
|
||||||
|
/// service-ek, routing, shell) ilyenkor a main.dart el sem indítja.
|
||||||
|
class UpdateRequiredView extends StatelessWidget {
|
||||||
|
final VersionGateResult result;
|
||||||
|
const UpdateRequiredView({super.key, required this.result});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(28),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.system_update_alt,
|
||||||
|
size: 72, color: Colors.orange),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
'Frissítés szükséges',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
result.message,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 15),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 28),
|
||||||
|
if (result.updateUrl.isNotEmpty)
|
||||||
|
FilledButton.icon(
|
||||||
|
icon: const Icon(Icons.download),
|
||||||
|
label: const Text('Frissítés'),
|
||||||
|
onPressed: () => launchUrl(
|
||||||
|
Uri.parse(result.updateUrl),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextButton.icon(
|
||||||
|
icon: const Icon(Icons.settings_outlined, size: 18),
|
||||||
|
label: const Text('Alkalmazás beállításainak megnyitása'),
|
||||||
|
onPressed: () => AppSettings.openAppSettings(
|
||||||
|
type: AppSettingsType.settings),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'(ha belső tesztelőként a Play Áruház link még nem elérhető)',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.grey.shade600),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
// SQLite → imported_layers tábla (metadata)
|
// SQLite → imported_layers tábla (metadata)
|
||||||
// Supabase → Storage (megosztás, később)
|
// Supabase → Storage (megosztás, később)
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
@@ -13,6 +14,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:terepi_seged/enums/layer_import_source_type.dart';
|
import 'package:terepi_seged/enums/layer_import_source_type.dart';
|
||||||
|
import 'package:terepi_seged/services/layer_sync_service.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../models/imported_layer.dart';
|
import '../models/imported_layer.dart';
|
||||||
@@ -201,6 +203,23 @@ class LayerImportService extends GetxService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Réteg megosztása a projekt csapatával (feltöltés a Storage-ba +
|
||||||
|
/// metaadat a terepi_seged_layers táblába).
|
||||||
|
Future<void> publishLayer(String id) async {
|
||||||
|
if (!Get.isRegistered<LayerSyncService>()) return;
|
||||||
|
final projectUuid = ProjectService.to.activeProject.value?.uuid;
|
||||||
|
if (projectUuid == null) {
|
||||||
|
throw Exception('Nincs aktív (megosztott) projekt.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final metas = await AppDatabase.instance.listImportedLayers();
|
||||||
|
final meta = metas.firstWhereOrNull((m) => m.id == id);
|
||||||
|
if (meta == null) throw Exception('A réteg nem található.');
|
||||||
|
|
||||||
|
await LayerSyncService.to
|
||||||
|
.publishLayer(layer: meta, projectUuid: projectUuid);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> removeLayer(String id) async {
|
Future<void> removeLayer(String id) async {
|
||||||
final metas = await AppDatabase.instance.listImportedLayers();
|
final metas = await AppDatabase.instance.listImportedLayers();
|
||||||
final meta = metas.firstWhereOrNull((m) => m.id == id);
|
final meta = metas.firstWhereOrNull((m) => m.id == id);
|
||||||
@@ -259,7 +278,9 @@ class LayerImportService extends GetxService {
|
|||||||
return _parseGeoJson(String.fromCharCodes(bytes), name, id,
|
return _parseGeoJson(String.fromCharCodes(bytes), name, id,
|
||||||
isVisible: isVisible);
|
isVisible: isVisible);
|
||||||
case 'kml':
|
case 'kml':
|
||||||
return _kmlParser.parseKml(String.fromCharCodes(bytes), name,
|
// return _kmlParser.parseKml(String.fromCharCodes(bytes), name,
|
||||||
|
// id: id, isVisible: isVisible);
|
||||||
|
return _kmlParser.parseKml(KmlParser.decodeText(bytes), name,
|
||||||
id: id, isVisible: isVisible);
|
id: id, isVisible: isVisible);
|
||||||
case 'kmz':
|
case 'kmz':
|
||||||
return _kmlParser.parseKmz(bytes, name, id: id, isVisible: isVisible);
|
return _kmlParser.parseKmz(bytes, name, id: id, isVisible: isVisible);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
import 'package:terepi_seged/services/app_logger.dart';
|
||||||
import 'package:terepi_seged/services/layer_import_service.dart';
|
import 'package:terepi_seged/services/layer_import_service.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ class LayerSyncService extends GetxService {
|
|||||||
static const bucket = 'terepi_seged-layers';
|
static const bucket = 'terepi_seged-layers';
|
||||||
|
|
||||||
final isSyncing = false.obs;
|
final isSyncing = false.obs;
|
||||||
|
final lastError = Rxn<String>();
|
||||||
|
|
||||||
SupabaseClient get _client => Supabase.instance.client;
|
SupabaseClient get _client => Supabase.instance.client;
|
||||||
AppDatabase get _db => AppDatabase.instance;
|
AppDatabase get _db => AppDatabase.instance;
|
||||||
@@ -55,10 +57,14 @@ class LayerSyncService extends GetxService {
|
|||||||
// Csak a szinkronizált (nem lokális) projektek rétegei érdekelnek.
|
// Csak a szinkronizált (nem lokális) projektek rétegei érdekelnek.
|
||||||
final projects = await _db.listProjects();
|
final projects = await _db.listProjects();
|
||||||
for (final project in projects.where((pr) => !pr.isLocalOnly)) {
|
for (final project in projects.where((pr) => !pr.isLocalOnly)) {
|
||||||
await _pullProjectLayers(project.id!, project.uuid);
|
try {
|
||||||
|
await _pullProjectLayers(project.id!, project.uuid);
|
||||||
|
} catch (e) {
|
||||||
|
// EGY projekt rétegeinek hibája ne akassza meg a többi
|
||||||
|
// projektet (vagy a háttér-szinkron többi lépését).
|
||||||
|
lastError.value = 'Réteg-szinkron hiba (${project.name}): $e';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Ha közben egy csapattárs frissített/megosztott egy réteget, ez
|
|
||||||
// jelenjen meg a térképen azonnal, újraindítás nélkül.
|
|
||||||
if (Get.isRegistered<LayerImportService>()) {
|
if (Get.isRegistered<LayerImportService>()) {
|
||||||
await LayerImportService.to.reload();
|
await LayerImportService.to.reload();
|
||||||
}
|
}
|
||||||
@@ -77,34 +83,54 @@ class LayerSyncService extends GetxService {
|
|||||||
|
|
||||||
for (final r in rows) {
|
for (final r in rows) {
|
||||||
final id = r['id'] as String;
|
final id = r['id'] as String;
|
||||||
final remoteVersion = (r['version'] as int?) ?? 1;
|
try {
|
||||||
final localVersion = await _db.getImportedLayerVersion(id);
|
final remoteVersion = (r['version'] as int?) ?? 1;
|
||||||
|
final localVersion = await _db.getImportedLayerVersion(id);
|
||||||
|
|
||||||
// Már megvan a legfrissebb verzió → nincs teendő.
|
// Már megvan a legfrissebb verzió → nincs teendő.
|
||||||
if (localVersion >= remoteVersion) continue;
|
if (localVersion >= remoteVersion) continue;
|
||||||
|
|
||||||
// Letöltés a Storage-ból, lokális cache-be.
|
// Letöltés a Storage-ból, lokális cache-be.
|
||||||
final storagePath = r['storage_path'] as String;
|
final storagePath = r['storage_path'] as String;
|
||||||
final bytes = await _client.storage.from(bucket).download(storagePath);
|
final bytes = await _client.storage.from(bucket).download(storagePath);
|
||||||
|
|
||||||
final dir = await _layerDir();
|
// A helyi fájl kiterjesztése kövesse a TÉNYLEGES forrás-típust —
|
||||||
final localPath = p.join(dir.path, '$id.geojson');
|
// eddig mindig .geojson-nak mentette, ami a KML/KMZ rétegeket
|
||||||
await File(localPath).writeAsBytes(bytes, flush: true);
|
// a GeoJSON-értelmezőre terelte (nyers zip-bájtok jsonDecode-ra).
|
||||||
|
final sourceType = (r['source_type'] as String?) ?? 'geojson';
|
||||||
|
final ext = switch (sourceType) {
|
||||||
|
'kml' => 'kml',
|
||||||
|
'kmz' => 'kmz',
|
||||||
|
_ => 'geojson',
|
||||||
|
};
|
||||||
|
|
||||||
// Metaadat upsert a meglévő imported_layers táblába — a térkép
|
final dir = await _layerDir();
|
||||||
// rétegkezelője innentől ugyanúgy látja, mint a kézi importot.
|
final localPath = p.join(dir.path, '$id.$ext');
|
||||||
await _db.upsertImportedLayerRaw({
|
await File(localPath).writeAsBytes(bytes, flush: true);
|
||||||
'id': id,
|
|
||||||
'name': r['name'],
|
// Metaadat upsert a meglévő imported_layers táblába — a térkép
|
||||||
'source_type': r['source_type'] ?? 'geojson',
|
// rétegkezelője innentől ugyanúgy látja, mint a kézi importot.
|
||||||
'local_path': localPath,
|
await _db.upsertImportedLayerRaw({
|
||||||
'storage_path': storagePath,
|
'id': id,
|
||||||
'is_visible': 1,
|
'name': r['name'],
|
||||||
'project_id': localProjectId,
|
'source_type': sourceType,
|
||||||
'imported_at': r['created_at'] ?? DateTime.now().toIso8601String(),
|
'local_path': localPath,
|
||||||
'synced_at': DateTime.now().toIso8601String(),
|
'storage_path': storagePath,
|
||||||
'version': remoteVersion,
|
'is_visible': 1,
|
||||||
});
|
'project_id': localProjectId,
|
||||||
|
'imported_at': r['created_at'] ?? DateTime.now().toIso8601String(),
|
||||||
|
'synced_at': DateTime.now().toIso8601String(),
|
||||||
|
'version': remoteVersion,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// EGY réteg hibája (jogosultság, hiányzó storage-fájl, hálózat)
|
||||||
|
// ne akassza meg a TÖBBI réteg letöltését — csak ezt hagyjuk ki,
|
||||||
|
// a következő ciklus újrapróbálja (a verziószám nem lép, tehát
|
||||||
|
// nem "felejtődik el" a hiba, csak nem blokkol tovább).
|
||||||
|
lastError.value = 'Réteg letöltési hiba ($id): $e';
|
||||||
|
print(lastError.value);
|
||||||
|
AppLogger.e('LAYER_SYNC', lastError.value!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
|
/// Az app fallback csomagneve, ha a konfigurációs táblában nincs
|
||||||
|
/// (vagy még nem tölthető le) update_url.
|
||||||
|
const _fallbackUpdateUrl =
|
||||||
|
'https://play.google.com/store/apps/details?id=hu.app_dev.terepi_seged';
|
||||||
|
|
||||||
|
class VersionGateResult {
|
||||||
|
final bool blocked;
|
||||||
|
final String message;
|
||||||
|
final String updateUrl;
|
||||||
|
|
||||||
|
const VersionGateResult({
|
||||||
|
required this.blocked,
|
||||||
|
this.message = '',
|
||||||
|
this.updateUrl = '',
|
||||||
|
});
|
||||||
|
|
||||||
|
static const allowed = VersionGateResult(blocked: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// App-indításkor lefutó minimum-build ellenőrzés.
|
||||||
|
///
|
||||||
|
/// FONTOS — az app offline-first: ha a lekérdezés BÁRMI okból nem
|
||||||
|
/// teljesül (nincs net, időtúllépés, a tábla/kulcs még nem létezik,
|
||||||
|
/// bármilyen váratlan hiba), az ellenőrzés MINDIG átenged (fail-open).
|
||||||
|
/// Kizárólag akkor blokkol, ha a szervertől egyértelmű, jól formázott
|
||||||
|
/// választ kapott, hogy a jelenlegi build a minimum alatt van.
|
||||||
|
Future<VersionGateResult> checkVersionGate() async {
|
||||||
|
try {
|
||||||
|
final rows = await Supabase.instance.client
|
||||||
|
.from('terepi_seged_app_config')
|
||||||
|
.select('key, value')
|
||||||
|
.inFilter('key', const [
|
||||||
|
'min_build_number',
|
||||||
|
'update_message',
|
||||||
|
'update_url',
|
||||||
|
]).timeout(const Duration(seconds: 4));
|
||||||
|
|
||||||
|
final config = {
|
||||||
|
for (final r in rows) r['key'] as String: r['value'] as String,
|
||||||
|
};
|
||||||
|
|
||||||
|
final minBuild = int.tryParse(config['min_build_number'] ?? '');
|
||||||
|
if (minBuild == null) {
|
||||||
|
// Nincs (érvényesen) beállítva minimum — nincs mit ellenőrizni.
|
||||||
|
return VersionGateResult.allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
final info = await PackageInfo.fromPlatform();
|
||||||
|
final currentBuild = int.tryParse(info.buildNumber);
|
||||||
|
if (currentBuild == null) {
|
||||||
|
// A saját build-számunkat sem sikerült megállapítani — ez a mi
|
||||||
|
// hibánk, nem a felhasználóé; nem blokkolunk emiatt.
|
||||||
|
return VersionGateResult.allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBuild < minBuild) {
|
||||||
|
return VersionGateResult(
|
||||||
|
blocked: true,
|
||||||
|
message: config['update_message'] ??
|
||||||
|
'Az alkalmazás egy régebbi verzióját használod, ami már nem '
|
||||||
|
'kompatibilis a szerverrel. Kérjük, telepítsd a legfrissebb '
|
||||||
|
'verziót a folytatáshoz.',
|
||||||
|
updateUrl: config['update_url'] ?? _fallbackUpdateUrl,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return VersionGateResult.allowed;
|
||||||
|
} catch (_) {
|
||||||
|
// Hálózati hiba, timeout, hiányzó tábla stb. → MINDIG átenged.
|
||||||
|
return VersionGateResult.allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import 'package:flutter_map/flutter_map.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:terepi_seged/enums/layer_import_source_type.dart';
|
import 'package:terepi_seged/enums/layer_import_source_type.dart';
|
||||||
|
import 'package:terepi_seged/services/project_service.dart';
|
||||||
|
|
||||||
import '../../models/imported_layer.dart';
|
import '../../models/imported_layer.dart';
|
||||||
import '../../services/layer_import_service.dart';
|
import '../../services/layer_import_service.dart';
|
||||||
@@ -203,6 +204,15 @@ class _LayerTile extends StatelessWidget {
|
|||||||
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
|
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
|
||||||
color: Colors.grey.shade500,
|
color: Colors.grey.shade500,
|
||||||
),
|
),
|
||||||
|
if (!(ProjectService.to.activeProject.value?.isLocalOnly ?? true))
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.cloud_upload_outlined, size: 16),
|
||||||
|
onPressed: () => _publish(context, layer),
|
||||||
|
tooltip: 'Megosztás a csapattal',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
|
||||||
|
color: Colors.blue.shade400,
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.palette_outlined, size: 16),
|
icon: const Icon(Icons.palette_outlined, size: 16),
|
||||||
onPressed: () => _openStyleSheet(context, layer),
|
onPressed: () => _openStyleSheet(context, layer),
|
||||||
@@ -250,6 +260,17 @@ class _LayerTile extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _publish(BuildContext context, ImportedLayer layer) async {
|
||||||
|
try {
|
||||||
|
await LayerImportService.to.publishLayer(layer.id);
|
||||||
|
Get.snackbar('Megosztva', '${layer.name} elérhető a csapattársaknak.',
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar('Megosztási hiba', e.toString(),
|
||||||
|
snackPosition: SnackPosition.BOTTOM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _stats() {
|
String _stats() {
|
||||||
final parts = <String>[];
|
final parts = <String>[];
|
||||||
if (layer.markers.isNotEmpty) parts.add('${layer.markers.length} pont');
|
if (layer.markers.isNotEmpty) parts.add('${layer.markers.length} pont');
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ dependencies:
|
|||||||
wakelock_plus: ^1.2.11
|
wakelock_plus: ^1.2.11
|
||||||
sensors_plus: ^7.1.0
|
sensors_plus: ^7.1.0
|
||||||
url_launcher: ^6.3.2
|
url_launcher: ^6.3.2
|
||||||
|
app_settings: ^7.0.0
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Reference in New Issue
Block a user