mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-25 11:52:53 +10:00
Merge main into chrome/main
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../storage/prefs_manager.dart';
|
||||
|
||||
/// Client-side accessibility/UI service that exposes a persistent shared text scale
|
||||
/// factor. No MeshCoreConnector/RoomServer or protocol interaction occurs, and the
|
||||
/// value is saved locally via SharedPreferences so it can be reused in Markdown
|
||||
/// viewers, log panels, or other text-heavy widgets without redundant network
|
||||
/// dependencies.
|
||||
///
|
||||
/// Widgets should scope rebuilds using the snippet below so only the scaled text
|
||||
/// is rebuilt instead of the entire chat list:
|
||||
/// ```dart
|
||||
/// context.select<ChatTextScaleService, double>(
|
||||
/// (service) => service.scale,
|
||||
/// )
|
||||
/// ```
|
||||
class ChatTextScaleService extends ChangeNotifier {
|
||||
static const _prefKey = 'chat_text_scale';
|
||||
static const double _minScale = 0.8;
|
||||
static const double _maxScale = 1.8;
|
||||
|
||||
double _scale = 1.0;
|
||||
Timer? _saveTimer;
|
||||
|
||||
double get scale => _scale;
|
||||
|
||||
Future<void> initialize() async {
|
||||
final stored = PrefsManager.instance.getDouble(_prefKey);
|
||||
if (stored != null) {
|
||||
_scale = _clamp(stored);
|
||||
}
|
||||
}
|
||||
|
||||
void setScale(double value, {bool persistImmediately = false}) {
|
||||
final next = _clamp(value);
|
||||
if (next == _scale) return;
|
||||
_scale = next;
|
||||
notifyListeners();
|
||||
if (persistImmediately) {
|
||||
_commitScale();
|
||||
} else {
|
||||
_scheduleSave();
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
setScale(1.0, persistImmediately: true);
|
||||
}
|
||||
|
||||
void persist() => _commitScale();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_saveTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scheduleSave() {
|
||||
_saveTimer?.cancel();
|
||||
_saveTimer = Timer(const Duration(milliseconds: 250), _commitScale);
|
||||
}
|
||||
|
||||
void _commitScale() {
|
||||
_saveTimer?.cancel();
|
||||
PrefsManager.instance.setDouble(_prefKey, _scale);
|
||||
}
|
||||
|
||||
double _clamp(double value) => value.clamp(_minScale, _maxScale).toDouble();
|
||||
}
|
||||
@@ -12,12 +12,14 @@ class LineOfSightSample {
|
||||
final double distanceMeters;
|
||||
final double terrainMeters;
|
||||
final double lineHeightMeters;
|
||||
final double refractedHeightMeters;
|
||||
final double clearanceMeters;
|
||||
|
||||
const LineOfSightSample({
|
||||
required this.distanceMeters,
|
||||
required this.terrainMeters,
|
||||
required this.lineHeightMeters,
|
||||
required this.refractedHeightMeters,
|
||||
required this.clearanceMeters,
|
||||
});
|
||||
}
|
||||
@@ -30,6 +32,8 @@ class LineOfSightResult {
|
||||
final double? firstObstructionDistanceMeters;
|
||||
final List<LineOfSightSample> samples;
|
||||
final String? errorMessage;
|
||||
final double usedKFactor;
|
||||
final double? frequencyMHz;
|
||||
|
||||
const LineOfSightResult({
|
||||
required this.hasData,
|
||||
@@ -38,12 +42,16 @@ class LineOfSightResult {
|
||||
required this.maxObstructionMeters,
|
||||
required this.firstObstructionDistanceMeters,
|
||||
required this.samples,
|
||||
required this.usedKFactor,
|
||||
this.frequencyMHz,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
const LineOfSightResult.error({
|
||||
required this.totalDistanceMeters,
|
||||
required this.errorMessage,
|
||||
this.usedKFactor = 4.0 / 3.0,
|
||||
this.frequencyMHz,
|
||||
}) : hasData = false,
|
||||
isClear = false,
|
||||
maxObstructionMeters = 0,
|
||||
@@ -89,6 +97,11 @@ class LineOfSightService {
|
||||
static const Duration _cacheTtl = Duration(hours: 24);
|
||||
static const int _maxFetchAttempts = 4; // initial try + 3 retries
|
||||
static const Duration _initialBackoff = Duration(milliseconds: 300);
|
||||
static const double _baselineFrequencyMHz = 915.0;
|
||||
static const double _baselineKFactor = 4.0 / 3.0;
|
||||
|
||||
static double get baselineFrequencyMHz => _baselineFrequencyMHz;
|
||||
static double get baselineKFactor => _baselineKFactor;
|
||||
|
||||
final http.Client _httpClient;
|
||||
final bool _ownsHttpClient;
|
||||
@@ -106,7 +119,7 @@ class LineOfSightService {
|
||||
List<LatLng> points, {
|
||||
double startAntennaHeightMeters = 1.5,
|
||||
double endAntennaHeightMeters = 1.5,
|
||||
double kFactor = 4.0 / 3.0,
|
||||
double? frequencyMHz,
|
||||
double obstructionToleranceMeters = 0.0,
|
||||
}) async {
|
||||
if (points.length < 2) {
|
||||
@@ -123,6 +136,7 @@ class LineOfSightService {
|
||||
var blockedSegments = 0;
|
||||
var unknownSegments = 0;
|
||||
|
||||
final kFactor = _kFactorForFrequency(frequencyMHz);
|
||||
for (int i = 0; i < points.length - 1; i++) {
|
||||
final result = await analyzeLink(
|
||||
points[i],
|
||||
@@ -130,6 +144,7 @@ class LineOfSightService {
|
||||
startAntennaHeightMeters: startAntennaHeightMeters,
|
||||
endAntennaHeightMeters: endAntennaHeightMeters,
|
||||
kFactor: kFactor,
|
||||
frequencyMHz: frequencyMHz,
|
||||
obstructionToleranceMeters: obstructionToleranceMeters,
|
||||
);
|
||||
segments.add(
|
||||
@@ -163,7 +178,8 @@ class LineOfSightService {
|
||||
LatLng end, {
|
||||
double startAntennaHeightMeters = 1.5,
|
||||
double endAntennaHeightMeters = 1.5,
|
||||
double kFactor = 4.0 / 3.0,
|
||||
required double kFactor,
|
||||
double? frequencyMHz,
|
||||
double obstructionToleranceMeters = 0.0,
|
||||
}) async {
|
||||
final totalDistanceMeters = _distance.as(LengthUnit.Meter, start, end);
|
||||
@@ -175,6 +191,8 @@ class LineOfSightService {
|
||||
maxObstructionMeters: 0,
|
||||
firstObstructionDistanceMeters: null,
|
||||
samples: const [],
|
||||
usedKFactor: kFactor,
|
||||
frequencyMHz: frequencyMHz,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,6 +203,8 @@ class LineOfSightService {
|
||||
return LineOfSightResult.error(
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
errorMessage: errorElevationUnavailable,
|
||||
usedKFactor: kFactor,
|
||||
frequencyMHz: frequencyMHz,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,6 +214,7 @@ class LineOfSightService {
|
||||
startAntennaHeightMeters: startAntennaHeightMeters,
|
||||
endAntennaHeightMeters: endAntennaHeightMeters,
|
||||
kFactor: kFactor,
|
||||
frequencyMHz: frequencyMHz,
|
||||
obstructionToleranceMeters: obstructionToleranceMeters,
|
||||
);
|
||||
}
|
||||
@@ -203,13 +224,16 @@ class LineOfSightService {
|
||||
required List<double> elevations,
|
||||
double startAntennaHeightMeters = 1.5,
|
||||
double endAntennaHeightMeters = 1.5,
|
||||
double kFactor = 4.0 / 3.0,
|
||||
required double kFactor,
|
||||
double? frequencyMHz,
|
||||
double obstructionToleranceMeters = 0.0,
|
||||
}) {
|
||||
if (points.length < 2 || elevations.length != points.length) {
|
||||
return const LineOfSightResult.error(
|
||||
return LineOfSightResult.error(
|
||||
totalDistanceMeters: 0,
|
||||
errorMessage: errorInvalidInput,
|
||||
usedKFactor: kFactor,
|
||||
frequencyMHz: frequencyMHz,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -238,6 +262,10 @@ class LineOfSightService {
|
||||
(2 * effectiveEarthRadius);
|
||||
final terrainHeight = elevations[i] + earthBulge;
|
||||
final clearance = lineHeight - terrainHeight;
|
||||
final unrefBulge =
|
||||
(distanceFromStart * (totalDistanceMeters - distanceFromStart)) /
|
||||
(2 * _earthRadiusMeters);
|
||||
final refractedHeight = lineHeight + (unrefBulge - earthBulge);
|
||||
|
||||
if (clearance < -obstructionToleranceMeters) {
|
||||
isClear = false;
|
||||
@@ -253,6 +281,7 @@ class LineOfSightService {
|
||||
distanceMeters: distanceFromStart,
|
||||
terrainMeters: terrainHeight,
|
||||
lineHeightMeters: lineHeight,
|
||||
refractedHeightMeters: refractedHeight,
|
||||
clearanceMeters: clearance,
|
||||
),
|
||||
);
|
||||
@@ -265,9 +294,20 @@ class LineOfSightService {
|
||||
maxObstructionMeters: maxObstructionMeters,
|
||||
firstObstructionDistanceMeters: firstObstructionDistanceMeters,
|
||||
samples: samples,
|
||||
usedKFactor: kFactor,
|
||||
frequencyMHz: frequencyMHz,
|
||||
);
|
||||
}
|
||||
|
||||
static double _kFactorForFrequency(double? frequencyMHz) {
|
||||
if (frequencyMHz == null) return _baselineKFactor;
|
||||
final delta =
|
||||
(frequencyMHz - _baselineFrequencyMHz) / _baselineFrequencyMHz;
|
||||
final adjustment = delta * 0.15;
|
||||
final scaled = _baselineKFactor * (1 + adjustment);
|
||||
return scaled.clamp(1.1, 1.6).toDouble();
|
||||
}
|
||||
|
||||
List<LatLng> _buildSamplePoints(
|
||||
LatLng start,
|
||||
LatLng end,
|
||||
|
||||
Reference in New Issue
Block a user