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:
@@ -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: [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user