아이폰 근접센서 주머니 감시, 학생 목록 타일 UI, 학교 서버 주소 반영

- iOS는 조도 센서/백그라운드 서비스가 불가능해서 근접센서(UIDevice
  proximityMonitoring) + 가이드 접근 안내로 대체 구현 (Core NFC와 마찬가지로
  네이티브 플러그인 필요해서 ProximityMonitorPlugin.swift 추가)
- 교사 대시보드 실시간 출석 현황: 모바일 리스트/데스크톱 표를 타일 그리드로
  통일해서 한눈에 보기 쉽게 변경
- 서버를 학교 자체 서버(Cloudflare Tunnel, api.backsanhi.shop)로 이전하면서
  기본 BASE_URL 갱신
- Firebase CLI 로컬 캐시(.firebase/)는 소스가 아니라 gitignore 처리

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 21:50:38 +09:00
co-authored by Claude Sonnet 5
parent f9fa05f817
commit 9354e2998a
8 changed files with 419 additions and 299 deletions
+39 -3
View File
@@ -1,6 +1,8 @@
// 📲 자습실 NFC 출석체크 화면 (UI 전용). 태깅 상태 화면/팝업/스낵바만 그린다.
// NFC 세션, 서버 통신, 백그라운드 감시 연동은
// lib/function/nfc_pocket_checkin_controller.dart가 담당한다.
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import '../function/nfc_pocket_checkin_controller.dart';
@@ -31,6 +33,7 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
onMessage: _handleMessage,
onCheckInSuccess: _showSuccessDialog,
onViolationDetected: _showViolationDialog,
onNeedGuidedAccessSetup: _showGuidedAccessDialog,
);
_controller.init();
}
@@ -85,6 +88,37 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
).then((_) => _isDialogOpen = false);
}
/// 🛡️ iOS 전용: 근접센서 감시가 화면 잠금으로 끊기지 않도록, "가이드 접근"을
/// 직접 켜달라고 안내하는 팝업. 학생이 켰다고 확인해야 실제 감시가 시작된다.
void _showGuidedAccessDialog(String pocketNumber) {
if (!mounted) return;
_closeAnyOpenDialog();
_isDialogOpen = true;
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text("🛡️ 감시 모드 켜기 전에"),
content: const Text(
"iOS는 화면을 잠그면 감시가 끊겨요. 아래 순서로 '가이드 접근'을 먼저 켜주세요.\n\n"
"1. 사이드(또는 홈) 버튼 3번 빠르게 누르기\n"
"2. 목록에서 '가이드 접근' 선택 → 화면 오른쪽 아래 '시작' 누르기\n\n"
"다 켜셨으면 아래 버튼을 눌러 감시를 시작하세요.\n"
"(감시를 끝낼 땐 먼저 사이드 버튼 3번으로 가이드 접근을 끈 다음 다시 태깅하세요.)",
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
_controller.beginIosProximityWatch(pocketNumber);
},
child: const Text("가이드 접근 켰어요"),
),
],
),
).then((_) => _isDialogOpen = false);
}
/// 🎊 출석 성공 알림창
void _showSuccessDialog(String pocketNumber) {
if (!mounted) return;
@@ -222,10 +256,12 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
),
),
const SizedBox(height: 8),
const Text(
"📴 화면을 꺼도 감시는 계속됩니다.\n알림바에서 상태를 확인할 수 있어요.\n(폰을 꺼내 다시 태깅하면 반출 처리됩니다)",
Text(
!kIsWeb && Platform.isIOS
? "🔒 가이드 접근을 켠 채로 화면만 잠기도록 두세요.\n(임의로 앱을 나가면 감시가 끊깁니다)\n감시를 끝내려면 가이드 접근을 먼저 끄고 다시 태깅하세요."
: "📴 화면을 꺼도 감시는 계속됩니다.\n알림바에서 상태를 확인할 수 있어요.\n(폰을 꺼내 다시 태깅하면 반출 처리됩니다)",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white38, fontSize: 13),
style: const TextStyle(color: Colors.white38, fontSize: 13),
),
],
),