feat: implement self-signed certificate support in API and UI

- Added support for self-signed TLS certificates in the ApiService, allowing configuration based on server settings.
- Introduced a toggle in the ServerConnectionPage to enable or disable trusting self-signed certificates.
- Updated localization files to include new strings for self-signed certificate settings in multiple languages.
- Enhanced the OptimizedStorageService to manage trusted servers based on user preferences for self-signed certificates.
- Improved error handling and logging throughout the affected services to ensure clarity and maintainability.
This commit is contained in:
cogwheel0
2025-10-09 01:49:56 +05:30
parent 10658d076a
commit 259fe3f9f0
27 changed files with 428 additions and 37 deletions

View File

@@ -36,6 +36,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
String? _connectionError;
bool _isConnecting = false;
bool _showAdvancedSettings = false;
bool _allowSelfSignedCertificates = false;
@override
void initState() {
@@ -45,9 +46,11 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
Future<void> _prefillFromState() async {
final activeServer = await ref.read(activeServerProvider.future);
if (activeServer != null) {
if (!mounted || activeServer == null) return;
setState(() {
_urlController.text = activeServer.url;
}
_allowSelfSignedCertificates = activeServer.allowSelfSignedCertificates;
});
}
@override
@@ -75,6 +78,7 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
url: url,
customHeaders: Map<String, String>.from(_customHeaders),
isActive: true,
allowSelfSignedCertificates: _allowSelfSignedCertificates,
);
final api = ApiService(serverConfig: tempConfig);
@@ -536,6 +540,69 @@ class _ServerConnectionPageState extends ConsumerState<ServerConnectionPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(Spacing.md),
margin: const EdgeInsets.only(bottom: Spacing.md),
decoration: BoxDecoration(
color: context.conduitTheme.surfaceContainer.withValues(
alpha: 0.3,
),
borderRadius: BorderRadius.circular(AppBorderRadius.small),
border: Border.all(
color: context.conduitTheme.dividerColor.withValues(alpha: 0.4),
width: BorderWidth.thin,
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Platform.isIOS
? CupertinoIcons.lock_shield
: Icons.verified_user,
color: context.conduitTheme.iconSecondary,
size: IconSize.small,
),
const SizedBox(width: Spacing.sm),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(
context,
)!.allowSelfSignedCertificates,
style: context.conduitTheme.bodySmall?.copyWith(
fontWeight: FontWeight.w600,
color: context.conduitTheme.textPrimary,
),
),
const SizedBox(height: Spacing.xs),
Text(
AppLocalizations.of(
context,
)!.allowSelfSignedCertificatesDescription,
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
),
),
],
),
),
const SizedBox(width: Spacing.sm),
Switch.adaptive(
value: _allowSelfSignedCertificates,
onChanged: (value) {
setState(() {
_allowSelfSignedCertificates = value;
});
},
activeTrackColor: context.conduitTheme.buttonPrimary,
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

View File

@@ -17,8 +17,7 @@ class VoiceCallNotificationService {
// Notification IDs and channels
static const String _channelId = 'voice_call_channel';
static const String _channelName = 'Voice Call';
static const String _channelDescription =
'Ongoing voice call notifications';
static const String _channelDescription = 'Ongoing voice call notifications';
static const int _notificationId = 2001;
// Action IDs
@@ -32,7 +31,9 @@ class VoiceCallNotificationService {
Future<void> initialize() async {
if (_initialized) return;
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const androidSettings = AndroidInitializationSettings(
'@mipmap/ic_launcher',
);
const iosSettings = DarwinInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
@@ -72,7 +73,8 @@ class VoiceCallNotificationService {
await _notifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
AndroidFlutterLocalNotificationsPlugin
>()
?.createNotificationChannel(androidChannel);
}
@@ -190,8 +192,10 @@ class VoiceCallNotificationService {
/// Check if notifications are enabled
Future<bool> areNotificationsEnabled() async {
if (Platform.isAndroid) {
final androidImpl = _notifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
final androidImpl = _notifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>();
return await androidImpl?.areNotificationsEnabled() ?? false;
} else if (Platform.isIOS) {
// iOS doesn't have a direct check, assume enabled if initialized
@@ -203,13 +207,17 @@ class VoiceCallNotificationService {
/// Request notification permissions
Future<bool> requestPermissions() async {
if (Platform.isAndroid) {
final androidImpl = _notifications.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
final androidImpl = _notifications
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin
>();
final granted = await androidImpl?.requestNotificationsPermission();
return granted ?? false;
} else if (Platform.isIOS) {
final iosImpl = _notifications
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin
>();
final granted = await iosImpl?.requestPermissions(
alert: true,
badge: false,

View File

@@ -148,9 +148,8 @@ class _VoiceCallPageState extends ConsumerState<VoiceCallPage>
icon: const Icon(CupertinoIcons.xmark),
onPressed: () async {
await _service?.stopCall();
if (mounted) {
Navigator.of(context).pop();
}
if (!context.mounted) return;
Navigator.of(context).pop();
},
),
),

View File

@@ -1480,9 +1480,7 @@ class _ModernChatInputState extends ConsumerState<ModernChatInput>
),
child: Center(
child: Icon(
Platform.isIOS
? CupertinoIcons.waveform
: Icons.graphic_eq,
Platform.isIOS ? CupertinoIcons.waveform : Icons.graphic_eq,
size: IconSize.large,
color: enabledVoiceCall
? context.conduitTheme.buttonPrimaryText