heavyrain 4 months ago
parent
commit
4805ba4ea0

+ 52 - 9
README.md

@@ -1,16 +1,59 @@
-# flutter_clock
+# Flutter Clock App
 
-A new Flutter application.
+A Flutter clock app with timer functionality. This app features:
+
+1. Clean, modern UI
+2. Timer with customizable duration
+3. Countdown timer with visual progress indicator
+4. Timer settings including sound, volume, vibration and loop options
+5. Screen wake lock feature to prevent screen from turning off during countdown
+
+## Features
+
+- **Timer Setup**: Set hours, minutes, and seconds using smooth wheel pickers
+- **Countdown View**: Visual circular progress indicator showing remaining time
+- **Settings**:
+  - Choose from different alarm sounds
+  - Adjust volume
+  - Enable/disable vibration
+  - Enable/disable loop timer functionality
+- **Screen Wake Lock**: Keep the screen on during countdown
 
 ## Getting Started
 
-This project is a starting point for a Flutter application.
+### Prerequisites
+
+- Flutter SDK 2.19.2 or higher
+- Dart SDK 2.19.0 or higher
+
+### Installation
+
+1. Clone the repository
+2. Navigate to the project directory
+3. Run the following command to get dependencies:
+
+```bash
+flutter pub get
+```
+
+4. Run the app:
+
+```bash
+flutter run
+```
+
+## Usage
 
-A few resources to get you started if this is your first Flutter project:
+1. Set the timer duration using the wheel pickers
+2. Press the center play button to start the timer
+3. Press the left button to toggle screen wake lock
+4. Press the right button to access settings
+5. In the settings, you can:
+   - Choose a sound
+   - Adjust volume
+   - Toggle vibration
+   - Toggle loop timer function
 
-- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
+## License
 
-For help getting started with Flutter, view our
-[online documentation](https://flutter.dev/docs), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
+This project is open source.

+ 0 - 0
assets/sounds/alarm.mp3


+ 0 - 0
assets/sounds/bell.mp3


+ 0 - 0
assets/sounds/dripping.mp3


+ 1 - 0
lib/dao/user_dao.dart

@@ -1,5 +1,6 @@
 import 'package:flutter_clock/model/user_model.dart';
 
+
 class UserDao {
   /// 登录
   Future<UserModel?> login(String username, String password) async {

+ 6 - 2
lib/main.dart

@@ -25,9 +25,13 @@ class MyApp extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return MaterialApp(
-      title: 'xkcd clock',
+      title: 'Flutter Clock',
       debugShowCheckedModeBanner: false,
-      theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'xkcd script'),
+      theme: ThemeData(
+        primarySwatch: Colors.blue,
+        visualDensity: VisualDensity.adaptivePlatformDensity,
+        fontFamily: 'Roboto',
+      ),
       home: MainPage(),
     );
   }

+ 22 - 0
lib/model/clock_model.dart

@@ -2,4 +2,26 @@ class ClockModel {
   String clockName;
   String location;
   DateTime dateTime;
+
+  ClockModel({
+    required this.clockName,
+    required this.location,
+    required this.dateTime,
+  });
+
+  factory ClockModel.fromJson(Map<String, dynamic> json) {
+    return ClockModel(
+      clockName: json['clockName'],
+      location: json['location'],
+      dateTime: json['dateTime'],
+    );
+  }
+
+  Map<String, dynamic> toJson() {
+    return {
+      'clockName': clockName,
+      'location': location,
+      'dateTime': dateTime,
+    };
+  }
 }

+ 40 - 0
lib/model/timer_settings.dart

@@ -0,0 +1,40 @@
+import 'package:shared_preferences/shared_preferences.dart';
+
+class TimerSettings {
+  static const String _soundKey = 'timer_sound';
+  static const String _volumeKey = 'timer_volume';
+  static const String _vibrateKey = 'timer_vibrate';
+  static const String _loopKey = 'timer_loop';
+
+  String sound;
+  double volume;
+  bool vibrate;
+  bool loop;
+
+  TimerSettings({
+    this.sound = 'Dripping',
+    this.volume = 0.7,
+    this.vibrate = true,
+    this.loop = true,
+  });
+
+  // Save settings to SharedPreferences
+  Future<void> saveSettings() async {
+    final prefs = await SharedPreferences.getInstance();
+    await prefs.setString(_soundKey, sound);
+    await prefs.setDouble(_volumeKey, volume);
+    await prefs.setBool(_vibrateKey, vibrate);
+    await prefs.setBool(_loopKey, loop);
+  }
+
+  // Load settings from SharedPreferences
+  static Future<TimerSettings> loadSettings() async {
+    final prefs = await SharedPreferences.getInstance();
+    return TimerSettings(
+      sound: prefs.getString(_soundKey) ?? 'Dripping',
+      volume: prefs.getDouble(_volumeKey) ?? 0.7,
+      vibrate: prefs.getBool(_vibrateKey) ?? true,
+      loop: prefs.getBool(_loopKey) ?? true,
+    );
+  }
+} 

+ 24 - 0
lib/model/user_model.dart

@@ -2,4 +2,28 @@ class UserModel {
   String userid;
   String password;
   String logo;
+
+  UserModel({
+    required this.userid,
+    required this.password,
+    required this.logo,
+  }); 
+
+  factory UserModel.fromJson(Map<String, dynamic> json) {
+    return UserModel(
+      userid: json['userid'],
+      password: json['password'],
+      logo: json['logo'],
+    );
+  }
+
+  Map<String, dynamic> toJson() {
+    return {
+      'userid': userid,
+      'password': password,
+      'logo': logo,
+    };
+  }
+  
+  
 }

+ 21 - 0
lib/pages/alarm_page.dart

@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+
+class AlarmPage extends StatefulWidget {
+  @override
+  State<AlarmPage> createState() => _AlarmPageState();
+}
+
+class _AlarmPageState extends State<AlarmPage> {
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      backgroundColor: Colors.white,
+      body: Center(
+        child: Text(
+          'Alarm Feature Coming Soon',
+          style: TextStyle(fontSize: 20),
+        ),
+      ),
+    );
+  }
+} 

+ 91 - 0
lib/pages/clock_page.dart

@@ -0,0 +1,91 @@
+import 'dart:async';
+import 'dart:math' as math;
+
+import 'package:flutter/material.dart';
+import 'package:flutter_clock/model/config.dart';
+import 'package:intl/intl.dart';
+
+class ClockPage extends StatefulWidget {
+  @override
+  State<StatefulWidget> createState() {
+    return _ClockPageState();
+  }
+}
+
+class _ClockPageState extends State<ClockPage> {
+  final _utcMidnightRadiansOffset = radiansFromDegrees(-64);
+  static const _secondsInDay = 86400;
+  DateTime _localTime = DateTime.now();
+
+  @override
+  void initState() {
+    super.initState();
+    Timer.periodic(Duration(seconds: 1),
+        (_) => setState(() => _localTime = DateTime.now()));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+        backgroundColor: Colors.white,
+        body: SingleChildScrollView(
+          child: Center(
+            child: Column(
+              children: [
+                SizedBox(height: 20),
+                Stack(
+                  children: [
+                    Image.asset(
+                      'assets/face.png',
+                      width: 350,
+                    ),
+                    Transform.rotate(
+                      angle: -(radiansFromTime(_localTime.toUtc()) +
+                          _utcMidnightRadiansOffset),
+                      child: ClipOval(
+                        clipper: InnerFaceClipper(),
+                        child: Image.asset(
+                          'assets/face.png',
+                          width: 350,
+                        ),
+                      ),
+                    ),
+                  ],
+                ),
+                SizedBox(height: 20),
+                Text(DateFormat.EEEE().format(_localTime),
+                    style: TextStyle(fontSize: 48)),
+                Text(DateFormat.yMMMMd().format(_localTime),
+                    style: TextStyle(fontSize: 20)),
+                Text(DateFormat.jms().format(_localTime),
+                    style: TextStyle(fontSize: 42)),
+              ],
+            ),
+          ),
+        ));
+  }
+
+  static double radiansFromDegrees(double degrees) => degrees * math.pi / 180;
+
+  static double radiansFromTime(DateTime time) {
+    final midnightToday = DateTime(time.year, time.month, time.day);
+    final secondsSinceMidnight = midnightToday.difference(time).inSeconds;
+    final percent = secondsSinceMidnight / _secondsInDay;
+    final degrees = percent * 360;
+    return radiansFromDegrees(degrees);
+  }
+}
+
+class InnerFaceClipper extends CustomClipper<Rect> {
+  final percent = 0.85;
+
+  @override
+  Rect getClip(Size size) => Rect.fromCenter(
+        center: size.center(Offset(0, 0)),
+        width: size.width * percent,
+        height: size.height * percent,
+      );
+
+  @override
+  bool shouldReclip(CustomClipper oldClipper) => true;
+} 

+ 48 - 51
lib/pages/main_page.dart

@@ -4,6 +4,10 @@ import 'dart:math' as math;
 import 'package:flutter/material.dart';
 import 'package:flutter_clock/model/config.dart';
 import 'package:intl/intl.dart';
+import 'package:flutter_clock/pages/alarm_page.dart';
+import 'package:flutter_clock/pages/clock_page.dart';
+import 'package:flutter_clock/pages/stopwatch_page.dart';
+import 'package:flutter_clock/pages/timer_page.dart';
 
 class MainPage extends StatefulWidget {
   @override
@@ -12,7 +16,15 @@ class MainPage extends StatefulWidget {
   }
 }
 
-class _MainPageState extends State<MainPage> {
+class _MainPageState extends State<MainPage> with SingleTickerProviderStateMixin {
+  late TabController _tabController;
+  final List<Widget> _tabs = [
+    Tab(child: Text('Alarm', style: TextStyle(fontSize: 16))),
+    Tab(child: Text('Clock', style: TextStyle(fontSize: 16))),
+    Tab(child: Text('Stopwatch', style: TextStyle(fontSize: 16))),
+    Tab(child: Text('Timer', style: TextStyle(fontSize: 16))),
+  ];
+
   final _utcMidnightRadiansOffset = radiansFromDegrees(-64);
   static const _secondsInDay = 86400;
   DateTime _localTime = DateTime.now();
@@ -20,65 +32,50 @@ class _MainPageState extends State<MainPage> {
   @override
   void initState() {
     super.initState();
+    _tabController = TabController(length: 4, vsync: this, initialIndex: 3); // Timer tab is selected by default
     Timer.periodic(Duration(seconds: 1),
         (_) => setState(() => _localTime = DateTime.now()));
   }
 
+  @override
+  void dispose() {
+    _tabController.dispose();
+    super.dispose();
+  }
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
+      appBar: AppBar(
         backgroundColor: Colors.white,
-        body: SingleChildScrollView(
-          child: Center(
-            child: Column(
-              children: [
-                Text(
-                  "Clock",
-                  style: TextStyle(
-                    fontSize: 30,
-                    color: Colors.red,
-                  ),
-                ),
-                Text(
-                  "author: 天问",
-                  style: TextStyle(fontFamily: Config.defaultFontFamily),
-                ),
-                Text(
-                  "contact: liuyuqi.gov@msn.cn",
-                  style: TextStyle(fontFamily: Config.defaultFontFamily),
-                ),
-                SizedBox(
-                  height: 5,
-                ),
-                Stack(
-                  children: [
-                    Image.asset(
-                      'assets/face.png',
-                      width: 350,
-                    ),
-                    Transform.rotate(
-                      angle: -(radiansFromTime(_localTime.toUtc()) +
-                          _utcMidnightRadiansOffset),
-                      child: ClipOval(
-                        clipper: InnerFaceClipper(),
-                        child: Image.asset(
-                          'assets/face.png',
-                          width: 350,
-                        ),
-                      ),
-                    ),
-                  ],
-                ),
-                Text(DateFormat.EEEE().format(_localTime),
-                    style: TextStyle(fontSize: 48)),
-                Text(DateFormat.yMMMMd().format(_localTime),
-                    style: TextStyle(fontSize: 20)),
-                Text(DateFormat.jms().format(_localTime),
-                    style: TextStyle(fontSize: 42)),
-              ],
-            ),
+        elevation: 0,
+        title: TabBar(
+          controller: _tabController,
+          tabs: _tabs,
+          labelColor: Colors.blue,
+          unselectedLabelColor: Colors.black,
+          indicatorColor: Colors.blue,
+          indicatorWeight: 3,
+        ),
+        actions: [
+          IconButton(
+            icon: Icon(Icons.more_vert, color: Colors.black),
+            onPressed: () {
+              // More options menu
+            },
           ),
-        ));
+        ],
+      ),
+      body: TabBarView(
+        controller: _tabController,
+        children: [
+          AlarmPage(),
+          ClockPage(),
+          StopwatchPage(),
+          TimerPage(),
+        ],
+      ),
+    );
   }
 
   static double radiansFromDegrees(double degrees) => degrees * math.pi / 180;

+ 21 - 0
lib/pages/stopwatch_page.dart

@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+
+class StopwatchPage extends StatefulWidget {
+  @override
+  State<StopwatchPage> createState() => _StopwatchPageState();
+}
+
+class _StopwatchPageState extends State<StopwatchPage> {
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      backgroundColor: Colors.white,
+      body: Center(
+        child: Text(
+          'Stopwatch Feature Coming Soon',
+          style: TextStyle(fontSize: 20),
+        ),
+      ),
+    );
+  }
+} 

+ 424 - 0
lib/pages/timer_page.dart

@@ -0,0 +1,424 @@
+import 'dart:async';
+import 'package:flutter/material.dart';
+import 'package:flutter_clock/model/timer_settings.dart';
+import 'package:flutter_clock/pages/timer_settings_page.dart';
+import 'package:flutter_clock/utils/audio_manager.dart';
+import 'package:flutter_clock/utils/screen_manager.dart';
+
+class TimerPage extends StatefulWidget {
+  @override
+  _TimerPageState createState() => _TimerPageState();
+}
+
+class _TimerPageState extends State<TimerPage> with WidgetsBindingObserver {
+  // Timer duration values
+  int _hours = 0;
+  int _minutes = 10;
+  int _seconds = 0;
+
+  // For timer controller
+  Timer? _timer;
+  bool _isRunning = false;
+  bool _isCompleted = false;
+  int _remainingSeconds = 0;
+
+  // Settings
+  late TimerSettings _settings;
+  final AudioManager _audioManager = AudioManager();
+
+  // Wheel controllers
+  final FixedExtentScrollController _hoursController =
+      FixedExtentScrollController(initialItem: 0);
+  final FixedExtentScrollController _minutesController =
+      FixedExtentScrollController(initialItem: 10);
+  final FixedExtentScrollController _secondsController =
+      FixedExtentScrollController(initialItem: 0);
+
+  @override
+  void initState() {
+    super.initState();
+    WidgetsBinding.instance.addObserver(this);
+    _loadSettings();
+  }
+
+  @override
+  void dispose() {
+    _timer?.cancel();
+    _audioManager.dispose();
+    if (ScreenManager.isWakeLockEnabled) {
+      ScreenManager.disableWakeLock();
+    }
+    WidgetsBinding.instance.removeObserver(this);
+    _hoursController.dispose();
+    _minutesController.dispose();
+    _secondsController.dispose();
+    super.dispose();
+  }
+
+  @override
+  void didChangeAppLifecycleState(AppLifecycleState state) {
+    if (state == AppLifecycleState.resumed && _isRunning) {
+      _syncTimer();
+    }
+  }
+
+  Future<void> _loadSettings() async {
+    _settings = await TimerSettings.loadSettings();
+    setState(() {});
+  }
+
+  void _startTimer() {
+    final totalSeconds = _hours * 3600 + _minutes * 60 + _seconds;
+    if (totalSeconds <= 0) return;
+
+    setState(() {
+      _isRunning = true;
+      _isCompleted = false;
+      _remainingSeconds = totalSeconds;
+    });
+
+    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
+      setState(() {
+        if (_remainingSeconds > 0) {
+          _remainingSeconds--;
+        } else {
+          _isCompleted = true;
+          _timerCompleted();
+        }
+      });
+    });
+  }
+
+  void _pauseTimer() {
+    _timer?.cancel();
+    setState(() {
+      _isRunning = false;
+    });
+  }
+
+  void _resetTimer() {
+    _timer?.cancel();
+    _audioManager.stopSound();
+    _audioManager.stopVibration();
+    setState(() {
+      _isRunning = false;
+      _isCompleted = false;
+    });
+  }
+
+  void _syncTimer() {
+    // Recalculate elapsed time if app was in background
+  }
+
+  Future<void> _timerCompleted() async {
+    _timer?.cancel();
+    _isRunning = false;
+
+    // Play sound and vibrate
+    if (_settings.vibrate) {
+      _audioManager.triggerVibration();
+    }
+    _audioManager.playSound(_settings.sound, _settings.volume, _settings.loop);
+
+    // If loop is enabled, restart the timer
+    if (_settings.loop) {
+      Future.delayed(Duration(seconds: 2), () {
+        _audioManager.stopSound();
+        _audioManager.stopVibration();
+        _startTimer();
+      });
+    }
+  }
+
+  String _formatTime(int seconds) {
+    final hours = seconds ~/ 3600;
+    final minutes = (seconds % 3600) ~/ 60;
+    final secs = seconds % 60;
+    return '${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (_isRunning || _isCompleted) {
+      return _buildCountdownView();
+    } else {
+      return _buildTimerSetupView();
+    }
+  }
+
+  Widget _buildTimerSetupView() {
+    return Scaffold(
+      backgroundColor: Colors.white,
+      body: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          Expanded(
+            child: Row(
+              children: [
+                Expanded(
+                  child: _buildTimerWheel(
+                    _hoursController,
+                    List.generate(24, (index) => index),
+                    (value) {
+                      setState(() {
+                        _hours = value;
+                      });
+                    },
+                    'H',
+                  ),
+                ),
+                Expanded(
+                  child: _buildTimerWheel(
+                    _minutesController,
+                    List.generate(60, (index) => index),
+                    (value) {
+                      setState(() {
+                        _minutes = value;
+                      });
+                    },
+                    'M',
+                  ),
+                ),
+                Expanded(
+                  child: _buildTimerWheel(
+                    _secondsController,
+                    List.generate(60, (index) => index),
+                    (value) {
+                      setState(() {
+                        _seconds = value;
+                      });
+                    },
+                    'S',
+                  ),
+                ),
+              ],
+            ),
+          ),
+          SizedBox(height: 20),
+          Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceAround,
+              children: [
+                _buildCircleButton(
+                  Icons.phone_android,
+                  Colors.grey[600]!,
+                  () {
+                    ScreenManager.toggleWakeLock();
+                    setState(() {});
+                  },
+                  isActive: ScreenManager.isWakeLockEnabled,
+                ),
+                _buildCircleButton(
+                  Icons.play_arrow,
+                  Colors.blue,
+                  () => _startTimer(),
+                ),
+                _buildCircleButton(
+                  Icons.settings,
+                  Colors.grey[600]!,
+                  () async {
+                    final result = await Navigator.push(
+                      context,
+                      MaterialPageRoute(
+                          builder: (context) =>
+                              TimerSettingsPage(settings: _settings)),
+                    );
+                    if (result != null) {
+                      setState(() {
+                        _settings = result;
+                      });
+                    }
+                  },
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildCountdownView() {
+    final totalMinutes = _remainingSeconds ~/ 60;
+    return Scaffold(
+      backgroundColor: Colors.white,
+      body: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          Expanded(
+            child: Center(
+              child: Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                children: [
+                  Container(
+                    width: 300,
+                    height: 300,
+                    decoration: BoxDecoration(
+                      shape: BoxShape.circle,
+                      border: Border.all(
+                        color: Colors.blue.withOpacity(0.3),
+                        width: 1,
+                      ),
+                    ),
+                    child: Stack(
+                      alignment: Alignment.center,
+                      children: [
+                        // Timer progress
+                        SizedBox(
+                          width: 300,
+                          height: 300,
+                          child: CircularProgressIndicator(
+                            value: _isCompleted
+                                ? 1
+                                : _remainingSeconds /
+                                    (_hours * 3600 + _minutes * 60 + _seconds),
+                            strokeWidth: 1,
+                            backgroundColor: Colors.grey.withOpacity(0.1),
+                            color: Colors.blue,
+                          ),
+                        ),
+                        // Time display
+                        Column(
+                          mainAxisAlignment: MainAxisAlignment.center,
+                          children: [
+                            Text(
+                              _formatTime(_remainingSeconds),
+                              style: TextStyle(
+                                  fontSize: 40, fontWeight: FontWeight.bold),
+                            ),
+                            Text(
+                              'Total ${totalMinutes} minutes',
+                              style:
+                                  TextStyle(fontSize: 16, color: Colors.grey),
+                            ),
+                          ],
+                        ),
+                        // Indicator dot
+                        Positioned(
+                          bottom: 0,
+                          child: Container(
+                            width: 10,
+                            height: 10,
+                            decoration: BoxDecoration(
+                              color: Colors.blue,
+                              shape: BoxShape.circle,
+                            ),
+                          ),
+                        ),
+                      ],
+                    ),
+                  ),
+                ],
+              ),
+            ),
+          ),
+          Padding(
+            padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20),
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceAround,
+              children: [
+                _buildCircleButton(
+                  Icons.stop,
+                  Colors.red,
+                  () => _resetTimer(),
+                ),
+                _isRunning
+                    ? _buildCircleButton(
+                        Icons.pause,
+                        Colors.blue,
+                        () => _pauseTimer(),
+                      )
+                    : _buildCircleButton(
+                        Icons.play_arrow,
+                        Colors.blue,
+                        () => _startTimer(),
+                      ),
+                _buildCircleButton(
+                  Icons.settings,
+                  Colors.grey[600]!,
+                  () async {
+                    final result = await Navigator.push(
+                      context,
+                      MaterialPageRoute(
+                          builder: (context) =>
+                              TimerSettingsPage(settings: _settings)),
+                    );
+                    if (result != null) {
+                      setState(() {
+                        _settings = result;
+                      });
+                    }
+                  },
+                ),
+              ],
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildCircleButton(IconData icon, Color color, VoidCallback onPressed,
+      {bool isActive = false}) {
+    return Container(
+      width: 70,
+      height: 70,
+      decoration: BoxDecoration(
+        shape: BoxShape.circle,
+        color: isActive ? color : Colors.white,
+        boxShadow: [
+          BoxShadow(
+            color: Colors.black.withOpacity(0.1),
+            blurRadius: 8,
+            offset: Offset(0, 2),
+          ),
+        ],
+      ),
+      child: IconButton(
+        icon: Icon(icon, size: 30),
+        color: isActive ? Colors.white : color,
+        onPressed: onPressed,
+      ),
+    );
+  }
+
+  Widget _buildTimerWheel(
+    FixedExtentScrollController controller,
+    List<int> items,
+    ValueChanged<int> onChanged,
+    String unit,
+  ) {
+    return Column(
+      children: [
+        Expanded(
+          child: ListWheelScrollView(
+            controller: controller,
+            physics: FixedExtentScrollPhysics(),
+            diameterRatio: 1.5,
+            itemExtent: 50,
+            children: items.map((value) {
+              return Center(
+                child: Text(
+                  value.toString().padLeft(2, '0'),
+                  style: TextStyle(
+                    fontSize: 30,
+                    color: Colors.black,
+                  ),
+                ),
+              );
+            }).toList(),
+            onSelectedItemChanged: onChanged,
+          ),
+        ),
+        Text(
+          unit,
+          style: TextStyle(
+            fontSize: 18,
+            fontWeight: FontWeight.bold,
+          ),
+        ),
+      ],
+    );
+  }
+}

+ 222 - 0
lib/pages/timer_settings_page.dart

@@ -0,0 +1,222 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_clock/model/timer_settings.dart';
+
+class TimerSettingsPage extends StatefulWidget {
+  final TimerSettings settings;
+
+  const TimerSettingsPage({Key? key, required this.settings}) : super(key: key);
+
+  @override
+  _TimerSettingsPageState createState() => _TimerSettingsPageState();
+}
+
+class _TimerSettingsPageState extends State<TimerSettingsPage> {
+  late String _sound;
+  late double _volume;
+  late bool _vibrate;
+  late bool _loop;
+
+  final List<String> _availableSounds = ['Dripping', 'Alarm', 'Bell'];
+
+  @override
+  void initState() {
+    super.initState();
+    _sound = widget.settings.sound;
+    _volume = widget.settings.volume;
+    _vibrate = widget.settings.vibrate;
+    _loop = widget.settings.loop;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      backgroundColor: Colors.white,
+      appBar: AppBar(
+        backgroundColor: Colors.white,
+        elevation: 0,
+        leading: IconButton(
+          icon: Icon(Icons.close, color: Colors.black),
+          onPressed: () => Navigator.pop(context),
+        ),
+        title: Text(
+          'Settings',
+          style: TextStyle(color: Colors.black, fontSize: 24),
+        ),
+        centerTitle: true,
+      ),
+      body: ListView(
+        children: [
+          _buildSoundSetting(),
+          _buildVolumeSetting(),
+          _buildVibrateSetting(),
+          _buildLoopSetting(),
+        ],
+      ),
+    );
+  }
+
+  Widget _buildSoundSetting() {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Padding(
+          padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              Text(
+                'Sound',
+                style: TextStyle(fontSize: 18),
+              ),
+              GestureDetector(
+                onTap: () {
+                  _showSoundPicker();
+                },
+                child: Row(
+                  children: [
+                    Text(
+                      _sound,
+                      style: TextStyle(fontSize: 16, color: Colors.grey[700]),
+                    ),
+                    Icon(
+                      Icons.chevron_right,
+                      color: Colors.grey[700],
+                    ),
+                  ],
+                ),
+              ),
+            ],
+          ),
+        ),
+        Divider(),
+      ],
+    );
+  }
+
+  Widget _buildVolumeSetting() {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Padding(
+          padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
+          child: Text(
+            'Vol.',
+            style: TextStyle(fontSize: 18),
+          ),
+        ),
+        Slider(
+          value: _volume,
+          min: 0.0,
+          max: 1.0,
+          activeColor: Colors.orange,
+          inactiveColor: Colors.grey[300],
+          onChanged: (value) {
+            setState(() {
+              _volume = value;
+            });
+          },
+        ),
+        Divider(),
+      ],
+    );
+  }
+
+  Widget _buildVibrateSetting() {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Padding(
+          padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              Text(
+                'Vibrate on ring',
+                style: TextStyle(fontSize: 18),
+              ),
+              Switch(
+                value: _vibrate,
+                activeColor: Colors.orange,
+                onChanged: (value) {
+                  setState(() {
+                    _vibrate = value;
+                  });
+                },
+              ),
+            ],
+          ),
+        ),
+        Divider(),
+      ],
+    );
+  }
+
+  Widget _buildLoopSetting() {
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Padding(
+          padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              Text(
+                'Loop timer',
+                style: TextStyle(fontSize: 18),
+              ),
+              Switch(
+                value: _loop,
+                activeColor: Colors.orange,
+                onChanged: (value) {
+                  setState(() {
+                    _loop = value;
+                  });
+                },
+              ),
+            ],
+          ),
+        ),
+        Divider(),
+      ],
+    );
+  }
+
+  void _showSoundPicker() {
+    showModalBottomSheet(
+      context: context,
+      builder: (context) {
+        return Column(
+          mainAxisSize: MainAxisSize.min,
+          children: _availableSounds.map((sound) {
+            return ListTile(
+              title: Text(sound),
+              onTap: () {
+                setState(() {
+                  _sound = sound;
+                });
+                Navigator.pop(context);
+              },
+              trailing: _sound == sound
+                  ? Icon(Icons.check, color: Colors.blue)
+                  : null,
+            );
+          }).toList(),
+        );
+      },
+    );
+  }
+
+  @override
+  void dispose() {
+    // Save settings before exiting
+    final updatedSettings = TimerSettings(
+      sound: _sound,
+      volume: _volume,
+      vibrate: _vibrate,
+      loop: _loop,
+    );
+    updatedSettings.saveSettings();
+    Navigator.pop(context, updatedSettings);
+    super.dispose();
+  }
+}

+ 65 - 0
lib/utils/audio_manager.dart

@@ -0,0 +1,65 @@
+import 'package:audioplayers/audioplayers.dart';
+import 'package:vibration/vibration.dart';
+
+class AudioManager {
+  static final AudioManager _instance = AudioManager._internal();
+  final AudioPlayer _audioPlayer = AudioPlayer();
+  bool _isPlaying = false;
+  
+  factory AudioManager() {
+    return _instance;
+  }
+  
+  AudioManager._internal();
+  
+  Future<void> playSound(String sound, double volume, bool loop) async {
+    if (_isPlaying) {
+      await stopSound();
+    }
+    
+    // For this example, we're using dummy sound mapping
+    // In a real app, you would have actual sound files
+    String soundAsset;
+    switch (sound) {
+      case 'Dripping':
+        soundAsset = 'assets/sounds/dripping.mp3';
+        break;
+      case 'Alarm':
+        soundAsset = 'assets/sounds/alarm.mp3';
+        break;
+      case 'Bell':
+        soundAsset = 'assets/sounds/bell.mp3';
+        break;
+      default:
+        soundAsset = 'assets/sounds/dripping.mp3';
+    }
+    
+    await _audioPlayer.setVolume(volume);
+    await _audioPlayer.setReleaseMode(loop ? ReleaseMode.loop : ReleaseMode.release);
+    await _audioPlayer.play(AssetSource(soundAsset));
+    _isPlaying = true;
+  }
+  
+  Future<void> stopSound() async {
+    await _audioPlayer.stop();
+    _isPlaying = false;
+  }
+  
+  Future<void> triggerVibration() async {
+    if (await Vibration.hasVibrator() ?? false) {
+      Vibration.vibrate(pattern: [500, 1000, 500, 1000], repeat: 1);
+    }
+  }
+  
+  Future<void> stopVibration() async {
+    if (await Vibration.hasVibrator() ?? false) {
+      Vibration.cancel();
+    }
+  }
+  
+  Future<void> dispose() async {
+    await stopSound();
+    await stopVibration();
+    await _audioPlayer.dispose();
+  }
+} 

+ 25 - 0
lib/utils/screen_manager.dart

@@ -0,0 +1,25 @@
+import 'package:wakelock/wakelock.dart';
+
+class ScreenManager {
+  static bool _wakeLockEnabled = false;
+  
+  static bool get isWakeLockEnabled => _wakeLockEnabled;
+  
+  static Future<void> enableWakeLock() async {
+    await Wakelock.enable();
+    _wakeLockEnabled = true;
+  }
+  
+  static Future<void> disableWakeLock() async {
+    await Wakelock.disable();
+    _wakeLockEnabled = false;
+  }
+  
+  static Future<void> toggleWakeLock() async {
+    if (_wakeLockEnabled) {
+      await disableWakeLock();
+    } else {
+      await enableWakeLock();
+    }
+  }
+} 

+ 4 - 1
lib/views/side_menu.dart

@@ -5,7 +5,7 @@ import 'package:flutter_clock/views/side_menu_item.dart';
 /// 左侧滑动菜单
 class SideMenu extends StatelessWidget {
   const SideMenu({
-    Key key,
+    Key? key,
   }) : super(key: key);
 
   @override
@@ -42,12 +42,14 @@ class SideMenu extends StatelessWidget {
                 title: "Sent",
                 iconSrc: "assets/Icons/Send.svg",
                 isActive: false,
+                itemCount: 0,
               ),
               SideMenuItem(
                 press: () {},
                 title: "Drafts",
                 iconSrc: "assets/Icons/File.svg",
                 isActive: false,
+                itemCount: 0,
               ),
               SideMenuItem(
                 press: () {},
@@ -55,6 +57,7 @@ class SideMenu extends StatelessWidget {
                 iconSrc: "assets/Icons/Trash.svg",
                 isActive: false,
                 showBorder: false,
+                itemCount: 0,
               ),
 
             ],

+ 7 - 7
lib/views/side_menu_item.dart

@@ -3,14 +3,14 @@ import 'package:flutter_clock/model/constants.dart';
 
 class SideMenuItem extends StatelessWidget {
   const SideMenuItem({
-    Key key,
-    this.isActive,
+    Key? key,
+    required this.isActive,
     this.isHover = false,
-    this.itemCount,
+    required this.itemCount,
     this.showBorder = true,
-    @required this.iconSrc,
-    @required this.title,
-    @required this.press,
+    required this.iconSrc,
+    required this.title,
+    required this.press,
   }) : super(key: key);
 
   final bool isActive, isHover, showBorder;
@@ -42,7 +42,7 @@ class SideMenuItem extends StatelessWidget {
                     SizedBox(width: kDefaultPadding * 0.75),
                     Text(
                       title,
-                      style: Theme.of(context).textTheme.button.copyWith(
+                      style: Theme.of(context).textTheme.button?.copyWith(
                             color:
                                 (isActive || isHover) ? kTextColor : kGrayColor,
                           ),

+ 4 - 0
linux/flutter/generated_plugin_registrant.cc

@@ -6,6 +6,10 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <audioplayers_linux/audioplayers_linux_plugin.h>
 
 void fl_register_plugins(FlPluginRegistry* registry) {
+  g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
+  audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
 }

+ 1 - 0
linux/flutter/generated_plugins.cmake

@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  audioplayers_linux
 )
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST

+ 327 - 1
pubspec.lock

@@ -9,6 +9,62 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.11.0"
+  audioplayers:
+    dependency: "direct main"
+    description:
+      name: audioplayers
+      sha256: c05c6147124cd63e725e861335a8b4d57300b80e6e92cea7c145c739223bbaef
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "5.2.1"
+  audioplayers_android:
+    dependency: transitive
+    description:
+      name: audioplayers_android
+      sha256: b00e1a0e11365d88576320ec2d8c192bc21f1afb6c0e5995d1c57ae63156acb5
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.3"
+  audioplayers_darwin:
+    dependency: transitive
+    description:
+      name: audioplayers_darwin
+      sha256: "3034e99a6df8d101da0f5082dcca0a2a99db62ab1d4ddb3277bed3f6f81afe08"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "5.0.2"
+  audioplayers_linux:
+    dependency: transitive
+    description:
+      name: audioplayers_linux
+      sha256: "60787e73fefc4d2e0b9c02c69885402177e818e4e27ef087074cf27c02246c9e"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.0"
+  audioplayers_platform_interface:
+    dependency: transitive
+    description:
+      name: audioplayers_platform_interface
+      sha256: "365c547f1bb9e77d94dd1687903a668d8f7ac3409e48e6e6a3668a1ac2982adb"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "6.1.0"
+  audioplayers_web:
+    dependency: transitive
+    description:
+      name: audioplayers_web
+      sha256: "22cd0173e54d92bd9b2c80b1204eb1eb159ece87475ab58c9788a70ec43c2a62"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.1.0"
+  audioplayers_windows:
+    dependency: transitive
+    description:
+      name: audioplayers_windows
+      sha256: "9536812c9103563644ada2ef45ae523806b0745f7a78e89d1b5fb1951de90e1a"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.0"
   boolean_selector:
     dependency: transitive
     description:
@@ -41,6 +97,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.17.2"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.3"
   cupertino_icons:
     dependency: "direct main"
     description:
@@ -57,6 +121,22 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.3.1"
+  ffi:
+    dependency: transitive
+    description:
+      name: ffi
+      sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "7.0.1"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -67,6 +147,27 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_web_plugins:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  http:
+    dependency: transitive
+    description:
+      name: http
+      sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.2"
   intl:
     dependency: "direct main"
     description:
@@ -75,6 +176,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "0.19.0"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.7"
   matcher:
     dependency: transitive
     description:
@@ -107,6 +216,126 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.8.3"
+  path_provider:
+    dependency: transitive
+    description:
+      name: path_provider
+      sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.3"
+  path_provider_android:
+    dependency: transitive
+    description:
+      name: path_provider_android
+      sha256: "51f0d2c554cfbc9d6a312ab35152fc77e2f0b758ce9f1a444a3a1e5b8f3c6b7f"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.3"
+  path_provider_foundation:
+    dependency: transitive
+    description:
+      name: path_provider_foundation
+      sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.3.2"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.1"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.2"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.1"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.4"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.8"
+  shared_preferences:
+    dependency: "direct main"
+    description:
+      name: shared_preferences
+      sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.3"
+  shared_preferences_android:
+    dependency: transitive
+    description:
+      name: shared_preferences_android
+      sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.1"
+  shared_preferences_foundation:
+    dependency: transitive
+    description:
+      name: shared_preferences_foundation
+      sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.3.5"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.3.2"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.3.2"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.1"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.3.2"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -120,6 +349,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.10.0"
+  sprintf:
+    dependency: transitive
+    description:
+      name: sprintf
+      sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "7.0.0"
   stack_trace:
     dependency: transitive
     description:
@@ -144,6 +381,14 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.2.0"
+  synchronized:
+    dependency: transitive
+    description:
+      name: synchronized
+      sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.0+1"
   term_glyph:
     dependency: transitive
     description:
@@ -160,6 +405,22 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "0.6.0"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.3.2"
+  uuid:
+    dependency: transitive
+    description:
+      name: uuid
+      sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.2.2"
   vector_math:
     dependency: transitive
     description:
@@ -168,6 +429,54 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.1.4"
+  vibration:
+    dependency: "direct main"
+    description:
+      name: vibration
+      sha256: "2938d4bf4ecfdb1cdac6b8f20f40cd3e7b7783edd6ca551a46d144a134473626"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.7.7"
+  wakelock:
+    dependency: "direct main"
+    description:
+      name: wakelock
+      sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.2"
+  wakelock_macos:
+    dependency: transitive
+    description:
+      name: wakelock_macos
+      sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.4.0"
+  wakelock_platform_interface:
+    dependency: transitive
+    description:
+      name: wakelock_platform_interface
+      sha256: "1f4aeb81fb592b863da83d2d0f7b8196067451e4df91046c26b54a403f9de621"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.3.0"
+  wakelock_web:
+    dependency: transitive
+    description:
+      name: wakelock_web
+      sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.4.0"
+  wakelock_windows:
+    dependency: transitive
+    description:
+      name: wakelock_windows
+      sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.1"
   web:
     dependency: transitive
     description:
@@ -176,5 +485,22 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "0.1.4-beta"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.4"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.4"
 sdks:
-  dart: ">=3.1.0-185.0.dev <4.0.0"
+  dart: ">=3.1.0 <4.0.0"
+  flutter: ">=3.13.0"

+ 5 - 0
pubspec.yaml

@@ -10,6 +10,10 @@ dependencies:
     sdk: flutter
   cupertino_icons: ^1.0.5
   intl: ^0.19.0
+  vibration: ^1.7.7
+  wakelock: ^0.6.2
+  audioplayers: ^5.2.1
+  shared_preferences: ^2.2.2
 
 dev_dependencies:
   flutter_test:
@@ -19,6 +23,7 @@ flutter:
   uses-material-design: true
   assets:
     - assets/face.png
+    - assets/sounds/
   fonts:
     - family: xkcd script
       fonts:

+ 3 - 0
windows/flutter/generated_plugin_registrant.cc

@@ -6,6 +6,9 @@
 
 #include "generated_plugin_registrant.h"
 
+#include <audioplayers_windows/audioplayers_windows_plugin.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
+  AudioplayersWindowsPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
 }

+ 1 - 0
windows/flutter/generated_plugins.cmake

@@ -3,6 +3,7 @@
 #
 
 list(APPEND FLUTTER_PLUGIN_LIST
+  audioplayers_windows
 )
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST