프로필 꾸미기 미리보기 테두리/체험 버튼 추가, 확인 버튼 엔터 지원, 비밀번호 홀드 보기 추가
- 프로필 꾸미기: 미리보기 중인 큰 타일의 테두리도 테마 색으로 바뀌게 수정, 미구매 템플릿에 "체험하기" 버튼을 따로 둬서 사기 전에도 눌러볼 수 있게 함 - 앱 전반의 확인/삭제/저장 같은 다이얼로그 기본 액션 버튼에 autofocus를 줘서 엔터 키로도 눌리게 함 (텍스트 입력이 있는 다이얼로그는 입력창 쪽에 autofocus + onSubmitted를 달아 입력 중 바로 엔터로 제출되게 처리) - HoldToRevealPasswordField 위젯 추가 - 눈 아이콘을 누르고 있는 동안만 비밀번호가 평문으로 보임. 로그인/간편인증/초기 비밀번호 변경 화면에 적용
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:http/http.dart' as http;
|
|||||||
import '../config.dart';
|
import '../config.dart';
|
||||||
import '../theme/app_palette.dart';
|
import '../theme/app_palette.dart';
|
||||||
import '../ui/app_notice.dart';
|
import '../ui/app_notice.dart';
|
||||||
|
import '../ui/hold_to_reveal_password_field.dart';
|
||||||
import '../ui/login_screen.dart';
|
import '../ui/login_screen.dart';
|
||||||
|
|
||||||
// 💡 main.dart 파일의 최하단(다른 클래스 중괄호 밖)에 붙여넣으세요.
|
// 💡 main.dart 파일의 최하단(다른 클래스 중괄호 밖)에 붙여넣으세요.
|
||||||
@@ -75,13 +76,12 @@ class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
|
|||||||
style: TextStyle(color: Colors.grey),
|
style: TextStyle(color: Colors.grey),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
TextField(
|
HoldToRevealPasswordField(
|
||||||
controller: _pwController,
|
controller: _pwController,
|
||||||
obscureText: true,
|
autofocus: true,
|
||||||
decoration: const InputDecoration(
|
labelText: '새로운 비밀번호 입력',
|
||||||
labelText: '새로운 비밀번호 입력',
|
textInputAction: TextInputAction.done,
|
||||||
border: OutlineInputBorder(),
|
onSubmitted: (_) => _updatePassword(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class _AdminDashboardState extends State<AdminDashboard> {
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppPalette.danger,
|
backgroundColor: AppPalette.danger,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ class _BoardPostDetailScreenState extends State<BoardPostDetailScreen> {
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(dialogContext, true),
|
onPressed: () => Navigator.pop(dialogContext, true),
|
||||||
child: const Text('삭제', style: TextStyle(color: AppPalette.danger)),
|
child: const Text('삭제', style: TextStyle(color: AppPalette.danger)),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ class _DeviceCheckoutLedgerPageState extends State<DeviceCheckoutLedgerPage> {
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppPalette.ink,
|
backgroundColor: AppPalette.ink,
|
||||||
foregroundColor: AppPalette.paper,
|
foregroundColor: AppPalette.paper,
|
||||||
@@ -208,6 +209,7 @@ class _DeviceCheckoutLedgerPageState extends State<DeviceCheckoutLedgerPage> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(dialogContext),
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
child: const Text('닫기'),
|
child: const Text('닫기'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ class _FriendsScreenState extends State<FriendsScreen> {
|
|||||||
title: const Text('친구 추가'),
|
title: const Text('친구 추가'),
|
||||||
content: TextField(
|
content: TextField(
|
||||||
controller: idController,
|
controller: idController,
|
||||||
|
autofocus: true,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onSubmitted: (_) => Navigator.pop(dialogContext, true),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: '친구 학번',
|
labelText: '친구 학번',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
@@ -99,6 +102,7 @@ class _FriendsScreenState extends State<FriendsScreen> {
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(dialogContext, true),
|
onPressed: () => Navigator.pop(dialogContext, true),
|
||||||
child: const Text('삭제', style: TextStyle(color: AppPalette.danger)),
|
child: const Text('삭제', style: TextStyle(color: AppPalette.danger)),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen> {
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppPalette.ink,
|
backgroundColor: AppPalette.ink,
|
||||||
foregroundColor: AppPalette.paper,
|
foregroundColor: AppPalette.paper,
|
||||||
@@ -195,6 +196,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen> {
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(dialogContext, true),
|
onPressed: () => Navigator.pop(dialogContext, true),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'나가기',
|
'나가기',
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ class _GroupListScreenState extends State<GroupListScreen> {
|
|||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
|
autofocus: true,
|
||||||
maxLength: 20,
|
maxLength: 20,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onSubmitted: (_) => Navigator.pop(dialogContext, true),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: '그룹 이름',
|
labelText: '그룹 이름',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// 👁️ 비밀번호 입력창 - 오른쪽 눈 아이콘을 누르고 있는 동안만 평문으로 보여준다.
|
||||||
|
// 토글형(눌러서 계속 보기) 대신 "홀드"로 만든 이유: 손을 떼는 순간 바로 다시 가려져서
|
||||||
|
// 누가 옆에서 보더라도 짧게 확인만 하고 끝낼 수 있다.
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HoldToRevealPasswordField extends StatefulWidget {
|
||||||
|
final TextEditingController controller;
|
||||||
|
final String labelText;
|
||||||
|
final String? hintText;
|
||||||
|
final IconData prefixIcon;
|
||||||
|
final bool autofocus;
|
||||||
|
final TextInputType? keyboardType;
|
||||||
|
final TextInputAction? textInputAction;
|
||||||
|
final ValueChanged<String>? onSubmitted;
|
||||||
|
|
||||||
|
const HoldToRevealPasswordField({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
required this.labelText,
|
||||||
|
this.hintText,
|
||||||
|
this.prefixIcon = Icons.lock,
|
||||||
|
this.autofocus = false,
|
||||||
|
this.keyboardType,
|
||||||
|
this.textInputAction,
|
||||||
|
this.onSubmitted,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HoldToRevealPasswordField> createState() =>
|
||||||
|
_HoldToRevealPasswordFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HoldToRevealPasswordFieldState extends State<HoldToRevealPasswordField> {
|
||||||
|
bool _revealed = false;
|
||||||
|
|
||||||
|
void _setRevealed(bool value) {
|
||||||
|
if (_revealed != value) setState(() => _revealed = value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextField(
|
||||||
|
controller: widget.controller,
|
||||||
|
obscureText: !_revealed,
|
||||||
|
autofocus: widget.autofocus,
|
||||||
|
keyboardType: widget.keyboardType,
|
||||||
|
textInputAction: widget.textInputAction,
|
||||||
|
onSubmitted: widget.onSubmitted,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: widget.labelText,
|
||||||
|
hintText: widget.hintText,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
prefixIcon: Icon(widget.prefixIcon),
|
||||||
|
suffixIcon: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTapDown: (_) => _setRevealed(true),
|
||||||
|
onTapUp: (_) => _setRevealed(false),
|
||||||
|
onTapCancel: () => _setRevealed(false),
|
||||||
|
child: Icon(
|
||||||
|
_revealed ? Icons.visibility_rounded : Icons.visibility_off_rounded,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
-50
@@ -6,6 +6,7 @@ import '../function/login_controller.dart';
|
|||||||
import '../function/session_store.dart';
|
import '../function/session_store.dart';
|
||||||
import '../theme/app_palette.dart';
|
import '../theme/app_palette.dart';
|
||||||
import 'app_notice.dart';
|
import 'app_notice.dart';
|
||||||
|
import 'hold_to_reveal_password_field.dart';
|
||||||
import 'main_dashboard.dart';
|
import 'main_dashboard.dart';
|
||||||
import 'teacher_register_screen.dart';
|
import 'teacher_register_screen.dart';
|
||||||
|
|
||||||
@@ -112,6 +113,36 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
|
|
||||||
return StatefulBuilder(
|
return StatefulBuilder(
|
||||||
builder: (context, setDialogState) {
|
builder: (context, setDialogState) {
|
||||||
|
Future<void> submit() async {
|
||||||
|
String newPassword = newPwController.text.trim();
|
||||||
|
if (newPassword.isEmpty) {
|
||||||
|
AppNotice.show(context, '새 비밀번호를 입력해 주세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDialogState(() => isUpdating = true);
|
||||||
|
|
||||||
|
final (success, message) = await _controller.changePassword(
|
||||||
|
studentId: studentId,
|
||||||
|
newPassword: newPassword,
|
||||||
|
);
|
||||||
|
setDialogState(() => isUpdating = false);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Navigator.pop(dialogContext);
|
||||||
|
AppNotice.show(context, message);
|
||||||
|
_navigateBasedOnRole(
|
||||||
|
role,
|
||||||
|
studentId,
|
||||||
|
name,
|
||||||
|
isDeviceMatched,
|
||||||
|
grade,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
AppNotice.show(context, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'초기 비밀번호 변경',
|
'초기 비밀번호 변경',
|
||||||
@@ -125,14 +156,13 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
style: TextStyle(color: AppPalette.danger),
|
style: TextStyle(color: AppPalette.danger),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
HoldToRevealPasswordField(
|
||||||
controller: newPwController,
|
controller: newPwController,
|
||||||
obscureText: true,
|
autofocus: true,
|
||||||
decoration: const InputDecoration(
|
labelText: '새 비밀번호',
|
||||||
labelText: '새 비밀번호',
|
prefixIcon: Icons.lock_reset,
|
||||||
border: OutlineInputBorder(),
|
textInputAction: TextInputAction.done,
|
||||||
prefixIcon: Icon(Icons.lock_reset),
|
onSubmitted: (_) => submit(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -147,36 +177,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
backgroundColor: AppPalette.ink,
|
backgroundColor: AppPalette.ink,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: submit,
|
||||||
String newPassword = newPwController.text.trim();
|
|
||||||
if (newPassword.isEmpty) {
|
|
||||||
AppNotice.show(context, '새 비밀번호를 입력해 주세요.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDialogState(() => isUpdating = true);
|
|
||||||
|
|
||||||
final (success, message) = await _controller
|
|
||||||
.changePassword(
|
|
||||||
studentId: studentId,
|
|
||||||
newPassword: newPassword,
|
|
||||||
);
|
|
||||||
setDialogState(() => isUpdating = false);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
Navigator.pop(dialogContext);
|
|
||||||
AppNotice.show(context, message);
|
|
||||||
_navigateBasedOnRole(
|
|
||||||
role,
|
|
||||||
studentId,
|
|
||||||
name,
|
|
||||||
isDeviceMatched,
|
|
||||||
grade,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
AppNotice.show(context, message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('변경하고 시작하기'),
|
child: const Text('변경하고 시작하기'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -234,6 +235,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
content: Text(message),
|
content: Text(message),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(ctx),
|
onPressed: () => Navigator.pop(ctx),
|
||||||
child: const Text('확인'),
|
child: const Text('확인'),
|
||||||
),
|
),
|
||||||
@@ -254,9 +256,11 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
builder: (BuildContext dialogContext) {
|
builder: (BuildContext dialogContext) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('$title 권한 인증 (기존 방식)'),
|
title: Text('$title 권한 인증 (기존 방식)'),
|
||||||
content: TextField(
|
content: HoldToRevealPasswordField(
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
obscureText: true,
|
autofocus: true,
|
||||||
|
labelText: '비밀번호',
|
||||||
|
hintText: '비밀번호 4자리를 입력하세요',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
@@ -270,10 +274,6 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
AppNotice.show(context, '비밀번호가 올바르지 않습니다.');
|
AppNotice.show(context, '비밀번호가 올바르지 않습니다.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: '비밀번호 4자리를 입력하세요',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -341,16 +341,11 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
HoldToRevealPasswordField(
|
||||||
controller: _pwController,
|
controller: _pwController,
|
||||||
obscureText: true,
|
labelText: '비밀번호',
|
||||||
textInputAction: TextInputAction.done,
|
textInputAction: TextInputAction.done,
|
||||||
onSubmitted: (_) => _login(),
|
onSubmitted: (_) => _login(),
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: '비밀번호',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
prefixIcon: Icon(Icons.lock),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
InkWell(
|
InkWell(
|
||||||
|
|||||||
@@ -157,6 +157,15 @@ class _MainDashboardState extends State<MainDashboard> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: idController,
|
controller: idController,
|
||||||
|
autofocus: true,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onSubmitted: (value) {
|
||||||
|
final inputId = value.trim();
|
||||||
|
if (inputId.isNotEmpty) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_deleteUser(inputId);
|
||||||
|
}
|
||||||
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: '학번 또는 교직원 번호',
|
labelText: '학번 또는 교직원 번호',
|
||||||
hintText: '예: 201101 또는 T1001',
|
hintText: '예: 201101 또는 T1001',
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text("확인"),
|
child: const Text("확인"),
|
||||||
),
|
),
|
||||||
@@ -114,6 +115,7 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_controller.beginIosProximityWatch(pocketNumber);
|
_controller.beginIosProximityWatch(pocketNumber);
|
||||||
@@ -139,6 +141,7 @@ class _NfcPocketCheckInScreenState extends State<NfcPocketCheckInScreen> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text("확인"),
|
child: const Text("확인"),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -295,7 +295,10 @@ class _CategoryTile extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: selected ? AppPalette.ink : AppPalette.sage,
|
color: hasPreview
|
||||||
|
? previewSecondary!
|
||||||
|
: (selected ? AppPalette.ink : AppPalette.sage),
|
||||||
|
width: hasPreview ? 2 : 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -517,23 +520,38 @@ class _TemplateTile extends StatelessWidget {
|
|||||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SizedBox(
|
if (template.equipped)
|
||||||
width: double.infinity,
|
SizedBox(
|
||||||
child: template.equipped
|
width: double.infinity,
|
||||||
? OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: null,
|
onPressed: null,
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: AppPalette.approved,
|
foregroundColor: AppPalette.approved,
|
||||||
side: const BorderSide(color: AppPalette.approved),
|
side: const BorderSide(color: AppPalette.approved),
|
||||||
),
|
),
|
||||||
child: const Text('장착중'),
|
child: const Text('장착중'),
|
||||||
)
|
),
|
||||||
: template.owned
|
)
|
||||||
? OutlinedButton(
|
else if (template.owned)
|
||||||
onPressed: onEquip,
|
SizedBox(
|
||||||
child: const Text('장착하기'),
|
width: double.infinity,
|
||||||
)
|
child: OutlinedButton(
|
||||||
: ElevatedButton(
|
onPressed: onEquip,
|
||||||
|
child: const Text('장착하기'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: onPreview,
|
||||||
|
child: const Text('체험하기'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
onPressed: canAfford ? onPurchase : null,
|
onPressed: canAfford ? onPurchase : null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppPalette.ink,
|
backgroundColor: AppPalette.ink,
|
||||||
@@ -542,7 +560,9 @@ class _TemplateTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: const Text('구매하기'),
|
child: const Text('구매하기'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class _StudyCalendarScreenState extends State<StudyCalendarScreen> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(dialogContext),
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
child: const Text('닫기'),
|
child: const Text('닫기'),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class _TeacherAttendancePageState extends State<TeacherAttendancePage> {
|
|||||||
child: const Text('취소'),
|
child: const Text('취소'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_runAction(() => _controller.resetTestPermissions());
|
_runAction(() => _controller.resetTestPermissions());
|
||||||
@@ -98,7 +99,14 @@ class _TeacherAttendancePageState extends State<TeacherAttendancePage> {
|
|||||||
title: Text('$studentName 학생 반출 허용'),
|
title: Text('$studentName 학생 반출 허용'),
|
||||||
content: TextField(
|
content: TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onSubmitted: (value) {
|
||||||
|
final minutes = int.tryParse(value.trim()) ?? 5;
|
||||||
|
Navigator.pop(context);
|
||||||
|
_runAction(() => _controller.allowRemoval(studentId, minutes));
|
||||||
|
},
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: '허용 시간 (분)',
|
labelText: '허용 시간 (분)',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
@@ -139,7 +147,14 @@ class _TeacherAttendancePageState extends State<TeacherAttendancePage> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
|
textInputAction: TextInputAction.done,
|
||||||
|
onSubmitted: (value) {
|
||||||
|
final minutes = int.tryParse(value.trim()) ?? 10;
|
||||||
|
Navigator.pop(context);
|
||||||
|
_runAction(() => _controller.setPermissionWindow(minutes));
|
||||||
|
},
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: '지금부터 허용 시간 (분)',
|
labelText: '지금부터 허용 시간 (분)',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
@@ -180,6 +195,7 @@ class _TeacherAttendancePageState extends State<TeacherAttendancePage> {
|
|||||||
child: const Text('취소'),
|
child: const Text('취소'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_runAction(() => _controller.dismissAll());
|
_runAction(() => _controller.dismissAll());
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ class _TeacherStudentManagementPageState
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
_runBulkImport(rows);
|
_runBulkImport(rows);
|
||||||
@@ -236,6 +237,7 @@ class _TeacherStudentManagementPageState
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
autofocus: true,
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
child: const Text('확인'),
|
child: const Text('확인'),
|
||||||
),
|
),
|
||||||
@@ -296,6 +298,7 @@ class _TeacherStudentManagementPageState
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppPalette.ink,
|
backgroundColor: AppPalette.ink,
|
||||||
foregroundColor: AppPalette.paper,
|
foregroundColor: AppPalette.paper,
|
||||||
@@ -341,6 +344,7 @@ class _TeacherStudentManagementPageState
|
|||||||
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
child: const Text('취소', style: TextStyle(color: Colors.grey)),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
|
autofocus: true,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppPalette.danger,
|
backgroundColor: AppPalette.danger,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
|
|||||||
Reference in New Issue
Block a user