diff --git a/android/app/build.gradle b/android/app/build.gradle
index e6563eb..fe5af34 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -60,6 +60,15 @@ android {
signingConfig signingConfigs.debug
}
}
+
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'mips', 'mips64', 'arm64-v8a'
+ universalApk true
+ }
+ }
}
flutter {
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 581886f..58143ab 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,7 +11,9 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
- android:windowSoftInputMode="adjustResize">
+ android:windowSoftInputMode="adjustResize"
+ android:showWhenLocked="true"
+ android:turnScreenOn="true">
+
diff --git a/android/app/src/main/res/raw/simple_alarm_music.mp3 b/android/app/src/main/res/raw/simple_alarm_music.mp3
new file mode 100644
index 0000000..ee35837
Binary files /dev/null and b/android/app/src/main/res/raw/simple_alarm_music.mp3 differ
diff --git a/lib/main.dart b/lib/main.dart
index 202509b..2947234 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
+import 'model/ringing_alarm_model.dart';
+import 'view/ringing_alarm_view.dart';
-void main() {
+void main() async {
+ setupRingingAlarm();
runApp(const MyApp());
}
@@ -102,10 +105,25 @@ class _MyHomePageState extends State {
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
+ FloatingActionButton(
+ //RingingAlarmPageに飛ぶボタン
+ heroTag: 'ringing_alarm_page_button',
+ onPressed: () async {
+ // "push"で新規画面に遷移
+ await Navigator.of(context).push(
+ MaterialPageRoute(builder: (context) {
+ return const RingingAlarmTestPage(
+ title: 'Ringing Alarm Test Page');
+ }),
+ );
+ },
+ child: const Icon(Icons.alarm),
+ ),
],
),
),
floatingActionButton: FloatingActionButton(
+ heroTag: 'increment_button',
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
diff --git a/lib/model/ringing_alarm_model.dart b/lib/model/ringing_alarm_model.dart
new file mode 100644
index 0000000..8dc70b5
--- /dev/null
+++ b/lib/model/ringing_alarm_model.dart
@@ -0,0 +1,161 @@
+import 'package:flutter/material.dart';
+import 'dart:async';
+import 'package:intl/intl.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:timezone/data/latest.dart' as tz;
+import 'package:timezone/timezone.dart' as tz;
+
+/// RingingAlarm の初期化をする。mainで呼ぶ。
+void setupRingingAlarm() {
+ _setupTimeZone();
+}
+
+// タイムゾーンを設定する
+Future _setupTimeZone() async {
+ tz.initializeTimeZones();
+ var tokyo = tz.getLocation('Asia/Tokyo');
+ tz.setLocalLocation(tokyo);
+}
+
+///ミリ秒まで表示するフォーマッター
+///- 返り値は hh:mm:ss.x の形の String
+String formatTime(DateTime dt) {
+ return "${DateFormat.Hms().format(dt)}.${dt.millisecond.toString().padRight(2, '0').substring(0, 1)}";
+}
+
+///アラームの管理をするクラス
+class RingingAlarm {
+ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
+ FlutterLocalNotificationsPlugin();
+ Timer? _timer; // タイマーオブジェクト
+ DateTime _time = DateTime.utc(0, 0, 0)
+ .add(const Duration(seconds: 10)); // タイマーで管理している時間。10秒をカウントダウンする設定
+ bool _isTimerActive = false; // バックグラウンドに遷移した際にタイマーが起動中かどうか
+ DateTime? _pausedTime; // バックグラウンドに遷移した時間
+ int? _notificationId; // 通知ID
+ bool _notificationOnceFlag = false; //連続して通知ができないようにするフラグ
+
+ DateTime get displayTime => _time;
+
+ ///コンストラクタ
+ RingingAlarm();
+
+ /// アプリがバックグラウンドに遷移した際のハンドラ
+ void handleOnPaused() {
+ if (_timer != null && _timer!.isActive) {
+ _isTimerActive = true;
+ _timer!.cancel(); // タイマーを停止する
+ }
+ _pausedTime = DateTime.now(); // バックグラウンドに遷移した時間を記録
+ if (_isTimerActive) {
+ if (_notificationOnceFlag) {
+ _notificationOnceFlag = false;
+ if (_time
+ .difference(DateTime.utc(0, 0, 0))
+ .compareTo(const Duration(seconds: 1)) >
+ 0) {
+ //タイマーの停止直後に再度通知を作りたがるので間隔を制限
+ _notificationId = _scheduleLocalNotification(
+ _time.difference(DateTime.utc(0, 0, 0))); // ローカル通知をスケジュール登録
+ }
+ }
+ }
+ }
+
+ // フォアグラウンドに復帰した時のハンドラ
+ void handleOnResumed() {
+ if (_isTimerActive == false) return; // タイマーが動いてなければ何もしない
+ Duration backgroundDuration =
+ DateTime.now().difference(_pausedTime!); // バックグラウンドでの経過時間
+ // バックグラウンドでの経過時間が終了予定を超えていた場合(この場合は通知実行済みのはず)
+ if (_time.difference(DateTime.utc(0, 0, 0)).compareTo(backgroundDuration) <
+ 0) {
+ _time = DateTime.utc(0, 0, 0); // 時間をリセットする
+ } else {
+ _time = _time.add(-backgroundDuration); // バックグラウンド経過時間分時間を進める
+ startTimer(); // タイマーを再開する
+ }
+ }
+
+ //鳴っている通知を消す(正確には最新の通知)
+ void resetNotification() {
+ if (_notificationId != null) {
+ flutterLocalNotificationsPlugin.cancel(_notificationId!); // 通知をキャンセル
+ }
+ _isTimerActive = false; // リセット
+ _notificationId = null; // リセット
+ _pausedTime = null;
+ _notificationOnceFlag = false;
+ }
+
+ // タイマーを開始する
+ void startTimer() {
+ _notificationOnceFlag = true;
+ _timer = Timer.periodic(const Duration(milliseconds: 100), (Timer timer) {
+ _time = _time.add(const Duration(milliseconds: -100));
+ _handleTimeIsOver();
+ }); // 1秒ずつ時間を減らす
+ }
+
+ // タイマーを初期化してから開始する
+ void restartTimer() {
+ if (_timer != null) _timer!.cancel();
+ _time = DateTime.utc(0, 0, 0);
+ _time = _time.add(const Duration(seconds: 10));
+ startTimer();
+ }
+
+ // 時間がゼロになったらタイマーを止める
+ void _handleTimeIsOver() {
+ if (_timer != null &&
+ _timer!.isActive &&
+ //_time != null &&
+ _time == DateTime.utc(0, 0, 0)) {
+ _timer!.cancel();
+ _notificationOnceFlag = false;
+ }
+ }
+
+ //タイマーを止める
+ void cancelTimer() {
+ if (_timer != null && _timer!.isActive) {
+ _timer!.cancel();
+ }
+ }
+
+ /// タイマー終了をローカル通知。実質メイン
+ int _scheduleLocalNotification(Duration duration) {
+ flutterLocalNotificationsPlugin.initialize(
+ const InitializationSettings(
+ android:
+ AndroidInitializationSettings('@mipmap/ic_launcher'), //通知アイコン
+ iOS: IOSInitializationSettings()),
+ );
+ int notificationId =
+ DateTime.now().hashCode; //現在時刻から生成しているが、通知を管理するIDを指定できる
+ DateTime tzDT = tz.TZDateTime.now(tz.local).add(duration);
+ flutterLocalNotificationsPlugin.zonedSchedule(
+ notificationId, //通知のID
+ 'Time is over', //通知のタイトル
+ 'Wake up! It\'s ${formatTime(tzDT)}', //通知の本文
+ tz.TZDateTime.now(tz.local).add(duration), //通知の予約時間
+ const NotificationDetails(
+ android: AndroidNotificationDetails(
+ 'your channel id', 'your channel name',
+ channelDescription: 'your channel description',
+ importance: Importance.high,
+ priority: Priority.high,
+ //https://esffects.net/361.html からとってきた音源。
+ //alarm2022\android\app\src\main\res\raw\simple_alarm_music.mp3 に配置。
+ sound:
+ RawResourceAndroidNotificationSound('simple_alarm_music'),
+ fullScreenIntent: true),
+ iOS: IOSNotificationDetails()),
+ uiLocalNotificationDateInterpretation:
+ UILocalNotificationDateInterpretation.absoluteTime,
+ androidAllowWhileIdle: true);
+ debugPrint("■■■Scheduled for ${formatTime(tzDT)}");
+ debugPrint("■■■ from ${formatTime(tz.TZDateTime.now(tz.local))}");
+ return notificationId;
+ }
+}
diff --git a/lib/view/ringing_alarm_view.dart b/lib/view/ringing_alarm_view.dart
new file mode 100644
index 0000000..6a12246
--- /dev/null
+++ b/lib/view/ringing_alarm_view.dart
@@ -0,0 +1,121 @@
+import 'package:flutter/material.dart';
+import 'dart:async';
+import '../model/ringing_alarm_model.dart';
+
+//新しく作り直すと考えTestとつけている
+class RingingAlarmTestPage extends StatefulWidget {
+ const RingingAlarmTestPage({Key? key, this.title}) : super(key: key);
+ final String? title;
+ @override
+ // ignore: library_private_types_in_public_api
+ _RingingAlarmTestPageState createState() => _RingingAlarmTestPageState();
+}
+
+class _RingingAlarmTestPageState extends State
+ with WidgetsBindingObserver {
+ //modelの方の実体
+ final RingingAlarm _ringingAlarm = RingingAlarm();
+ //現在時刻表示用のTimer
+ Timer? _clockTimer;
+
+ //ページ遷移時にlogがバグったので。
+ //参照:https://stackoverflow.com/questions/49340116/setstate-called-after-dispose
+ @override
+ void setState(fn) {
+ if (mounted) {
+ super.setState(fn);
+ }
+ }
+
+ /// 初期化処理
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addObserver(this);
+ //現在時刻表示用だが画面の更新処理も担っている
+ _clockTimer =
+ Timer.periodic(const Duration(milliseconds: 100), (Timer clockTimer) {
+ setState(() {});
+ });
+ }
+
+ /// ライフサイクルが変更された際に呼び出される関数をoverrideして、変更を検知
+ @override
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ if (state == AppLifecycleState.paused) {
+ // バックグラウンドに遷移した時
+ setState(() {
+ _ringingAlarm.handleOnPaused();
+ });
+ } else if (state == AppLifecycleState.resumed) {
+ // フォアグラウンドに復帰した時
+ setState(() {
+ _ringingAlarm.handleOnResumed();
+ });
+ }
+ super.didChangeAppLifecycleState(state);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
+ Text(
+ //現在時刻表示
+ "Now : ${formatTime(DateTime.now())}",
+ ),
+ Text(
+ //タイマー表示
+ formatTime(_ringingAlarm.displayTime),
+ style: Theme.of(context).textTheme.headline2,
+ ),
+ Row(
+ //各種ボタンの配置
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ FloatingActionButton(
+ //前の画面に戻る
+ heroTag: 'back_button',
+ onPressed: () {
+ if (_clockTimer != null) _clockTimer!.cancel();
+ Navigator.of(context).pop();
+ },
+ child: const Text("Back"),
+ ),
+ FloatingActionButton(
+ //タイマーの一時停止
+ heroTag: 'stop_button',
+ onPressed: () {
+ _ringingAlarm.cancelTimer();
+ },
+ child: const Text("Stop"),
+ ),
+ FloatingActionButton(
+ //タイマーのカウントダウン再開
+ heroTag: 'start_button',
+ onPressed: () {
+ _ringingAlarm.startTimer();
+ },
+ child: const Text("Start"),
+ ),
+ FloatingActionButton(
+ //タイマーの時間をリセットしてスタート
+ heroTag: 'restart_button',
+ onPressed: () {
+ _ringingAlarm.restartTimer();
+ },
+ child: const Text("Restart"),
+ ),
+ FloatingActionButton(
+ //鳴ってるアラームを止める
+ heroTag: 'reset_button',
+ onPressed: () {
+ _ringingAlarm.resetNotification();
+ },
+ child: const Text("Reset"),
+ ),
+ ],
+ )
+ ]));
+ }
+}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index cccf817..d2b4c55 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation
+import flutter_local_notifications
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index 4319405..4f887e1 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,13 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ args:
+ dependency: transitive
+ description:
+ name: args
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.3.1"
async:
dependency: transitive
description:
@@ -50,6 +57,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
+ dbus:
+ dependency: transitive
+ description:
+ name: dbus
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.7.5"
fake_async:
dependency: transitive
description:
@@ -57,6 +71,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "6.1.2"
flutter:
dependency: "direct main"
description: flutter
@@ -69,11 +97,39 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
+ flutter_local_notifications:
+ dependency: "direct main"
+ description:
+ name: flutter_local_notifications
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "9.6.0"
+ flutter_local_notifications_linux:
+ dependency: transitive
+ description:
+ name: flutter_local_notifications_linux
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.5.0+1"
+ flutter_local_notifications_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_local_notifications_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "5.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
+ intl:
+ dependency: "direct main"
+ description:
+ name: intl
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.17.0"
lints:
dependency: transitive
description:
@@ -88,6 +144,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
+ material:
+ dependency: "direct main"
+ description:
+ name: material
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0+2"
material_color_utilities:
dependency: transitive
description:
@@ -109,6 +172,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "5.0.0"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.1.0"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.1.2"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "4.2.4"
sky_engine:
dependency: transitive
description: flutter
@@ -156,6 +247,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.9"
+ timezone:
+ dependency: "direct main"
+ description:
+ name: timezone
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.8.0"
vector_math:
dependency: transitive
description:
@@ -163,5 +261,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.0+1"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "6.1.0"
sdks:
dart: ">=2.17.1 <3.0.0"
+ flutter: ">=2.2.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 232b61c..4a2e95d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -34,6 +34,10 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
+ material: ^1.0.0+2
+ intl: ^0.17.0
+ flutter_local_notifications: ^9.6.0
+ timezone: ^0.8.0
dev_dependencies:
flutter_test: