k_card/k_phone/lib/main.dart

260 lines
7.7 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'enrollment_db.dart';
import 'filter_proxy.dart';
import 'proxy_service.dart';
import 'session_manager.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await ProxyService.initialize();
runApp(const KPhoneApp());
}
class KPhoneApp extends StatelessWidget {
const KPhoneApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'k_phone',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const ProxyStatusScreen(),
);
}
}
class ProxyStatusScreen extends StatefulWidget {
const ProxyStatusScreen({super.key});
@override
State<ProxyStatusScreen> createState() => _ProxyStatusScreenState();
}
class _ProxyStatusScreenState extends State<ProxyStatusScreen> {
bool _serviceRunning = false;
bool _cardAttached = false;
String _statusMessage = 'Stopped';
final List<String> _log = [];
// Debug-only test state — stripped in release builds via kDebugMode.
bool _testRunning = false;
final _db = EnrollmentDb();
final _session = SessionManager();
@override
void initState() {
super.initState();
_subscribeToService();
}
void _subscribeToService() {
final service = FlutterBackgroundService();
// Sync initial running state
service.isRunning().then((running) {
if (mounted) setState(() => _serviceRunning = running);
});
service.on('status').listen((event) {
if (event == null) return;
if (mounted) {
setState(() {
_serviceRunning = event['running'] as bool? ?? false;
_cardAttached = event['cardAttached'] as bool? ?? false;
_statusMessage = event['message'] as String? ?? '';
final log = event['log'] as String?;
if (log != null) {
_log.insert(0, log);
if (_log.length > 200) _log.removeLast();
}
});
}
});
}
void _addLog(String msg) {
setState(() {
_log.insert(0, msg);
if (_log.length > 200) _log.removeLast();
});
}
Future<void> _runRegistrationLoginTest() async {
if (_testRunning) return;
setState(() => _testRunning = true);
try {
// --- Registrering ---
_addLog('[REGISTRATION] Starter: enrolling "testbruger"');
try {
final enrollment = await _db.register(
username: 'testbruger',
displayName: 'Test Bruger',
);
_addLog('[REGISTRATION] OK: bruger="${enrollment.username}" '
'displayName="${enrollment.displayName}"');
} on StateError {
_addLog('[REGISTRATION] INFO: "testbruger" allerede enrollet — sletter og prøver igen');
await _db.delete('testbruger');
final enrollment = await _db.register(
username: 'testbruger',
displayName: 'Test Bruger',
);
_addLog('[REGISTRATION] OK: bruger="${enrollment.username}" genregistreret');
}
final list = await _db.list();
_addLog('[REGISTRATION] Enrollment-liste: ${list.map((e) => e.username).join(', ')}');
_addLog('[REGISTRATION] Test duplikat afvisning...');
try {
await _db.register(username: 'testbruger');
_addLog('[REGISTRATION] FEJL: duplikat burde være afvist!');
} on StateError catch (e) {
_addLog('[REGISTRATION] OK: duplikat afvist — ${e.message}');
}
// --- Login ---
_addLog('[LOGIN] Udsteder session for "testbruger"...');
final token = _session.issue('testbruger');
_addLog('[LOGIN] Token: ${token.substring(0, 8)}... (${token.length} tegn)');
final valid = _session.isValid(token);
_addLog('[LOGIN] Session gyldig: $valid');
final entry = _session.getSession(token);
_addLog('[LOGIN] Udløber: ${entry?.expires.toLocal().toString().substring(0, 19)}');
_addLog('[LOGIN] Tilbagekalder session...');
_session.revoke(token);
_addLog('[LOGIN] Session gyldig efter revoke: ${_session.isValid(token)}');
_addLog('[LOGIN] FÆRDIG — alle flows OK');
} catch (e) {
_addLog('[FEJL] $e');
} finally {
setState(() => _testRunning = false);
}
}
Future<void> _toggleService() async {
final service = FlutterBackgroundService();
final running = await service.isRunning();
if (running) {
service.invoke('stop');
setState(() {
_serviceRunning = false;
_cardAttached = false;
_statusMessage = 'Stopped';
});
} else {
await service.startService();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('k_phone — ChromeCard proxy'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_StatusTile(
label: 'Filter proxy (Comp 1)',
ok: _serviceRunning,
value: _serviceRunning ? 'Running on :$kFilterProxyPort' : 'Stopped',
),
const SizedBox(height: 8),
_StatusTile(
label: 'Auth proxy (Comp 2)',
ok: _serviceRunning,
value: _serviceRunning ? 'Running on :$kProxyPort' : 'Stopped',
),
const SizedBox(height: 8),
_StatusTile(
label: 'ChromeCard (USB)',
ok: _cardAttached,
value: _cardAttached ? 'Attached' : 'Not detected',
),
const SizedBox(height: 8),
Text(
_statusMessage,
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 16),
Row(
children: [
FilledButton(
onPressed: _toggleService,
child: Text(_serviceRunning ? 'Stop proxy' : 'Start proxy'),
),
if (kDebugMode) ...[
const SizedBox(width: 12),
FilledButton.tonal(
onPressed: _testRunning ? null : _runRegistrationLoginTest,
child: _testRunning
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Kør registrering & login'),
),
],
],
),
const Divider(height: 32),
const Text('Log', style: TextStyle(fontWeight: FontWeight.bold)),
Expanded(
child: ListView.builder(
itemCount: _log.length,
itemBuilder: (_, i) => Text(
_log[i],
style: const TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
),
),
],
),
),
);
}
}
class _StatusTile extends StatelessWidget {
final String label;
final bool ok;
final String value;
const _StatusTile({
required this.label,
required this.ok,
required this.value,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(
ok ? Icons.check_circle : Icons.radio_button_unchecked,
color: ok ? Colors.green : Colors.grey,
size: 18,
),
const SizedBox(width: 8),
Text('$label: ', style: const TextStyle(fontWeight: FontWeight.w600)),
Text(value),
],
);
}
}