// ๐Ÿ”‘ ๋กœ๊ทธ์ธ ํ™”๋ฉด์˜ ๊ธฐ๋Šฅ(์„œ๋ฒ„ ํ†ต์‹ /์ธ์ฆ ๋ถ„๊ธฐ ๋กœ์ง) ๋‹ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ. // ์œ„์ ฏ/BuildContext/๋‹ค์ด์–ผ๋กœ๊ทธ ์ฝ”๋“œ๋Š” ์—†๋‹ค โ€” ์ „๋ถ€ UI ํŒŒ์ผ(lib/screens/login_screen.dart) ๋ชซ. import 'dart:convert'; import 'package:flutter/foundation.dart' show ChangeNotifier, kIsWeb; import 'package:http/http.dart' as http; import 'package:firebase_messaging/firebase_messaging.dart'; import '../config.dart'; enum LoginOutcome { masterSuccess, needsPasswordChange, success, error } /// ๋กœ๊ทธ์ธ ์‹œ๋„ ๊ฒฐ๊ณผ. UI๋Š” [outcome]๋งŒ ๋ณด๊ณ  ์–ด๋–ค ํ™”๋ฉด/๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ๋„์šธ์ง€ ๋ถ„๊ธฐํ•œ๋‹ค. class LoginResult { final LoginOutcome outcome; final String? role; final String? studentId; final String? name; 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.needsPasswordChange({ required this.studentId, required this.name, required this.role, required this.isDeviceMatched, }) : outcome = LoginOutcome.needsPasswordChange, errorMessage = null; const LoginResult.success({ required this.role, required this.studentId, required this.name, required this.isDeviceMatched, }) : outcome = LoginOutcome.success, errorMessage = null; const LoginResult.error(this.errorMessage) : outcome = LoginOutcome.error, role = null, studentId = null, name = null, isDeviceMatched = false; } class LoginController extends ChangeNotifier { bool _isLoading = false; bool get isLoading => _isLoading; Future login(String id, String password) async { if (id.isEmpty || password.isEmpty) { return const LoginResult.error('โš ๏ธ ํ•™๋ฒˆ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ชจ๋‘ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.'); } // ๐Ÿ”ฅ [๊ฐœ๋ฐœ์ž ๋งˆ์Šคํ„ฐ ๊ณ„์ •] if (id == "2061") { if (password == "happy9642!") { return const LoginResult.masterSuccess( studentId: "2061", name: "ํ›—์ถง๊ฐ€๋ฃป", ); } else { return const LoginResult.error('๊ฐœ๋ฐœ์ž ๊ณ„์ •์˜ ๋งˆ์Šคํ„ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.'); } } _isLoading = true; notifyListeners(); try { String deviceUuid = await getDeviceUuid(); String fcmToken = ""; try { fcmToken = kIsWeb ? await FirebaseMessaging.instance.getToken( vapidKey: webVapidKey, ) ?? "" : await FirebaseMessaging.instance.getToken() ?? ""; } catch (e) { // ํŒŒ์ด์–ด๋ฒ ์ด์Šค ๋ฏธ์„ค์ • ๋“ฑ - ๋กœ๊ทธ์ธ ์ž์ฒด๋Š” ๊ณ„์† ์ง„ํ–‰ } final response = await http.post( Uri.parse('$baseUrl/login'), headers: {"Content-Type": "application/json"}, body: jsonEncode({ "studentId": id, "password": password, "deviceUuid": deviceUuid, "fcmToken": fcmToken, }), ); final resData = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200) { String role = ''; String name = ''; String studentId = ''; int isFirstLogin = 0; var userObj = resData['user']; if (userObj != null) { role = userObj['role']?.toString() ?? ''; name = userObj['name']?.toString() ?? userObj['studentName']?.toString() ?? userObj['student_name']?.toString() ?? ''; studentId = userObj['studentId']?.toString() ?? userObj['student_id']?.toString() ?? id; var rawFirst = userObj['isFirstLogin'] ?? userObj['is_first_login']; isFirstLogin = (rawFirst is bool) ? (rawFirst ? 1 : 0) : (int.tryParse(rawFirst.toString()) ?? 0); } else { role = resData['role']?.toString() ?? ''; name = resData['name']?.toString() ?? resData['studentName']?.toString() ?? ''; studentId = resData['studentId']?.toString() ?? id; var rawFirst = resData['isFirstLogin'] ?? resData['is_first_login']; isFirstLogin = (rawFirst is bool) ? (rawFirst ? 1 : 0) : (int.tryParse(rawFirst.toString()) ?? 0); } if (name.trim().isEmpty) { name = "์•Œ์ˆ˜์—†์Œ"; } // ๐Ÿ†• ์„œ๋ฒ„ ์‘๋‹ต ๋ฐ์ดํ„ฐ์—์„œ ๊ธฐ๊ธฐ ์ผ์น˜ ์—ฌ๋ถ€ ์ถ”์ถœํ•˜๊ธฐ // (์„œ๋ฒ„๊ฐ€ ์ฃผ๋Š” Key ์ด๋ฆ„์— ๋งž์ถฐ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์•„๋ž˜ ํ•ญ๋ชฉ ์ค‘ ๋งž๋Š” ๊ฒŒ ์•Œ์•„์„œ ๋“ค์–ด๊ฐ) bool isDeviceMatched = resData['isDeviceMatched'] ?? resData['isUuidMatched'] ?? resData['is_matched'] ?? (userObj != null ? (userObj['isDeviceMatched'] ?? userObj['is_matched'] ?? false) : false); if (isFirstLogin == 1) { return LoginResult.needsPasswordChange( studentId: studentId, name: name, role: role, isDeviceMatched: isDeviceMatched, ); } return LoginResult.success( role: role, studentId: studentId, name: name, isDeviceMatched: isDeviceMatched, ); } else { String errorMsg = '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'; if (resData is Map) { errorMsg = resData['detail']?.toString() ?? resData['message']?.toString() ?? errorMsg; } return LoginResult.error(errorMsg); } } catch (e) { return LoginResult.error('์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์„ธ์š”!\n($e)'); } finally { _isLoading = false; notifyListeners(); } } /// ๐Ÿ› ๏ธ ์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ. (์„ฑ๊ณต์—ฌ๋ถ€, ๋ฉ”์‹œ์ง€)๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. Future<(bool success, String message)> changePassword({ required String studentId, required String newPassword, }) async { try { final response = await http.post( Uri.parse('$baseUrl/api/users/change-password'), headers: {"Content-Type": "application/json"}, body: jsonEncode({"studentId": studentId, "newPassword": newPassword}), ); final resData = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200 && resData['status'] == 'success') { return (true, 'โœ… ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!'); } else { return (false, 'โŒ ์‹คํŒจ: ${resData['message']}'); } } catch (e) { return (false, '์„œ๋ฒ„ ์—๋Ÿฌ ๋ฐœ์ƒ: $e'); } } }