Initial commit: Minimal Flutter timer with push notifications
This commit is contained in:
156
lib/main.dart
Normal file
156
lib/main.dart
Normal 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('Сброс')),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user