Add TWP Softphone Flutter app and complete mobile backend API
All checks were successful
Create Release / build (push) Successful in 4s
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>
This commit is contained in:
68
mobile/lib/screens/settings_screen.dart
Normal file
68
mobile/lib/screens/settings_screen.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
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);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user