mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Remove unused path_provider entry
This commit is contained in:
@@ -231,10 +231,10 @@ extension ContactLocalization on Contact {
|
||||
}
|
||||
|
||||
String get routeSummary {
|
||||
if (routeIsUnknown || !routeHasPath) {
|
||||
return 'Flood/Unknown';
|
||||
if (routeIsUnknown) {
|
||||
return 'Unknown';
|
||||
}
|
||||
if (routeHopCount == 0) {
|
||||
if (!routeHasPath || routeHopCount == 0) {
|
||||
return 'Direct';
|
||||
}
|
||||
return '$routeHopCount hop${routeHopCount == 1 ? '' : 's'} via $routeHashSize-byte hashes';
|
||||
|
||||
@@ -2015,6 +2015,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
try {
|
||||
_error = null;
|
||||
await _activeService.resetPath(contactPublicKey);
|
||||
} catch (e) {
|
||||
_error = 'Failed to reset path: $e';
|
||||
@@ -2034,6 +2035,7 @@ class ConnectionProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
try {
|
||||
_error = null;
|
||||
final updatedContact = contact.copyWith(
|
||||
outPathLen: signedEncodedPathLen,
|
||||
outPath: Uint8List.fromList(paddedPathBytes),
|
||||
|
||||
@@ -715,36 +715,33 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
|
||||
],
|
||||
),
|
||||
),
|
||||
// Settings cog - hidden in simple mode
|
||||
if (!context.watch<AppProvider>().isSimpleMode) ...[
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DeviceConfigScreen(),
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DeviceConfigScreen(),
|
||||
),
|
||||
);
|
||||
},
|
||||
onLongPress: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PacketLogScreen(
|
||||
bleService: provider.bleService,
|
||||
),
|
||||
);
|
||||
},
|
||||
onLongPress: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PacketLogScreen(
|
||||
bleService: provider.bleService,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(Icons.settings, size: 18),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(Icons.settings, size: 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -59,7 +59,7 @@ class MapTab extends StatefulWidget {
|
||||
|
||||
class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
final MapController _mapController = MapController();
|
||||
final TileCacheService _tileCache = TileCacheService();
|
||||
late final TileCacheService _tileCache;
|
||||
// DO NOT create a new LocationTrackingService instance here
|
||||
// Use the singleton from AppProvider instead via _locationService getter
|
||||
final MapMarkerService _markerService = MapMarkerService();
|
||||
@@ -117,6 +117,7 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tileCache = context.read<TileCacheService>();
|
||||
// Initialize Slovenian WMS layers with CRS
|
||||
_slovenianAerialLayer = MapLayer.getSlovenianAerial2024(slovenianCrs);
|
||||
_dtk25Layer = MapLayer.getDTK25(slovenianCrs);
|
||||
@@ -446,7 +447,6 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
// Restore the original callback instead of setting to null
|
||||
_locationService.onPositionUpdate = _originalLocationCallback;
|
||||
_mapController.dispose();
|
||||
_tileCache.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,47 +10,66 @@ class TileCacheService {
|
||||
|
||||
// Global flag to ensure ObjectBox is only initialized once
|
||||
static bool _objectBoxInitialized = false;
|
||||
static final _initLock = <String, Future<void>>{};
|
||||
static Future<void>? _objectBoxInitialization;
|
||||
|
||||
late final FMTCStore _store;
|
||||
Future<void>? _initializeFuture;
|
||||
FMTCStore? _store;
|
||||
bool _isInitialized = false;
|
||||
bool _isDownloading = false;
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
_initializeFuture ??= _initializeInternal();
|
||||
await _initializeFuture;
|
||||
}
|
||||
|
||||
// Ensure we only initialize ObjectBox once globally
|
||||
if (!_objectBoxInitialized) {
|
||||
// Use a lock to prevent concurrent initialization attempts
|
||||
final initFuture = _initLock.putIfAbsent('objectbox', () async {
|
||||
try {
|
||||
await FMTCObjectBoxBackend().initialise();
|
||||
_objectBoxInitialized = true;
|
||||
} catch (e) {
|
||||
// Already initialized or error - that's okay
|
||||
_objectBoxInitialized = true;
|
||||
}
|
||||
});
|
||||
await initFuture;
|
||||
}
|
||||
|
||||
Future<void> _initializeInternal() async {
|
||||
try {
|
||||
_store = FMTCStore(_storeName);
|
||||
await _store.manage.create();
|
||||
_isInitialized = true;
|
||||
} catch (e) {
|
||||
// Store might already exist
|
||||
_store = FMTCStore(_storeName);
|
||||
await _ensureObjectBoxInitialized();
|
||||
|
||||
final store = FMTCStore(_storeName);
|
||||
try {
|
||||
await store.manage.create();
|
||||
} catch (_) {
|
||||
// The store may already exist from a prior initialization.
|
||||
}
|
||||
|
||||
_store = store;
|
||||
_isInitialized = true;
|
||||
} catch (_) {
|
||||
_initializeFuture = null;
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
FMTCTileProvider getTileProvider(MapLayer layer) {
|
||||
if (!_isInitialized) {
|
||||
Future<void> _ensureObjectBoxInitialized() async {
|
||||
if (_objectBoxInitialized) return;
|
||||
|
||||
_objectBoxInitialization ??= () async {
|
||||
try {
|
||||
await FMTCObjectBoxBackend().initialise();
|
||||
} catch (_) {
|
||||
// Treat repeated backend initialization as a no-op.
|
||||
} finally {
|
||||
_objectBoxInitialized = true;
|
||||
}
|
||||
}();
|
||||
|
||||
await _objectBoxInitialization;
|
||||
}
|
||||
|
||||
FMTCStore _requireStore() {
|
||||
final store = _store;
|
||||
if (!_isInitialized || store == null) {
|
||||
throw StateError(
|
||||
'TileCacheService not initialized. Call initialize() first.',
|
||||
);
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
TileProvider getTileProvider(MapLayer layer) {
|
||||
_requireStore();
|
||||
return FMTCTileProvider(
|
||||
stores: {_storeName: BrowseStoreStrategy.readUpdateCreate},
|
||||
loadingStrategy: BrowseLoadingStrategy.cacheFirst,
|
||||
@@ -60,12 +79,8 @@ class TileCacheService {
|
||||
|
||||
/// Get tile provider for WMS layers with caching support
|
||||
/// WMS layers require special handling because they use WMSTileLayerOptions
|
||||
FMTCTileProvider getTileProviderForWms(MapLayer layer) {
|
||||
if (!_isInitialized) {
|
||||
throw StateError(
|
||||
'TileCacheService not initialized. Call initialize() first.',
|
||||
);
|
||||
}
|
||||
TileProvider getTileProviderForWms(MapLayer layer) {
|
||||
_requireStore();
|
||||
if (!layer.isWms) {
|
||||
throw ArgumentError('Layer must be a WMS layer');
|
||||
}
|
||||
@@ -97,6 +112,7 @@ class TileCacheService {
|
||||
}
|
||||
|
||||
_isDownloading = true;
|
||||
final store = _requireStore();
|
||||
|
||||
try {
|
||||
final region = RectangleRegion(bounds);
|
||||
@@ -107,7 +123,7 @@ class TileCacheService {
|
||||
options: TileLayer(urlTemplate: layer.urlTemplate),
|
||||
);
|
||||
|
||||
final download = _store.download.startForeground(region: downloadable);
|
||||
final download = store.download.startForeground(region: downloadable);
|
||||
|
||||
await for (final progress in download.downloadProgress) {
|
||||
if (onProgress != null && progress.maxTilesCount > 0) {
|
||||
@@ -127,24 +143,25 @@ class TileCacheService {
|
||||
|
||||
Future<void> cancelDownload() async {
|
||||
if (!_isInitialized) return;
|
||||
await _store.download.cancel();
|
||||
await _requireStore().download.cancel();
|
||||
}
|
||||
|
||||
Future<void> clearCache() async {
|
||||
if (!_isInitialized) return;
|
||||
await _store.manage.delete();
|
||||
await _store.manage.create();
|
||||
final store = _requireStore();
|
||||
await store.manage.delete();
|
||||
await store.manage.create();
|
||||
}
|
||||
|
||||
Future<int> getCachedTileCount() async {
|
||||
if (!_isInitialized) return 0;
|
||||
final stats = await _store.stats.length;
|
||||
final stats = await _requireStore().stats.length;
|
||||
return stats;
|
||||
}
|
||||
|
||||
Future<double> getCacheSizeMB() async {
|
||||
if (!_isInitialized) return 0.0;
|
||||
final stats = await _store.stats.size;
|
||||
final stats = await _requireStore().stats.size;
|
||||
return stats / (1024 * 1024);
|
||||
}
|
||||
|
||||
@@ -162,8 +179,9 @@ class TileCacheService {
|
||||
Future<Map<String, dynamic>> getStoreStats() async {
|
||||
if (!_isInitialized) return {};
|
||||
|
||||
final length = await _store.stats.length;
|
||||
final size = await _store.stats.all.then((a) => a.size);
|
||||
final store = _requireStore();
|
||||
final length = await store.stats.length;
|
||||
final size = await store.stats.all.then((a) => a.size);
|
||||
|
||||
return {
|
||||
'tileCount': length,
|
||||
|
||||
@@ -3,6 +3,18 @@ import 'package:flutter/material.dart';
|
||||
import '../../models/contact.dart';
|
||||
import '../../services/route_hash_preferences.dart';
|
||||
|
||||
class ContactRouteDialogResult {
|
||||
final ParsedContactRoute? route;
|
||||
final bool shouldClear;
|
||||
|
||||
const ContactRouteDialogResult._({this.route, required this.shouldClear});
|
||||
|
||||
const ContactRouteDialogResult.set(ParsedContactRoute route)
|
||||
: this._(route: route, shouldClear: false);
|
||||
|
||||
const ContactRouteDialogResult.clear() : this._(shouldClear: true);
|
||||
}
|
||||
|
||||
class ContactRouteDialog extends StatefulWidget {
|
||||
final Contact contact;
|
||||
final List<Contact> availableContacts;
|
||||
@@ -13,12 +25,12 @@ class ContactRouteDialog extends StatefulWidget {
|
||||
required this.availableContacts,
|
||||
});
|
||||
|
||||
static Future<ParsedContactRoute?> show(
|
||||
static Future<ContactRouteDialogResult?> show(
|
||||
BuildContext context, {
|
||||
required Contact contact,
|
||||
required List<Contact> availableContacts,
|
||||
}) {
|
||||
return showModalBottomSheet<ParsedContactRoute>(
|
||||
return showModalBottomSheet<ContactRouteDialogResult>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
showDragHandle: true,
|
||||
@@ -212,11 +224,20 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
if (widget.contact.routeHasPath)
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(
|
||||
context,
|
||||
).pop(const ContactRouteDialogResult.clear()),
|
||||
child: const Text('Clear Route'),
|
||||
),
|
||||
const Spacer(),
|
||||
FilledButton(
|
||||
onPressed: _parsedRoute == null
|
||||
? null
|
||||
: () => Navigator.of(context).pop(_parsedRoute),
|
||||
: () => Navigator.of(
|
||||
context,
|
||||
).pop(ContactRouteDialogResult.set(_parsedRoute!)),
|
||||
child: const Text('Set Route'),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -11,6 +11,7 @@ import '../../providers/map_provider.dart';
|
||||
import '../../providers/app_provider.dart';
|
||||
import 'contact_route_dialog.dart';
|
||||
import 'room_login_sheet.dart';
|
||||
import '../common/contact_avatar.dart';
|
||||
import '../../utils/location_formats.dart';
|
||||
import '../../utils/toast_logger.dart';
|
||||
import '../../utils/battery_display_helper.dart';
|
||||
@@ -147,54 +148,28 @@ class ContactTile extends StatelessWidget {
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.3,
|
||||
);
|
||||
final timeAgoText = _getLocalizedTimeSinceLastSeen(context);
|
||||
final timeAgoStyle = Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: contact.isRecentlySeen
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
final subtitleWidget = isSimpleMode
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.access_time,
|
||||
size: 12,
|
||||
color: contact.isRecentlySeen ? Colors.green : Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${AppLocalizations.of(context)!.lastSeen}: ${_getLocalizedTimeSinceLastSeen(context)}',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (location != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.location_on, size: 12, color: Colors.blue),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'GPS: ${location.latitude.toStringAsFixed(5)}, ${location.longitude.toStringAsFixed(5)}',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 2),
|
||||
_buildLocationLine(
|
||||
context,
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
distanceText: distanceText,
|
||||
),
|
||||
if (contact.type != ContactType.channel ||
|
||||
distanceText != null) ...[
|
||||
if (contact.type != ContactType.channel) ...[
|
||||
const SizedBox(height: 6),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 6,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
if (contact.type != ContactType.channel)
|
||||
_buildRoutePill(context, contact),
|
||||
if (distanceText != null)
|
||||
_buildDistancePill(context, distanceText),
|
||||
],
|
||||
),
|
||||
Row(children: [_buildRoutePill(context, contact)]),
|
||||
],
|
||||
] else
|
||||
Padding(
|
||||
@@ -285,40 +260,17 @@ class ContactTile extends StatelessWidget {
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.access_time,
|
||||
size: 12,
|
||||
color: contact.isRecentlySeen ? Colors.green : Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_getLocalizedTimeSinceLastSeen(context),
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
if (location != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
const Text('•', style: TextStyle(color: Colors.grey)),
|
||||
const SizedBox(width: 8),
|
||||
if (hasTelemetry)
|
||||
const Icon(Icons.sensors, size: 12, color: Colors.green)
|
||||
else
|
||||
const Icon(
|
||||
Icons.sensors_off,
|
||||
size: 12,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'GPS: ${location.latitude.toStringAsFixed(5)}, ${location.longitude.toStringAsFixed(5)}',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
child: _buildLocationLine(
|
||||
context,
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
distanceText: distanceText,
|
||||
telemetryActive: hasTelemetry,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(width: 8),
|
||||
const Text('•', style: TextStyle(color: Colors.grey)),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.sensors_off, size: 12, color: Colors.grey),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
@@ -328,20 +280,9 @@ class ContactTile extends StatelessWidget {
|
||||
],
|
||||
],
|
||||
),
|
||||
if (contact.type != ContactType.channel ||
|
||||
distanceText != null) ...[
|
||||
if (contact.type != ContactType.channel) ...[
|
||||
const SizedBox(height: 6),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 6,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
if (contact.type != ContactType.channel)
|
||||
_buildRoutePill(context, contact),
|
||||
if (distanceText != null)
|
||||
_buildDistancePill(context, distanceText),
|
||||
],
|
||||
),
|
||||
Row(children: [_buildRoutePill(context, contact)]),
|
||||
],
|
||||
],
|
||||
);
|
||||
@@ -378,51 +319,12 @@ class ContactTile extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
_getTypeColor(contact.type, context),
|
||||
_getTypeColor(
|
||||
contact.type,
|
||||
context,
|
||||
).withValues(alpha: 0.72),
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: _getTypeColor(
|
||||
contact.type,
|
||||
context,
|
||||
).withValues(alpha: 0.22),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: contact.roleEmoji != null
|
||||
? Text(
|
||||
contact.roleEmoji!,
|
||||
style: const TextStyle(fontSize: 22),
|
||||
)
|
||||
: Icon(
|
||||
_getTypeIcon(contact.type),
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
ContactAvatar(contact: contact, radius: 24),
|
||||
if (contact.isNew)
|
||||
Positioned(
|
||||
top: -2,
|
||||
@@ -480,26 +382,7 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest
|
||||
.withValues(alpha: 0.75),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
contact.type.displayName,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(timeAgoText, style: timeAgoStyle),
|
||||
if (!isSimpleMode && battery != null) ...[
|
||||
const SizedBox(width: 6),
|
||||
_buildBatteryBadge(context, battery),
|
||||
@@ -656,18 +539,7 @@ class ContactTile extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: _getTypeColor(contact.type, context),
|
||||
child: contact.roleEmoji != null
|
||||
? Text(
|
||||
contact.roleEmoji!,
|
||||
style: const TextStyle(fontSize: 24),
|
||||
)
|
||||
: Icon(
|
||||
_getTypeIcon(contact.type),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
ContactAvatar(contact: contact, radius: 20),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
@@ -1116,17 +988,42 @@ class ContactTile extends StatelessWidget {
|
||||
.where((candidate) => candidate.publicKeyHex != contact.publicKeyHex)
|
||||
.toList();
|
||||
|
||||
final parsedRoute = await ContactRouteDialog.show(
|
||||
final routeResult = await ContactRouteDialog.show(
|
||||
context,
|
||||
contact: contact,
|
||||
availableContacts: availableContacts,
|
||||
);
|
||||
if (parsedRoute == null || !context.mounted) {
|
||||
if (routeResult == null || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final previousSignedPathLen = contact.routeSignedPathLen;
|
||||
final previousPathBytes = Uint8List.fromList(contact.outPath);
|
||||
if (routeResult.shouldClear) {
|
||||
contactsProvider.resetContactRouteLocal(contact.publicKey);
|
||||
await connectionProvider.resetPath(contact.publicKey);
|
||||
final error = connectionProvider.error;
|
||||
if (error != null) {
|
||||
contactsProvider.setContactRouteLocal(
|
||||
contact.publicKey,
|
||||
signedEncodedPathLen: previousSignedPathLen,
|
||||
paddedPathBytes: previousPathBytes,
|
||||
);
|
||||
if (context.mounted) {
|
||||
ToastLogger.error(context, 'Failed to clear route: $error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Route cleared')));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final parsedRoute = routeResult.route!;
|
||||
contactsProvider.setContactRouteLocal(
|
||||
contact.publicKey,
|
||||
signedEncodedPathLen: parsedRoute.signedEncodedPathLen,
|
||||
@@ -1217,6 +1114,73 @@ class ContactTile extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLocationMeta(
|
||||
BuildContext context,
|
||||
double latitude,
|
||||
double longitude, {
|
||||
bool telemetryActive = true,
|
||||
}) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 18,
|
||||
height: 18,
|
||||
decoration: BoxDecoration(
|
||||
color: telemetryActive
|
||||
? colorScheme.primary.withValues(alpha: 0.12)
|
||||
: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Icon(
|
||||
telemetryActive
|
||||
? Icons.navigation_rounded
|
||||
: Icons.location_searching,
|
||||
size: 11,
|
||||
color: telemetryActive
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'${latitude.toStringAsFixed(5)}, ${longitude.toStringAsFixed(5)}',
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
fontFamily: 'monospace',
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
letterSpacing: 0.1,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLocationLine(
|
||||
BuildContext context, {
|
||||
required double latitude,
|
||||
required double longitude,
|
||||
String? distanceText,
|
||||
bool telemetryActive = true,
|
||||
}) {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 6,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
_buildLocationMeta(
|
||||
context,
|
||||
latitude,
|
||||
longitude,
|
||||
telemetryActive: telemetryActive,
|
||||
),
|
||||
if (distanceText != null) _buildDistancePill(context, distanceText),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to Degrees Minutes Seconds (DMS) format
|
||||
String _convertToDMS(double lat, double lon) {
|
||||
String latDir = lat >= 0 ? 'N' : 'S';
|
||||
@@ -1273,19 +1237,6 @@ class ContactTile extends StatelessWidget {
|
||||
return '$zone$letter (approximate)';
|
||||
}
|
||||
|
||||
IconData _getTypeIcon(ContactType type) {
|
||||
switch (type) {
|
||||
case ContactType.chat:
|
||||
return Icons.person;
|
||||
case ContactType.repeater:
|
||||
return Icons.router;
|
||||
case ContactType.room:
|
||||
return Icons.tag;
|
||||
default:
|
||||
return Icons.help;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getTypeColor(ContactType type, BuildContext context) {
|
||||
switch (type) {
|
||||
case ContactType.chat:
|
||||
@@ -1411,6 +1362,7 @@ class ContactTile extends StatelessWidget {
|
||||
|
||||
Widget _buildRoutePill(BuildContext context, Contact contact) {
|
||||
final hasPath = contact.routeHasPath;
|
||||
final isDirect = !hasPath || contact.routeHopCount <= 0;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final textColor = Theme.of(
|
||||
context,
|
||||
@@ -1418,9 +1370,7 @@ class ContactTile extends StatelessWidget {
|
||||
final iconColor = Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.color?.withValues(alpha: 0.7);
|
||||
final label = !hasPath
|
||||
? AppLocalizations.of(context)!.flood
|
||||
: contact.routeHopCount <= 0
|
||||
final label = isDirect
|
||||
? AppLocalizations.of(context)!.direct
|
||||
: contact.routeCanonicalText;
|
||||
|
||||
@@ -1434,7 +1384,7 @@ class ContactTile extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
hasPath ? Icons.alt_route : Icons.waves,
|
||||
isDirect ? Icons.north_east_rounded : Icons.alt_route,
|
||||
size: 11,
|
||||
color: iconColor,
|
||||
),
|
||||
@@ -1447,9 +1397,7 @@ class ContactTile extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: hasPath && contact.routeHopCount > 0
|
||||
? 'monospace'
|
||||
: null,
|
||||
fontFamily: !isDirect ? 'monospace' : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1479,7 +1427,7 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Text(
|
||||
'${AppLocalizations.of(context)!.distance}: $distanceText',
|
||||
distanceText,
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
|
||||
Reference in New Issue
Block a user