Initial commit: Minimal Flutter timer with push notifications

This commit is contained in:
Developer
2026-01-09 00:20:38 +08:00
commit 2a2f718a4b
26 changed files with 1050 additions and 0 deletions

156
lib/main.dart Normal file
View File

@@ -0,0 +1,156 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final notifications = FlutterLocalNotificationsPlugin();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const settings = InitializationSettings(android: androidSettings);
await notifications.initialize(settings);
await notifications.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?.requestNotificationsPermission();
runApp(const TimerApp());
}
class TimerApp extends StatelessWidget {
const TimerApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
theme: ThemeData(useMaterial3: true, colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
)),
home: const TimerScreen(),
);
}
class TimerScreen extends StatefulWidget {
const TimerScreen({super.key});
@override
State<TimerScreen> createState() => _TimerScreenState();
}
class _TimerScreenState extends State<TimerScreen> {
int duration = 60;
int remaining = 60;
Timer? timer;
bool isRunning = false;
void _showNotification() async {
const androidDetails = AndroidNotificationDetails(
'timer_channel',
'Таймер',
channelDescription: 'Уведомления таймера',
importance: Importance.high,
priority: Priority.high,
);
const details = NotificationDetails(android: androidDetails);
await notifications.show(0, 'Таймер завершён!', 'Время вышло.', details);
}
void _startTimer() {
setState(() => isRunning = true);
timer = Timer.periodic(const Duration(seconds: 1), (_) {
if (remaining > 0) {
setState(() => remaining--);
} else {
timer?.cancel();
_showNotification();
setState(() => isRunning = false);
}
});
}
void _pauseTimer() {
timer?.cancel();
setState(() => isRunning = false);
}
void _resetTimer() {
timer?.cancel();
setState(() {
isRunning = false;
remaining = duration;
});
}
void _selectDuration() {
showModalBottomSheet(
context: context,
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: [1, 5, 10, 15, 30, 60].map((m) {
return ListTile(
title: Text('$m мин'),
onTap: () {
setState(() {
duration = m * 60;
remaining = duration;
});
Navigator.pop(context);
},
);
}).toList(),
),
);
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final minutes = (remaining / 60).floor();
final seconds = remaining % 60;
final progress = duration > 0 ? (remaining / duration) as double : 0.0;
return Scaffold(
appBar: AppBar(title: const Text('Таймер')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: _selectDuration,
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 200,
height: 200,
child: CircularProgressIndicator(
value: progress,
strokeWidth: 8,
),
),
Text(
'${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}',
style: Theme.of(context).textTheme.displayMedium,
),
],
),
),
const SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
onPressed: isRunning ? _pauseTimer : _startTimer,
child: Icon(isRunning ? Icons.pause : Icons.play_arrow),
),
if (remaining != duration) ...[
const SizedBox(width: 16),
OutlinedButton(onPressed: _resetTimer, child: const Text('Сброс')),
],
],
),
],
),
);
}
}