From f74c0396af0cb701a0917c64af14c34511899fe3 Mon Sep 17 00:00:00 2001 From: KIMGYEONGRAN Date: Thu, 2 Oct 2025 18:35:35 +0900 Subject: [PATCH] Redesign main screen UI --- lib/main.dart | 1024 ++++++++++++++++++++++++++++++------------------- 1 file changed, 632 insertions(+), 392 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ed85a0d..cceb7e2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ 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; void main() { runApp(const SmartHelmetApp()); @@ -26,13 +27,8 @@ class SmartHelmetApp extends StatelessWidget { primaryColor: const Color(0xFF30343B), fontFamily: 'Pretendard', textTheme: const TextTheme( - bodyLarge: TextStyle(color: Colors.white), - bodyMedium: TextStyle(color: Colors.white70), - ), - appBarTheme: AppBarTheme( - backgroundColor: const Color(0xFF27292B), - elevation: 0, - foregroundColor: Colors.white, + bodyLarge: TextStyle(color: Colors.white, fontWeight: FontWeight.w500), + bodyMedium: TextStyle(color: Colors.white70, fontWeight: FontWeight.w400), ), ), home: const HomeScreen(), @@ -49,24 +45,22 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { int _selectedIndex = 0; + static const double _uniformGap = 16.0; - final Map _overviewToggles = { - 'UV LED': true, - 'CHARGING': false, + final Map _controlToggles = { + 'UV LED': false, + 'CHARGING': true, 'HELMET': true, 'FAN': false, }; @override Widget build(BuildContext context) { - const double horizontalPadding = 29.1; - return MediaQuery( data: MediaQuery.of(context).copyWith( textScaler: TextScaler.linear(1.0), ), child: Scaffold( - appBar: null, body: SafeArea( bottom: false, child: Column( @@ -75,30 +69,22 @@ class _HomeScreenState extends State { Expanded( child: SingleChildScrollView( padding: - const EdgeInsets.symmetric(horizontal: horizontalPadding), + const EdgeInsets.symmetric(horizontal: _uniformGap), child: Column( children: [ - const SizedBox(height: 3.43), - _buildOverviewHeader(), - const SizedBox(height: 3.0), - Row( - children: [ - Expanded(child: _buildImageCard()), - const SizedBox(width: 3.0), - Expanded(child: _buildInfoCard()), - ], - ), - const SizedBox(height: 3.0), + const SizedBox(height: _uniformGap), + _buildOverviewSection(), + const SizedBox(height: _uniformGap), _buildBatteryStatusCard(), - const SizedBox(height: 3.0), - _buildOverviewCard(), - const SizedBox(height: 3.0), + const SizedBox(height: _uniformGap), + _buildControlCard(), + const SizedBox(height: _uniformGap), _buildEnvironmentSensorsCard(), - const SizedBox(height: 3.0), + const SizedBox(height: _uniformGap), _buildMyLocationCard(), - const SizedBox(height: 3.0), + const SizedBox(height: _uniformGap), _buildActivityCard(), - const SizedBox(height: 32.0), + const SizedBox(height: _uniformGap * 2), ], ), ), @@ -106,38 +92,29 @@ class _HomeScreenState extends State { ], ), ), - bottomNavigationBar: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 0.38, - color: const Color(0xFF5C5D5F), - ), - BottomNavigationBar( - currentIndex: _selectedIndex, - onTap: (index) { - setState(() { - _selectedIndex = index; - }); - }, - type: BottomNavigationBarType.fixed, - backgroundColor: const Color(0xFF30343B), - elevation: 0, - selectedItemColor: Colors.white, - unselectedItemColor: Colors.white54, - 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'), - ], - ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: _selectedIndex, + onTap: (index) { + setState(() { + _selectedIndex = index; + }); + }, + type: BottomNavigationBarType.fixed, + backgroundColor: const Color(0xFF1C1C1E), + elevation: 0, + selectedItemColor: Colors.white, + unselectedItemColor: Colors.grey[600], + 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'), ], ), ), @@ -146,318 +123,535 @@ class _HomeScreenState extends State { Widget _buildCustomHeader() { return Container( - height: 64.0, - decoration: const BoxDecoration( - color: Color(0xFF30343B), - border: Border(bottom: BorderSide(color: Color(0xFF5C5D5F), width: 1.0))), - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Center( + 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( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: [ - const Icon(Icons.settings_input_component_outlined, - color: Colors.white), - const SizedBox(width: 12), - Text('SMART HELMET SYSTEMS', - style: TextStyle( - fontSize: 13.71, - fontWeight: FontWeight.bold, - color: Colors.white)) - ]), - Text('2025/10/02 ・ 03:19 PM', - style: TextStyle(color: Colors.white70, fontSize: 10.67)) - ]))); + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: _buildImageCard()), + const SizedBox(width: 12), + Expanded(child: _buildInfoCard()), + ], + ), + ), + ], + ), + ); } Widget _buildOverviewHeader() { - return Container( - height: 27.43, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7)), - child: Row(children: [ - Text('SYSTEM OVERVIEW', + return Padding( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 0), + child: Row( + children: [ + const Text('SYSTEM OVERVIEW', style: TextStyle( - color: Colors.white.withOpacity(0.9), - fontSize: 9.14, + color: Colors.white, + fontSize: 11, fontWeight: FontWeight.bold)), const Spacer(), - SizedBox( - width: 24, - height: 24, - child: IconButton( - onPressed: () {}, - icon: const Icon(Icons.search, color: Colors.white), - iconSize: 17.14, - splashRadius: 18, - padding: EdgeInsets.zero)), - SizedBox( - width: 24, - height: 24, - child: IconButton( - onPressed: () {}, - icon: const Icon(Icons.notifications_outlined, - color: Colors.white), - iconSize: 17.14, - splashRadius: 18, - padding: EdgeInsets.zero)) - ])); + 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 Container( - height: 137.0, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7), - ), - child: Center( - child: Image.asset( - 'assets/images/helmet.png', // 이 경로는 실제 프로젝트에 맞게 확인해주세요. - width: 100.0, - height: 117.0, - fit: BoxFit.contain, + 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: Colors.grey[850], + 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 _buildInfoCard() { + Widget _buildLedIndicator(Color color) { return Container( - height: 137.0, + 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: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7)), - child: Column(children: [ - Expanded( - child: Row(children: [ - const Expanded( - child: Center( - child: Text('Name /\nNumber', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.white70, fontSize: 11)))), - const VerticalDivider(color: Colors.white24, thickness: 1), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.person_outline, - size: 16, color: Colors.white70), - const SizedBox(height: 4), - const Text('J.LIM', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 13)), - const Text('001', - style: - TextStyle(fontWeight: FontWeight.bold, fontSize: 13)) - ])) - ])), - const Divider(color: Colors.white24, height: 1), - Expanded( - child: Row(children: [ - const Expanded( - child: Center( - child: Text('STATUS', - style: TextStyle(color: Colors.white70, fontSize: 11)))), - const VerticalDivider(color: Colors.white24, thickness: 1), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('UNLOCKED', - style: TextStyle( - color: Colors.redAccent, - fontWeight: FontWeight.bold, - fontSize: 11)), - const SizedBox(height: 4), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - width: 7, - height: 7, - decoration: const BoxDecoration( - color: Colors.redAccent, shape: BoxShape.circle)), - const SizedBox(width: 4), - const Text('ACTIVE', - style: TextStyle( - color: Colors.redAccent, - fontWeight: FontWeight.bold, - fontSize: 11)) - ]) - ])) - ])) - ])); + 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 Container( - height: 132.0, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7)), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('BATTERY STATUS (%)', - style: TextStyle(color: Colors.white70, fontSize: 12)), - const SizedBox(height: 12), - Expanded( - child: Row(children: [ + 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: 70, - height: 70, - child: Stack(fit: StackFit.expand, children: [ - const CircularProgressIndicator( - value: 1.0, - strokeWidth: 6, - backgroundColor: Colors.white24, - valueColor: AlwaysStoppedAnimation(Colors.green)), // 초록색으로 수정 - Center( - child: Text('100', - style: Theme.of(context) - .textTheme - .bodyLarge - ?.copyWith( - fontSize: 24, fontWeight: FontWeight.bold))) - ])), - const SizedBox(width: 16), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, + width: 80, + height: 80, + child: Stack( + alignment: Alignment.center, children: [ - const Text('NOW', - style: TextStyle(color: Colors.white70, fontSize: 11)), - const SizedBox(height: 4), - const Text('사용 중', + SizedBox.expand( + child: CustomPaint( + painter: _BatteryArcPainter( + backgroundColor: Colors.grey.shade800, + color: Colors.white, + percentage: 1.0, + ), + ), + ), + const Text('86', style: TextStyle( - color: Colors.green, - fontWeight: FontWeight.bold, - fontSize: 11)) - ]), - const Spacer(), - Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, + fontSize: 24, fontWeight: FontWeight.w600)), + ], + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( children: [ - const Text('Solar Panel', - style: TextStyle(color: Colors.white70, fontSize: 11)), - const SizedBox(height: 4), - const Text('전압: 00 전류: 00', - style: TextStyle(color: Colors.white, fontSize: 11)) - ]) - ])) - ])); + 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 _buildOverviewCard() { - return Container( - height: 88.0, - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7)), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('OVERVIEW', style: TextStyle(color: Colors.white70, fontSize: 11)), - const SizedBox(height: 2), - Expanded( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(5.7)), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - _buildOverviewToggleSwitch('UV LED'), - _buildOverviewToggleSwitch('CHARGING'), - _buildOverviewToggleSwitch('HELMET'), - _buildOverviewToggleSwitch('FAN') - ]))) - ])); + 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 Container( - height: 88.0, - padding: const EdgeInsets.all(10), + 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: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7)), - child: Column(children: [ - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('ENVIRONMENT SENSORS', - style: TextStyle(color: Colors.white70, fontSize: 11)), - Row(children: [ - Text('VIEW HISTORY', - style: TextStyle(color: Colors.white70, fontSize: 9)), - Icon(Icons.arrow_forward_ios, size: 8, color: Colors.white70) - ]) - ]), - const SizedBox(height: 4), - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSensorInfoRow( - Icons.water_drop_outlined, 'HUMID: 60%'), - _buildSensorInfoRow(Icons.thermostat, 'TEMP: 24.5℃') - ]), - const SizedBox(width: 12), - const Expanded(child: _LineChartPlaceholder()) - ])) - ])); + 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 Container( - height: 174.0, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(5.7), + return Card( + clipBehavior: Clip.antiAlias, + child: SizedBox( + height: 200.0, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), + padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, children: [ const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('My Location', style: TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold)), + Text('My Location', + style: TextStyle( + fontSize: 13, + color: Colors.white, + fontWeight: FontWeight.bold)), SizedBox(height: 4), - Text('주소: 금주 남구 효덕로 277', style: TextStyle(fontSize: 12, color: Colors.white70)), + Text('주소: 남구 효덕로 277', + style: + TextStyle(fontSize: 11, color: Colors.white70)), ], ), - Row( - children: [ - Text('VIEW MORE', style: TextStyle(color: Colors.white70, fontSize: 12)), - Icon(Icons.arrow_forward_ios, size: 10, 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]), + ], + ), ), ], ), ), - const SizedBox(height: 12), Expanded( child: FlutterMap( options: MapOptions( initialCenter: exampleLocation, initialZoom: 15.0, + interactionOptions: + const InteractionOptions(flags: InteractiveFlag.none), ), children: [ TileLayer( - urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'com.example.smart_helmet_app', + urlTemplate: + 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', + subdomains: const ['a', 'b', 'c', 'd'], ), MarkerLayer( markers: [ @@ -465,7 +659,8 @@ class _HomeScreenState extends State { point: exampleLocation, width: 80, height: 80, - child: const Icon(Icons.location_pin, size: 40, color: Colors.red), + child: const Icon(Icons.location_pin, + size: 40, color: Colors.white), ), ], ), @@ -479,77 +674,52 @@ class _HomeScreenState extends State { } Widget _buildActivityCard() { - return Container( - height: 86.0, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor, - borderRadius: BorderRadius.circular(5.7)), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Activity', - style: - TextStyle(fontSize: 12, color: Colors.white, fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('08:15 AM - Battery fully Charged', - style: TextStyle(fontSize: 10, color: Colors.white70)), - Text('9:30 AM - UV LED Actived', - style: TextStyle(fontSize: 10, color: Colors.white70)) - ])), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('10:45 AM - Helmet Unlocked', - style: TextStyle(fontSize: 10, color: Colors.white70)), - Text('11:00 AM - Helmet Off', - style: TextStyle(fontSize: 10, color: Colors.white70)) - ])) - ])) - ])); + 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 _buildOverviewToggleSwitch(String title) { - return Expanded( - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(title, style: const TextStyle(fontSize: 9, color: Colors.white)), - Transform.scale( - scale: 0.6, - child: Switch.adaptive( - value: _overviewToggles[title]!, - onChanged: (bool value) { - setState(() { - _overviewToggles[title] = value; - }); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - activeColor: Colors.white, - activeTrackColor: Colors.blueAccent, - inactiveThumbColor: Colors.grey.shade400, - inactiveTrackColor: Colors.grey.shade700)) - ])); - } - - Widget _buildSensorInfoRow(IconData icon, String text) { - return Row(children: [ - Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), shape: BoxShape.circle), - child: Icon(icon, color: Colors.white, size: 10)), - const SizedBox(width: 6), - Text(text, style: const TextStyle(fontSize: 9, color: Colors.white)) - ]); + Widget _activityText(String text) { + return Text(text, + style: const TextStyle(fontSize: 11, color: Colors.white70)); } } @@ -559,9 +729,10 @@ class _LineChartPlaceholder extends StatelessWidget { Widget build(BuildContext context) { return Column(children: [ Expanded( - child: CustomPaint(painter: _LineChartPainter(), size: Size.infinite)), + child: + CustomPaint(painter: _LineChartPainter(), size: Size.infinite)), const SizedBox(height: 4), - Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + 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)) @@ -580,9 +751,12 @@ class _LineChartPainter extends CustomPainter { 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); + 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); } @@ -590,4 +764,70 @@ class _LineChartPainter extends CustomPainter { 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