import 'dart:async'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import '../models/sms_message.dart'; import '../main.dart'; import '../services/sms_foreground_handler.dart'; class SmsListScreen extends StatefulWidget { const SmsListScreen({super.key}); @override State createState() => _SmsListScreenState(); } class _SmsListScreenState extends State { List _messages = []; final _selected = {}; Set _uploadedKeys = {}; bool _loading = false; bool _hasSmsPermission = false; StreamSubscription? _smsSub; static String _dedupKey(SmsMessage m) => '${m.phoneNumber}|${m.content}|${m.smsDate}'; @override void initState() { super.initState(); _requestPermission(); } Future _requestPermission() async { final status = await Permission.sms.request(); if (mounted) { setState(() => _hasSmsPermission = status.isGranted); if (status.isGranted) { _loadSms(); if (settings.autoUpload) _startAutoUpload(); } } } Future _loadSms() async { setState(() => _loading = true); try { final local = await smsReaderService.queryAllSms(); Set serverKeys = {}; try { final serverMsgs = await apiService.fetchServerSms(); serverKeys = serverMsgs.map((m) { final phone = m['phone_number'] as String? ?? ''; final content = m['content'] as String? ?? ''; final date = m['sms_date'] as String? ?? ''; return '$phone|$content|$date'; }).toSet(); } catch (_) {} setState(() { _messages = local; _uploadedKeys = serverKeys; }); } catch (e) { /* ignore */ } if (mounted) setState(() => _loading = false); } bool _isUploaded(SmsMessage m) => _uploadedKeys.contains(_dedupKey(m)); Future _uploadSelected() async { if (_selected.isEmpty) return; final toUpload = _messages .asMap() .entries .where((e) => _selected.contains(e.key) && !_isUploaded(e.value)) .map((e) => e.value) .toList(); if (toUpload.isEmpty) return; try { await apiService.uploadSms(toUpload); final newKeys = toUpload.map(_dedupKey).toSet(); setState(() { _uploadedKeys = _uploadedKeys.union(newKeys); _selected.clear(); }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已上传 ${toUpload.length} 条短信')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('上传失败: $e')), ); } } } Future _uploadAll() async { final toUpload = _messages.where((m) => !_isUploaded(m)).toList(); if (toUpload.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('没有新短信需要上传')), ); } return; } try { await apiService.uploadSms(toUpload); final newKeys = toUpload.map(_dedupKey).toSet(); setState(() { _uploadedKeys = _uploadedKeys.union(newKeys); }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('已上传 ${toUpload.length} 条短信')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('上传失败: $e')), ); } } } Future _toggleAutoUpload() async { final newVal = !settings.autoUpload; settings.setAutoUpload(newVal); if (newVal) { // Request notification permission for foreground service final notifResult = await FlutterForegroundTask.requestNotificationPermission(); if (notifResult != NotificationPermission.granted) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('需要通知权限才能保持后台运行')), ); } settings.setAutoUpload(false); setState(() {}); return; } await _startForegroundService(); _startAutoUpload(); } else { _stopForegroundService(); _smsSub?.cancel(); _smsSub = null; } setState(() {}); } Future _startForegroundService() async { final result = await FlutterForegroundTask.startService( serviceId: 888, notificationTitle: 'SMS Monitor', notificationText: '正在监听新短信...', callback: startCallback, ); if (result is ServiceRequestFailure && mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('后台服务启动失败: ${result.error}')), ); settings.setAutoUpload(false); setState(() {}); } } void _stopForegroundService() { FlutterForegroundTask.stopService(); } void _startAutoUpload() { _smsSub = smsReaderService.listenToIncoming().listen((sms) async { try { await apiService.uploadSms([sms]); setState(() { _uploadedKeys.add(_dedupKey(sms)); _messages.insert(0, sms); }); } catch (_) {} }); } @override void dispose() { _smsSub?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; return Scaffold( appBar: AppBar( title: const Text('短信'), actions: [ if (_selected.isNotEmpty) IconButton(icon: const Icon(Icons.cloud_upload), onPressed: _uploadSelected, tooltip: '上传选中'), IconButton(icon: const Icon(Icons.refresh), onPressed: _hasSmsPermission ? _loadSms : _requestPermission), ], ), body: _hasSmsPermission == false ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('需要短信权限'), const SizedBox(height: 12), FilledButton(onPressed: _requestPermission, child: const Text('授权')), ], ), ) : _loading ? const Center(child: CircularProgressIndicator()) : _messages.isEmpty ? const Center(child: Text('暂无短信')) : RefreshIndicator( onRefresh: _loadSms, child: ListView.builder( itemCount: _messages.length, itemBuilder: (_, i) { final m = _messages[i]; final sel = _selected.contains(i); final uploaded = _isUploaded(m); return ListTile( selected: sel, leading: CircleAvatar( backgroundColor: sel ? colors.primaryContainer : colors.surfaceContainerHighest, child: Icon( uploaded ? Icons.cloud_done : (m.type == 'received' ? Icons.arrow_downward : Icons.arrow_upward), size: 18, ), ), title: Text( m.contactName ?? m.phoneNumber, maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text(m.content, maxLines: 1, overflow: TextOverflow.ellipsis), trailing: uploaded ? Icon(Icons.cloud_done, color: colors.primary, size: 20) : IconButton( icon: Icon(sel ? Icons.check_circle : Icons.circle_outlined), onPressed: () { setState(() { sel ? _selected.remove(i) : _selected.add(i); }); }, ), onTap: uploaded ? null : () { setState(() { sel ? _selected.remove(i) : _selected.add(i); }); }, ); }, ), ), floatingActionButton: Column( mainAxisSize: MainAxisSize.min, children: [ FloatingActionButton.small( heroTag: 'auto', onPressed: _toggleAutoUpload, backgroundColor: settings.autoUpload ? colors.errorContainer : colors.surfaceContainerHighest, child: Icon(settings.autoUpload ? Icons.sync_disabled : Icons.sync, color: colors.onPrimaryContainer), ), const SizedBox(height: 8), FloatingActionButton( heroTag: 'upload', onPressed: _uploadAll, tooltip: '全部上传', child: const Icon(Icons.cloud_upload), ), ], ), ); } }