Browse Source

简单完成 alaram 和 stopwatch页面

heavyrain 3 months ago
parent
commit
68562daca0
2 changed files with 439 additions and 10 deletions
  1. 266 5
      lib/pages/alarm_page.dart
  2. 173 5
      lib/pages/stopwatch_page.dart

+ 266 - 5
lib/pages/alarm_page.dart

@@ -1,4 +1,52 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:intl/intl.dart';
+
+class AlarmModel {
+  final int id;
+  final TimeOfDay time;
+  final List<bool> days; // 7 days from Monday to Sunday
+  final String label;
+  bool isActive;
+
+  AlarmModel({
+    required this.id,
+    required this.time,
+    required this.days,
+    required this.label,
+    this.isActive = true,
+  });
+
+  String get timeString {
+    final hour = time.hour;
+    final minute = time.minute;
+    return '${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
+  }
+
+  String get repeatDaysString {
+    List<String> dayShortNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
+    List<String> activeDays = [];
+
+    for (int i = 0; i < 7; i++) {
+      if (days[i]) {
+        activeDays.add(dayShortNames[i]);
+      }
+    }
+
+    if (activeDays.isEmpty) {
+      return 'One time';
+    } else if (activeDays.length == 7) {
+      return 'Every day';
+    } else if (activeDays.length == 5 && 
+               days[0] && days[1] && days[2] && days[3] && days[4]) {
+      return 'Weekdays';
+    } else if (activeDays.length == 2 && days[5] && days[6]) {
+      return 'Weekends';
+    } else {
+      return activeDays.join(', ');
+    }
+  }
+}
 
 class AlarmPage extends StatefulWidget {
   @override
@@ -6,16 +54,229 @@ class AlarmPage extends StatefulWidget {
 }
 
 class _AlarmPageState extends State<AlarmPage> {
+  List<AlarmModel> _alarms = [];
+  int _nextId = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    // Add some sample alarms
+    _addAlarm(
+      TimeOfDay(hour: 8, minute: 0),
+      [true, true, true, true, true, false, false],
+      'Wake up',
+    );
+    _addAlarm(
+      TimeOfDay(hour: 18, minute: 30),
+      [true, true, true, true, true, true, true],
+      'Evening reminder',
+    );
+  }
+
+  void _addAlarm(TimeOfDay time, List<bool> days, String label) {
+    setState(() {
+      _alarms.add(
+        AlarmModel(
+          id: _nextId++,
+          time: time,
+          days: days,
+          label: label,
+        ),
+      );
+    });
+  }
+
+  void _toggleAlarm(int id) {
+    setState(() {
+      for (int i = 0; i < _alarms.length; i++) {
+        if (_alarms[i].id == id) {
+          _alarms[i].isActive = !_alarms[i].isActive;
+          break;
+        }
+      }
+    });
+  }
+
+  void _deleteAlarm(int id) {
+    setState(() {
+      _alarms.removeWhere((alarm) => alarm.id == id);
+    });
+  }
+
+  Future<void> _showAddAlarmDialog() async {
+    final TimeOfDay initialTime = TimeOfDay.now();
+    final List<bool> days = List.generate(7, (_) => false);
+    final TextEditingController labelController = TextEditingController();
+
+    await showDialog(
+      context: context,
+      builder: (BuildContext context) {
+        TimeOfDay selectedTime = initialTime;
+
+        return StatefulBuilder(
+          builder: (context, setState) {
+            return AlertDialog(
+              title: Text('Add Alarm'),
+              content: SingleChildScrollView(
+                child: Column(
+                  mainAxisSize: MainAxisSize.min,
+                  children: [
+                    // Time picker button
+                    ListTile(
+                      title: Text('Time'),
+                      subtitle: Text(
+                        '${selectedTime.hour.toString().padLeft(2, '0')}:${selectedTime.minute.toString().padLeft(2, '0')}',
+                        style: TextStyle(fontSize: 24.sp),
+                      ),
+                      onTap: () async {
+                        final TimeOfDay? picked = await showTimePicker(
+                          context: context,
+                          initialTime: selectedTime,
+                        );
+                        if (picked != null) {
+                          setState(() {
+                            selectedTime = picked;
+                          });
+                        }
+                      },
+                    ),
+                    SizedBox(height: 10.h),
+                    // Repeat days
+                    Text('Repeat', style: TextStyle(fontWeight: FontWeight.bold)),
+                    _buildWeekdaySelector(days, setState),
+                    SizedBox(height: 10.h),
+                    // Label
+                    TextField(
+                      controller: labelController,
+                      decoration: InputDecoration(
+                        labelText: 'Label',
+                        border: OutlineInputBorder(),
+                      ),
+                    ),
+                  ],
+                ),
+              ),
+              actions: [
+                TextButton(
+                  onPressed: () => Navigator.of(context).pop(),
+                  child: Text('Cancel'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    _addAlarm(
+                      selectedTime,
+                      List.from(days),
+                      labelController.text.isNotEmpty
+                          ? labelController.text
+                          : 'Alarm',
+                    );
+                    Navigator.of(context).pop();
+                  },
+                  child: Text('Save'),
+                ),
+              ],
+            );
+          },
+        );
+      },
+    );
+  }
+
+  Widget _buildWeekdaySelector(List<bool> days, StateSetter setState) {
+    List<String> dayShortNames = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      children: List.generate(7, (index) {
+        return InkWell(
+          onTap: () {
+            setState(() {
+              days[index] = !days[index];
+            });
+          },
+          child: CircleAvatar(
+            radius: 18.r,
+            backgroundColor: days[index] ? Colors.blue : Colors.grey[300],
+            child: Text(
+              dayShortNames[index],
+              style: TextStyle(
+                color: days[index] ? Colors.white : Colors.black87,
+                fontWeight: FontWeight.bold,
+              ),
+            ),
+          ),
+        );
+      }),
+    );
+  }
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       backgroundColor: Colors.white,
-      body: Center(
-        child: Text(
-          'Alarm Feature Coming Soon',
-          style: TextStyle(fontSize: 20),
-        ),
+      floatingActionButton: FloatingActionButton(
+        onPressed: _showAddAlarmDialog,
+        child: Icon(Icons.add),
+        tooltip: 'Add Alarm',
       ),
+      body: _alarms.isEmpty
+          ? Center(
+              child: Text(
+                'No alarms set',
+                style: TextStyle(fontSize: 18.sp, color: Colors.grey),
+              ),
+            )
+          : ListView.builder(
+              itemCount: _alarms.length,
+              itemBuilder: (context, index) {
+                final alarm = _alarms[index];
+                return Card(
+                  margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
+                  child: ListTile(
+                    title: Text(
+                      alarm.timeString,
+                      style: TextStyle(
+                        fontSize: 24.sp,
+                        fontWeight: FontWeight.bold,
+                        color: alarm.isActive ? Colors.black : Colors.grey,
+                      ),
+                    ),
+                    subtitle: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        Text(
+                          alarm.repeatDaysString,
+                          style: TextStyle(
+                            fontSize: 14.sp,
+                            color: alarm.isActive ? Colors.black87 : Colors.grey,
+                          ),
+                        ),
+                        Text(
+                          alarm.label,
+                          style: TextStyle(
+                            fontSize: 16.sp,
+                            fontWeight: FontWeight.w500,
+                            color: alarm.isActive ? Colors.black87 : Colors.grey,
+                          ),
+                        ),
+                      ],
+                    ),
+                    trailing: Row(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [
+                        Switch(
+                          value: alarm.isActive,
+                          onChanged: (_) => _toggleAlarm(alarm.id),
+                        ),
+                        IconButton(
+                          icon: Icon(Icons.delete, color: Colors.red),
+                          onPressed: () => _deleteAlarm(alarm.id),
+                        ),
+                      ],
+                    ),
+                  ),
+                );
+              },
+            ),
     );
   }
 }

+ 173 - 5
lib/pages/stopwatch_page.dart

@@ -1,4 +1,6 @@
+import 'dart:async';
 import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
 
 class StopwatchPage extends StatefulWidget {
   @override
@@ -6,16 +8,182 @@ class StopwatchPage extends StatefulWidget {
 }
 
 class _StopwatchPageState extends State<StopwatchPage> {
+  bool _isRunning = false;
+  Stopwatch _stopwatch = Stopwatch();
+  Timer? _timer;
+  List<String> _laps = [];
+  
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    _timer?.cancel();
+    super.dispose();
+  }
+
+  void _startStop() {
+    setState(() {
+      if (_isRunning) {
+        _isRunning = false;
+        _timer?.cancel();
+        _stopwatch.stop();
+      } else {
+        _isRunning = true;
+        _stopwatch.start();
+        _timer = Timer.periodic(Duration(milliseconds: 10), (timer) {
+          setState(() {});
+        });
+      }
+    });
+  }
+
+  void _resetStopwatch() {
+    setState(() {
+      _stopwatch.reset();
+      _laps.clear();
+    });
+  }
+
+  void _addLap() {
+    if (_isRunning) {
+      setState(() {
+        _laps.insert(0, _formatTime(_stopwatch.elapsedMilliseconds));
+      });
+    }
+  }
+
+  String _formatTime(int milliseconds) {
+    int hundreds = (milliseconds / 10).truncate() % 100;
+    int seconds = (milliseconds / 1000).truncate() % 60;
+    int minutes = (milliseconds / 60000).truncate() % 60;
+    int hours = (milliseconds / 3600000).truncate();
+
+    String hoursStr = hours > 0 ? '${hours.toString().padLeft(2, '0')}:' : '';
+    String minutesStr = minutes.toString().padLeft(2, '0');
+    String secondsStr = seconds.toString().padLeft(2, '0');
+    String hundredsStr = hundreds.toString().padLeft(2, '0');
+
+    return '$hoursStr$minutesStr:$secondsStr.$hundredsStr';
+  }
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
       backgroundColor: Colors.white,
-      body: Center(
-        child: Text(
-          'Stopwatch Feature Coming Soon',
-          style: TextStyle(fontSize: 20),
-        ),
+      body: Column(
+        children: [
+          SizedBox(height: 50.h),
+          // Stopwatch display
+          Text(
+            _formatTime(_stopwatch.elapsedMilliseconds),
+            style: TextStyle(
+              fontSize: 60.sp,
+              fontWeight: FontWeight.bold,
+              fontFamily: 'monospace',
+            ),
+          ),
+          SizedBox(height: 30.h),
+          // Controls
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+            children: [
+              _buildControlButton(
+                icon: _isRunning ? Icons.pause : Icons.play_arrow,
+                label: _isRunning ? 'Stop' : 'Start',
+                onPressed: _startStop,
+                color: _isRunning ? Colors.red : Colors.green,
+              ),
+              _buildControlButton(
+                icon: Icons.refresh,
+                label: 'Reset',
+                onPressed: _stopwatch.elapsedMilliseconds > 0 ? _resetStopwatch : null,
+                color: Colors.blue,
+              ),
+              _buildControlButton(
+                icon: Icons.flag,
+                label: 'Lap',
+                onPressed: _isRunning ? _addLap : null,
+                color: Colors.orange,
+              ),
+            ],
+          ),
+          SizedBox(height: 20.h),
+          // Laps
+          Expanded(
+            child: _laps.isEmpty
+                ? Center(
+                    child: Text(
+                      'No laps recorded',
+                      style: TextStyle(
+                        fontSize: 16.sp,
+                        color: Colors.grey,
+                      ),
+                    ),
+                  )
+                : ListView.builder(
+                    itemCount: _laps.length,
+                    itemBuilder: (context, index) {
+                      return ListTile(
+                        dense: true,
+                        leading: CircleAvatar(
+                          radius: 14.r,
+                          backgroundColor: Colors.blue[100],
+                          child: Text(
+                            '${_laps.length - index}',
+                            style: TextStyle(
+                              fontSize: 12.sp,
+                              color: Colors.black87,
+                            ),
+                          ),
+                        ),
+                        title: Text(
+                          'Lap ${_laps.length - index}',
+                          style: TextStyle(
+                            fontSize: 16.sp,
+                          ),
+                        ),
+                        trailing: Text(
+                          _laps[index],
+                          style: TextStyle(
+                            fontSize: 16.sp,
+                            fontFamily: 'monospace',
+                            fontWeight: FontWeight.bold,
+                          ),
+                        ),
+                      );
+                    },
+                  ),
+          ),
+        ],
       ),
     );
   }
+
+  Widget _buildControlButton({
+    required IconData icon,
+    required String label,
+    required VoidCallback? onPressed,
+    required Color color,
+  }) {
+    return Column(
+      children: [
+        FloatingActionButton(
+          onPressed: onPressed,
+          backgroundColor: onPressed == null ? Colors.grey[300] : color,
+          child: Icon(icon, color: Colors.white),
+        ),
+        SizedBox(height: 8.h),
+        Text(
+          label,
+          style: TextStyle(
+            fontSize: 14.sp,
+            color: onPressed == null ? Colors.grey : Colors.black87,
+          ),
+        ),
+      ],
+    );
+  }
 }