타일 UI 편집 기능 임시 보류 + 로그인 유지 체크박스 추가
- 폰처럼 작은 화면에서 6칸 고정 격자 때문에 레이아웃이 깨지는 문제로, 타일 드래그 이동/크기조절 기능을 kTileEditingEnabled 플래그로 임시 비활성화 (관련 코드는 그대로 유지 - 작은 화면 대응 방안 마련 후 플래그만 켜면 재사용 가능) - 비활성화 상태에서는 화면 폭에 따라 열이 자동으로 줄어드는 기본 그리드 사용 - 설정 화면의 "UI 편집" 항목은 비활성화 표시로 안내 - 로그인 화면에 "로그인 상태 유지" 체크박스 추가 (기본 체크됨, 해제 시 세션 저장 안 함) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
// 🧩 대시보드 타일의 격자 위치/크기(1x1 ~ 4x4)를 표현하는 값 객체.
|
// 🧩 대시보드 타일의 격자 위치/크기(1x1 ~ 4x4)를 표현하는 값 객체.
|
||||||
// 전체 배치 캔버스는 가로 6칸 × 세로 4칸으로 고정한다.
|
// 전체 배치 캔버스는 가로 6칸 × 세로 4칸으로 고정한다.
|
||||||
|
|
||||||
|
// 📱 [기능 보류] 데스크탑에서는 잘 나오지만, 폰처럼 화면이 작은 기기에서는
|
||||||
|
// 6칸 고정 격자 때문에 타일이 너무 작아져 레이아웃이 깨진다.
|
||||||
|
// 작은 화면 대응 방안을 마련하기 전까지 편집 기능을 임시로 꺼둔다.
|
||||||
|
// (다시 켜려면 이 값만 true로 바꾸면 됨 — 관련 코드는 그대로 남겨둠)
|
||||||
|
const bool kTileEditingEnabled = false;
|
||||||
|
|
||||||
const int kGridCols = 6;
|
const int kGridCols = 6;
|
||||||
const int kGridRows = 4;
|
const int kGridRows = 4;
|
||||||
const int kMinTileSize = 1;
|
const int kMinTileSize = 1;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// ⚙️ 대시보드 설정 화면 (UI 전용). 지금은 "UI 편집" 진입점 하나만 있다.
|
// ⚙️ 대시보드 설정 화면 (UI 전용). 지금은 "UI 편집" 진입점 하나만 있다.
|
||||||
// "UI 편집"을 누르면 이 화면을 pop('edit_ui')로 닫고, 대시보드가 그 결과를 받아 편집 모드로 들어간다.
|
// "UI 편집"을 누르면 이 화면을 pop('edit_ui')로 닫고, 대시보드가 그 결과를 받아 편집 모드로 들어간다.
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/dashboard_tile_layout.dart';
|
||||||
import '../theme/app_palette.dart';
|
import '../theme/app_palette.dart';
|
||||||
|
|
||||||
class DashboardSettingsPage extends StatelessWidget {
|
class DashboardSettingsPage extends StatelessWidget {
|
||||||
@@ -29,6 +30,7 @@ class DashboardSettingsPage extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
|
enabled: kTileEditingEnabled,
|
||||||
leading: const Icon(
|
leading: const Icon(
|
||||||
Icons.grid_view_rounded,
|
Icons.grid_view_rounded,
|
||||||
color: AppPalette.ink,
|
color: AppPalette.ink,
|
||||||
@@ -37,9 +39,18 @@ class DashboardSettingsPage extends StatelessWidget {
|
|||||||
'UI 편집',
|
'UI 편집',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
subtitle: const Text('타일을 드래그해서 옮기고 크기를 바꿀 수 있어요.'),
|
// 📱 [기능 보류] 작은 화면에서 레이아웃이 깨지는 문제로 잠시 막아둠.
|
||||||
trailing: const Icon(Icons.chevron_right_rounded),
|
subtitle: Text(
|
||||||
onTap: () => Navigator.pop(context, 'edit_ui'),
|
kTileEditingEnabled
|
||||||
|
? '타일을 드래그해서 옮기고 크기를 바꿀 수 있어요.'
|
||||||
|
: '작은 화면 대응을 준비 중이라 잠시 사용할 수 없어요.',
|
||||||
|
),
|
||||||
|
trailing: kTileEditingEnabled
|
||||||
|
? const Icon(Icons.chevron_right_rounded)
|
||||||
|
: null,
|
||||||
|
onTap: kTileEditingEnabled
|
||||||
|
? () => Navigator.pop(context, 'edit_ui')
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
+50
-17
@@ -23,6 +23,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
final LoginController _controller = LoginController();
|
final LoginController _controller = LoginController();
|
||||||
final TextEditingController _idController = TextEditingController();
|
final TextEditingController _idController = TextEditingController();
|
||||||
final TextEditingController _pwController = TextEditingController();
|
final TextEditingController _pwController = TextEditingController();
|
||||||
|
bool _keepLoggedIn = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -44,14 +45,18 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
_showErrorDialog(result.errorMessage!);
|
_showErrorDialog(result.errorMessage!);
|
||||||
break;
|
break;
|
||||||
case LoginOutcome.masterSuccess:
|
case LoginOutcome.masterSuccess:
|
||||||
await SessionStore.save(
|
if (_keepLoggedIn) {
|
||||||
SavedSession(
|
await SessionStore.save(
|
||||||
userId: result.studentId!,
|
SavedSession(
|
||||||
userName: result.name!,
|
userId: result.studentId!,
|
||||||
role: 'student',
|
userName: result.name!,
|
||||||
isDeviceMatched: true,
|
role: 'student',
|
||||||
),
|
isDeviceMatched: true,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await SessionStore.clear();
|
||||||
|
}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
@@ -194,14 +199,18 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
String name,
|
String name,
|
||||||
bool isDeviceMatched,
|
bool isDeviceMatched,
|
||||||
) async {
|
) async {
|
||||||
await SessionStore.save(
|
if (_keepLoggedIn) {
|
||||||
SavedSession(
|
await SessionStore.save(
|
||||||
userId: studentId,
|
SavedSession(
|
||||||
userName: name,
|
userId: studentId,
|
||||||
role: role,
|
userName: name,
|
||||||
isDeviceMatched: isDeviceMatched,
|
role: role,
|
||||||
),
|
isDeviceMatched: isDeviceMatched,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await SessionStore.clear();
|
||||||
|
}
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pushReplacement(
|
Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
@@ -349,7 +358,31 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
prefixIcon: Icon(Icons.lock),
|
prefixIcon: Icon(Icons.lock),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: () =>
|
||||||
|
setState(() => _keepLoggedIn = !_keepLoggedIn),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Checkbox(
|
||||||
|
value: _keepLoggedIn,
|
||||||
|
activeColor: AppPalette.ink,
|
||||||
|
onChanged: (value) =>
|
||||||
|
setState(() => _keepLoggedIn = value ?? true),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'로그인 상태 유지',
|
||||||
|
style: TextStyle(color: AppPalette.ink),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
_controller.isLoading
|
_controller.isLoading
|
||||||
? const CircularProgressIndicator()
|
? const CircularProgressIndicator()
|
||||||
: SizedBox(
|
: SizedBox(
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ class _MainDashboardState extends State<MainDashboard> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_layout = DashboardLayoutController(_displayId);
|
_layout = DashboardLayoutController(_displayId);
|
||||||
_layout.load(_tileSpecs().map((t) => t.id).toList());
|
// 📱 [기능 보류] kTileEditingEnabled가 false인 동안은 서버에서 배치를 불러올 필요가 없다.
|
||||||
|
if (kTileEditingEnabled) {
|
||||||
|
_layout.load(_tileSpecs().map((t) => t.id).toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -193,7 +196,7 @@ class _MainDashboardState extends State<MainDashboard> {
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => const DashboardSettingsPage()),
|
MaterialPageRoute(builder: (context) => const DashboardSettingsPage()),
|
||||||
);
|
);
|
||||||
if (result == 'edit_ui') {
|
if (result == 'edit_ui' && kTileEditingEnabled) {
|
||||||
_layout.enterEditMode();
|
_layout.enterEditMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,12 +580,16 @@ class _MainDashboardState extends State<MainDashboard> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_layout.isLoading
|
// 📱 [기능 보류] kTileEditingEnabled가 꺼져 있는 동안은 화면 크기에 맞춰
|
||||||
? const Padding(
|
// 알아서 줄서는 기본 그리드를 쓰고, 드래그 편집용 캔버스는 쓰지 않는다.
|
||||||
padding: EdgeInsets.symmetric(vertical: 40),
|
kTileEditingEnabled
|
||||||
child: Center(child: CircularProgressIndicator()),
|
? (_layout.isLoading
|
||||||
)
|
? const Padding(
|
||||||
: _buildGridCanvas(tiles, isEditing),
|
padding: EdgeInsets.symmetric(vertical: 40),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
)
|
||||||
|
: _buildGridCanvas(tiles, isEditing))
|
||||||
|
: _buildSimpleGrid(tiles),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -592,6 +599,26 @@ class _MainDashboardState extends State<MainDashboard> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 📱 [기능 보류] kTileEditingEnabled가 꺼져 있을 때 쓰는 기본 그리드.
|
||||||
|
// 화면 폭에 맞춰 열 개수가 자동으로 줄어들기 때문에 폰에서도 안 깨진다.
|
||||||
|
Widget _buildSimpleGrid(List<({String id, Widget child})> tiles) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
child: GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
|
maxCrossAxisExtent: 160,
|
||||||
|
mainAxisSpacing: 12,
|
||||||
|
crossAxisSpacing: 12,
|
||||||
|
childAspectRatio: 1,
|
||||||
|
),
|
||||||
|
itemCount: tiles.length,
|
||||||
|
itemBuilder: (context, index) => tiles[index].child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildGridCanvas(
|
Widget _buildGridCanvas(
|
||||||
List<({String id, Widget child})> tiles,
|
List<({String id, Widget child})> tiles,
|
||||||
bool isEditing,
|
bool isEditing,
|
||||||
|
|||||||
Reference in New Issue
Block a user