This commit is contained in:
2026-04-28 22:04:24 +08:00
parent 80ee99e564
commit 71c940ab46
156 changed files with 5700 additions and 304 deletions

View File

@@ -16,24 +16,23 @@ class ApiService {
if (_token != null) 'Authorization': 'Bearer $_token',
};
Future<String> login(String username, String password) async {
Future<bool> verifyToken(String token) async {
final res = await http.post(
Uri.parse('$_baseUrl/api/auth/login'),
Uri.parse('$_baseUrl/api/auth/verify'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'username': username, 'password': password}),
body: jsonEncode({'token': token}),
);
if (res.statusCode != 200) throw Exception(_error(res));
return jsonDecode(res.body)['token'];
return res.statusCode == 200;
}
Future<String> register(String username, String password) async {
final res = await http.post(
Uri.parse('$_baseUrl/api/auth/register'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'username': username, 'password': password}),
Future<List<Map<String, dynamic>>> fetchServerSms() async {
final res = await http.get(
Uri.parse('$_baseUrl/api/sms?limit=999999'),
headers: _headers,
);
if (res.statusCode != 201) throw Exception(_error(res));
return jsonDecode(res.body)['token'];
if (res.statusCode != 200) throw Exception(_error(res));
final data = jsonDecode(res.body);
return List<Map<String, dynamic>>.from(data['messages']);
}
Future<int> uploadSms(List<SmsMessage> messages) async {

View File

@@ -0,0 +1,76 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:telephony/telephony.dart' as tel;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import '../models/sms_message.dart';
class SmsForegroundHandler extends TaskHandler {
StreamSubscription? _smsSub;
int _uploadedCount = 0;
@override
Future<void> onStart(DateTime timestamp, TaskStarter starter) async {
_startListening();
}
@override
void onRepeatEvent(DateTime timestamp) {}
@override
Future<void> onDestroy(DateTime timestamp) async {
_smsSub?.cancel();
}
@override
void onNotificationPressed() {
FlutterForegroundTask.launchApp('/');
}
void _startListening() async {
final telephony = tel.Telephony.instance;
final prefs = await SharedPreferences.getInstance();
final baseUrl = prefs.getString('server_url') ?? '';
final token = prefs.getString('token') ?? '';
telephony.listenIncomingSms(
onNewMessage: (tel.SmsMessage sms) async {
final msg = SmsMessage(
phoneNumber: sms.address ?? '',
contactName: sms.address,
content: sms.body ?? '',
type: 'received',
smsDate: DateTime.now().toUtc().toIso8601String(),
);
if (baseUrl.isNotEmpty && token.isNotEmpty) {
try {
await http.post(
Uri.parse('$baseUrl/api/sms/upload'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode([msg.toJson()]),
);
_uploadedCount++;
FlutterForegroundTask.updateService(
notificationText: '已自动上传 $_uploadedCount 条短信',
);
} catch (_) {
FlutterForegroundTask.updateService(
notificationText: '上传失败,等待重试... (已上传 $_uploadedCount 条)',
);
}
}
},
listenInBackground: false,
);
}
}
@pragma('vm:entry-point')
void startCallback() {
FlutterForegroundTask.setTaskHandler(SmsForegroundHandler());
}

View File

@@ -1,36 +1,60 @@
import 'dart:async';
import 'package:sms_maintained/sms_maintained.dart';
import 'package:telephony/telephony.dart' as tel;
import '../models/sms_message.dart';
class SmsReaderService {
final _telephony = tel.Telephony.instance;
Future<List<SmsMessage>> queryAllSms() async {
final messages = <SmsMessage>[];
final all = await SmsQuery().querySms(
kinds: [SmsQueryKind.Inbox, SmsQueryKind.Sent],
final inbox = await _telephony.getInboxSms(
columns: [tel.SmsColumn.ADDRESS, tel.SmsColumn.BODY, tel.SmsColumn.DATE],
);
for (final s in all) {
for (final s in inbox) {
messages.add(SmsMessage(
phoneNumber: s.address ?? '',
contactName: s.sender ?? s.address,
contactName: s.address,
content: s.body ?? '',
type: s.kind == SmsQueryKind.Sent ? 'sent' : 'received',
smsDate: DateTime.fromMillisecondsSinceEpoch(s.date!).toUtc().toIso8601String(),
type: 'received',
smsDate: s.date != null
? DateTime.fromMillisecondsSinceEpoch(s.date!).toUtc().toIso8601String()
: DateTime.now().toUtc().toIso8601String(),
));
}
final sent = await _telephony.getSentSms(
columns: [tel.SmsColumn.ADDRESS, tel.SmsColumn.BODY, tel.SmsColumn.DATE],
);
for (final s in sent) {
messages.add(SmsMessage(
phoneNumber: s.address ?? '',
contactName: s.address,
content: s.body ?? '',
type: 'sent',
smsDate: s.date != null
? DateTime.fromMillisecondsSinceEpoch(s.date!).toUtc().toIso8601String()
: DateTime.now().toUtc().toIso8601String(),
));
}
return messages;
}
Stream<SmsMessage> listenToIncoming() {
final controller = StreamController<SmsMessage>.broadcast();
SmsReceiver().onSmsReceived!.listen((SmsMessage sms) {
controller.add(SmsMessage(
phoneNumber: sms.address ?? '',
contactName: sms.sender ?? sms.address,
content: sms.body ?? '',
type: 'received',
smsDate: DateTime.now().toUtc().toIso8601String(),
));
});
_telephony.listenIncomingSms(
onNewMessage: (tel.SmsMessage sms) {
controller.add(SmsMessage(
phoneNumber: sms.address ?? '',
contactName: sms.address,
content: sms.body ?? '',
type: 'received',
smsDate: DateTime.now().toUtc().toIso8601String(),
));
},
listenInBackground: false,
);
return controller.stream;
}
}