Skip to content

Commit 60cfe5a

Browse files
authored
feat: 운동 기록 상세 화면 구현 및 운동 기록 화면과 운동 시간 연동 (#72)
# 구현 요소 1. 운동 기록 상세 화면 구현 완료 2. 운동 기록 화면과 운동 시간 연동 완료 # 추후 해야 할 일 1. 백엔드 운동 기록 관련 작업 완료되면, 날짜별 운동 시간을 DB와 연결하여 저장되게 하기 2. 운동 기록 화면 및 상세 화면에 nav 바 추가
1 parent 6ccf8b4 commit 60cfe5a

File tree

3 files changed

+331
-64
lines changed

3 files changed

+331
-64
lines changed
Lines changed: 200 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'package:flutter/material.dart';
2-
import '../../core/app_colors.dart';
2+
import 'package:ongi/core/app_colors.dart';
3+
import 'package:ongi/widgets/date_carousel.dart';
4+
import 'package:ongi/widgets/time_grid.dart';
35

4-
class ExerciseRecordDetailScreen extends StatelessWidget {
6+
class ExerciseRecordDetailScreen extends StatefulWidget {
57
final DateTime date;
68
final int hours;
79
final int minutes;
@@ -13,90 +15,229 @@ class ExerciseRecordDetailScreen extends StatelessWidget {
1315
required this.minutes,
1416
});
1517

18+
@override
19+
State<ExerciseRecordDetailScreen> createState() =>
20+
_ExerciseRecordDetailScreenState();
21+
}
22+
23+
class _ExerciseRecordDetailScreenState
24+
extends State<ExerciseRecordDetailScreen> {
25+
List<int> selected = [];
26+
27+
String _calExTime(List<int> selectedCells) {
28+
final totalMinutes = selectedCells.length * 10;
29+
final hours = totalMinutes ~/ 60;
30+
final minutes = totalMinutes % 60;
31+
32+
String result = '';
33+
if (hours > 0) {
34+
result += '$hours시간';
35+
if (minutes > 0) {
36+
result += ' $minutes분';
37+
}
38+
} else if (minutes > 0) {
39+
result += '$minutes분';
40+
} else {
41+
result = '0분';
42+
}
43+
44+
return result;
45+
}
46+
47+
// Calculate hours and minutes from selected cells
48+
Map<String, int> _calculateHoursAndMinutes(List<int> selectedCells) {
49+
final totalMinutes = selectedCells.length * 10;
50+
final hours = totalMinutes ~/ 60;
51+
final minutes = totalMinutes % 60;
52+
return {'hours': hours, 'minutes': minutes};
53+
}
54+
55+
// Handle back navigation with exercise time data
56+
void _handleBack() {
57+
final timeData = _calculateHoursAndMinutes(selected);
58+
Navigator.pop(context, timeData);
59+
}
60+
1661
@override
1762
Widget build(BuildContext context) {
1863
final screenWidth = MediaQuery.of(context).size.width;
1964
final circleSize = screenWidth * 1.56;
2065

21-
return Scaffold(
22-
backgroundColor: AppColors.ongiLigntgrey,
23-
body: SafeArea(
24-
child: Stack(
25-
clipBehavior: Clip.none,
26-
children: [
27-
Align(
28-
alignment: Alignment.topCenter,
29-
child: Transform.translate(
30-
offset: Offset(0, -circleSize * 0.76),
31-
child: OverflowBox(
32-
maxWidth: double.infinity,
33-
maxHeight: double.infinity,
66+
return PopScope(
67+
canPop: false,
68+
onPopInvoked: (didPop) {
69+
if (!didPop) {
70+
_handleBack();
71+
}
72+
},
73+
child: Scaffold(
74+
backgroundColor: AppColors.ongiLigntgrey,
75+
body: SafeArea(
76+
child: Stack(
77+
clipBehavior: Clip.none,
78+
children: [
79+
Align(
80+
alignment: Alignment.topCenter,
81+
child: Transform.translate(
82+
offset: Offset(0, -circleSize * 0.76),
83+
child: OverflowBox(
84+
maxWidth: double.infinity,
85+
maxHeight: double.infinity,
86+
child: Container(
87+
width: circleSize,
88+
height: circleSize,
89+
decoration: BoxDecoration(
90+
shape: BoxShape.circle,
91+
color: AppColors.ongiOrange,
92+
),
93+
child: Center(
94+
child: Padding(
95+
padding: EdgeInsets.only(top: circleSize * 0.62),
96+
child: OverflowBox(
97+
maxHeight: double.infinity,
98+
child: Column(
99+
children: [
100+
const Text(
101+
'오늘 목표 운동 시간,',
102+
style: TextStyle(
103+
fontSize: 25,
104+
color: Colors.white,
105+
fontWeight: FontWeight.w600,
106+
height: 1,
107+
),
108+
),
109+
const Text(
110+
'다 채우셨나요?',
111+
style: TextStyle(
112+
fontSize: 40,
113+
color: Colors.white,
114+
fontWeight: FontWeight.w600,
115+
),
116+
),
117+
],
118+
),
119+
),
120+
),
121+
),
122+
),
123+
),
124+
),
125+
),
126+
127+
Positioned(
128+
top: circleSize * 0.5,
129+
left: 0,
130+
right: 0,
131+
bottom: 0,
132+
child: Padding(
133+
padding: const EdgeInsets.only(
134+
left: 15,
135+
right: 15,
136+
bottom: 20,
137+
),
34138
child: Container(
35-
width: circleSize,
36-
height: circleSize,
37139
decoration: BoxDecoration(
38-
shape: BoxShape.circle,
39-
color: AppColors.ongiOrange,
140+
color: Colors.white,
141+
borderRadius: BorderRadius.circular(20),
40142
),
41-
child: Center(
42-
child: Padding(
43-
padding: EdgeInsets.only(top: circleSize),
44-
child: OverflowBox(
45-
maxHeight: double.infinity,
143+
child: Stack(
144+
children: [
145+
Positioned(
146+
top: 20,
147+
right: 0,
148+
child: SizedBox(
149+
width: 200,
150+
height: 100,
151+
child: DateCarousel(
152+
initialDate: widget.date,
153+
onDateChanged: (selectedDate) {},
154+
),
155+
),
156+
),
157+
Padding(
158+
padding: EdgeInsets.only(left: 25, top: 75),
46159
child: Column(
160+
crossAxisAlignment: CrossAxisAlignment.start,
47161
children: [
48162
const Text(
49-
'오늘 목표 운동 시간,',
163+
'오늘은',
50164
style: TextStyle(
51-
fontSize: 25,
52-
color: Colors.white,
165+
fontSize: 20,
166+
color: AppColors.ongiOrange,
53167
fontWeight: FontWeight.w600,
54-
height: 1,
55168
),
56169
),
57-
const Text(
58-
'다 채우셨나요?',
59-
style: TextStyle(
60-
fontSize: 40,
61-
color: Colors.white,
62-
fontWeight: FontWeight.w600,
63-
),
64-
),
65-
Container(
66-
margin: const EdgeInsets.symmetric(vertical: 6),
67-
child: Image.asset(
68-
'assets/images/exercise_record_title_logo.png',
69-
width: circleSize * 0.3,
170+
RichText(
171+
text: TextSpan(
172+
text: '${_calExTime(selected)} ',
173+
style: TextStyle(
174+
fontSize: 35,
175+
color: AppColors.ongiOrange,
176+
fontWeight: FontWeight.w600,
177+
),
178+
children: [
179+
TextSpan(
180+
text: '운동했어요!',
181+
style: TextStyle(
182+
fontSize: 20,
183+
color: AppColors.ongiOrange,
184+
fontWeight: FontWeight.w600,
185+
),
186+
),
187+
],
70188
),
71189
),
72190
],
73191
),
74192
),
75-
),
193+
Positioned(
194+
top: 160,
195+
left: 7,
196+
right: 7,
197+
bottom: 20,
198+
child: Center(
199+
child: TimeGrid(
200+
initialSelected: selected,
201+
cellColor: Colors.white,
202+
cellSelectedColor: AppColors.ongiOrange,
203+
borderColor: AppColors.ongiOrange,
204+
onValueChanged: (newList) {
205+
setState(() => selected = newList);
206+
},
207+
),
208+
),
209+
),
210+
],
76211
),
77212
),
78213
),
79214
),
80-
),
81-
Positioned(
82-
top: circleSize * 0.5,
83-
left: 0,
84-
right: 0,
85-
child: Center(
215+
Positioned(
216+
top: circleSize * 0.23,
217+
left: 0,
218+
right: 0,
219+
child: Center(
220+
child: Container(
221+
margin: const EdgeInsets.symmetric(vertical: 6),
222+
child: Image.asset(
223+
'assets/images/exercise_record_title_logo.png',
224+
width: circleSize * 0.23,
225+
),
226+
),
227+
),
86228
),
87-
),
88-
// Back button
89-
Positioned(
90-
top: 16,
91-
left: 16,
92-
child: IconButton(
93-
icon: const Icon(Icons.arrow_back, color: Colors.white),
94-
onPressed: () => Navigator.pop(context),
229+
Positioned(
230+
top: 16,
231+
left: 16,
232+
child: IconButton(
233+
icon: const Icon(Icons.arrow_back, color: Colors.white),
234+
onPressed: _handleBack,
235+
),
95236
),
96-
),
97-
],
237+
],
238+
),
98239
),
99240
),
100241
);
101242
}
102-
}
243+
}

frontend/ongi/lib/screens/health/exercise_record_screen.dart

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,21 @@ class _ExerciseRecordScreenState extends State<ExerciseRecordScreen> {
2020
bool _isSwipeTriggered =
2121
false; // Prevent button callback when triggered by swipe
2222

23-
// Get exercise time for a specific date (placeholder: returns 0)
23+
// Store exercise times for different dates
24+
Map<String, Map<String, int>> exerciseTimes = {};
25+
26+
// Get exercise time for a specific date
2427
Map<String, int> getExerciseTime(DateTime date) {
25-
// TODO: Replace with real backend call
26-
return {'hours': 1, 'minutes': 30};
28+
final dateKey = "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
29+
return exerciseTimes[dateKey] ?? {'hours': 0, 'minutes': 0};
30+
}
31+
32+
// Save exercise time for a specific date
33+
void saveExerciseTime(DateTime date, int hours, int minutes) {
34+
final dateKey = "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}";
35+
setState(() {
36+
exerciseTimes[dateKey] = {'hours': hours, 'minutes': minutes};
37+
});
2738
}
2839

2940
// Convert page index to date for exercise PageView
@@ -149,8 +160,8 @@ class _ExerciseRecordScreenState extends State<ExerciseRecordScreen> {
149160
getExerciseTime(date);
150161

151162
return GestureDetector(
152-
onTap: () {
153-
Navigator.push(
163+
onTap: () async {
164+
final result = await Navigator.push(
154165
context,
155166
MaterialPageRoute(
156167
builder: (_) =>
@@ -165,6 +176,13 @@ class _ExerciseRecordScreenState extends State<ExerciseRecordScreen> {
165176
),
166177
),
167178
);
179+
180+
// Handle returned exercise time data
181+
if (result != null && result is Map<String, dynamic>) {
182+
final returnedHours = result['hours'] as int;
183+
final returnedMinutes = result['minutes'] as int;
184+
saveExerciseTime(date, returnedHours, returnedMinutes);
185+
}
168186
},
169187
child: Padding(
170188
padding:

0 commit comments

Comments
 (0)