feat: багофикс по запросам волбачия

This commit is contained in:
Jganenokk
2026-08-15 15:51:52 +07:00
parent 198f137826
commit ecaa132e8f
33 changed files with 904 additions and 154 deletions
+9
View File
@@ -78,6 +78,15 @@ class CallBridge {
}
}
Future<void> ensureOngoing({String? caller}) async {
if (!_android) return;
try {
await _method.invokeMethod<void>('ensureOngoing', {'caller': caller});
} catch (e) {
logger.w('CallBridge.ensureOngoing: $e');
}
}
Future<void> setScreenShare(bool enabled, {String? caller}) async {
if (!_android) return;
try {
+39
View File
@@ -78,6 +78,7 @@ class CallSession {
int _peerDeviceIdx = 0;
bool _muted = false;
bool _speakerOn = false;
bool _accepted = false;
bool _peerMuted = false;
bool _peerVideo = false;
@@ -207,6 +208,7 @@ class CallSession {
bool get peerIsKomet => _peerIsKomet;
bool get isMuted => _muted;
bool get isSpeaker => _speakerOn;
bool get peerMuted => _peerMuted;
bool get peerVideo => _peerVideo;
bool get mediaConnected => _mediaConnected;
@@ -857,6 +859,7 @@ class CallSession {
if (role == CallRole.joiner || _topology == 'SERVER') {
_setState(CallSessionState.active);
}
unawaited(applyAudioRoute());
unawaited(_resolvePath());
unawaited(_collectReceivers());
}
@@ -874,6 +877,7 @@ class CallSession {
}
Future<void> _addLocalMedia(RTCPeerConnection pc) async {
await _prepareAudioSession();
_localStream = await navigator.mediaDevices.getUserMedia({
'audio': true,
'video': _wantVideo,
@@ -881,8 +885,43 @@ class CallSession {
for (final track in _localStream!.getTracks()) {
await pc.addTrack(track, _localStream!);
}
await applyAudioRoute();
}
Future<void> setSpeaker(bool on) async {
if (_speakerOn == on) return;
_speakerOn = on;
await applyAudioRoute();
_notifyInfo();
}
Future<void> _prepareAudioSession() async {
if (!_canRouteAudio) return;
if (defaultTargetPlatform == TargetPlatform.android) {
try {
await Helper.setAndroidAudioConfiguration(
AndroidAudioConfiguration.communication,
);
} catch (e) {
logger.w('[call] setAndroidAudioConfiguration: $e');
}
}
await applyAudioRoute();
}
Future<void> applyAudioRoute() async {
if (!_canRouteAudio) return;
try {
await Helper.setSpeakerphoneOn(_speakerOn);
} catch (e) {
logger.w('[call] setSpeakerphoneOn($_speakerOn) недоступен: $e');
}
}
static bool get _canRouteAudio =>
defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS;
Future<void> _openSfuChannels(RTCPeerConnection pc) async {
await _closeSfuChannels();
final commands = SfuCommandChannel();
+31 -4
View File
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'custom_font_service.dart';
const String kDisplayFontFamily = 'Outfit';
@immutable
@@ -25,7 +27,14 @@ class AppFont {
final String label;
final String? fontFamily;
const AppFont({required this.id, required this.label, this.fontFamily});
final double metricScale;
const AppFont({
required this.id,
required this.label,
this.fontFamily,
this.metricScale = 1.0,
});
bool get isSystem => fontFamily == null;
bool get isCustom => id.startsWith(AppFonts.customPrefix);
@@ -42,8 +51,18 @@ class AppFonts {
static const List<AppFont> builtIn = [
AppFont(id: 'system', label: 'Системный'),
AppFont(id: 'inter', label: 'Inter', fontFamily: 'Inter'),
AppFont(id: 'unbounded', label: 'Unbounded', fontFamily: 'Unbounded'),
AppFont(
id: 'inter',
label: 'Inter',
fontFamily: 'Inter',
metricScale: 0.967,
),
AppFont(
id: 'unbounded',
label: 'Unbounded',
fontFamily: 'Unbounded',
metricScale: 0.933,
),
];
static AppFont get fallback => builtIn.first;
@@ -53,11 +72,19 @@ class AppFonts {
static AppFont resolve(String id) {
if (id.startsWith(customPrefix)) {
final family = id.substring(customPrefix.length);
return AppFont(id: id, label: family, fontFamily: family);
return AppFont(
id: id,
label: family,
fontFamily: family,
metricScale: CustomFontService.metricScaleFor(family),
);
}
return builtIn.firstWhere((f) => f.id == id, orElse: () => fallback);
}
static double effectiveScale(String id, double userScale) =>
userScale * resolve(id).metricScale;
static String? displayFamily(String id) {
final font = resolve(id);
return font.isSystem ? kDisplayFontFamily : font.fontFamily;
+19
View File
@@ -39,6 +39,25 @@ final Map<String, CountryName> countriesByCode = {
for (final country in allCountries) country.code: country,
};
const Map<String, String> primaryCountryByPhoneCode = {'+7': 'RU', '+1': 'US'};
bool isPrimaryForPhoneCode(CountryName country) =>
primaryCountryByPhoneCode[country.phoneCode] == country.code;
List<CountryName> sortedByDisplayName(
Iterable<CountryName> countries,
String languageCode,
) {
final list = countries.toList();
list.sort(
(a, b) => a
.displayName(languageCode)
.toLowerCase()
.compareTo(b.displayName(languageCode).toLowerCase()),
);
return list;
}
List<CountryName> countriesInServerOrder(Iterable<String> codes) {
final out = <CountryName>[];
for (final raw in codes) {
+8
View File
@@ -7,6 +7,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/logger.dart';
import 'font_metrics.dart';
class CustomFontService {
static const String prefKey = 'app_custom_fonts';
@@ -15,6 +16,9 @@ class CustomFontService {
'AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30';
static final Set<String> _loaded = <String>{};
static final Map<String, double> _metricScales = <String, double>{};
static double metricScaleFor(String family) => _metricScales[family] ?? 1.0;
static Future<List<String>> families() async {
final prefs = await SharedPreferences.getInstance();
@@ -85,6 +89,10 @@ class CustomFontService {
..addFont(Future<ByteData>.value(ByteData.sublistView(bytes)));
await loader.load();
_loaded.add(family);
final xHeight = FontMetrics.xHeightRatio(bytes);
if (xHeight != null) {
_metricScales[family] = FontMetrics.scaleForXHeight(xHeight);
}
}
static bool _isSfnt(Uint8List b) {
+44
View File
@@ -0,0 +1,44 @@
import 'dart:typed_data';
abstract class FontMetrics {
static const double referenceXHeight = 0.528;
static const double minScale = 0.85;
static const double maxScale = 1.15;
static double scaleForXHeight(double xHeightRatio) {
if (xHeightRatio <= 0) return 1.0;
return (referenceXHeight / xHeightRatio)
.clamp(minScale, maxScale)
.toDouble();
}
static double? xHeightRatio(Uint8List bytes) {
try {
if (bytes.length < 12) return null;
final data = ByteData.sublistView(bytes);
if (data.getUint32(0) == 0x74746366) return null;
final numTables = data.getUint16(4);
int? headOffset;
int? os2Offset;
for (var i = 0; i < numTables; i++) {
final record = 12 + i * 16;
if (record + 16 > bytes.length) return null;
final tag = String.fromCharCodes(bytes, record, record + 4);
if (tag == 'head') headOffset = data.getUint32(record + 8);
if (tag == 'OS/2') os2Offset = data.getUint32(record + 8);
}
if (headOffset == null || os2Offset == null) return null;
if (headOffset + 20 > bytes.length || os2Offset + 88 > bytes.length) {
return null;
}
final unitsPerEm = data.getUint16(headOffset + 18);
if (unitsPerEm == 0) return null;
if (data.getUint16(os2Offset) < 2) return null;
final xHeight = data.getInt16(os2Offset + 86);
if (xHeight <= 0) return null;
return xHeight / unitsPerEm;
} catch (_) {
return null;
}
}
}
+11 -7
View File
@@ -9,6 +9,7 @@ class DeviceContactsService {
DeviceContactsService._();
static const _grantedKey = 'phonebook_granted';
static const _deniedKey = 'phonebook_denied';
static final Map<String, String> _byLast10 = {};
static bool _loaded = false;
@@ -41,12 +42,17 @@ class DeviceContactsService {
await _readBook();
}
static Future<bool> ensureLoadedInteractive() async {
static Future<bool> ensureLoadedInteractive({bool force = false}) async {
if (_loaded || !_supported) return false;
if (!AppPhonebookNames.current.value) return false;
final granted = await FlutterContacts.requestPermission(readonly: true);
if (!granted) return false;
final prefs = await SharedPreferences.getInstance();
if (!force && prefs.getBool(_deniedKey) == true) return false;
final granted = await FlutterContacts.requestPermission(readonly: true);
if (!granted) {
await prefs.setBool(_deniedKey, true);
return false;
}
await prefs.remove(_deniedKey);
await prefs.setBool(_grantedKey, true);
return _readBook();
}
@@ -54,14 +60,12 @@ class DeviceContactsService {
static Future<bool> reload() async {
_loaded = false;
_byLast10.clear();
return ensureLoadedInteractive();
return ensureLoadedInteractive(force: true);
}
static Future<bool> _readBook() async {
try {
final contacts = await FlutterContacts.getContacts(
withProperties: true,
);
final contacts = await FlutterContacts.getContacts(withProperties: true);
_byLast10.clear();
for (final contact in contacts) {
final name = contact.displayName.trim();