1. 하단 네비게이션 바 수정
2. 대여소 지도 및 리스트 화면 제작 (RentReturnScreen) 3. 대여/반납 상세 진행 화면 구현 (RentalProcessScreen)
This commit is contained in:
BIN
assets/images/open.png
Normal file
BIN
assets/images/open.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -1,15 +0,0 @@
|
|||||||
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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,6 @@ class HomeScreenContent extends StatefulWidget {
|
|||||||
class _HomeScreenContentState extends State<HomeScreenContent> {
|
class _HomeScreenContentState extends State<HomeScreenContent> {
|
||||||
static const double _uniformGap = 16.0;
|
static const double _uniformGap = 16.0;
|
||||||
|
|
||||||
// 포인트 컬러 (배터리, 상태 표시 등에는 유지)
|
|
||||||
final Color _pointColor = Colors.redAccent;
|
final Color _pointColor = Colors.redAccent;
|
||||||
|
|
||||||
final Map<String, bool> _controlToggles = {
|
final Map<String, bool> _controlToggles = {
|
||||||
@@ -117,8 +116,8 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 20.0),
|
padding: const EdgeInsets.only(bottom: 20.0),
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
'assets/images/helmet.png',
|
'assets/images/open.png',
|
||||||
width: 100,
|
width: 120,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@@ -381,7 +380,6 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
height: 30,
|
height: 30,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(15),
|
||||||
// ⭐ [수정됨] ON(화이트), OFF(다크그레이) -> 블랙 앤 화이트 테마 적용
|
|
||||||
color: value ? Colors.white : Colors.grey.shade700,
|
color: value ? Colors.white : Colors.grey.shade700,
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@@ -397,7 +395,6 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
height: 26,
|
height: 26,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
// ⭐ [수정됨] ON(블랙), OFF(화이트) -> 배경과 대비되는 색상
|
|
||||||
color: value ? const Color(0xFF27292B) : Colors.white,
|
color: value ? const Color(0xFF27292B) : Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -411,7 +408,6 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
// ⭐ [수정됨] ON 텍스트 블랙
|
|
||||||
color: value
|
color: value
|
||||||
? const Color(0xFF27292B)
|
? const Color(0xFF27292B)
|
||||||
: Colors.transparent)))),
|
: Colors.transparent)))),
|
||||||
@@ -564,6 +560,7 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
TileLayer(
|
TileLayer(
|
||||||
urlTemplate:
|
urlTemplate:
|
||||||
'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
|
'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
|
||||||
|
userAgentPackageName: 'com.example.app',
|
||||||
subdomains: const ['a', 'b', 'c', 'd'],
|
subdomains: const ['a', 'b', 'c', 'd'],
|
||||||
retinaMode: true,
|
retinaMode: true,
|
||||||
),
|
),
|
||||||
@@ -636,10 +633,8 @@ class _HomeScreenContentState extends State<HomeScreenContent> {
|
|||||||
style: const TextStyle(fontSize: 11, color: Colors.white70));
|
style: const TextStyle(fontSize: 11, color: Colors.white70));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- _HomeScreenContentState 끝 ---
|
|
||||||
|
|
||||||
|
|
||||||
// 👇 HOME 화면에 사용되는 4가지 커스텀 클래스 정의
|
|
||||||
|
|
||||||
class _LineChartPlaceholder extends StatelessWidget {
|
class _LineChartPlaceholder extends StatelessWidget {
|
||||||
const _LineChartPlaceholder();
|
const _LineChartPlaceholder();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:smarthelmet_app/home_screen_content.dart';
|
import 'package:smarthelmet_app/home_screen_content.dart';
|
||||||
import 'package:smarthelmet_app/control_screen.dart';
|
import 'package:smarthelmet_app/control_screen.dart';
|
||||||
import 'package:smarthelmet_app/history_screen.dart';
|
import 'package:smarthelmet_app/history_screen.dart';
|
||||||
import 'package:smarthelmet_app/alerts_report_screen.dart';
|
import 'package:smarthelmet_app/rent_return_screen.dart';
|
||||||
import 'package:smarthelmet_app/settings_screen.dart';
|
import 'package:smarthelmet_app/settings_screen.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -51,7 +51,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
const HomeScreenContent(),
|
const HomeScreenContent(),
|
||||||
const HistoryScreen(),
|
const HistoryScreen(),
|
||||||
const ControlScreen(),
|
const ControlScreen(),
|
||||||
const AlertsReportScreen(),
|
const RentReturnScreen(),
|
||||||
const SettingsScreen(),
|
const SettingsScreen(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -91,11 +91,11 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'HOME'),
|
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'HOME'),
|
||||||
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.settings_input_component), label: 'CONTROL'),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.notifications_active), label: 'ALERTS'),
|
BottomNavigationBarItem(icon: Icon(Icons.assignment_return_outlined), label: 'RENT/RETURN'),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'SETTINGS'),
|
BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'SETTINGS'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
356
lib/rent_return_screen.dart
Normal file
356
lib/rent_return_screen.dart
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'rental_process_screen.dart';
|
||||||
|
|
||||||
|
class StationInfo {
|
||||||
|
final String name;
|
||||||
|
final String address;
|
||||||
|
final String status;
|
||||||
|
final String distance;
|
||||||
|
|
||||||
|
StationInfo({
|
||||||
|
required this.name,
|
||||||
|
required this.address,
|
||||||
|
required this.status,
|
||||||
|
required this.distance,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class RentReturnScreen extends StatelessWidget {
|
||||||
|
const RentReturnScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final LatLng centerLocation = const LatLng(35.1595, 126.8526);
|
||||||
|
|
||||||
|
|
||||||
|
final List<StationInfo> stations = [
|
||||||
|
StationInfo(
|
||||||
|
name: 'STATION A - GU.UNIV',
|
||||||
|
address: '277 Hyodeok-ro',
|
||||||
|
status: 'ONLINE',
|
||||||
|
distance: '0.5 km away'),
|
||||||
|
StationInfo(
|
||||||
|
name: 'STATION B - CITY HALL',
|
||||||
|
address: '123 City Hall Ave',
|
||||||
|
status: 'ONLINE',
|
||||||
|
distance: '1.2 km away'),
|
||||||
|
StationInfo(
|
||||||
|
name: 'STATION C - PARK',
|
||||||
|
address: '55 Park Lane',
|
||||||
|
status: 'OFFLINE',
|
||||||
|
distance: '2.8 km away'),
|
||||||
|
StationInfo(
|
||||||
|
name: 'STATION D - TECH HUB',
|
||||||
|
address: '88 Innovation Blvd',
|
||||||
|
status: 'ONLINE',
|
||||||
|
distance: '4.1 km away'),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFF1E1E1E),
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text(
|
||||||
|
'RENT/ RETURN',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
|
||||||
|
),
|
||||||
|
backgroundColor: const Color(0xFF2C2C2E),
|
||||||
|
elevation: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||||
|
onPressed: () {},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 50,
|
||||||
|
child: FlutterMap(
|
||||||
|
options: MapOptions(
|
||||||
|
initialCenter: centerLocation,
|
||||||
|
initialZoom: 15.0,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TileLayer(
|
||||||
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||||
|
userAgentPackageName: 'com.example.helmet_app',
|
||||||
|
),
|
||||||
|
MarkerLayer(
|
||||||
|
markers: [
|
||||||
|
_buildBlackMarker(centerLocation),
|
||||||
|
_buildBlackMarker(const LatLng(35.162, 126.855)),
|
||||||
|
_buildBlackMarker(const LatLng(35.157, 126.850)),
|
||||||
|
_buildBlackMarker(const LatLng(35.160, 126.858)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
flex: 45,
|
||||||
|
child: Container(
|
||||||
|
color: const Color(0xFF1E1E1E),
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 20, 16, 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'LIST VIEW',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
|
itemCount: stations.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _buildStationItem(context, stations[index]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStationItem(BuildContext context, StationInfo station) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF2C2C2E),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[800],
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/storage.png',
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
errorBuilder: (context, error, stackTrace) => const Icon(Icons.inventory_2, color: Colors.white54),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
station.name,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
station.address,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey, fontSize: 11, height: 1.2),
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
'STATUS: ${station.status}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: station.status.startsWith('OFFLINE')
|
||||||
|
? Colors.redAccent
|
||||||
|
: Colors.grey,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
station.distance,
|
||||||
|
style: const TextStyle(color: Colors.grey, fontSize: 11),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => RentalProcessScreen(
|
||||||
|
stationName: station.name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.black,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
|
minimumSize: const Size(80, 40),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'SELECT',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w500, letterSpacing: 0.8, fontSize: 14)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF1E1E1E),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'LOGS',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _showLogHistory(context, station.name),
|
||||||
|
child: Text(
|
||||||
|
'VIEW MORE >',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[400],
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
const Text(
|
||||||
|
'Available: Door Fully Closed',
|
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 11),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'(2025-11-19)/(08:58)',
|
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 11),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Marker _buildBlackMarker(LatLng point) {
|
||||||
|
return Marker(
|
||||||
|
point: point,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
color: Colors.black,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showLogHistory(BuildContext context, String stationName) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.6,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF1E1E1E),
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
width: 40, height: 4,
|
||||||
|
decoration: BoxDecoration(color: Colors.grey[600], borderRadius: BorderRadius.circular(2)),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Text("LOGS: $stationName", style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)),
|
||||||
|
),
|
||||||
|
const Divider(color: Colors.white24, height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
children: [
|
||||||
|
_buildLogItem("08:58:33", "Door Fully Closed", Icons.door_front_door, Colors.green),
|
||||||
|
_buildLogItem("08:58:30", "Helmet Returned", Icons.check_circle_outline, Colors.blue),
|
||||||
|
_buildLogItem("08:55:12", "User Unlocked Door", Icons.lock_open, Colors.white),
|
||||||
|
_buildLogItem("08:30:00", "UV Sanitization Complete", Icons.cleaning_services, Colors.purpleAccent),
|
||||||
|
_buildLogItem("08:00:00", "System Boot Up", Icons.power_settings_new, Colors.grey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLogItem(String time, String message, IconData icon, Color color) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(time, style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: color, size: 20),
|
||||||
|
Container(width: 2, height: 20, color: Colors.white12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(message, style: const TextStyle(color: Colors.white70, fontSize: 14)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
349
lib/rental_process_screen.dart
Normal file
349
lib/rental_process_screen.dart
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RentalProcessScreen extends StatelessWidget {
|
||||||
|
final String stationName;
|
||||||
|
|
||||||
|
const RentalProcessScreen({
|
||||||
|
super.key,
|
||||||
|
required this.stationName,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const Color kBackgroundColor = Color(0xFF1E1E1E);
|
||||||
|
static const Color kCardColor = Color(0xFF2C2C2E);
|
||||||
|
static const Color kPrimaryGreen = Color(0xFF4CAF50);
|
||||||
|
static const Color kPrimaryOrange = Color(0xFFFF3D00);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: kBackgroundColor,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
stationName,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white),
|
||||||
|
),
|
||||||
|
backgroundColor: kCardColor,
|
||||||
|
elevation: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.more_vert, color: Colors.white),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildStatusCard(context),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
_buildProcessSectionTitle('HELMET RENTAL PROCESS'),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kCardColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildStepRow(1, 'UNLOCK & OPEN', Icons.lock_open, isCompleted: true),
|
||||||
|
_buildDivider(),
|
||||||
|
_buildStepRow(2, 'DOOR STATUS', Icons.sensor_door_outlined),
|
||||||
|
_buildDivider(),
|
||||||
|
_buildStepRow(3, 'TAKE HELMET', Icons.outbox),
|
||||||
|
_buildDivider(),
|
||||||
|
_buildStepRow(4, 'ENJOY RIDING!', Icons.sentiment_satisfied_alt, showDivider: false),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
_buildProcessSectionTitle('HELMET RETURN PROCESS'),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kCardColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildStepRow(1, 'UNLOCK & OPEN', Icons.lock_open),
|
||||||
|
_buildDivider(),
|
||||||
|
_buildStepRow(2, 'INSERT HELMET', Icons.move_to_inbox),
|
||||||
|
_buildDivider(),
|
||||||
|
_buildStepRow(3, 'SENSOR SCANNING\n& RETURN COMPLETE', Icons.sync),
|
||||||
|
_buildDivider(),
|
||||||
|
_buildStepRow(4, 'SANITIZING', Icons.shield_outlined, showDivider: false),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
_buildErrorAlertBox(),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
_buildLogsCard(context),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatusCard(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kCardColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.circle, color: kPrimaryOrange, size: 16),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text('IN USE', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kBackgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/open.png',
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
errorBuilder: (context, error, stackTrace) => const Icon(Icons.inventory_2, size: 50, color: Colors.white54),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text('NOW', style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('문이 열렸습니다! (OPEN 버튼 눌림)'),
|
||||||
|
duration: Duration(seconds: 1),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: Colors.black,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
),
|
||||||
|
child: const Text('OPEN', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kBackgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
'SENSOR ERROR:\nLOCK FAIL',
|
||||||
|
style: TextStyle(color: kPrimaryOrange, fontWeight: FontWeight.bold, height: 1.2),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProcessSectionTitle(String title) {
|
||||||
|
return Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStepRow(int step, String title, IconData icon, {bool isCompleted = false, bool showDivider = true}) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text('STEP $step', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||||
|
if (isCompleted) ...[
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(Icons.check_circle, color: kPrimaryGreen, size: 16),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(title, style: const TextStyle(color: Colors.white, fontSize: 18, letterSpacing: 0.6)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Icon(icon, color: isCompleted ? Colors.white : Colors.grey, size: 48),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDivider() {
|
||||||
|
return const Divider(color: Colors.white, height: 1, thickness: 1, indent: 20, endIndent: 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildErrorAlertBox() {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kCardColor.withOpacity(0.5),
|
||||||
|
border: Border.all(color: kPrimaryOrange, width: 2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.warning_amber_rounded, color: kPrimaryOrange, size: 40),
|
||||||
|
SizedBox(width: 16),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('SENSOR ERROR:', style: TextStyle(color: kPrimaryOrange, fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
|
Text('LOCK FAILURE', style: TextStyle(color: kPrimaryOrange, fontWeight: FontWeight.bold, fontSize: 16)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLogsCard(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: kCardColor,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text('LOGS', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => _showLogHistory(context),
|
||||||
|
child: Text('VIEW MORE >', style: TextStyle(color: Colors.grey[400], fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Row(
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.check_circle, color: kPrimaryGreen, size: 16),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Available: Door Fully Closed', style: TextStyle(color: Colors.white70)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 24.0, top: 4),
|
||||||
|
child: Text('(2025-11-19)/(08:58)', style: TextStyle(color: Colors.grey[600], fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showLogHistory(BuildContext context) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.6,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF1E1E1E),
|
||||||
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Container(
|
||||||
|
width: 40, height: 4,
|
||||||
|
decoration: BoxDecoration(color: Colors.grey[600], borderRadius: BorderRadius.circular(2)),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(20.0),
|
||||||
|
child: Text("DEVICE LOGS", style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
|
),
|
||||||
|
const Divider(color: Colors.white24, height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
children: [
|
||||||
|
_buildLogItem("08:58:33", "Door Fully Closed", Icons.door_front_door, Colors.green),
|
||||||
|
_buildLogItem("08:58:30", "Helmet Returned (Sensor A)", Icons.check_circle_outline, Colors.blue),
|
||||||
|
_buildLogItem("08:55:12", "User Unlocked Door", Icons.lock_open, Colors.white),
|
||||||
|
_buildLogItem("08:30:00", "UV Sanitization Complete", Icons.cleaning_services, Colors.purpleAccent),
|
||||||
|
_buildLogItem("08:00:00", "System Boot Up", Icons.power_settings_new, Colors.grey),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLogItem(String time, String message, IconData icon, Color color) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(time, style: const TextStyle(color: Colors.grey, fontSize: 12)),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: color, size: 20),
|
||||||
|
Container(width: 2, height: 20, color: Colors.white12),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(message, style: const TextStyle(color: Colors.white70, fontSize: 14)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ dependencies:
|
|||||||
flutter_map: ^6.1.0
|
flutter_map: ^6.1.0
|
||||||
latlong2: ^0.9.0
|
latlong2: ^0.9.0
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Reference in New Issue
Block a user