All checks were successful
Create Release / build (push) Successful in 4s
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for mobile VoIP, implement unhold_call() with call leg detection, wire FCM push notifications into call queue and webhook missed call handlers, add data-only FCM message support for Android background wake, and add Twilio API Key / Push Credential settings fields. Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth with auto-refresh, SSE real-time queue updates, FCM push notifications, Material 3 UI with dashboard, active call screen, dialpad, and call controls (mute/speaker/hold/transfer). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
69 lines
1.9 KiB
Dart
69 lines
1.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import '../providers/auth_provider.dart';
|
|
|
|
class SettingsScreen extends StatefulWidget {
|
|
const SettingsScreen({super.key});
|
|
|
|
@override
|
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
|
}
|
|
|
|
class _SettingsScreenState extends State<SettingsScreen> {
|
|
String? _serverUrl;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadServerUrl();
|
|
}
|
|
|
|
Future<void> _loadServerUrl() async {
|
|
const storage = FlutterSecureStorage();
|
|
final url = await storage.read(key: 'server_url');
|
|
if (mounted) setState(() => _serverUrl = url);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final auth = context.watch<AuthProvider>();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Settings')),
|
|
body: ListView(
|
|
children: [
|
|
ListTile(
|
|
leading: const Icon(Icons.dns),
|
|
title: const Text('Server'),
|
|
subtitle: Text(_serverUrl ?? 'Not configured'),
|
|
),
|
|
if (auth.user != null) ...[
|
|
ListTile(
|
|
leading: const Icon(Icons.person),
|
|
title: const Text('User'),
|
|
subtitle: Text(auth.user!.displayName),
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.badge),
|
|
title: const Text('Login'),
|
|
subtitle: Text(auth.user!.login),
|
|
),
|
|
],
|
|
const Divider(),
|
|
ListTile(
|
|
leading: const Icon(Icons.logout, color: Colors.red),
|
|
title: const Text('Logout', style: TextStyle(color: Colors.red)),
|
|
onTap: () async {
|
|
await auth.logout();
|
|
if (context.mounted) {
|
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|