diff --git a/assets/images/helmet.png b/assets/images/helmet.png deleted file mode 100644 index c62fe36..0000000 Binary files a/assets/images/helmet.png and /dev/null differ diff --git a/assets/images/top.png b/assets/images/top.png new file mode 100644 index 0000000..0171e23 Binary files /dev/null and b/assets/images/top.png differ diff --git a/lib/control_screen.dart b/lib/control_screen.dart index 9f51b6d..1d5e5d2 100644 --- a/lib/control_screen.dart +++ b/lib/control_screen.dart @@ -44,12 +44,6 @@ class _ControlScreenState extends State { scrolledUnderElevation: 0, elevation: 0, centerTitle: false, - actions: [ - IconButton( - icon: Icon(Icons.more_vert, color: _mainTextColor), - onPressed: () {}, - ), - ], ), body: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), @@ -108,7 +102,7 @@ class _ControlScreenState extends State { borderRadius: BorderRadius.circular(8), ), child: Image.asset( - 'assets/images/storage.png', + 'assets/images/top.png', fit: BoxFit.contain, ), ), @@ -136,7 +130,7 @@ class _ControlScreenState extends State { const SizedBox(height: 10), Expanded( child: _buildStatusButton( - text: 'ERROR: 문 열림', + text: '센서 경고 : 문 열림', textColor: _warningColor, bgColor: _accentContainerColor, isError: true, @@ -191,7 +185,7 @@ class _ControlScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '문 잠금 제어', + '보관함 문 제어', style: TextStyle( color: _mainTextColor, fontSize: 14, @@ -256,7 +250,7 @@ class _ControlScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '조작 기록 및 경고', + '실시간 상태', style: TextStyle( color: _mainTextColor, fontSize: 14, @@ -283,7 +277,7 @@ class _ControlScreenState extends State { text: TextSpan( children: [ TextSpan( - text: '센서 경고: ', + text: '센서 경고 : ', style: TextStyle( color: _warningColor, fontWeight: FontWeight.bold, @@ -291,9 +285,9 @@ class _ControlScreenState extends State { ), ), TextSpan( - text: '문 닫힘 불완전', + text: '문 열림', style: TextStyle( - color: _mainTextColor, + color: _warningColor, fontSize: 12, fontWeight: FontWeight.bold, ), @@ -334,7 +328,7 @@ class _ControlScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '조작 기록 및 경고', + '시스템 기록', style: TextStyle( color: _mainTextColor, fontSize: 14, @@ -357,7 +351,7 @@ class _ControlScreenState extends State { color: _warningColor, size: 14), const SizedBox(width: 8), Text( - '센서 경고: 문 열림', + '1:50 PM - 센서 경고 : 문 열림', style: TextStyle( color: _mainTextColor, fontSize: 12, diff --git a/lib/history_screen.dart b/lib/history_screen.dart index 14f2640..2ad35ec 100644 --- a/lib/history_screen.dart +++ b/lib/history_screen.dart @@ -32,16 +32,14 @@ class HistoryScreen extends StatefulWidget { } class _HistoryScreenState extends State { - // 🟦 최종 확정 테마 컬러 정의 (대비 구조 교체) - final Color _mainBlueColor = const Color(0xFF002FA7); // 진한 블루 (0xFF002FA7 -> 0xFF0A68FF로 통일) - final Color _mainTextColor = const Color(0xFF1C1C1E); // 어두운 텍스트 - final Color _subTextColor = const Color(0xFF6A717B); // 보조 텍스트 - final Color _pageBackgroundColor = const Color(0xFFF5F7F9); // 🚩 Scaffold 배경 (연한 그레이) - final Color _cardBackgroundColor = Colors.white; // 🚩 카드 배경 (순수 화이트) - final Color _accentContainerColor = const Color(0xFFF0F2F5); // 🚩 내부 상세 박스 배경 (중간 그레이) + final Color _mainBlueColor = const Color(0xFF002FA7); + final Color _mainTextColor = const Color(0xFF1C1C1E); + final Color _subTextColor = const Color(0xFF6A717B); + final Color _pageBackgroundColor = const Color(0xFFF5F7F9); + final Color _cardBackgroundColor = Colors.white; + final Color _accentContainerColor = const Color(0xFFF0F2F5); final Color _borderColor = const Color(0xFF0A68FF).withOpacity(0.8); - // 🚩 그림자 스타일 정의 static const BoxShadow _cleanShadow = BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.07), blurRadius: 8, @@ -53,45 +51,45 @@ class _HistoryScreenState extends State { final List _allHistoryList = [ UsageHistory( - date: '2025.11.25 (화)', // 🇰🇷 요일 변경 + date: '2025.11.25 (화)', startTime: '18:30', endTime: '19:15', duration: 45, - startStation: 'STATION A - GU.UNIV', - endStation: 'STATION B - CITY HALL', + startStation: '스테이션 A - 광주대', + endStation: '스테이션 B - 시청', status: 'RETURNED', isSanitized: true, isDried: true, ), UsageHistory( - date: '2025.11.23 (일)', // 🇰🇷 요일 변경 + date: '2025.11.23 (일)', startTime: '14:00', endTime: '15:30', duration: 90, - startStation: 'STATION B - CITY HALL', - endStation: 'STATION B - CITY HALL', + startStation: '스테이션 B - 시청', + endStation: '스테이션 B - 시청', status: 'RETURNED', isSanitized: true, isDried: false, ), UsageHistory( - date: '2025.11.06 (목)', // 🇰🇷 요일 변경 + date: '2025.11.06 (목)', startTime: '09:00', endTime: '09:30', duration: 30, - startStation: 'STATION A - GU.UNIV', - endStation: 'STATION C - PARK', + startStation: '스테이션 A - 광주대', + endStation: '스테이션 C - 시민공원', status: 'RETURNED', isSanitized: true, isDried: true, ), UsageHistory( - date: '2025.09.20 (토)', // 🇰🇷 요일 변경 + date: '2025.09.20 (토)', startTime: '20:00', endTime: '21:00', duration: 60, - startStation: 'STATION C - PARK', - endStation: 'STATION B - CITY HALL', + startStation: '스테이션 C - 시민공원', + endStation: '스테이션 B - 시청', status: 'RETURNED', isSanitized: true, isDried: true, @@ -109,14 +107,9 @@ class _HistoryScreenState extends State { String dateStr = history.date.split(' ')[0]; List parts = dateStr.split('.'); DateTime historyDate = DateTime( - int.parse(parts[0]), - int.parse(parts[1]), - int.parse(parts[2]) - ); + int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2])); - int difference = now - .difference(historyDate) - .inDays; + int difference = now.difference(historyDate).inDays; if (_selectedFilterIndex == 1) { return difference <= 7; @@ -134,16 +127,16 @@ class _HistoryScreenState extends State { return Scaffold( backgroundColor: _pageBackgroundColor, appBar: AppBar( - title: Text('나의 기록', style: TextStyle(fontWeight: FontWeight - .bold, fontSize: 18, letterSpacing: 0.5, color: _mainTextColor)), + title: Text('나의 기록', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + letterSpacing: 0.5, + color: _mainTextColor)), backgroundColor: _pageBackgroundColor, scrolledUnderElevation: 0, elevation: 0, centerTitle: false, - actions: [ - IconButton(icon: Icon(Icons.filter_list, color: _mainTextColor), - onPressed: () {}), - ], ), body: Column( children: [ @@ -151,7 +144,8 @@ class _HistoryScreenState extends State { Expanded( child: currentList.isEmpty ? Center( - child: Text("기록이 없습니다.", style: TextStyle(color: _subTextColor))) + child: Text("기록이 없습니다.", + style: TextStyle(color: _subTextColor))) : ListView.builder( padding: const EdgeInsets.all(16), itemCount: currentList.length, @@ -159,7 +153,8 @@ class _HistoryScreenState extends State { final history = currentList[index]; bool showHeader = true; - if (index > 0 && currentList[index - 1].date == history.date) { + if (index > 0 && + currentList[index - 1].date == history.date) { showHeader = false; } @@ -216,10 +211,7 @@ class _HistoryScreenState extends State { child: Text( date, style: TextStyle( - color: _mainTextColor, - fontSize: 14, - fontWeight: FontWeight.bold - ), + color: _mainTextColor, fontSize: 14, fontWeight: FontWeight.bold), ), ); } @@ -230,8 +222,7 @@ class _HistoryScreenState extends State { decoration: BoxDecoration( color: _cardBackgroundColor, borderRadius: BorderRadius.circular(8), - boxShadow: [_cleanShadow] - ), + boxShadow: [_cleanShadow]), child: InkWell( onTap: () => _showHistoryDetail(context, history), borderRadius: BorderRadius.circular(8), @@ -244,20 +235,22 @@ class _HistoryScreenState extends State { children: [ Text( '${history.startTime} - ${history.endTime}', - style: TextStyle(color: _mainTextColor, + style: TextStyle( + color: _mainTextColor, fontSize: 16, fontWeight: FontWeight.bold), ), Container( - padding: const EdgeInsets.symmetric( - horizontal: 8, vertical: 4), + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _mainBlueColor, borderRadius: BorderRadius.circular(8), ), child: Text( - '${history.duration}분', // 🇰🇷 min -> 분 - style: const TextStyle(color: Colors.white, + '${history.duration}분', + style: const TextStyle( + color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600), ), @@ -269,9 +262,10 @@ class _HistoryScreenState extends State { children: [ _buildDot(_subTextColor), const SizedBox(width: 8), - Expanded(child: Text(history.startStation, - style: TextStyle(color: _mainTextColor, fontSize: 13), - overflow: TextOverflow.ellipsis)), + Expanded( + child: Text(history.startStation, + style: TextStyle(color: _mainTextColor, fontSize: 13), + overflow: TextOverflow.ellipsis)), ], ), Container( @@ -279,40 +273,45 @@ class _HistoryScreenState extends State { height: 10, decoration: BoxDecoration( border: Border( - left: BorderSide(color: _subTextColor.withOpacity(0.5), width: 1)), + left: BorderSide( + color: _subTextColor.withOpacity(0.5), width: 1)), ), ), Row( children: [ _buildDot(_mainBlueColor), const SizedBox(width: 8), - Expanded(child: Text(history.endStation, - style: TextStyle(color: _mainTextColor, fontSize: 13), - overflow: TextOverflow.ellipsis)), + Expanded( + child: Text(history.endStation, + style: TextStyle(color: _mainTextColor, fontSize: 13), + overflow: TextOverflow.ellipsis)), ], ), const SizedBox(height: 12), - Divider(color: _subTextColor.withOpacity(0.5), thickness: 0.5,), + Divider( + color: _subTextColor.withOpacity(0.5), + thickness: 0.5, + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ - if (history.isSanitized) - _buildDot(_mainBlueColor), - if (history.isSanitized) - const SizedBox(width: 8), + if (history.isSanitized) _buildDot(_mainBlueColor), + if (history.isSanitized) const SizedBox(width: 8), Text( - history.isSanitized ? "살균 완료" : "진행 중", // 🇰🇷 Pending -> 진행 중 + history.isSanitized ? "살균 완료" : "진행 중", style: TextStyle( - color: history.isSanitized ? _mainTextColor : _subTextColor, + color: history.isSanitized + ? _mainTextColor + : _subTextColor, fontSize: 14, fontWeight: FontWeight.bold), ), ], ), Text( - "상세 보기 >", // 🇰🇷 VIEW DETAILS > -> 상세 보기 > + "더보기 >", style: TextStyle(color: _subTextColor, fontSize: 11), ), ], @@ -325,7 +324,8 @@ class _HistoryScreenState extends State { } Widget _buildDot(Color color) { - return Container(width: 8, + return Container( + width: 8, height: 8, decoration: BoxDecoration(color: color, shape: BoxShape.circle)); } @@ -338,21 +338,20 @@ class _HistoryScreenState extends State { isScrollControlled: true, builder: (context) { return Container( - height: MediaQuery - .of(context) - .size - .height * 0.75, + height: MediaQuery.of(context).size.height * 0.75, decoration: BoxDecoration( color: _cardBackgroundColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(12)), - boxShadow: [_cleanShadow] - ), + borderRadius: + const BorderRadius.vertical(top: Radius.circular(12)), + boxShadow: [_cleanShadow]), child: Column( children: [ const SizedBox(height: 12), - Container(width: 40, + Container( + width: 40, height: 4, - decoration: BoxDecoration(color: _subTextColor, + decoration: BoxDecoration( + color: _subTextColor, borderRadius: BorderRadius.circular(2))), Expanded( child: SingleChildScrollView( @@ -360,10 +359,11 @@ class _HistoryScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("이용 상세 정보", style: TextStyle( // 🇰🇷 HISTORY DETAILS - color: _mainTextColor, - fontSize: 20, - fontWeight: FontWeight.bold)), + Text("이용 상세 정보", + style: TextStyle( + color: _mainTextColor, + fontSize: 20, + fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text( history.date, @@ -372,7 +372,8 @@ class _HistoryScreenState extends State { const SizedBox(height: 30), Container( padding: const EdgeInsets.all(20), - decoration: BoxDecoration(color: _accentContainerColor, + decoration: BoxDecoration( + color: _accentContainerColor, borderRadius: BorderRadius.circular(8)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -380,38 +381,45 @@ class _HistoryScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("이용 시간", style: TextStyle( // 🇰🇷 이용 시간 - color: _subTextColor, fontSize: 12)), + Text("이용 시간", + style: TextStyle( + color: _subTextColor, fontSize: 12)), const SizedBox(height: 4), - Text("${history.duration}분", // 🇰🇷 min -> 분 - style: TextStyle(color: _mainTextColor, + Text("${history.duration}분", + style: TextStyle( + color: _mainTextColor, fontSize: 24, fontWeight: FontWeight.bold)), ], ), Container( - width: 1, height: 40, color: _subTextColor.withOpacity(0.5)), + width: 1, + height: 40, + color: _subTextColor.withOpacity(0.5)), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("이용 금액", style: TextStyle( // 🇰🇷 이용 금액 - color: _subTextColor, fontSize: 12)), + Text("이용 금액", + style: TextStyle( + color: _subTextColor, fontSize: 12)), const SizedBox(height: 4), - Text("₩ $cost", style: TextStyle( // 🇰🇷 ₩ 위치 수정 - color: _mainTextColor, - fontSize: 24, - fontWeight: FontWeight.bold)), + Text("₩ $cost", + style: TextStyle( + color: _mainTextColor, + fontSize: 24, + fontWeight: FontWeight.bold)), ], ), ], ), ), const SizedBox(height: 30), - Text("시간별 기록", style: TextStyle( // 🇰🇷 TIMELINE - color: _mainTextColor, - fontSize: 16, - fontWeight: FontWeight.bold, - letterSpacing: 1.0)), + Text("시간별 기록", + style: TextStyle( + color: _mainTextColor, + fontSize: 16, + fontWeight: FontWeight.bold, + letterSpacing: 1.0)), const SizedBox(height: 20), _buildTimelineItem( history.startTime, "대여 시작", history.startStation, @@ -420,26 +428,27 @@ class _HistoryScreenState extends State { history.endTime, "반납 완료", history.endStation, isCompleted: true), _buildTimelineItem( - history.endTime, "살균 시작", "자동 살균 시스템", // 🇰🇷 Auto-Cleaning System + history.endTime, "살균 시작", "자동 살균 시스템", isCompleted: true), _buildTimelineItem( - "15분 후", "살균 완료", "다음 사용자 준비 완료", // 🇰🇷 15 min later, Ready for next user + "15분 후", "살균 완료", "다음 사용자 준비 완료", isLast: true, isCompleted: history.isSanitized), const SizedBox(height: 30), - Text("살균/건조 상태", style: TextStyle( // 🇰🇷 CONDITION - color: _mainTextColor, - fontSize: 16, - fontWeight: FontWeight.bold)), + Text("살균/건조 상태", + style: TextStyle( + color: _mainTextColor, + fontSize: 16, + fontWeight: FontWeight.bold)), const SizedBox(height: 8), Row( children: [ - Expanded(child: _buildConditionCard( - "살균", history.isSanitized, - Icons.sentiment_satisfied_alt)), + Expanded( + child: _buildConditionCard("살균", + history.isSanitized, Icons.sentiment_satisfied_alt)), const SizedBox(width: 12), - Expanded(child: _buildConditionCard( - "건조", history.isDried, - Icons.sentiment_very_satisfied)), + Expanded( + child: _buildConditionCard("건조", history.isDried, + Icons.sentiment_very_satisfied)), ], ), ], @@ -455,7 +464,6 @@ class _HistoryScreenState extends State { Widget _buildTimelineItem(String time, String title, String subtitle, {bool isFirst = false, bool isLast = false, bool isCompleted = false}) { - // 🇰🇷 15 min later -> 15분 후 로직 처리 String displayTime = time.contains("min later") ? "15분 후" : time; return Row( @@ -463,16 +471,18 @@ class _HistoryScreenState extends State { children: [ SizedBox( width: 60, - child: Text( - displayTime, style: TextStyle(color: _mainTextColor, fontSize: 12)), + child: Text(displayTime, + style: TextStyle(color: _mainTextColor, fontSize: 12)), ), Column( children: [ Icon( isCompleted ? Icons.check_circle : Icons.radio_button_unchecked, - color: isCompleted ? _mainBlueColor : _subTextColor, size: 20), + color: isCompleted ? _mainBlueColor : _subTextColor, + size: 20), if (!isLast) - Container(width: 2, + Container( + width: 2, height: 40, color: isCompleted ? _mainBlueColor.withOpacity(0.5) @@ -484,10 +494,11 @@ class _HistoryScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: TextStyle( - color: isCompleted ? _mainTextColor : _subTextColor, - fontWeight: FontWeight.bold, - fontSize: 14)), + Text(title, + style: TextStyle( + color: isCompleted ? _mainTextColor : _subTextColor, + fontWeight: FontWeight.bold, + fontSize: 14)), const SizedBox(height: 4), Text(subtitle, style: TextStyle(color: _subTextColor, fontSize: 12)), @@ -505,10 +516,13 @@ class _HistoryScreenState extends State { width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: _accentContainerColor, // 내부 상세 박스 배경 (중간 그레이) + color: _accentContainerColor, boxShadow: isDone ? [_cleanShadow] : null, border: Border.all( - color: isDone ? _mainBlueColor.withOpacity(0.9) : _subTextColor.withOpacity(0.3), width: 1.0), + color: isDone + ? _mainBlueColor.withOpacity(0.9) + : _subTextColor.withOpacity(0.3), + width: 1.0), borderRadius: BorderRadius.circular(8), ), child: Column( @@ -526,12 +540,11 @@ class _HistoryScreenState extends State { ), const SizedBox(height: 4), Text( - isDone ? "완료됨" : "진행 중", // 🇰🇷 Completed/In Progress + isDone ? "완료됨" : "진행 중", style: TextStyle( color: _subTextColor, fontWeight: FontWeight.bold, - fontSize: 14 - ), + fontSize: 14), ), ], ), diff --git a/lib/home_screen_content.dart b/lib/home_screen_content.dart index 8bb991c..97bac80 100644 --- a/lib/home_screen_content.dart +++ b/lib/home_screen_content.dart @@ -21,8 +21,9 @@ class _HomeScreenContentState extends State { final Color _cardBackgroundColor = Colors.white; final Color _accentContainerColor = const Color(0xFFF0F2F5); final Color _toggleOffTrackColor = const Color(0xFFE0E0E0); - final Color _toggleOffKnobBorderColor = const Color(0xFFB0B0B0); - final Color _toggleOffTextColor = const Color(0xFF909090); + final Color _toggleOffKnobBorderColor = const Color(0xFFE0E0E0); + + int _selectedImageIndex = 0; static const BoxShadow _cleanShadow = BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.07), @@ -103,11 +104,7 @@ class _HomeScreenContentState extends State { padding: const EdgeInsets.fromLTRB(12, 12, 12, 0), child: Row( children: [ - Text('장치 개요', - style: TextStyle( - color: _mainTextColor, - fontSize: 11, - fontWeight: FontWeight.bold)), + Text('장비 상태 / 사용자 정보', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _mainTextColor)), const Spacer(), Icon(Icons.search, color: _subTextColor, size: 20), const SizedBox(width: 8), @@ -138,21 +135,19 @@ class _HomeScreenContentState extends State { Padding( padding: const EdgeInsets.only(bottom: 20.0), child: Image.asset( - 'assets/images/storage.png', - width: 90, + _selectedImageIndex == 0 + ? 'assets/images/storage.png' + : 'assets/images/top.png', + width: 100, ), ), Positioned( bottom: 12, child: Row( children: [ - _buildLedIndicator(Colors.grey.shade300), - const SizedBox(width: 4), - _buildLedIndicator(Colors.grey.shade300), - const SizedBox(width: 4), - _buildLedIndicator(Colors.grey.shade300), - const SizedBox(width: 4), - _buildLedIndicator(_mainTextColor), + _buildSelectableIndicator(0), + const SizedBox(width: 8), + _buildSelectableIndicator(1), ], ), ) @@ -162,13 +157,21 @@ class _HomeScreenContentState extends State { ); } - Widget _buildLedIndicator(Color color) { - return Container( - width: 18, - height: 6, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(3), + Widget _buildSelectableIndicator(int index) { + final bool isSelected = _selectedImageIndex == index; + return GestureDetector( + onTap: () { + setState(() { + _selectedImageIndex = index; + }); + }, + child: Container( + width: 30, + height: 6, + decoration: BoxDecoration( + color: isSelected ? _mainTextColor : Colors.grey.shade300, + borderRadius: BorderRadius.circular(3), + ), ), ); } @@ -179,10 +182,10 @@ class _HomeScreenContentState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildInfoRow('이름 / \n회원번호', Icon(Icons.person, color: _mainTextColor, size: 20), 'USER', '001', + _buildInfoRow('ID', Icon(Icons.person, color: _mainTextColor, size: 20), 'USER', '001', value1Color: _mainTextColor, value2Color: _mainTextColor), const SizedBox(height: 8), - _buildInfoRow('상태', null, '잠금 해제됨', '● 활성', + _buildInfoRow('STATUS', null, '잠금 해제', '● 활성', value1Color: _mainTextColor, value2Color: _mainTextColor), ], ), @@ -234,13 +237,13 @@ class _HomeScreenContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('배터리 상태 (%)', style: TextStyle(color: _mainTextColor, fontSize: 12, fontWeight: FontWeight.bold)), - const SizedBox(height: 16), + Text('배터리 상태 (%)', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _mainTextColor)), + const SizedBox(height: 20), Row( children: [ SizedBox( - width: 80, - height: 80, + width: 70, + height: 70, child: Stack( alignment: Alignment.center, children: [ @@ -315,7 +318,8 @@ class _HomeScreenContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('', style: TextStyle(color: _mainTextColor, fontSize: 11, fontWeight: FontWeight.bold)), + Text('보관함 원격 제어', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _mainTextColor)), + const SizedBox(height: 16), SizedBox( height: 70, @@ -326,7 +330,7 @@ class _HomeScreenContentState extends State { 'UV LED', _controlToggles['UV LED']!, (val) => setState(() => _controlToggles['UV LED'] = val))), - VerticalDivider(color: _subTextColor.withOpacity(0.5), indent: 10, endIndent: 10), + VerticalDivider(color: _mainBlueColor.withOpacity(0.5), indent: 10, endIndent: 10), Expanded( child: _buildStyledToggleSwitch( 'CHARGING', @@ -367,7 +371,7 @@ class _HomeScreenContentState extends State { height: 30, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), - color: value ? _mainTextColor.withOpacity(0.8) : _toggleOffTrackColor, + color: value ? _mainBlueColor : _toggleOffTrackColor, ), child: Stack( children: [ @@ -383,7 +387,9 @@ class _HomeScreenContentState extends State { decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white, - border: Border.all(color: value ? _mainTextColor : _toggleOffKnobBorderColor, width: 1.5), + border: Border.all( + color: value ? _mainBlueColor : _toggleOffKnobBorderColor, + width: 1.5), ), ), ), @@ -425,13 +431,13 @@ class _HomeScreenContentState extends State { Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text('환경 센서', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _mainTextColor)), + Text('환경 모니터링', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _mainTextColor)), const Spacer(), InkWell( onTap: () {}, child: Row( children: [ - Text('기록 보기', style: TextStyle(color: _mainTextColor, fontSize: 10)), + Text('더보기', style: TextStyle(color: _mainTextColor, fontSize: 9)), const SizedBox(width: 4), Icon(Icons.arrow_forward_ios, size: 10, color: _subTextColor), ], @@ -501,7 +507,7 @@ class _HomeScreenContentState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('현재 위치', style: TextStyle(fontSize: 13, color: _mainTextColor, fontWeight: FontWeight.bold)), + Text('현위치', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _mainTextColor)), const SizedBox(height: 4), Text('주소: 남구 효덕로 277', style: TextStyle(fontSize: 11, color: _mainTextColor)), ], @@ -510,7 +516,7 @@ class _HomeScreenContentState extends State { onTap: () {}, child: Row( children: [ - Text('더 보기', style: TextStyle(color: _mainTextColor, fontSize: 9)), + Text('더보기', style: TextStyle(color: _mainTextColor, fontSize: 9)), const SizedBox(width: 4), Icon(Icons.arrow_forward_ios, size: 10, color: _subTextColor), ], @@ -561,11 +567,7 @@ class _HomeScreenContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Activity', - style: TextStyle( - fontSize: 13, - color: _mainTextColor, - fontWeight: FontWeight.bold)), + Text('최근 활동', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: _mainTextColor)), const SizedBox(height: 12), Row( children: [ @@ -573,9 +575,9 @@ class _HomeScreenContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _activityText('08:15 AM - Battery fully Charged'), + _activityText('08:15 AM - 배터리 충전 완료'), const SizedBox(height: 8), - _activityText('9:30 AM - UV LED Actived'), + _activityText('9:30 AM - UV LED 활성화 됨'), ], ), ), @@ -584,9 +586,9 @@ class _HomeScreenContentState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _activityText('10:45 AM - Helmet Unlocked'), + _activityText('10:45 AM - 헬멧 잠금 해제'), const SizedBox(height: 8), - _activityText('11:00 AM - Helmet Off'), + _activityText('11:00 AM - 헬멧 착용 해제'), ], ), ), @@ -622,9 +624,9 @@ class _LineChartPlaceholder extends StatelessWidget { CustomPaint(painter: _LineChartPainter(color: lineColor), size: Size.infinite)), const SizedBox(height: 4), Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('24H AGO', style: TextStyle(fontSize: 8, color: subLabelColor)), - Text('12H AGO', style: TextStyle(fontSize: 8, color: subLabelColor)), - Text('NOW', style: TextStyle(fontSize: 8, color: subLabelColor)) + Text('24 시간 전', style: TextStyle(fontSize: 8, color: subLabelColor)), + Text('12시간 전', style: TextStyle(fontSize: 8, color: subLabelColor)), + Text('현재', style: TextStyle(fontSize: 8, color: subLabelColor)) ]) ]); } diff --git a/lib/rent_return_screen.dart b/lib/rent_return_screen.dart index 8cf3ac6..e7f3bfc 100644 --- a/lib/rent_return_screen.dart +++ b/lib/rent_return_screen.dart @@ -40,25 +40,25 @@ class RentReturnScreen extends StatelessWidget { final LatLng centerLocation = LatLng(35.1595, 126.8526); final List stations = [ StationInfo( - name: 'STATION A - GU.UNIV', - address: '277 Hyodeok-ro', - status: 'ONLINE', - distance: '0.5 km away'), + name: '스테이션 A - 광주대학교', + address: '효덕로 277', + status: '온라인', + distance: '0.5 km'), StationInfo( - name: 'STATION B - CITY HALL', - address: '123 City Hall Ave', - status: 'ONLINE', - distance: '1.2 km away'), + name: '스테이션 B - 시청', + address: '시청대로 123', + status: '온라인', + distance: '1.2 km'), StationInfo( - name: 'STATION C - PARK', - address: '55 Park Lane', - status: 'OFFLINE', - distance: '2.8 km away'), + name: '스테이션 C - 공원', + address: '공원길 55', + status: '오프라인', + distance: '2.8 km'), StationInfo( - name: 'STATION D - TECH HUB', - address: '88 Innovation Blvd', - status: 'ONLINE', - distance: '4.1 km away'), + name: '스테이션 D - 테크 허브', + address: '이노베이션대로 88', + status: '온라인', + distance: '4.1 km'), ]; return Scaffold( @@ -88,18 +88,13 @@ class RentReturnScreen extends StatelessWidget { ), backgroundColor: Colors.transparent, elevation: 0, - actions: [ - IconButton( - icon: Icon(Icons.more_vert, color: _mainTextColor), - onPressed: () {}, - ) - ], ), ), ), body: SingleChildScrollView( child: Column( children: [ + SizedBox( height: 300.0, width: double.infinity, @@ -110,8 +105,7 @@ class RentReturnScreen extends StatelessWidget { ), children: [ TileLayer( - urlTemplate: - 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', + urlTemplate: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', userAgentPackageName: 'com.example.helmet_app', subdomains: const ['a', 'b', 'c', 'd'], retinaMode: true, @@ -127,6 +121,7 @@ class RentReturnScreen extends StatelessWidget { ], ), ), + Container( color: _pageBackgroundColor, padding: const EdgeInsets.fromLTRB(16, 20, 16, 0), @@ -134,7 +129,7 @@ class RentReturnScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'LIST VIEW', + '목록 보기', style: TextStyle( color: _mainTextColor, fontSize: 12, @@ -142,6 +137,7 @@ class RentReturnScreen extends StatelessWidget { ), ), const SizedBox(height: 10), + ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -208,6 +204,7 @@ class RentReturnScreen extends StatelessWidget { ), ), const SizedBox(width: 12), + Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -233,7 +230,9 @@ class RentReturnScreen extends StatelessWidget { Text( 'STATUS: ${station.status}', style: TextStyle( - color: isOffline ? Colors.redAccent : _mainBlueColor, + color: isOffline + ? Colors.redAccent + : _mainBlueColor, fontSize: 11, ), ), @@ -244,10 +243,9 @@ class RentReturnScreen extends StatelessWidget { ], ), ), + ElevatedButton( - onPressed: isOffline - ? null - : () { + onPressed: isOffline ? null : () { Navigator.push( context, MaterialPageRoute( @@ -258,26 +256,25 @@ class RentReturnScreen extends StatelessWidget { ); }, style: ElevatedButton.styleFrom( - backgroundColor: - isOffline ? Colors.grey.shade400 : _mainBlueColor, + backgroundColor: isOffline ? Colors.grey.shade400 : _mainBlueColor, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), ), - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), minimumSize: const Size(80, 40), elevation: 0, ), - child: const Text('SELECT', - style: TextStyle( - fontWeight: FontWeight.w500, - letterSpacing: 0.8, - fontSize: 14)), + child: const Text( + 'SELECT', + style: TextStyle(fontWeight: FontWeight.w500, letterSpacing: 0.8, fontSize: 14) + ), ), ], ), + const SizedBox(height: 12), + Container( width: double.infinity, padding: const EdgeInsets.all(12), @@ -292,7 +289,7 @@ class RentReturnScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '기록', + '이용 기록', style: TextStyle( color: _mainTextColor, fontWeight: FontWeight.bold, @@ -302,7 +299,7 @@ class RentReturnScreen extends StatelessWidget { InkWell( onTap: () => _showLogHistory(context, station.name), child: Text( - 'VIEW MORE >', + '더보기 >', style: TextStyle( color: _subTextColor, fontSize: 10, @@ -313,7 +310,7 @@ class RentReturnScreen extends StatelessWidget { ), const SizedBox(height: 6), Text( - 'Available: Door Fully Closed', + '이용 가능: 문 닫힘', style: TextStyle(color: _subTextColor, fontSize: 11), ), Text( @@ -358,35 +355,23 @@ class RentReturnScreen extends StatelessWidget { children: [ const SizedBox(height: 12), Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: _subTextColor, - borderRadius: BorderRadius.circular(2)), + width: 40, height: 4, + decoration: BoxDecoration(color: _subTextColor, borderRadius: BorderRadius.circular(2)), ), Padding( padding: const EdgeInsets.all(20.0), - child: Text("LOGS: $stationName", - style: TextStyle( - color: _mainTextColor, - fontSize: 16, - fontWeight: FontWeight.bold)), + child: Text("LOGS: $stationName", style: TextStyle(color: _mainTextColor, fontSize: 16, fontWeight: FontWeight.bold)), ), Divider(color: _subTextColor.withOpacity(0.5), height: 1), Expanded( child: ListView( padding: const EdgeInsets.all(20), children: [ - _buildLogItem("08:58:33", "Door Fully Closed", - Icons.door_front_door, _mainBlueColor), - _buildLogItem("08:58:30", "Helmet Returned", - Icons.check_circle_outline, _mainBlueColor), - _buildLogItem("08:55:12", "User Unlocked Door", - Icons.lock_open, _mainTextColor), - _buildLogItem("08:30:00", "UV Sanitization Complete", - Icons.cleaning_services, _mainBlueColor), - _buildLogItem("08:00:00", "System Boot Up", - Icons.power_settings_new, _subTextColor), + _buildLogItem("08:58:33", "문 닫힘", Icons.door_front_door, _mainBlueColor), + _buildLogItem("08:58:30", "헬멧 반납 확인", Icons.check_circle_outline, _mainBlueColor), + _buildLogItem("08:55:12", "사용자 문 잠금 해제", Icons.lock_open, _mainTextColor), + _buildLogItem("08:30:00", "UV 살균 완료", Icons.cleaning_services, _mainBlueColor), + _buildLogItem("08:00:00", "시스템 가동 중", Icons.power_settings_new, _subTextColor), ], ), ), @@ -397,8 +382,7 @@ class RentReturnScreen extends StatelessWidget { ); } - Widget _buildLogItem( - String time, String message, IconData icon, Color color) { + Widget _buildLogItem(String time, String message, IconData icon, Color color) { return Padding( padding: const EdgeInsets.only(bottom: 20.0), child: Row( @@ -409,14 +393,12 @@ class RentReturnScreen extends StatelessWidget { Column( children: [ Icon(icon, color: color, size: 20), - Container( - width: 2, height: 20, color: _subTextColor.withOpacity(0.5)), + Container(width: 2, height: 20, color: _subTextColor.withOpacity(0.5)), ], ), const SizedBox(width: 16), Expanded( - child: Text(message, - style: TextStyle(color: _mainTextColor, fontSize: 14)), + child: Text(message, style: TextStyle(color: _mainTextColor, fontSize: 14)), ), ], ), diff --git a/lib/rental_process_screen.dart b/lib/rental_process_screen.dart index 1ce62d0..982205b 100644 --- a/lib/rental_process_screen.dart +++ b/lib/rental_process_screen.dart @@ -35,12 +35,6 @@ class RentalProcessScreen extends StatelessWidget { ), backgroundColor: _cardBackgroundColor, elevation: 0, - actions: [ - IconButton( - icon: const Icon(Icons.more_vert, color: _mainTextColor), - onPressed: () {}, - ), - ], ), body: SingleChildScrollView( child: Padding( @@ -50,7 +44,7 @@ class RentalProcessScreen extends StatelessWidget { children: [ _buildStatusCard(context), const SizedBox(height: 24), - _buildProcessSectionTitle('헬멧 대여 순서'), + _buildProcessSectionTitle('헬멧 대여'), const SizedBox(height: 12), Container( decoration: BoxDecoration( @@ -66,13 +60,13 @@ class RentalProcessScreen extends StatelessWidget { _buildDivider(), _buildStepRow(3, '헬멧 꺼내기', Icons.outbox), _buildDivider(), - _buildStepRow(4, '주행 시작 !', Icons.sentiment_satisfied_alt, + _buildStepRow(4, '주행 시작', Icons.sentiment_satisfied_alt, showDivider: false), ], ), ), const SizedBox(height: 24), - _buildProcessSectionTitle('헬멧 반납 과정'), + _buildProcessSectionTitle('헬멧 반납'), const SizedBox(height: 12), Container( decoration: BoxDecoration( @@ -135,7 +129,7 @@ class RentalProcessScreen extends StatelessWidget { borderRadius: BorderRadius.circular(12), ), child: Image.asset( - 'assets/images/open.png', + 'assets/images/top.png', fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) => const Icon( Icons.inventory_2, @@ -149,10 +143,11 @@ class RentalProcessScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('NOW', - style: TextStyle(color: _subTextColor, fontSize: 12)), + style: TextStyle(color: Colors.black, fontSize: 12)), const SizedBox(height: 8), SizedBox( width: double.infinity, + height: 68, child: ElevatedButton( onPressed: () { ScaffoldMessenger.of(context).showSnackBar( @@ -175,7 +170,7 @@ class RentalProcessScreen extends StatelessWidget { fontWeight: FontWeight.bold, fontSize: 16)), ), ), - const SizedBox(height: 12), + const SizedBox(height: 10), Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12), @@ -185,7 +180,7 @@ class RentalProcessScreen extends StatelessWidget { ), child: const Center( child: Text( - 'ERROR:\nLOCK FAIL', + '센서 경고 : 잠금 실패', style: TextStyle( color: _primaryOrange, fontWeight: FontWeight.bold, @@ -271,12 +266,7 @@ class RentalProcessScreen extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('SENSOR ERROR:', - style: TextStyle( - color: _primaryOrange, - fontWeight: FontWeight.bold, - fontSize: 16)), - Text('LOCK FAILURE', + Text('센서 오류 : 잠금 실패', style: TextStyle( color: _primaryOrange, fontWeight: FontWeight.bold, @@ -300,12 +290,12 @@ class RentalProcessScreen extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text('LOGS', + const Text('로그', style: TextStyle( color: _mainTextColor, fontWeight: FontWeight.bold)), InkWell( onTap: () => _showLogHistory(context), - child: Text('VIEW MORE >', + child: Text('더보기 >', style: TextStyle(color: _subTextColor, fontSize: 12)), ), ], @@ -315,12 +305,12 @@ class RentalProcessScreen extends StatelessWidget { children: [ const Icon(Icons.check_circle, color: _mainBlueColor, size: 16), const SizedBox(width: 8), - const Text('Available: Door Fully Closed', + const Text('Available: 문 닫힘', style: TextStyle(color: _mainTextColor)), ], ), Padding( - padding: const EdgeInsets.only(left: 24.0, top: 4), + padding: const EdgeInsets.only(left: 210, top: 4), child: Text('(2025-11-19)/(08:58)', style: TextStyle(color: _subTextColor, fontSize: 12)), ), @@ -364,15 +354,15 @@ class RentalProcessScreen extends StatelessWidget { child: ListView( padding: const EdgeInsets.all(20), children: [ - _buildLogItem("08:58:33", "Door Fully Closed", + _buildLogItem("08:58:33", "문 열림", Icons.door_front_door, _mainBlueColor), - _buildLogItem("08:58:30", "Helmet Returned (Sensor A)", + _buildLogItem("08:58:30", "헬멧 반납 확인(센서 A)", Icons.check_circle_outline, _mainBlueColor), - _buildLogItem("08:55:12", "User Unlocked Door", + _buildLogItem("08:55:12", "사용자 문 잠금 해제", Icons.lock_open, _mainTextColor), - _buildLogItem("08:30:00", "UV Sanitization Complete", + _buildLogItem("08:30:00", "UV 살균 완료", Icons.cleaning_services, _mainBlueColor), - _buildLogItem("08:00:00", "System Boot Up", + _buildLogItem("08:00:00", "시스템 가동 중", Icons.power_settings_new, _subTextColor), ], ), diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index 5b8ee84..aec5e55 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -6,7 +6,7 @@ final Color _subTextColor = const Color(0xFF6A717B); final Color _pageBackgroundColor = const Color(0xFFF5F7F9); final Color _cardBackgroundColor = Colors.white; final Color _accentContainerColor = const Color(0xFFF0F2F5); -final Color _warningColor = Colors.redAccent; +final Color _warningColor = const Color(0xFFFF3B30); const BoxShadow _cleanShadow = BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.07), @@ -15,7 +15,6 @@ const BoxShadow _cleanShadow = BoxShadow( spreadRadius: 0, ); - class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -24,14 +23,495 @@ class SettingsScreen extends StatefulWidget { } class _SettingsScreenState extends State { - bool _isPushEnabled = true; bool _isRentalAlertEnabled = true; bool _isStorageStatusAlert = true; - bool _isAutoSyncEnabled = false; + bool _isEnvSensorAlert = true; - final String _currentAppVersion = "v1.0.0"; + bool _isBiometricEnabled = false; + bool _isAutoLogoutEnabled = true; + bool _isLoginNotificationEnabled = true; + bool _isLocationEnabled = true; + final String _currentAppVersion = "v1.0.2"; + + void _showCommonModal(BuildContext context, String title, Widget content, {String rightButtonLabel = '닫기'}) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return Container( + height: MediaQuery.of(context).size.height * 0.85, + decoration: BoxDecoration( + color: _pageBackgroundColor, + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Column( + children: [ + Center( + child: Container( + margin: const EdgeInsets.only(top: 12, bottom: 20), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: _mainTextColor, + ), + ), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(rightButtonLabel, + style: TextStyle(color: _mainBlueColor, fontWeight: FontWeight.bold)), + ) + ], + ), + ), + const Divider(), + Expanded( + child: content, + ), + ], + ), + ); + }, + ); + } + + void _showEditProfileSheet(BuildContext context) { + _showCommonModal( + context, + '내 정보 수정', + ListView( + padding: const EdgeInsets.all(20), + children: [ + _buildInputGroup('닉네임', '현재 닉네임'), + const SizedBox(height: 20), + _buildInputGroup('이메일', 'user@example.com', isReadOnly: true), + const SizedBox(height: 20), + _buildInputGroup('전화번호', '010-1234-5678'), + ], + ), + rightButtonLabel: '완료', + ); + } + + void _showPasswordChangeSheet(BuildContext context) { + _showCommonModal( + context, + '비밀번호 변경', + ListView( + padding: const EdgeInsets.all(20), + children: [ + _buildInputGroup('현재 비밀번호', '사용 중인 비밀번호 입력', isObscure: true), + const SizedBox(height: 20), + _buildInputGroup('새 비밀번호', '영문, 숫자, 특수문자 포함 8자 이상', isObscure: true), + const SizedBox(height: 20), + _buildInputGroup('새 비밀번호 확인', '비밀번호 재입력', isObscure: true), + const SizedBox(height: 30), + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: _mainBlueColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + child: const Text('변경하기', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), + ), + ) + ], + ), + ); + } + + void _showDeviceListSheet(BuildContext context) { + _showCommonModal( + context, + '로그인 관리', + ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildSectionTitle('현재 접속 중인 기기'), + const SizedBox(height: 10), + _buildDeviceItem('Galaxy S24 Ultra', '서울, 대한민국 • 지금 활동 중', true), + const SizedBox(height: 30), + _buildSectionTitle('다른 접속 기기'), + const SizedBox(height: 10), + _buildDeviceItem('iPhone 15 Pro', '부산, 대한민국 • 3시간 전', false), + _buildDeviceItem('Chrome (Windows)', '경기도 성남시 • 1일 전', false), + ], + ), + ); + } + + void _showNotificationStyleSheet(BuildContext context) { + _showCommonModal( + context, + '알림 방식 설정', + ListView( + padding: const EdgeInsets.all(20), + children: [ + _buildRadioItem('배너 + 진동', true), + _buildRadioItem('배너만 표시', false), + _buildRadioItem('진동만 울림', false), + _buildRadioItem('무음', false), + ], + ), + rightButtonLabel: '저장', + ); + } + + void _showClearCacheSheet(BuildContext context) { + _showCommonModal( + context, + '캐시 데이터 관리', + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.cleaning_services_outlined, size: 60, color: _subTextColor), + const SizedBox(height: 20), + Text( + '저장된 캐시 데이터: 12.5 MB', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: _mainTextColor), + ), + const SizedBox(height: 10), + Text( + '캐시 데이터를 삭제하면 앱의 로딩 속도가 느려질 수 있지만,\n저장 공간을 확보할 수 있습니다.\n로그인 정보나 중요한 설정은 삭제되지 않습니다.', + textAlign: TextAlign.center, + style: TextStyle(color: _subTextColor, height: 1.5), + ), + const SizedBox(height: 40), + SizedBox( + width: double.infinity, + height: 50, + child: TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + backgroundColor: _warningColor.withOpacity(0.1), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + child: Text('모두 삭제하기', style: TextStyle(color: _warningColor, fontWeight: FontWeight.bold)), + ), + ), + ], + ), + ), + ); + } + + void _showTermsSheet(BuildContext context) { + _showCommonModal( + context, + '이용 약관', + SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Text( + '제 1 조 (목적)\n이 약관은 스마트 헬멧 서비스(이하 "서비스")의 이용 조건 및 절차, 이용자와 회사의 권리, 의무, 책임 사항을 규정함을 목적으로 합니다.\n\n제 2 조 (용어의 정의)\n1. "이용자"란 앱에 접속하여 본 약관에 따라 서비스를 이용하는 회원을 말합니다.\n2. "헬멧"이란 회사가 대여하는 스마트 IoT 안전모를 말합니다.\n\n(이하 생략... 더미 데이터입니다.)\n\n제 3 조 (약관의 효력)\n본 약관은 서비스를 신청한 때부터 효력이 발생합니다.', + style: TextStyle(color: _subTextColor, height: 1.6), + ), + ), + ); + } + + void _showLicenseSheet(BuildContext context) { + _showCommonModal( + context, + '오픈소스 라이선스', + ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildLicenseItem('Flutter', 'Google', 'BSD-style'), + _buildLicenseItem('Cupertino Icons', 'Google', 'MIT'), + _buildLicenseItem('Kakao Maps SDK', 'Kakao Corp.', 'Apache 2.0'), + _buildLicenseItem('Firebase Core', 'Google', 'Apache 2.0'), + ], + ), + ); + } + + void _showSupportSheet(BuildContext context) { + _showCommonModal( + context, + '고객센터', + ListView( + padding: const EdgeInsets.all(20), + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: _accentContainerColor, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + children: [ + const Text('운영 시간: 평일 09:00 ~ 18:00', style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 4), + Text('(점심시간 12:00 ~ 13:00)', style: TextStyle(color: _subTextColor, fontSize: 12)), + ], + ), + ), + const SizedBox(height: 20), + _buildInfoLink('전화 상담 연결', Icons.phone, value: '1588-0000'), + const SizedBox(height: 10), + _buildInfoLink('1:1 채팅 상담', Icons.chat_bubble_outline), + const SizedBox(height: 10), + _buildInfoLink('이메일 문의', Icons.email_outlined, value: 'help@smarthelmet.com'), + ], + ), + ); + } + + void _showFAQSheet(BuildContext context) { + _showCommonModal( + context, + '자주 묻는 질문 (FAQ)', + ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildFAQItem('Q. 헬멧 대여는 어떻게 하나요?', 'A. 메인 화면의 지도에서 가까운 보관함을 찾은 후, QR코드를 스캔하여 대여할 수 있습니다.'), + _buildFAQItem('Q. 반납이 안 될 때는 어떻게 하나요?', 'A. 보관함의 통신 상태를 확인해 주세요. 지속적으로 실패할 경우 고객센터로 연락 바랍니다.'), + _buildFAQItem('Q. 결제 수단 변경은 어디서 하나요?', 'A. [마이페이지] > [결제 관리] 메뉴에서 카드 정보를 변경하실 수 있습니다.'), + ], + ), + ); + } + + void _showMapInfoSheet(BuildContext context) { + _showCommonModal( + context, + '서비스 지역 안내', + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.map_outlined, size: 80, color: Colors.grey.shade300), + const SizedBox(height: 20), + Text('서비스 지역 지도 표시 영역', style: TextStyle(fontSize: 16, color: _subTextColor, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Text('현재 서울, 경기 일부 지역에서\n서비스를 이용하실 수 있습니다.', textAlign: TextAlign.center, style: TextStyle(color: _subTextColor)), + ], + ), + ), + ); + } + + void _showPushPermissionSheet(BuildContext context) { + _showCommonModal( + context, + '알림 설정 안내', + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('알림 권한이 꺼져 있나요?', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 10), + Text('중요한 헬멧 안전 경고 및 반납 알림을 받으려면 기기 설정에서 알림을 허용해야 합니다.', style: TextStyle(color: _subTextColor, height: 1.5)), + const SizedBox(height: 30), + _buildInfoLink('기기 설정으로 이동', Icons.settings, onTap: () => Navigator.pop(context)), + ], + ), + ), + ); + } + + void _showCameraPermissionSheet(BuildContext context) { + _showCommonModal( + context, + '카메라 권한 설정', + Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: _accentContainerColor, + shape: BoxShape.circle, + ), + child: Icon(Icons.camera_alt, size: 40, color: _mainBlueColor), + ), + const SizedBox(height: 24), + const Text( + '카메라 권한이 필요합니다', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + Text( + '헬멧 대여 및 반납 시 QR코드를 스캔하기 위해\n카메라 접근 권한이 반드시 필요합니다.\n권한을 거부하면 서비스를 이용할 수 없습니다.', + textAlign: TextAlign.center, + style: TextStyle(color: _subTextColor, height: 1.5), + ), + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade200), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('현재 상태', style: TextStyle(fontWeight: FontWeight.bold)), + Text('허용됨', style: TextStyle(color: _mainBlueColor, fontWeight: FontWeight.bold)), + ], + ), + ), + const Spacer(), + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + }, + style: ElevatedButton.styleFrom( + backgroundColor: _mainTextColor, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ), + child: const Text('기기 설정에서 변경하기', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), + ), + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } + + Widget _buildDeviceItem(String name, String info, bool isCurrent) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: isCurrent ? _mainBlueColor : Colors.transparent, width: 1.5), + boxShadow: [_cleanShadow], + ), + child: Row( + children: [ + Icon(isCurrent ? Icons.phone_android : Icons.devices_other, color: isCurrent ? _mainBlueColor : _subTextColor), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(name, style: TextStyle(fontWeight: FontWeight.bold, color: _mainTextColor)), + const SizedBox(height: 4), + Text(info, style: TextStyle(fontSize: 12, color: _subTextColor)), + ], + ), + ), + if (!isCurrent) + TextButton( + onPressed: () {}, + child: Text('로그아웃', style: TextStyle(color: _warningColor, fontSize: 12)), + ), + if (isCurrent) + Text('현재 기기', style: TextStyle(color: _mainBlueColor, fontSize: 12, fontWeight: FontWeight.bold)), + ], + ), + ); + } + + Widget _buildRadioItem(String title, bool isSelected) { + return Container( + margin: const EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + ), + child: ListTile( + title: Text(title, style: TextStyle(color: _mainTextColor, fontSize: 15)), + trailing: isSelected ? Icon(Icons.check_circle, color: _mainBlueColor) : Icon(Icons.circle_outlined, color: Colors.grey.shade300), + onTap: () {}, + ), + ); + } + + Widget _buildLicenseItem(String libName, String author, String licenseType) { + return Container( + margin: const EdgeInsets.only(bottom: 8), + child: ExpansionTile( + title: Text(libName, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15)), + subtitle: Text('$author • $licenseType'), + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Permission is hereby granted, free of charge, to any person obtaining a copy of this software...', + style: TextStyle(color: _subTextColor, fontSize: 12), + ), + ) + ], + ), + ); + } + + Widget _buildFAQItem(String question, String answer) { + return Card( + elevation: 0, + color: Colors.white, + margin: const EdgeInsets.only(bottom: 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12), side: BorderSide(color: _accentContainerColor)), + child: ExpansionTile( + title: Text(question, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: _mainTextColor)), + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + color: _accentContainerColor.withOpacity(0.5), + child: Text(answer, style: TextStyle(color: _subTextColor, fontSize: 13, height: 1.5)), + ) + ], + ), + ); + } + + Widget _buildInputGroup(String label, String placeholder, {bool isReadOnly = false, bool isObscure = false}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: TextStyle(color: _subTextColor, fontSize: 13, fontWeight: FontWeight.w600)), + const SizedBox(height: 8), + TextField( + readOnly: isReadOnly, + obscureText: isObscure, + decoration: InputDecoration( + hintText: placeholder, + hintStyle: TextStyle(color: _subTextColor.withOpacity(0.5)), + filled: true, + fillColor: isReadOnly ? _accentContainerColor : Colors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + ), + ), + ], + ); + } @override Widget build(BuildContext context) { @@ -41,19 +521,15 @@ class _SettingsScreenState extends State { scrolledUnderElevation: 0, title: Text( '설정', - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 16, - color: _mainTextColor, - ), + style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16, color: _mainTextColor), ), backgroundColor: _pageBackgroundColor, elevation: 0, centerTitle: false, actions: [ IconButton( - icon: Icon(Icons.more_vert, color: _mainTextColor), - onPressed: () {}, + icon: Icon(Icons.close, color: _mainTextColor), + onPressed: () => Navigator.pop(context), ), ], ), @@ -63,21 +539,157 @@ class _SettingsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSectionTitle('계정 및 보안'), - const SizedBox(height: 12), - _buildAccountSecurityCard(), + const SizedBox(height: 8), + _buildCardWrapper( + child: Column( + children: [ + _buildInfoLink('내 정보 수정', Icons.person_outline, onTap: () => _showEditProfileSheet(context)), + _buildDivider(16), + _buildInfoLink('비밀번호 변경', Icons.lock_outline, onTap: () => _showPasswordChangeSheet(context)), + _buildDivider(16), + _buildInfoLink('로그인 관리 / 기기 목록', Icons.devices, onTap: () => _showDeviceListSheet(context)), + _buildDivider(0), + _buildToggleItem( + '생체 인증 사용', + '앱 잠금 해제 및 인증', + _isBiometricEnabled, + (val) => setState(() => _isBiometricEnabled = val), + showDivider: true, + ), + _buildToggleItem( + '이상 감지 시 자동 로그아웃', + '보안 위험 감지 시 즉시 로그아웃', + _isAutoLogoutEnabled, + (val) => setState(() => _isAutoLogoutEnabled = val), + showDivider: true, + ), + _buildToggleItem( + '새 기기 로그인 알림', + '이메일로 알림 발송', + _isLoginNotificationEnabled, + (val) => setState(() => _isLoginNotificationEnabled = val), + showDivider: false, + ), + ], + ), + ), const SizedBox(height: 24), _buildSectionTitle('알림 설정'), - const SizedBox(height: 12), - _buildNotificationCard(), + const SizedBox(height: 8), + _buildCardWrapper( + child: Column( + children: [ + _buildToggleItem( + '알림 전체 수신', + '모든 푸시 알림을 켜고 끕니다.', + _isPushEnabled, + (val) => setState(() => _isPushEnabled = val), + showDivider: true, + ), + if (_isPushEnabled) ...[ + _buildToggleItem( + '대여/반납 알림', + '시작, 종료, 반납 실패', + _isRentalAlertEnabled, + (val) => setState(() => _isRentalAlertEnabled = val), + showDivider: true, + ), + _buildToggleItem( + '보관함 상태 알림', + '살균/건조 완료, 시스템 오류', + _isStorageStatusAlert, + (val) => setState(() => _isStorageStatusAlert = val), + showDivider: true, + ), + _buildToggleItem( + '환경 센서 경고', + '비정상 온도, 배터리 부족, 통신 장애', + _isEnvSensorAlert, + (val) => setState(() => _isEnvSensorAlert = val), + showDivider: true, + ), + _buildDivider(16), + _buildInfoLink('알림 방식', Icons.notifications_active_outlined, value: '배너 + 진동', onTap: () => _showNotificationStyleSheet(context)), + ], + ], + ), + ), + + const SizedBox(height: 24), + _buildSectionTitle('데이터 및 캐시'), + const SizedBox(height: 8), + _buildCardWrapper( + child: Column( + children: [ + _buildInfoLink('캐시 데이터 삭제', Icons.cleaning_services_outlined, value: '12.5 MB', onTap: () => _showClearCacheSheet(context)), + ], + ), + ), + + const SizedBox(height: 24), + _buildSectionTitle('위치 및 권한'), + const SizedBox(height: 8), + _buildCardWrapper( + child: Column( + children: [ + _buildToggleItem( + '위치 서비스 사용', + '내 주변 보관함 찾기', + _isLocationEnabled, + (val) => setState(() => _isLocationEnabled = val), + showDivider: true, + ), + _buildInfoLink('카메라 권한', Icons.camera_alt_outlined, value: '허용됨', onTap: () => _showCameraPermissionSheet(context)), + _buildDivider(16), + _buildInfoLink('푸시 권한 설정 안내', Icons.settings_applications_outlined, onTap: () => _showPushPermissionSheet(context)), + ], + ), + ), const SizedBox(height: 24), _buildSectionTitle('앱 정보'), - const SizedBox(height: 12), - _buildAppInfoCard(), + const SizedBox(height: 8), + _buildCardWrapper( + child: Column( + children: [ + _buildInfoLink('버전 정보', null, value: _currentAppVersion, showArrow: false), + _buildDivider(16), + _buildInfoLink('이용 약관 및 개인정보 처리방침', Icons.article_outlined, onTap: () => _showTermsSheet(context)), + _buildDivider(16), + _buildInfoLink('오픈소스 라이선스', Icons.code, onTap: () => _showLicenseSheet(context)), + _buildDivider(16), + _buildInfoLink('고객센터 / 1:1 문의', Icons.headset_mic_outlined, onTap: () => _showSupportSheet(context)), + _buildDivider(16), + _buildInfoLink('FAQ', Icons.help_outline, onTap: () => _showFAQSheet(context)), + _buildDivider(16), + _buildInfoLink('서비스 지역 지도 보기', Icons.map_outlined, onTap: () => _showMapInfoSheet(context)), + ], + ), + ), const SizedBox(height: 40), - _buildWithdrawalButton(), + Center( + child: TextButton( + onPressed: () {}, + child: Text( + '회원 탈퇴 신청', + style: TextStyle( + color: _warningColor, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + ), + ), + const SizedBox(height: 20), + Center( + child: Text( + 'Smart Helmet App © 2025', + style: TextStyle(color: _subTextColor.withOpacity(0.5), fontSize: 12), + ), + ), + const SizedBox(height: 40), ], ), ), @@ -86,14 +698,10 @@ class _SettingsScreenState extends State { Widget _buildSectionTitle(String title) { return Padding( - padding: const EdgeInsets.only(bottom: 8.0), + padding: const EdgeInsets.only(left: 4.0), child: Text( title, - style: TextStyle( - color: _mainTextColor, - fontSize: 15, - fontWeight: FontWeight.bold, - ), + style: TextStyle(color: _subTextColor, fontSize: 13, fontWeight: FontWeight.w600), ), ); } @@ -102,79 +710,19 @@ class _SettingsScreenState extends State { return Container( decoration: BoxDecoration( color: _cardBackgroundColor, - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(10), boxShadow: [_cleanShadow], ), - child: child, - ); - } - - Widget _buildNotificationCard() { - return _buildCardWrapper( - child: Column( - children: [ - _buildToggleItem( - '알림 전체 수신', - '모든 알림을 켜고 끕니다.', - _isPushEnabled, - (val) => setState(() => _isPushEnabled = val), - showDivider: true, - ), - _buildToggleItem( - '대여/반납 알림', - '대여 시작/종료, 반납 실패 알림', - _isRentalAlertEnabled, - (val) => setState(() => _isRentalAlertEnabled = val), - showDivider: true, - ), - _buildToggleItem( - '보관함 상태 알림', - '살균 완료, 시스템 오류 등 상태 알림', - _isStorageStatusAlert, - (val) => setState(() => _isStorageStatusAlert = val), - showDivider: true, - ), - _buildInfoLink('알림 시간대 설정', Icons.schedule_outlined), - _buildDivider(16), - _buildInfoLink('글자 크기 조정', Icons.format_size), - ], + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: child, ), ); } - Widget _buildAccountSecurityCard() { - return _buildCardWrapper( - child: Column( - children: [ - _buildInfoLink('내 정보 수정', Icons.person_outline), - _buildDivider(16), - _buildInfoLink('비밀번호 변경', Icons.lock_outline), - _buildDivider(16), - _buildInfoLink('캐시 데이터 삭제', Icons.cleaning_services_outlined), - ], - ), - ); - } - - Widget _buildAppInfoCard() { - return _buildCardWrapper( - child: Column( - children: [ - _buildInfoLink('버전 정보', null, value: _currentAppVersion), - _buildDivider(16), - _buildInfoLink('이용 약관 및 정책', Icons.gavel), - _buildDivider(16), - _buildInfoLink('고객센터 및 FAQ', Icons.headset_mic_outlined), - _buildDivider(16), - _buildInfoLink('위치 권한 설정', Icons.location_on_outlined), - _buildDivider(16), - _buildInfoLink('서비스 지역 지도 보기', Icons.map_outlined), - ], - ), - ); - } - - Widget _buildToggleItem(String title, String subtitle, bool value, ValueChanged onChanged, {required bool showDivider}) { + Widget _buildToggleItem( + String title, String subtitle, bool value, ValueChanged onChanged, + {required bool showDivider}) { return Column( children: [ Padding( @@ -186,46 +734,37 @@ class _SettingsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: TextStyle( - color: _mainTextColor, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - subtitle, - style: TextStyle( - color: _subTextColor, - fontSize: 12, - ), - ), + Text(title, style: TextStyle(color: _mainTextColor, fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(height: 2), + Text(subtitle, style: TextStyle(color: _subTextColor, fontSize: 11)), ], ), ), - Switch( - value: value, - onChanged: onChanged, - activeColor: _mainBlueColor, - inactiveThumbColor: _accentContainerColor, - inactiveTrackColor: Colors.grey.shade400, + Transform.scale( + scale: 0.8, + child: Switch( + value: value, + onChanged: onChanged, + activeColor: _cardBackgroundColor, + activeTrackColor: _mainBlueColor, + inactiveThumbColor: Colors.white, + inactiveTrackColor: _accentContainerColor, + trackOutlineColor: MaterialStateProperty.resolveWith((states) => Colors.transparent), + ), ), ], ), ), - if (showDivider) - _buildDivider(0), + if (showDivider) _buildDivider(16), ], ); } - Widget _buildInfoLink(String title, IconData? icon, {String? value}) { + Widget _buildInfoLink(String title, IconData? icon, {String? value, VoidCallback? onTap, bool showArrow = true}) { return InkWell( - onTap: () { /* Navigation logic */ }, + onTap: onTap ?? () {}, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 14.0), child: Row( children: [ if (icon != null) ...[ @@ -233,18 +772,14 @@ class _SettingsScreenState extends State { const SizedBox(width: 12), ], Expanded( - child: Text( - title, - style: TextStyle(color: _mainTextColor, fontSize: 14), - ), + child: Text(title, style: TextStyle(color: _mainTextColor, fontSize: 14, fontWeight: FontWeight.w500)), ), if (value != null) - Text( - value, - style: TextStyle(color: _subTextColor, fontSize: 14), - ) - else - Icon(Icons.arrow_forward_ios, color: _subTextColor, size: 16), + Text(value, style: TextStyle(color: _subTextColor, fontSize: 13)), + if (showArrow) ...[ + const SizedBox(width: 8), + Icon(Icons.arrow_forward_ios, color: _subTextColor.withOpacity(0.5), size: 14), + ], ], ), ), @@ -252,18 +787,12 @@ class _SettingsScreenState extends State { } Widget _buildDivider(double indent) { - return Divider(color: _subTextColor.withOpacity(0.4), height: 1, thickness: 0.5, indent: indent, endIndent: 16); - } - - Widget _buildWithdrawalButton() { - return Center( - child: TextButton( - onPressed: () {}, - child: Text( - '회원 탈퇴 신청', - style: TextStyle(color: _warningColor, fontWeight: FontWeight.bold), - ), - ), + return Divider( + color: _accentContainerColor, + height: 1, + thickness: 1, + indent: indent, + endIndent: 0, ); } } \ No newline at end of file diff --git a/lib/widgets/custom_header.dart b/lib/widgets/custom_header.dart index d1e3bbe..8c6d78b 100644 --- a/lib/widgets/custom_header.dart +++ b/lib/widgets/custom_header.dart @@ -4,12 +4,10 @@ final Color _mainBlueColor = const Color(0xFF0033CC); final Color _mainTextColor = const Color(0xFF1C1C1E); final Color _subTextColor = const Color(0xFF6A717B); final Color _pageBackgroundColor = const Color(0xFFF5F7F9); -final Color _cardBackgroundColor = const Color(0xFFF0F2F5); + class CustomHeader extends StatelessWidget { const CustomHeader({super.key}); - static const double _uniformGap = 16.0; - @override Widget build(BuildContext context) { return Container( @@ -21,28 +19,35 @@ class CustomHeader extends StatelessWidget { color: Color.fromRGBO(0, 0, 0, 0.07), offset: Offset(0, 4), blurRadius: 6, - spreadRadius: 0, ), ], ), - padding: const EdgeInsets.symmetric(horizontal: _uniformGap), + padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row(children: [ - Icon(Icons.settings_input_component, color: Colors.black), - const SizedBox(width: 10), - Text( - '스마트 헬멧', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: _mainTextColor, - letterSpacing: 0.5, + Row( + children: [ + Container( + width: 18, + height: 18, + decoration: BoxDecoration( + color: _mainBlueColor, + shape: BoxShape.circle, + ), ), - ) - ]), + const SizedBox(width: 10), + Text( + '스마트 헬멧 보관함', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: _mainTextColor, + letterSpacing: 0.5, + ), + ), + ], + ), Text( '2025/09/26 - 10:44 AM', style: TextStyle(color: _subTextColor, fontSize: 11), @@ -52,3 +57,47 @@ class CustomHeader extends StatelessWidget { ); } } + +void main() { + runApp(const MaterialApp(home: ScrollableHeaderPage())); +} + +class ScrollableHeaderPage extends StatelessWidget { + const ScrollableHeaderPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: _pageBackgroundColor, + body: SingleChildScrollView( + child: Column( + children: [ + const CustomHeader(), + for (int i = 0; i < 20; i++) + Container( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + height: 100, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: const [ + BoxShadow( + color: Color.fromRGBO(0, 0, 0, 0.05), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Center( + child: Text( + '컨텐츠 카드 ${i + 1}', + style: TextStyle(fontSize: 16, color: _mainTextColor), + ), + ), + ), + ], + ), + ), + ); + } +}