UI 문구 이모티콘 제거 + 계정 강제 삭제 아이콘 검정으로 통일

화면 타이틀, 스낵바/다이얼로그 메시지, 백그라운드 알림 문구 등
사용자에게 노출되는 텍스트에서 이모티콘을 전부 제거 (코드 주석은
대상 아님). 아이콘 위젯은 그대로 유지.

main_dashboard.dart의 "계정 강제 삭제" 타일 아이콘 색상도 빨간색
대신 다른 타일과 동일한 검정(AppPalette.ink)으로 통일.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 22:31:03 +09:00
co-authored by Claude Sonnet 5
parent dce5f1ad1b
commit 9089b9032e
22 changed files with 157 additions and 160 deletions
+3 -3
View File
@@ -17,12 +17,12 @@ class AdminController extends ChangeNotifier {
try {
final response = await http.get(url);
if (response.statusCode == 200) {
return '💥 DB가 깔끔하게 초기화되었습니다! (출석 번호 1번부터 시작)';
return 'DB가 깔끔하게 초기화되었습니다! (출석 번호 1번부터 시작)';
} else {
return '❌ 초기화 실패: 서버 권한 오류 (${response.statusCode})';
return '초기화 실패: 서버 권한 오류 (${response.statusCode})';
}
} catch (e) {
return '❌ 네트워크 에러: 서버와 연결할 수 없습니다.';
return '네트워크 에러: 서버와 연결할 수 없습니다.';
} finally {
_isLoading = false;
notifyListeners();
+3 -3
View File
@@ -34,12 +34,12 @@ class DeviceCheckoutController extends ChangeNotifier {
);
final result = jsonDecode(utf8.decode(response.bodyBytes));
if (response.statusCode == 200 && result['status'] == 'success') {
return (true, '✅ ${result['message'] ?? '요청이 접수되었습니다.'}');
return (true, '${result['message'] ?? '요청이 접수되었습니다.'}');
} else {
return (false, '❌ ${result['message'] ?? '요청 실패'}');
return (false, '${result['message'] ?? '요청 실패'}');
}
} catch (e) {
return (false, '🚨 네트워크 에러: $e');
return (false, '네트워크 에러: $e');
} finally {
_isSubmitting = false;
notifyListeners();
@@ -87,11 +87,11 @@ class DeviceCheckoutLedgerController extends ChangeNotifier {
final result = jsonDecode(utf8.decode(response.bodyBytes));
if (response.statusCode == 200 && result['status'] == 'success') {
await fetchAll();
return (true, '✅ ${result['message'] ?? '처리되었습니다.'}');
return (true, '${result['message'] ?? '처리되었습니다.'}');
}
return (false, '❌ ${result['message'] ?? '처리 실패'}');
return (false, '${result['message'] ?? '처리 실패'}');
} catch (e) {
return (false, '🚨 네트워크 에러: $e');
return (false, '네트워크 에러: $e');
} finally {
_isWorking = false;
_safeNotify();
+9 -14
View File
@@ -17,13 +17,11 @@ class LoginResult {
final bool isDeviceMatched;
final String? errorMessage;
const LoginResult.masterSuccess({
required this.studentId,
required this.name,
}) : outcome = LoginOutcome.masterSuccess,
role = null,
isDeviceMatched = true,
errorMessage = null;
const LoginResult.masterSuccess({required this.studentId, required this.name})
: outcome = LoginOutcome.masterSuccess,
role = null,
isDeviceMatched = true,
errorMessage = null;
const LoginResult.needsPasswordChange({
required this.studentId,
@@ -55,16 +53,13 @@ class LoginController extends ChangeNotifier {
Future<LoginResult> login(String id, String password) async {
if (id.isEmpty || password.isEmpty) {
return const LoginResult.error('⚠️ 학번과 비밀번호를 모두 입력해 주세요.');
return const LoginResult.error('학번과 비밀번호를 모두 입력해 주세요.');
}
// 🔥 [개발자 마스터 계정]
if (id == "2061") {
if (password == "happy9642!") {
return const LoginResult.masterSuccess(
studentId: "2061",
name: "훗춧가룻",
);
return const LoginResult.masterSuccess(studentId: "2061", name: "훗춧가룻");
} else {
return const LoginResult.error('개발자 계정의 마스터 비밀번호가 올바르지 않습니다.');
}
@@ -200,9 +195,9 @@ class LoginController extends ChangeNotifier {
final resData = jsonDecode(utf8.decode(response.bodyBytes));
if (response.statusCode == 200 && resData['status'] == 'success') {
return (true, '✅ 비밀번호가 변경되었습니다!');
return (true, '비밀번호가 변경되었습니다!');
} else {
return (false, '❌ 실패: ${resData['message']}');
return (false, '실패: ${resData['message']}');
}
} catch (e) {
return (false, '서버 에러 발생: $e');
+29 -25
View File
@@ -17,8 +17,12 @@ import '../config.dart' show baseUrl;
// 🛡️ iOS 근접센서 감시용 네이티브 채널 (ios/Runner/ProximityMonitorPlugin.swift 참고).
// 조도 센서 API가 없는 iOS에서, 통화 중 화면이 꺼지는 것과 같은 원리인
// 근접센서로 "주머니 안/밖" 상태 변화를 감지해 대체한다.
const MethodChannel _proximityMethodChannel = MethodChannel('proximity_monitor');
const EventChannel _proximityEventChannel = EventChannel('proximity_monitor/events');
const MethodChannel _proximityMethodChannel = MethodChannel(
'proximity_monitor',
);
const EventChannel _proximityEventChannel = EventChannel(
'proximity_monitor/events',
);
enum WatchMessageLevel { info, success, warning, error }
@@ -78,22 +82,22 @@ class NfcPocketCheckinController extends ChangeNotifier {
// 화면이 열려있는 동안은 실시간으로 위반 알림을 받아 팝업을 띄운다.
// 무단 반출이 한 번 감지되면 "신뢰된 감시 세션"은 끝난 것으로 보고,
// 다음 태깅은 체크아웃이 아니라 새 출석(재출석)으로 처리되도록 감시 상태를 해제한다.
_violationSub = FlutterBackgroundService().on('violation_detected').listen((
event,
) {
_isWatching = false;
notifyListeners();
onViolationDetected(event?['pocketNumber']?.toString());
});
_violationSub = FlutterBackgroundService()
.on('violation_detected')
.listen((event) {
_isWatching = false;
notifyListeners();
onViolationDetected(event?['pocketNumber']?.toString());
});
// 🏫 하교 처리 등으로 반출이 허용된 상태에서 폰을 꺼내면, 위반이 아니라 정상 회수로 처리된다.
_checkedOutSub = FlutterBackgroundService().on('checked_out').listen((
event,
) {
_isWatching = false;
_activePocketNumber = null;
_statusMessage = "✅ 폰을 회수했습니다. 수고하셨습니다!";
_statusMessage = "폰을 회수했습니다. 수고하셨습니다!";
notifyListeners();
onMessage("✅ 폰이 정상적으로 회수되었습니다.", WatchMessageLevel.success);
onMessage("폰이 정상적으로 회수되었습니다.", WatchMessageLevel.success);
});
}
}
@@ -117,7 +121,7 @@ class NfcPocketCheckinController extends ChangeNotifier {
void _startNfcSession() async {
bool isAvailable = await NfcManager.instance.isAvailable();
if (!isAvailable) {
_statusMessage = "❌ 이 스마트폰은 NFC 기능이 꺼져있거나 지원되지 않습니다.";
_statusMessage = "이 스마트폰은 NFC 기능이 꺼져있거나 지원되지 않습니다.";
notifyListeners();
return;
}
@@ -134,7 +138,7 @@ class NfcPocketCheckinController extends ChangeNotifier {
}
_isProcessing = true;
_statusMessage = "⏳ 태그 인식 완료! 서버에 출석 전송 중...";
_statusMessage = "태그 인식 완료! 서버에 출석 전송 중...";
notifyListeners();
try {
@@ -163,7 +167,7 @@ class NfcPocketCheckinController extends ChangeNotifier {
tagUid: _getTagUid(tag),
);
} catch (e) {
onMessage("❌ 태그 읽기 실패: $e", WatchMessageLevel.error);
onMessage("태그 읽기 실패: $e", WatchMessageLevel.error);
} finally {
// 3초 후 연타 방지 해제 (쿨다운)
await Future.delayed(const Duration(seconds: 3));
@@ -216,16 +220,16 @@ class NfcPocketCheckinController extends ChangeNotifier {
onMessage("ℹ️ 이 기기는 백그라운드 감시를 지원하지 않습니다.", WatchMessageLevel.info);
}
} else {
onMessage("⚠️ 출석 실패: ${result['message']}", WatchMessageLevel.warning);
onMessage("출석 실패: ${result['message']}", WatchMessageLevel.warning);
}
} else {
onMessage(
"🚨 서버 에러 (코드: ${response.statusCode})",
"서버 에러 (코드: ${response.statusCode})",
WatchMessageLevel.error,
);
}
} catch (e) {
onMessage("❌ 서버 연결 실패: $e", WatchMessageLevel.error);
onMessage("서버 연결 실패: $e", WatchMessageLevel.error);
}
}
@@ -255,7 +259,7 @@ class NfcPocketCheckinController extends ChangeNotifier {
_isWatching = true;
_activePocketNumber = pocketNumber;
_statusMessage = "🛡️ 백그라운드에서 감시 중입니다. 화면을 꺼도 계속 감시돼요.";
_statusMessage = "백그라운드에서 감시 중입니다. 화면을 꺼도 계속 감시돼요.";
notifyListeners();
}
@@ -268,9 +272,9 @@ class NfcPocketCheckinController extends ChangeNotifier {
}
_isWatching = false;
_activePocketNumber = null;
_statusMessage = "✅ 폰을 회수했습니다. 감시가 종료되었습니다.";
_statusMessage = "폰을 회수했습니다. 감시가 종료되었습니다.";
notifyListeners();
onMessage("✅ 감시가 종료되었습니다. 수고하셨습니다!", WatchMessageLevel.success);
onMessage("감시가 종료되었습니다. 수고하셨습니다!", WatchMessageLevel.success);
}
// -----------------------------------------------------------------------
@@ -291,13 +295,13 @@ class NfcPocketCheckinController extends ChangeNotifier {
_activePocketNumber = pocketNumber;
_isWatching = true;
_statusMessage = "📥 주머니에 넣는 중...";
_statusMessage = "주머니에 넣는 중...";
notifyListeners();
try {
await _proximityMethodChannel.invokeMethod('start');
} catch (e) {
onMessage("❌ 근접센서 감시 시작 실패: $e", WatchMessageLevel.error);
onMessage("근접센서 감시 시작 실패: $e", WatchMessageLevel.error);
_isWatching = false;
_isIosProximityWatch = false;
notifyListeners();
@@ -307,7 +311,7 @@ class NfcPocketCheckinController extends ChangeNotifier {
// 학생이 폰을 주머니에 넣을 시간을 준 뒤, 그 시점 상태를 기준값으로 삼는다.
await Future.delayed(const Duration(seconds: 4));
_statusMessage = "🛡️ 감시 중입니다. 화면을 잠그지 말고 주머니에 넣어주세요.";
_statusMessage = "감시 중입니다. 화면을 잠그지 말고 주머니에 넣어주세요.";
notifyListeners();
_proximitySub = _proximityEventChannel.receiveBroadcastStream().listen((
@@ -359,9 +363,9 @@ class NfcPocketCheckinController extends ChangeNotifier {
if (isPermitted) {
_activePocketNumber = null;
_statusMessage = "✅ 폰을 회수했습니다. 수고하셨습니다!";
_statusMessage = "폰을 회수했습니다. 수고하셨습니다!";
notifyListeners();
onMessage("✅ 폰이 정상적으로 회수되었습니다.", WatchMessageLevel.success);
onMessage("폰이 정상적으로 회수되었습니다.", WatchMessageLevel.success);
return;
}
+9 -9
View File
@@ -37,19 +37,19 @@ class NfcTagWriterController extends ChangeNotifier {
Future<void> startWriteSession(String number) async {
if (number.isEmpty) {
onMessage("⚠️ 주머니 번호를 입력해주세요.", true);
onMessage("주머니 번호를 입력해주세요.", true);
return;
}
final String pocketLabel = "POCKET_$number";
bool isAvailable = await NfcManager.instance.isAvailable();
if (!isAvailable) {
onMessage("❌ NFC가 꺼져있거나 지원되지 않습니다.", true);
onMessage("NFC가 꺼져있거나 지원되지 않습니다.", true);
return;
}
_isWriting = true;
_statusMessage = "📡 [$pocketLabel] 쓸 준비 완료! 스티커에 폰 뒷면을 대주세요.";
_statusMessage = "[$pocketLabel] 쓸 준비 완료! 스티커에 폰 뒷면을 대주세요.";
notifyListeners();
NfcManager.instance.startSession(
@@ -58,11 +58,11 @@ class NfcTagWriterController extends ChangeNotifier {
try {
final ndef = Ndef.from(tag);
if (ndef == null) {
onMessage("❌ 이 태그는 NDEF 쓰기를 지원하지 않는 종류입니다.", true);
onMessage("이 태그는 NDEF 쓰기를 지원하지 않는 종류입니다.", true);
return;
}
if (!ndef.isWritable) {
onMessage("❌ 이 태그는 쓰기 잠금(read-only) 상태입니다.", true);
onMessage("이 태그는 쓰기 잠금(read-only) 상태입니다.", true);
return;
}
@@ -78,8 +78,8 @@ class NfcTagWriterController extends ChangeNotifier {
onMessage(
tagUid != null
? "✅ [$pocketLabel] 쓰기 + UID 등록 성공!"
: "⚠️ [$pocketLabel] 쓰기는 성공했지만 UID를 못 읽어 등록은 안 됐습니다.",
? "[$pocketLabel] 쓰기 + UID 등록 성공!"
: "[$pocketLabel] 쓰기는 성공했지만 UID를 못 읽어 등록은 안 됐습니다.",
tagUid == null,
);
// 다음 스티커를 연달아 쓰기 편하도록 번호를 자동으로 1 올려준다.
@@ -88,7 +88,7 @@ class NfcTagWriterController extends ChangeNotifier {
_statusMessage = "다음 번호를 확인하고 '쓰기 시작'을 다시 눌러주세요.";
notifyListeners();
} catch (e) {
onMessage("❌ 쓰기 실패: $e", true);
onMessage("쓰기 실패: $e", true);
} finally {
await NfcManager.instance.stopSession();
_isWriting = false;
@@ -117,7 +117,7 @@ class NfcTagWriterController extends ChangeNotifier {
body: jsonEncode({"tagUid": tagUid, "pocketNumber": pocketLabel}),
);
} catch (e) {
onMessage("❌ 서버에 UID 등록 실패: $e", true);
onMessage("서버에 UID 등록 실패: $e", true);
}
}
@@ -18,13 +18,13 @@ class StudentDashboardController extends ChangeNotifier {
final response = await http.delete(url);
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body);
return (true, '✅ ${responseData['message']}');
return (true, '${responseData['message']}');
} else {
final errorData = jsonDecode(response.body);
return (false, '❌ 삭제 실패: ${errorData['detail'] ?? '알 수 없는 오류'}');
return (false, '삭제 실패: ${errorData['detail'] ?? '알 수 없는 오류'}');
}
} catch (e) {
return (false, '❌ 서버와 연결할 수 없습니다. (네트워크 에러)');
return (false, '서버와 연결할 수 없습니다. (네트워크 에러)');
} finally {
_isLoading = false;
notifyListeners();
@@ -145,7 +145,7 @@ class TeacherAttendanceController extends ChangeNotifier {
await fetchAll();
return result['message'] ?? '하교 처리되었습니다.';
} catch (e) {
return '❌ 하교 처리 실패: $e';
return '하교 처리 실패: $e';
}
}
@@ -161,7 +161,7 @@ class TeacherAttendanceController extends ChangeNotifier {
await fetchAll();
return result['message'] ?? '처리되었습니다.';
} catch (e) {
return '❌ 반출 허용 실패: $e';
return '반출 허용 실패: $e';
}
}
@@ -177,7 +177,7 @@ class TeacherAttendanceController extends ChangeNotifier {
await fetchAll();
return result['message'] ?? '처리되었습니다.';
} catch (e) {
return '❌ 설정 실패: $e';
return '설정 실패: $e';
}
}
@@ -193,7 +193,7 @@ class TeacherAttendanceController extends ChangeNotifier {
await fetchAll();
return result['message'] ?? '처리되었습니다.';
} catch (e) {
return '❌ 출석시간 설정 실패: $e';
return '출석시간 설정 실패: $e';
}
}
@@ -207,7 +207,7 @@ class TeacherAttendanceController extends ChangeNotifier {
await fetchAll();
return result['message'] ?? '처리되었습니다.';
} catch (e) {
return '❌ 초기화 실패: $e';
return '초기화 실패: $e';
}
}
@@ -36,13 +36,13 @@ class TeacherRegisterController extends ChangeNotifier {
final res = jsonDecode(response.body);
if (response.statusCode == 200 && res['status'] == 'success') {
return (true, '✅ ${res['message']}');
return (true, '${res['message']}');
} else {
String errorMsg = res['detail'] ?? res['message'] ?? '회원가입에 실패했습니다.';
return (false, '❌ $errorMsg');
return (false, errorMsg);
}
} catch (e) {
return (false, '❌ 서버와 통신에 실패했습니다.');
return (false, '서버와 통신에 실패했습니다.');
} finally {
_isLoading = false;
notifyListeners();
@@ -76,11 +76,11 @@ class TeacherStudentManagementController extends ChangeNotifier {
);
if (response.statusCode == 200) {
await fetchStudents();
return (true, '✅ $studentName 학생 기기 초기화 완료! (비밀번호도 1234로 초기화됨)');
return (true, '$studentName 학생 기기 초기화 완료! (비밀번호도 1234로 초기화됨)');
}
return (false, '❌ 초기화 통신 실패');
return (false, '초기화 통신 실패');
} catch (e) {
return (false, '❌ 초기화 통신 실패: $e');
return (false, '초기화 통신 실패: $e');
} finally {
_isWorking = false;
notifyListeners();
@@ -211,12 +211,12 @@ class TeacherStudentManagementController extends ChangeNotifier {
final result = jsonDecode(utf8.decode(response.bodyBytes));
if (response.statusCode == 200 && result['status'] == 'success') {
return (true, '✅ ${result['message'] ?? '계정이 생성되었습니다.'}');
return (true, '${result['message'] ?? '계정이 생성되었습니다.'}');
} else {
return (false, '❌ 생성 실패: ${result['message'] ?? '오류 발생'}');
return (false, '생성 실패: ${result['message'] ?? '오류 발생'}');
}
} catch (e) {
return (false, '🚨 네트워크 에러: $e');
return (false, '네트워크 에러: $e');
}
}
@@ -238,12 +238,12 @@ class TeacherStudentManagementController extends ChangeNotifier {
if (response.statusCode == 200 && result['status'] == 'success') {
await fetchStudents();
return (true, '✅ ${result['message'] ?? '학년이 지정되었습니다.'}');
return (true, '${result['message'] ?? '학년이 지정되었습니다.'}');
} else {
return (false, '❌ 지정 실패: ${result['message'] ?? '오류 발생'}');
return (false, '지정 실패: ${result['message'] ?? '오류 발생'}');
}
} catch (e) {
return (false, '🚨 네트워크 에러: $e');
return (false, '네트워크 에러: $e');
} finally {
_isWorking = false;
notifyListeners();
@@ -266,12 +266,12 @@ class TeacherStudentManagementController extends ChangeNotifier {
if (response.statusCode == 200) {
final resData = jsonDecode(utf8.decode(response.bodyBytes));
await fetchStudents();
return (true, '✅ ${resData['message']}');
return (true, '${resData['message']}');
} else {
return (false, '❌ 삭제 실패하였습니다.');
return (false, '삭제 실패하였습니다.');
}
} catch (e) {
return (false, '❌ 서버 에러 발생');
return (false, '서버 에러 발생');
} finally {
_isWorking = false;
notifyListeners();