Added Line Of Sight Feature for repeater placement, Added app wide Units Setting (#198)

* feat: add LOS workflow, global units, l10n cleanup, and mobile UI overflow fixes

Squashes prior PR commits into one changeset including: LOS map/service/tests, global metric/imperial unit system adoption, notification/BLE safety fixes, app-wide localization backfill/mojibake cleanup, and responsive UI title/overflow hardening.

* l10n: revert unrelated locale churn for LOS feature

* feat: keep LOS with app-wide unit settings

* fix: resolve post-merge app bar/import analyzer errors

* style: format screen files for CI
This commit is contained in:
just_stuff_tm
2026-02-21 01:08:23 -05:00
committed by GitHub
parent d2b693e5ce
commit f4b18d97a1
52 changed files with 6078 additions and 214 deletions
+89 -62
View File
@@ -58,11 +58,17 @@ class NotificationService {
requestBadgePermission: true,
requestSoundPermission: true,
);
const windowsSettings = WindowsInitializationSettings(
appName: 'MeshCore Open',
appUserModelId: 'org.meshcore.open.app',
guid: 'e7ea8f85-72f5-4f36-91f6-038f740ccf86',
);
const initSettings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
macOS: macSettings,
windows: windowsSettings,
);
try {
@@ -76,6 +82,13 @@ class NotificationService {
}
}
Future<bool> _ensureInitialized() async {
if (!_isInitialized) {
await initialize();
}
return _isInitialized;
}
Future<bool> requestPermissions() async {
if (!_isInitialized) {
await initialize();
@@ -114,9 +127,7 @@ class NotificationService {
String? contactId,
int? badgeCount,
}) async {
if (!_isInitialized) {
await initialize();
}
if (!await _ensureInitialized()) return;
final androidDetails = AndroidNotificationDetails(
'messages',
@@ -148,13 +159,17 @@ class NotificationService {
macOS: macDetails,
);
await _notifications.show(
id: contactId?.hashCode ?? 0,
title: contactName,
body: message,
notificationDetails: notificationDetails,
payload: 'message:$contactId',
);
try {
await _notifications.show(
id: contactId?.hashCode ?? 0,
title: contactName,
body: message,
notificationDetails: notificationDetails,
payload: 'message:$contactId',
);
} catch (e) {
debugPrint('Failed to show message notification: $e');
}
}
Future<void> _showAdvertNotificationImpl({
@@ -162,9 +177,7 @@ class NotificationService {
required String contactType,
String? contactId,
}) async {
if (!_isInitialized) {
await initialize();
}
if (!await _ensureInitialized()) return;
const androidDetails = AndroidNotificationDetails(
'adverts',
@@ -193,13 +206,17 @@ class NotificationService {
macOS: macDetails,
);
await _notifications.show(
id: contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
title: _l10n.notification_newTypeDiscovered(contactType),
body: contactName,
notificationDetails: notificationDetails,
payload: 'advert:$contactId',
);
try {
await _notifications.show(
id: contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
title: _l10n.notification_newTypeDiscovered(contactType),
body: contactName,
notificationDetails: notificationDetails,
payload: 'advert:$contactId',
);
} catch (e) {
debugPrint('Failed to show advert notification: $e');
}
}
Future<void> _showChannelMessageNotificationImpl({
@@ -208,9 +225,7 @@ class NotificationService {
int? channelIndex,
int? badgeCount,
}) async {
if (!_isInitialized) {
await initialize();
}
if (!await _ensureInitialized()) return;
final androidDetails = AndroidNotificationDetails(
'channel_messages',
@@ -247,13 +262,17 @@ class NotificationService {
? _l10n.notification_receivedNewMessage
: preview;
await _notifications.show(
id: channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
title: channelName,
body: body,
notificationDetails: notificationDetails,
payload: 'channel:$channelIndex',
);
try {
await _notifications.show(
id: channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
title: channelName,
body: body,
notificationDetails: notificationDetails,
payload: 'channel:$channelIndex',
);
} catch (e) {
debugPrint('Failed to show channel notification: $e');
}
}
/// Returns a privacy-safe identifier for debug logging.
@@ -396,35 +415,39 @@ class NotificationService {
Future<void> _showNotificationImmediately(
_PendingNotification notification,
) async {
switch (notification.type) {
case _NotificationType.message:
await _showMessageNotificationImpl(
contactName: notification.title,
message: notification.body,
contactId: notification.id,
badgeCount: notification.badgeCount,
);
break;
case _NotificationType.advert:
await _showAdvertNotificationImpl(
contactName: notification.body,
contactType: notification.title,
contactId: notification.id,
);
break;
case _NotificationType.channelMessage:
await _showChannelMessageNotificationImpl(
channelName: notification.title,
message: notification.body,
channelIndex: int.tryParse(notification.id ?? ''),
badgeCount: notification.badgeCount,
);
break;
try {
switch (notification.type) {
case _NotificationType.message:
await _showMessageNotificationImpl(
contactName: notification.title,
message: notification.body,
contactId: notification.id,
badgeCount: notification.badgeCount,
);
break;
case _NotificationType.advert:
await _showAdvertNotificationImpl(
contactName: notification.body,
contactType: notification.title,
contactId: notification.id,
);
break;
case _NotificationType.channelMessage:
await _showChannelMessageNotificationImpl(
channelName: notification.title,
message: notification.body,
channelIndex: int.tryParse(notification.id ?? ''),
badgeCount: notification.badgeCount,
);
break;
}
} catch (e) {
debugPrint('Failed to show immediate notification: $e');
}
}
Future<void> _showBatchSummary(List<_PendingNotification> batch) async {
if (!_isInitialized) await initialize();
if (!await _ensureInitialized()) return;
// Group by type
final messages = batch
@@ -468,13 +491,17 @@ class NotificationService {
const notificationDetails = NotificationDetails(android: androidDetails);
await _notifications.show(
id: 'batch_summary'.hashCode,
title: _l10n.notification_activityTitle,
body: parts.join(', '),
notificationDetails: notificationDetails,
payload: 'batch',
);
try {
await _notifications.show(
id: 'batch_summary'.hashCode,
title: _l10n.notification_activityTitle,
body: parts.join(', '),
notificationDetails: notificationDetails,
payload: 'batch',
);
} catch (e) {
debugPrint('Failed to show batch summary notification: $e');
}
}
}