Control 페이지 UI 구현 및 앱 테마 컬러(Red) 변경 완료
This commit is contained in:
15
lib/alerts_report_screen.dart
Normal file
15
lib/alerts_report_screen.dart
Normal file
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
438
lib/control_screen.dart
Normal file
438
lib/control_screen.dart
Normal file
@@ -0,0 +1,438 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ControlScreen extends StatefulWidget {
|
||||
const ControlScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ControlScreen> createState() => _ControlScreenState();
|
||||
}
|
||||
|
||||
class _ControlScreenState extends State<ControlScreen> {
|
||||
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<Color>(_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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
15
lib/history_screen.dart
Normal file
15
lib/history_screen.dart
Normal file
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
752
lib/home_screen_content.dart
Normal file
752
lib/home_screen_content.dart
Normal file
@@ -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<HomeScreenContent> createState() => _HomeScreenContentState();
|
||||
}
|
||||
|
||||
class _HomeScreenContentState extends State<HomeScreenContent> {
|
||||
static const double _uniformGap = 16.0;
|
||||
|
||||
// 포인트 컬러 (배터리, 상태 표시 등에는 유지)
|
||||
final Color _pointColor = Colors.redAccent;
|
||||
|
||||
final Map<String, bool> _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<bool> 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;
|
||||
}
|
||||
}
|
||||
15
lib/location_screen.dart
Normal file
15
lib/location_screen.dart
Normal file
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
771
lib/main.dart
771
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<HomeScreen> {
|
||||
int _selectedIndex = 0;
|
||||
static const double _uniformGap = 16.0;
|
||||
|
||||
final Map<String, bool> _controlToggles = {
|
||||
'UV LED': false,
|
||||
'CHARGING': true,
|
||||
'HELMET': true,
|
||||
'FAN': false,
|
||||
};
|
||||
final List<Widget> _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<HomeScreen> {
|
||||
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<bool> 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;
|
||||
}
|
||||
}
|
||||
15
lib/settings_screen.dart
Normal file
15
lib/settings_screen.dart
Normal file
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/widgets/custom_header.dart
Normal file
31
lib/widgets/custom_header.dart
Normal file
@@ -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))
|
||||
]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user