diff --git a/assets/images/storage.png b/assets/images/storage.png new file mode 100644 index 0000000..9712aff Binary files /dev/null and b/assets/images/storage.png differ diff --git a/lib/alerts_report_screen.dart b/lib/alerts_report_screen.dart new file mode 100644 index 0000000..57ada3b --- /dev/null +++ b/lib/alerts_report_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class AlertsReportScreen extends StatelessWidget { + const AlertsReportScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text( + '4. 알림/신고 (Alerts/Report) 페이지', + style: TextStyle(color: Colors.white70, fontSize: 20), + ), + ); + } +} \ No newline at end of file diff --git a/lib/control_screen.dart b/lib/control_screen.dart new file mode 100644 index 0000000..7c5b28a --- /dev/null +++ b/lib/control_screen.dart @@ -0,0 +1,438 @@ +import 'package:flutter/material.dart'; + +class ControlScreen extends StatefulWidget { + const ControlScreen({super.key}); + + @override + State createState() => _ControlScreenState(); +} + +class _ControlScreenState extends State { + final Color _bgColor = const Color(0xFF27292B); + final Color _cardColor = const Color(0xFF30343B); + final Color _buttonDarkColor = const Color(0xFF212327); + final Color _errorColor = Colors.redAccent; + + bool _isSecurityLocked = true; + int _selectedDoorIndex = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: _bgColor, + appBar: AppBar( + title: const Text( + 'CONTROL CENTER', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 16, + color: Colors.white, + ), + ), + backgroundColor: _bgColor, + scrolledUnderElevation: 0, + elevation: 0, + centerTitle: false, + actions: [ + IconButton( + icon: const Icon(Icons.more_vert, color: Colors.white), + onPressed: () {}, + ), + ], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSystemStatusCard(), + const SizedBox(height: 16), + _buildDoorControlCard(), + const SizedBox(height: 16), + _buildControlResultsCard(), + const SizedBox(height: 16), + _buildAlertLogsCard(), + const SizedBox(height: 16), + _buildSecuritySwitchCard(), + const SizedBox(height: 24), + ], + ), + ), + ); + } + + Widget _buildSystemStatusCard() { + const double cardContentHeight = 150.0; + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: _cardColor, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'SYSTEM STATUS', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + SizedBox( + height: cardContentHeight, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 130, + height: double.infinity, + padding: const EdgeInsets.all(20.0), + decoration: BoxDecoration( + color: _buttonDarkColor, + borderRadius: BorderRadius.circular(8), + ), + child: Image.asset( + 'assets/images/storage.png', + fit: BoxFit.contain, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'NOW', + style: TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Expanded( + child: _buildStatusButton( + text: 'ONLINE', + textColor: Colors.white, + bgColor: _buttonDarkColor, + ), + ), + const SizedBox(height: 10), + Expanded( + child: _buildStatusButton( + text: 'SENSOR ERROR:\nLock Failure', + textColor: _errorColor, + bgColor: _buttonDarkColor, + isError: true, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildStatusButton({ + required String text, + required Color textColor, + required Color bgColor, + bool isError = false, + }) { + return Container( + width: double.infinity, + alignment: Alignment.center, + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + color: textColor, + fontWeight: FontWeight.bold, + fontSize: isError ? 12 : 14, + height: 1.2, + ), + ), + ); + } + + Widget _buildDoorControlCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: _cardColor, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'DOOR CONTROL', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + _buildDoorButton(0, 'OPEN'), + const SizedBox(width: 12), + _buildDoorButton(1, 'CLOSE'), + const SizedBox(width: 12), + _buildDoorButton(2, 'LOCK'), + ], + ), + ], + ), + ); + } + + Widget _buildDoorButton(int index, String text) { + final bool isSelected = _selectedDoorIndex == index; + + return Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _selectedDoorIndex = index; + }); + }, + child: Container( + height: 80, + decoration: BoxDecoration( + color: isSelected ? Colors.white : _buttonDarkColor, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text( + text, + style: TextStyle( + color: isSelected ? Colors.black : Colors.white, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + ), + ), + ); + } + + Widget _buildControlResultsCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: _cardColor, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Control Results & Alerts', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: _buttonDarkColor, + borderRadius: BorderRadius.circular(6), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.warning_amber_rounded, + color: _errorColor, size: 16), + const SizedBox(width: 8), + Expanded( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Sensor Alert: ', + style: TextStyle( + color: _errorColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + const TextSpan( + text: 'Door Not Fully Closed', + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 12), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: 0.6, + backgroundColor: Colors.grey[800], + valueColor: AlwaysStoppedAnimation(_errorColor), + minHeight: 6, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildAlertLogsCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: _cardColor, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Alert & Logs', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: _buttonDarkColor, + borderRadius: BorderRadius.circular(6), + ), + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.warning_amber_rounded, + color: Colors.redAccent, size: 14), + SizedBox(width: 8), + Text( + 'Sensor Alert: Door Not Fully Closed', + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + SizedBox(height: 8), + Row( + children: [ + Icon(Icons.bookmark, color: Colors.white70, size: 14), + SizedBox(width: 8), + Text( + '2:02 PM - UV LED Activated', + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildSecuritySwitchCard() { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: _cardColor, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Security Lock Mode', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 16), + Container( + padding: + const EdgeInsets.symmetric(horizontal: 22, vertical: 16), + decoration: BoxDecoration( + color: _buttonDarkColor, + borderRadius: BorderRadius.circular(6), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _isSecurityLocked + ? 'ON (Activated)' + : 'OFF (Deactivated)', + style: const TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + Transform.scale( + scale: 1.1, + child: Switch( + value: _isSecurityLocked, + onChanged: (val) { + setState(() { + _isSecurityLocked = val; + }); + }, + activeThumbColor: Colors.white, + activeTrackColor: _errorColor, + inactiveThumbColor: Colors.white, + inactiveTrackColor: Colors.grey, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/history_screen.dart b/lib/history_screen.dart new file mode 100644 index 0000000..e3c7e7c --- /dev/null +++ b/lib/history_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class HistoryScreen extends StatelessWidget { + const HistoryScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text( + 'HISTORY 기록 목록 화면', + style: TextStyle(color: Colors.white70, fontSize: 24), + ), + ); + } +} \ No newline at end of file diff --git a/lib/home_screen_content.dart b/lib/home_screen_content.dart new file mode 100644 index 0000000..553f7da --- /dev/null +++ b/lib/home_screen_content.dart @@ -0,0 +1,752 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; +import 'dart:ui' as ui; +import 'package:smarthelmet_app/widgets/custom_header.dart'; + +class HomeScreenContent extends StatefulWidget { + const HomeScreenContent({super.key}); + + @override + State createState() => _HomeScreenContentState(); +} + +class _HomeScreenContentState extends State { + static const double _uniformGap = 16.0; + + // 포인트 컬러 (배터리, 상태 표시 등에는 유지) + final Color _pointColor = Colors.redAccent; + + final Map _controlToggles = { + 'UV LED': false, + 'CHARGING': true, + 'HELMET': true, + 'FAN': false, + }; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const CustomHeader(), + + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: _uniformGap), + child: Column( + children: [ + const SizedBox(height: _uniformGap), + _buildOverviewSection(), + const SizedBox(height: _uniformGap), + _buildBatteryStatusCard(), + const SizedBox(height: _uniformGap), + _buildControlCard(), + const SizedBox(height: _uniformGap), + _buildEnvironmentSensorsCard(), + const SizedBox(height: _uniformGap), + _buildMyLocationCard(), + const SizedBox(height: _uniformGap), + _buildActivityCard(), + const SizedBox(height: _uniformGap * 2), + ], + ), + ), + ), + ], + ); + } + + Widget _buildOverviewSection() { + return Card( + child: Column( + children: [ + _buildOverviewHeader(), + Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildImageCard()), + const SizedBox(width: 12), + Expanded(child: _buildInfoCard()), + ], + ), + ), + ], + ), + ); + } + + Widget _buildOverviewHeader() { + return Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 0), + child: Row( + children: [ + const Text('SYSTEM OVERVIEW', + style: TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold)), + const Spacer(), + Icon(Icons.search, color: Colors.grey[400], size: 20), + const SizedBox(width: 8), + Icon(Icons.notifications_outlined, color: Colors.grey[400], size: 20), + ], + ), + ); + } + + Widget _buildImageCard() { + return AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: BorderRadius.circular(10), + ), + child: Stack( + alignment: Alignment.center, + children: [ + Container( + decoration: BoxDecoration( + color: const Color(0xFF2D2F33), + borderRadius: BorderRadius.circular(5) + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 20.0), + child: Image.asset( + 'assets/images/helmet.png', + width: 100, + ), + ), + Positioned( + bottom: 12, + child: Row( + children: [ + _buildLedIndicator(Colors.grey.shade700), + const SizedBox(width: 4), + _buildLedIndicator(Colors.grey.shade700), + const SizedBox(width: 4), + _buildLedIndicator(Colors.grey.shade700), + const SizedBox(width: 4), + _buildLedIndicator(Colors.white), + ], + ), + ) + ], + ), + ), + ); + } + + Widget _buildLedIndicator(Color color) { + return Container( + width: 18, + height: 6, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(3), + ), + ); + } + + Widget _buildInfoCard() { + return SizedBox( + height: 160, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildInfoRow('Name / \nNumber', const Icon(Icons.person, color: Colors.white, size: 20), 'USER', '001'), + const SizedBox(height: 8), + _buildInfoRow('STATUS', null, 'UNLOCKED', '● ACTIVE', value1Color: _pointColor, value2Color: _pointColor), + ], + ), + ); + } + + Widget _buildInfoRow(String title, Widget? icon, String value1, String value2, {Color? value1Color, Color? value2Color}) { + return Expanded( + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: [ + Expanded( + flex: 2, + child: Text(title, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[400], fontSize: 11, height: 1.4)), + ), + VerticalDivider(color: Colors.grey[700], indent: 10, endIndent: 10), + Expanded( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) ...[icon, const SizedBox(height: 4)], + Text(value1, style: TextStyle(color: value1Color ?? Colors.white, fontWeight: FontWeight.bold, fontSize: 12)), + const SizedBox(height: 2), + Text(value2, style: TextStyle(color: value2Color ?? Colors.white, fontWeight: FontWeight.w500, fontSize: 12)), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildBatteryStatusCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(_uniformGap), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('BATTERY STATUS (%)', + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold)), + const SizedBox(height: 16), + Row( + children: [ + SizedBox( + width: 80, + height: 80, + child: Stack( + alignment: Alignment.center, + children: [ + SizedBox.expand( + child: CustomPaint( + painter: _BatteryArcPainter( + backgroundColor: Colors.grey.shade800, + color: Colors.white, + percentage: 1.0, + ), + ), + ), + const Text('86', + style: TextStyle( + fontSize: 24, fontWeight: FontWeight.w600)), + ], + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('NOW', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold)), + Text('사용 중', + style: TextStyle( + color: _pointColor, + fontSize: 14, + fontWeight: FontWeight.bold)), + ], + ), + const Divider( + color: Color(0xFF555555), + height: 20, + thickness: 1, + ), + Row( + children: [ + const Expanded( + flex: 4, + child: Text('Solar Panel', + style: TextStyle( + color: Colors.white, fontSize: 14)), + ), + Expanded( + flex: 5, + child: Row( + children: [ + const Expanded( + child: Text('전압: 00', + style: TextStyle( + color: Colors.white70, + fontSize: 14)), + ), + SizedBox( + height: 20, + child: VerticalDivider( + color: Colors.grey[700], + thickness: 1, + ), + ), + const Expanded( + child: Padding( + padding: EdgeInsets.only(left: 8.0), + child: Text('전류: 00', + style: TextStyle( + color: Colors.white70, + fontSize: 14)), + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _buildControlCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(_uniformGap), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('CONTROL', + style: TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold)), + const SizedBox(height: 16), + SizedBox( + height: 70, + child: Row( + children: [ + Expanded( + child: _buildStyledToggleSwitch( + 'UV LED', + _controlToggles['UV LED']!, + (val) => setState(() => _controlToggles['UV LED'] = val))), + VerticalDivider( + color: Colors.grey[700], indent: 10, endIndent: 10), + Expanded( + child: _buildStyledToggleSwitch( + 'CHARGING', + _controlToggles['CHARGING']!, + (val) => setState(() => _controlToggles['CHARGING'] = val))), + VerticalDivider( + color: Colors.grey[700], indent: 10, endIndent: 10), + Expanded( + child: _buildStyledToggleSwitch( + 'HELMET', + _controlToggles['HELMET']!, + (val) => setState(() => _controlToggles['HELMET'] = val))), + VerticalDivider( + color: Colors.grey[700], indent: 10, endIndent: 10), + Expanded( + child: _buildStyledToggleSwitch('FAN', _controlToggles['FAN']!, + (val) => setState(() => _controlToggles['FAN'] = val))), + ], + ), + ) + ], + ), + ), + ); + } + + Widget _buildStyledToggleSwitch( + String title, bool value, ValueChanged onChanged) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(title, + style: const TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + GestureDetector( + onTap: () => onChanged(!value), + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + width: 60, + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + // ⭐ [수정됨] ON(화이트), OFF(다크그레이) -> 블랙 앤 화이트 테마 적용 + color: value ? Colors.white : Colors.grey.shade700, + ), + child: Stack( + children: [ + AnimatedAlign( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + alignment: value ? Alignment.centerRight : Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(2.0), + child: Container( + width: 26, + height: 26, + decoration: BoxDecoration( + shape: BoxShape.circle, + // ⭐ [수정됨] ON(블랙), OFF(화이트) -> 배경과 대비되는 색상 + color: value ? const Color(0xFF27292B) : Colors.white, + ), + ), + ), + ), + Row( + children: [ + Expanded( + child: Center( + child: Text('ON', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + // ⭐ [수정됨] ON 텍스트 블랙 + color: value + ? const Color(0xFF27292B) + : Colors.transparent)))), + Expanded( + child: Center( + child: Text('OFF', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: value + ? Colors.transparent + : Colors.white)))), + ], + ) + ], + ), + ), + ), + ], + ); + } + + Widget _buildEnvironmentSensorsCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(_uniformGap), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text('ENVIRONMENT SENSORS', + style: + TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + const Spacer(), + InkWell( + onTap: () {}, + child: Row( + children: [ + Text('VIEW HISTORY', + style: + TextStyle(color: Colors.grey[400], fontSize: 10)), + const SizedBox(width: 4), + Icon(Icons.arrow_forward_ios, + size: 10, color: Colors.grey[400]), + ], + ), + ), + ], + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + flex: 1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSensorInfoRow(Icons.water_drop_outlined, 'HUMID: 60%'), + const SizedBox(height: 24), + _buildSensorInfoRow(Icons.thermostat, 'TEMP: 24.5℃'), + ], + ), + ), + const SizedBox(width: 16), + const Expanded( + flex: 1, + child: SizedBox( + height: 60, child: _LineChartPlaceholder())), + ], + ), + ], + ), + ), + ); + } + + Widget _buildSensorInfoRow(IconData icon, String text) { + return Row(children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: Colors.black, size: 24), + ), + const SizedBox(width: 12), + Text(text, + style: const TextStyle( + fontSize: 14, + color: Colors.white, + fontWeight: FontWeight.w600)) + ]); + } + + Widget _buildMyLocationCard() { + const LatLng exampleLocation = LatLng(37.5665, 126.9780); + return Card( + clipBehavior: Clip.antiAlias, + child: SizedBox( + height: 200.0, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('My Location', + style: TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.bold)), + SizedBox(height: 4), + Text('주소: 남구 효덕로 277', + style: + TextStyle(fontSize: 11, color: Colors.white70)), + ], + ), + InkWell( + onTap: () {}, + child: Row( + children: [ + Text('VIEW MORE', + style: + TextStyle(color: Colors.grey[400], fontSize: 9)), + const SizedBox(width: 4), + Icon(Icons.arrow_forward_ios, + size: 10, color: Colors.grey[400]), + ], + ), + ), + ], + ), + ), + Expanded( + child: FlutterMap( + options: const MapOptions( + initialCenter: exampleLocation, + initialZoom: 15.0, + interactionOptions: + InteractionOptions(flags: InteractiveFlag.none), + ), + children: [ + TileLayer( + urlTemplate: + 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', + subdomains: const ['a', 'b', 'c', 'd'], + retinaMode: true, + ), + const MarkerLayer( + markers: [ + Marker( + point: exampleLocation, + width: 80, + height: 80, + child: Icon(Icons.location_pin, + size: 40, color: Colors.white), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildActivityCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(_uniformGap), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Activity', + style: TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _activityText('08:15 AM - Battery fully Charged'), + const SizedBox(height: 8), + _activityText('9:30 AM - UV LED Actived'), + ], + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _activityText('10:45 AM - Helmet Unlocked'), + const SizedBox(height: 8), + _activityText('11:00 AM - Helmet Off'), + ], + ), + ), + ], + ), + ], + ), + ), + ); + } + + Widget _activityText(String text) { + return Text(text, + style: const TextStyle(fontSize: 11, color: Colors.white70)); + } +} +// --- _HomeScreenContentState 끝 --- + + +// 👇 HOME 화면에 사용되는 4가지 커스텀 클래스 정의 + +class _LineChartPlaceholder extends StatelessWidget { + const _LineChartPlaceholder(); + @override + Widget build(BuildContext context) { + return Column(children: [ + Expanded( + child: + CustomPaint(painter: _LineChartPainter(), size: Size.infinite)), + const SizedBox(height: 4), + const Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text('24H AGO', style: TextStyle(fontSize: 8, color: Colors.white54)), + Text('12H AGO', style: TextStyle(fontSize: 8, color: Colors.white54)), + Text('NOW', style: TextStyle(fontSize: 8, color: Colors.white54)) + ]) + ]); + } +} + +class _LineChartPainter extends CustomPainter { + @override + void paint(ui.Canvas canvas, ui.Size size) { + final paint = Paint() + ..color = Colors.white.withOpacity(0.8) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke; + + final path = ui.Path(); + path.moveTo(0, size.height * 0.6); + path.cubicTo(size.width * 0.1, size.height * 0.8, size.width * 0.2, + size.height * 0.4, size.width * 0.3, size.height * 0.6); + path.cubicTo(size.width * 0.4, size.height * 0.8, size.width * 0.45, + size.height * 0.2, size.width * 0.6, size.height * 0.5); + path.cubicTo(size.width * 0.75, size.height * 0.8, size.width * 0.8, + size.height * 0.3, size.width, size.height * 0.2); + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} + +class Card extends StatelessWidget { + final Widget child; + final EdgeInsetsGeometry? padding; + final Clip clipBehavior; + + const Card({ + super.key, + required this.child, + this.padding, + this.clipBehavior = Clip.none, + }); + + @override + Widget build(BuildContext context) { + return Container( + clipBehavior: clipBehavior, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.circular(12), + ), + child: child, + ); + } +} + +class _BatteryArcPainter extends CustomPainter { + final Color backgroundColor; + final Color color; + final double percentage; + + _BatteryArcPainter({ + required this.backgroundColor, + required this.color, + required this.percentage, + }); + + @override + void paint(Canvas canvas, Size size) { + final Paint backgroundPaint = Paint() + ..color = backgroundColor + ..strokeWidth = 8 + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + + final Paint foregroundPaint = Paint() + ..color = color + ..strokeWidth = 8 + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round; + + final Rect rect = Rect.fromLTWH(0, 0, size.width, size.height); + const double startAngle = -2.35; + const double sweepAngle = 4.7; + + canvas.drawArc(rect, startAngle, sweepAngle, false, backgroundPaint); + + final double progressAngle = sweepAngle * percentage; + canvas.drawArc(rect, startAngle, progressAngle, false, foregroundPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} \ No newline at end of file diff --git a/lib/location_screen.dart b/lib/location_screen.dart new file mode 100644 index 0000000..3768cea --- /dev/null +++ b/lib/location_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class LocationScreen extends StatelessWidget { + const LocationScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text( + 'LOCATION 지도 화면', + style: TextStyle(color: Colors.white70, fontSize: 24), + ), + ); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 7010697..710ff6b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong2/latlong.dart'; -import 'dart:ui' as ui; -import 'dart:math' as math; +import 'package:smarthelmet_app/home_screen_content.dart'; +import 'package:smarthelmet_app/control_screen.dart'; +import 'package:smarthelmet_app/history_screen.dart'; +import 'package:smarthelmet_app/alerts_report_screen.dart'; +import 'package:smarthelmet_app/settings_screen.dart'; void main() { runApp(const SmartHelmetApp()); @@ -45,49 +46,28 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { int _selectedIndex = 0; - static const double _uniformGap = 16.0; - final Map _controlToggles = { - 'UV LED': false, - 'CHARGING': true, - 'HELMET': true, - 'FAN': false, - }; + final List _screens = [ + const HomeScreenContent(), + const HistoryScreen(), + const ControlScreen(), + const AlertsReportScreen(), + const SettingsScreen(), + ]; @override Widget build(BuildContext context) { return MediaQuery( data: MediaQuery.of(context).copyWith( - textScaler: TextScaler.linear(1.0), + textScaler: const TextScaler.linear(1.0), ), child: Scaffold( body: SafeArea( bottom: false, child: Column( children: [ - _buildCustomHeader(), Expanded( - child: SingleChildScrollView( - padding: - const EdgeInsets.symmetric(horizontal: _uniformGap), - child: Column( - children: [ - const SizedBox(height: _uniformGap), - _buildOverviewSection(), - const SizedBox(height: _uniformGap), - _buildBatteryStatusCard(), - const SizedBox(height: _uniformGap), - _buildControlCard(), - const SizedBox(height: _uniformGap), - _buildEnvironmentSensorsCard(), - const SizedBox(height: _uniformGap), - _buildMyLocationCard(), - const SizedBox(height: _uniformGap), - _buildActivityCard(), - const SizedBox(height: _uniformGap * 2), - ], - ), - ), + child: _screens[_selectedIndex], ), ], ), @@ -103,732 +83,19 @@ class _HomeScreenState extends State { backgroundColor: const Color(0xFF1C1C1E), elevation: 0, selectedItemColor: Colors.white, - unselectedItemColor: Colors.grey[600], + unselectedItemColor: Colors.grey, showUnselectedLabels: true, selectedFontSize: 12, unselectedFontSize: 12, items: const [ BottomNavigationBarItem(icon: Icon(Icons.home), label: 'HOME'), - BottomNavigationBarItem( - icon: Icon(Icons.tune), label: 'CONTROL'), - BottomNavigationBarItem( - icon: Icon(Icons.location_on), label: 'LOCATION'), - BottomNavigationBarItem( - icon: Icon(Icons.history), label: 'HISTORY'), + BottomNavigationBarItem(icon: Icon(Icons.history), label: 'HISTORY'), + BottomNavigationBarItem(icon: Icon(Icons.settings_input_component), label: 'CONTROL'), + BottomNavigationBarItem(icon: Icon(Icons.notifications_active), label: 'ALERTS'), + BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'SETTINGS'), ], ), ), ); } - - Widget _buildCustomHeader() { - return Container( - height: 60.0, - color: Theme.of(context).scaffoldBackgroundColor, - padding: const EdgeInsets.symmetric(horizontal: _uniformGap), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Row(children: [ - Icon(Icons.settings_input_component, color: Colors.grey[400]), - const SizedBox(width: 12), - const Text('SMART HELMET SYSTEMS', - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: Colors.white)) - ]), - Text('2025/09/26 - 10:44 AM', - style: TextStyle(color: Colors.grey[400], fontSize: 11)) - ])); - } - - Widget _buildOverviewSection() { - return Card( - child: Column( - children: [ - _buildOverviewHeader(), - Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded(child: _buildImageCard()), - const SizedBox(width: 12), - Expanded(child: _buildInfoCard()), - ], - ), - ), - ], - ), - ); - } - - Widget _buildOverviewHeader() { - return Padding( - padding: const EdgeInsets.fromLTRB(12, 12, 12, 0), - child: Row( - children: [ - const Text('SYSTEM OVERVIEW', - style: TextStyle( - color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.bold)), - const Spacer(), - Icon(Icons.search, color: Colors.grey[400], size: 20), - const SizedBox(width: 8), - Icon(Icons.notifications_outlined, color: Colors.grey[400], size: 20), - ], - ), - ); - } - - Widget _buildImageCard() { - return AspectRatio( - aspectRatio: 1.0, - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: BorderRadius.circular(10), - ), - child: Stack( - alignment: Alignment.center, - children: [ - Container( - decoration: BoxDecoration( - color: const Color(0xFF2D2F33), - borderRadius: BorderRadius.circular(5) - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 20.0), - child: Image.asset( - 'assets/images/helmet.png', - width: 100, - ), - ), - Positioned( - bottom: 12, - child: Row( - children: [ - _buildLedIndicator(Colors.grey.shade700), - const SizedBox(width: 4), - _buildLedIndicator(Colors.grey.shade700), - const SizedBox(width: 4), - _buildLedIndicator(Colors.grey.shade700), - const SizedBox(width: 4), - _buildLedIndicator(Colors.white), - ], - ), - ) - ], - ), - ), - ); - } - - Widget _buildLedIndicator(Color color) { - return Container( - width: 18, - height: 6, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular(3), - ), - ); - } - - Widget _buildInfoCard() { - const Color accentColor = Color(0xFFFF9500); - return SizedBox( - height: 160, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildInfoRow('Name / \nNumber', const Icon(Icons.person, color: Colors.white, size: 20), 'USER', '001'), - const SizedBox(height: 8), - _buildInfoRow('STATUS', null, 'UNLOCKED', '● ACTIVE', value1Color: accentColor, value2Color: accentColor), - ], - ), - ); - } - - Widget _buildInfoRow(String title, Widget? icon, String value1, String value2, {Color? value1Color, Color? value2Color}) { - return Expanded( - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - Expanded( - flex: 2, - child: Text(title, - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey[400], fontSize: 11, height: 1.4)), - ), - VerticalDivider(color: Colors.grey[700], indent: 10, endIndent: 10), - Expanded( - flex: 3, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) ...[icon, const SizedBox(height: 4)], - Text(value1, style: TextStyle(color: value1Color ?? Colors.white, fontWeight: FontWeight.bold, fontSize: 12)), - const SizedBox(height: 2), - Text(value2, style: TextStyle(color: value2Color ?? Colors.white, fontWeight: FontWeight.w500, fontSize: 12)), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildBatteryStatusCard() { - const Color accentColor = Color(0xFFFF9500); - return Card( - child: Padding( - padding: const EdgeInsets.all(_uniformGap), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('BATTERY STATUS (%)', - style: TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold)), - const SizedBox(height: 16), - Row( - children: [ - SizedBox( - width: 80, - height: 80, - child: Stack( - alignment: Alignment.center, - children: [ - SizedBox.expand( - child: CustomPaint( - painter: _BatteryArcPainter( - backgroundColor: Colors.grey.shade800, - color: Colors.white, - percentage: 1.0, - ), - ), - ), - const Text('86', - style: TextStyle( - fontSize: 24, fontWeight: FontWeight.w600)), - ], - ), - ), - const SizedBox(width: 20), - Expanded( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('NOW', - style: TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.bold)), - const Text('사용 중', - style: TextStyle( - color: accentColor, - fontSize: 14, - fontWeight: FontWeight.bold)), - ], - ), - const Divider( - color: Color(0xFF555555), - height: 20, - thickness: 1, - ), - Row( - children: [ - const Expanded( - flex: 4, - child: Text('Solar Panel', - style: TextStyle( - color: Colors.white, fontSize: 14)), - ), - Expanded( - flex: 5, - child: Row( - children: [ - const Expanded( - child: Text('전압: 00', - style: TextStyle( - color: Colors.white70, - fontSize: 14)), - ), - SizedBox( - height: 20, - child: VerticalDivider( - color: Colors.grey[700], - thickness: 1, - ), - ), - const Expanded( - child: Padding( - padding: EdgeInsets.only(left: 8.0), - child: Text('전류: 00', - style: TextStyle( - color: Colors.white70, - fontSize: 14)), - ), - ), - ], - ), - ), - ], - ), - ], - ), - ), - ], - ), - ], - ), - ), - ); - } - - Widget _buildControlCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(_uniformGap), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('CONTROL', - style: TextStyle( - color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.bold)), - const SizedBox(height: 16), - SizedBox( - height: 70, - child: Row( - children: [ - Expanded( - child: _buildStyledToggleSwitch( - 'UV LED', - _controlToggles['UV LED']!, - (val) => setState(() => _controlToggles['UV LED'] = val))), - VerticalDivider( - color: Colors.grey[700], indent: 10, endIndent: 10), - Expanded( - child: _buildStyledToggleSwitch( - 'CHARGING', - _controlToggles['CHARGING']!, - (val) => setState(() => _controlToggles['CHARGING'] = val))), - VerticalDivider( - color: Colors.grey[700], indent: 10, endIndent: 10), - Expanded( - child: _buildStyledToggleSwitch( - 'HELMET', - _controlToggles['HELMET']!, - (val) => setState(() => _controlToggles['HELMET'] = val))), - VerticalDivider( - color: Colors.grey[700], indent: 10, endIndent: 10), - Expanded( - child: _buildStyledToggleSwitch('FAN', _controlToggles['FAN']!, - (val) => setState(() => _controlToggles['FAN'] = val))), - ], - ), - ) - ], - ), - ), - ); - } - - Widget _buildStyledToggleSwitch( - String title, bool value, ValueChanged onChanged) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(title, - style: const TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold)), - const SizedBox(height: 12), - GestureDetector( - onTap: () => onChanged(!value), - child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - width: 60, - height: 30, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - color: value ? Colors.white : Colors.grey.shade700, - ), - child: Stack( - children: [ - AnimatedAlign( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - alignment: value ? Alignment.centerRight : Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(2.0), - child: Container( - width: 26, - height: 26, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: value ? Theme.of(context).primaryColor : Colors.white, - ), - ), - ), - ), - Row( - children: [ - Expanded( - child: Center( - child: Text('ON', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: value - ? Theme.of(context).primaryColor - : Colors.transparent)))), - Expanded( - child: Center( - child: Text('OFF', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: value - ? Colors.transparent - : Colors.white)))), - ], - ) - ], - ), - ), - ), - ], - ); - } - - Widget _buildEnvironmentSensorsCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(_uniformGap), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text('ENVIRONMENT SENSORS', - style: - TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - const Spacer(), - InkWell( - onTap: () {}, - child: Row( - children: [ - Text('VIEW HISTORY', - style: - TextStyle(color: Colors.grey[400], fontSize: 10)), - const SizedBox(width: 4), - Icon(Icons.arrow_forward_ios, - size: 10, color: Colors.grey[400]), - ], - ), - ), - ], - ), - const SizedBox(height: 20), - Row( - children: [ - Expanded( - flex: 1, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSensorInfoRow(Icons.water_drop_outlined, 'HUMID: 60%'), - const SizedBox(height: 24), - _buildSensorInfoRow(Icons.thermostat, 'TEMP: 24.5℃'), - ], - ), - ), - const SizedBox(width: 16), - Expanded( - flex: 1, - child: SizedBox( - height: 60, child: const _LineChartPlaceholder())), - ], - ), - ], - ), - ), - ); - } - - Widget _buildSensorInfoRow(IconData icon, String text) { - return Row(children: [ - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, color: Colors.black, size: 24), - ), - const SizedBox(width: 12), - Text(text, - style: const TextStyle( - fontSize: 14, - color: Colors.white, - fontWeight: FontWeight.w600)) - ]); - } - - Widget _buildMyLocationCard() { - final LatLng exampleLocation = LatLng(37.5665, 126.9780); - return Card( - clipBehavior: Clip.antiAlias, - child: SizedBox( - height: 200.0, - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('My Location', - style: TextStyle( - fontSize: 13, - color: Colors.white, - fontWeight: FontWeight.bold)), - SizedBox(height: 4), - Text('주소: 남구 효덕로 277', - style: - TextStyle(fontSize: 11, color: Colors.white70)), - ], - ), - InkWell( - onTap: () {}, - child: Row( - children: [ - Text('VIEW MORE', - style: - TextStyle(color: Colors.grey[400], fontSize: 9)), - const SizedBox(width: 4), - Icon(Icons.arrow_forward_ios, - size: 10, color: Colors.grey[400]), - ], - ), - ), - ], - ), - ), - Expanded( - child: FlutterMap( - options: MapOptions( - initialCenter: exampleLocation, - initialZoom: 15.0, - interactionOptions: - const InteractionOptions(flags: InteractiveFlag.none), - ), - children: [ - TileLayer( - urlTemplate: - 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', - subdomains: const ['a', 'b', 'c', 'd'], - ), - MarkerLayer( - markers: [ - Marker( - point: exampleLocation, - width: 80, - height: 80, - child: const Icon(Icons.location_pin, - size: 40, color: Colors.white), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ); - } - - Widget _buildActivityCard() { - return Card( - child: Padding( - padding: const EdgeInsets.all(_uniformGap), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Activity', - style: TextStyle( - fontSize: 13, - color: Colors.white, - fontWeight: FontWeight.bold)), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _activityText('08:15 AM - Battery fully Charged'), - const SizedBox(height: 8), - _activityText('9:30 AM - UV LED Actived'), - ], - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _activityText('10:45 AM - Helmet Unlocked'), - const SizedBox(height: 8), - _activityText('11:00 AM - Helmet Off'), - ], - ), - ), - ], - ), - ], - ), - ), - ); - } - - Widget _activityText(String text) { - return Text(text, - style: const TextStyle(fontSize: 11, color: Colors.white70)); - } -} - -class _LineChartPlaceholder extends StatelessWidget { - const _LineChartPlaceholder(); - @override - Widget build(BuildContext context) { - return Column(children: [ - Expanded( - child: - CustomPaint(painter: _LineChartPainter(), size: Size.infinite)), - const SizedBox(height: 4), - const Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('24H AGO', style: TextStyle(fontSize: 8, color: Colors.white54)), - Text('12H AGO', style: TextStyle(fontSize: 8, color: Colors.white54)), - Text('NOW', style: TextStyle(fontSize: 8, color: Colors.white54)) - ]) - ]); - } -} - -class _LineChartPainter extends CustomPainter { - @override - void paint(ui.Canvas canvas, ui.Size size) { - final paint = Paint() - ..color = Colors.white.withOpacity(0.8) - ..strokeWidth = 1.5 - ..style = PaintingStyle.stroke; - - final path = ui.Path(); - path.moveTo(0, size.height * 0.6); - path.cubicTo(size.width * 0.1, size.height * 0.8, size.width * 0.2, - size.height * 0.4, size.width * 0.3, size.height * 0.6); - path.cubicTo(size.width * 0.4, size.height * 0.8, size.width * 0.45, - size.height * 0.2, size.width * 0.6, size.height * 0.5); - path.cubicTo(size.width * 0.75, size.height * 0.8, size.width * 0.8, - size.height * 0.3, size.width, size.height * 0.2); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; - } -} - -class Card extends StatelessWidget { - final Widget child; - final EdgeInsetsGeometry? padding; - final Clip clipBehavior; - - const Card({ - super.key, - required this.child, - this.padding, - this.clipBehavior = Clip.none, - }); - - @override - Widget build(BuildContext context) { - return Container( - clipBehavior: clipBehavior, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(12), - ), - child: child, - ); - } } - -class _BatteryArcPainter extends CustomPainter { - final Color backgroundColor; - final Color color; - final double percentage; - - _BatteryArcPainter({ - required this.backgroundColor, - required this.color, - required this.percentage, - }); - - @override - void paint(Canvas canvas, Size size) { - final Paint backgroundPaint = Paint() - ..color = backgroundColor - ..strokeWidth = 8 - ..style = PaintingStyle.stroke - ..strokeCap = StrokeCap.round; - - - final Paint foregroundPaint = Paint() - ..color = color - ..strokeWidth = 8 - ..style = PaintingStyle.stroke - ..strokeCap = StrokeCap.round; - - final Rect rect = Rect.fromLTWH(0, 0, size.width, size.height); - const double startAngle = -2.35; - const double sweepAngle = 4.7; - - canvas.drawArc(rect, startAngle, sweepAngle, false, backgroundPaint); - - final double progressAngle = sweepAngle * percentage; - canvas.drawArc(rect, startAngle, progressAngle, false, foregroundPaint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; - } -} \ No newline at end of file diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart new file mode 100644 index 0000000..1498956 --- /dev/null +++ b/lib/settings_screen.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class SettingsScreen extends StatelessWidget { + const SettingsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: Text( + '5. 설정 (Settings) 페이지', + style: TextStyle(color: Colors.white70, fontSize: 20), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/custom_header.dart b/lib/widgets/custom_header.dart new file mode 100644 index 0000000..c318c42 --- /dev/null +++ b/lib/widgets/custom_header.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; + +class CustomHeader extends StatelessWidget { + const CustomHeader({super.key}); + + static const double _uniformGap = 16.0; + + @override + Widget build(BuildContext context) { + return Container( + height: 60.0, + color: Theme.of(context).scaffoldBackgroundColor, + padding: const EdgeInsets.symmetric(horizontal: _uniformGap), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row(children: [ + Icon(Icons.settings_input_component, color: Colors.grey[400]), + const SizedBox(width: 12), + const Text('SMART HELMET SYSTEMS', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Colors.white)) + ]), + Text('2025/09/26 - 10:44 AM', + style: TextStyle(color: Colors.grey[400], fontSize: 11)) + ])); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 9385525..2e68e74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,8 @@ dependencies: cupertino_icons: ^1.0.8 flutter_map: ^6.1.0 - latlong2: ^0.9.1 + latlong2: ^0.9.0 + dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index e248eb1..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package.flutter_test/flutter_test.dart'; -import 'package:smarthelmet_app/main.dart'; - -void main() { - testWidgets('Smart Helmet App UI smoke test', (WidgetTester tester) async { - // 1. 올바른 위젯 클래스 이름(SmartHelmetApp)으로 앱을 빌드합니다. - await tester.pumpWidget(const SmartHelmetApp()); - - // 2. 우리 앱의 UI에 실제로 있는 'SYSTEM OVERVIEW' 텍스트를 찾습니다. - expect(find.text('SYSTEM OVERVIEW'), findsOneWidget); - - // 3. 우리 앱에는 '1'이라는 텍스트가 없으므로, 없는 것을 확인하는 테스트를 추가할 수 있습니다. - expect(find.text('1'), findsNothing); - }); -} \ No newline at end of file