1 Commits
Author SHA1 Message Date
sihoo 5ab1c4c37c 하교 처리 후 무단반출 오탐 버그 수정 + 앱 버전 1.1.3
- 조도 센서가 폰 반출을 감지하면, 위반 경고를 울리기 전에 서버에 반출 허용 상태(하교/허용시간대)를 먼저 확인
- 허용된 상태면 위반 경고 없이 조용히 정상 회수로 처리
2026-08-04 14:36:38 +09:00
3 changed files with 38 additions and 2 deletions
+12
View File
@@ -34,6 +34,7 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
String? _activePocketNumber; String? _activePocketNumber;
StreamSubscription<Map<String, dynamic>?>? _violationSub; StreamSubscription<Map<String, dynamic>?>? _violationSub;
StreamSubscription<Map<String, dynamic>?>? _checkedOutSub;
bool _isDialogOpen = false; // 출석/위반 팝업이 겹쳐서 뜨는 것을 막기 위한 플래그 bool _isDialogOpen = false; // 출석/위반 팝업이 겹쳐서 뜨는 것을 막기 위한 플래그
bool get _watchServiceSupported => !kIsWeb && Platform.isAndroid; bool get _watchServiceSupported => !kIsWeb && Platform.isAndroid;
@@ -53,6 +54,16 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
setState(() => _isWatching = false); setState(() => _isWatching = false);
_showViolationDialog(event?['pocketNumber']?.toString()); _showViolationDialog(event?['pocketNumber']?.toString());
}); });
// 🏫 하교 처리 등으로 반출이 허용된 상태에서 폰을 꺼내면, 위반이 아니라 정상 회수로 처리된다.
_checkedOutSub = FlutterBackgroundService().on('checked_out').listen((event) {
if (!mounted) return;
setState(() {
_isWatching = false;
_activePocketNumber = null;
_statusMessage = "✅ 폰을 회수했습니다. 수고하셨습니다!";
});
_showSnackBar("✅ 폰이 정상적으로 회수되었습니다.", Colors.green);
});
} }
} }
@@ -61,6 +72,7 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
// 화면을 나갈 때 NFC 감지만 종료. 백그라운드 감시 서비스는 화면과 무관하게 계속 동작해야 하므로 건드리지 않는다. // 화면을 나갈 때 NFC 감지만 종료. 백그라운드 감시 서비스는 화면과 무관하게 계속 동작해야 하므로 건드리지 않는다.
NfcManager.instance.stopSession(); NfcManager.instance.stopSession();
_violationSub?.cancel(); _violationSub?.cancel();
_checkedOutSub?.cancel();
super.dispose(); super.dispose();
} }
+25 -1
View File
@@ -112,6 +112,30 @@ void onPocketWatchServiceStart(ServiceInstance service) {
} }
} }
/// 하교 처리/반출 허용 시간대라면 조용히 감시만 종료하고, 아니면 위반으로 경고한다.
Future<void> handlePhoneRemoved() async {
bool isPermitted = false;
try {
final res = await http.get(
Uri.parse("$baseUrl/api/permissions/check?studentId=$studentId"),
);
if (res.statusCode == 200) {
final data = jsonDecode(utf8.decode(res.bodyBytes));
isPermitted = data['permitted'] == true;
}
} catch (_) {
isPermitted = false; // 네트워크 오류 시엔 안전하게 위반으로 처리
}
if (isPermitted) {
updateNotification("✅ 폰 회수 완료", "[$pocketNumber] 주머니에서 정상적으로 회수되었습니다.");
service.invoke('checked_out', {"pocketNumber": pocketNumber});
service.stopSelf();
} else {
reportViolation();
}
}
void onLightReading(int luxValue) { void onLightReading(int luxValue) {
if (violated) return; if (violated) return;
@@ -129,7 +153,7 @@ void onPocketWatchServiceStart(ServiceInstance service) {
if (luxValue > baselineLux! + _luxJumpThreshold) { if (luxValue > baselineLux! + _luxJumpThreshold) {
violated = true; violated = true;
lightSub?.cancel(); lightSub?.cancel();
reportViolation(); handlePhoneRemoved();
} }
} }
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.1.2+3 version: 1.1.3+4
environment: environment:
sdk: ^3.12.2 sdk: ^3.12.2