From 5b19c422f2310425d11189c92afdc7c4974b9399 Mon Sep 17 00:00:00 2001 From: Nyra Sama <83142634+NyraSama@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:14:22 +0200 Subject: [PATCH] feat(android_notif): Add daily notification in android platform and set the option in profile view (#1380) --- yaki_mobile/android/app/build.gradle | 2 +- .../android/app/src/main/AndroidManifest.xml | 6 +- .../main/kotlin/com/xpeho/yaki/AlarmItem.kt | 8 +++ .../kotlin/com/xpeho/yaki/AlarmReceiver.kt | 55 +++++++++++++++++++ .../kotlin/com/xpeho/yaki/AlarmScheduler.kt | 6 ++ .../com/xpeho/yaki/AlarmSchedulerImpl.kt | 55 +++++++++++++++++++ .../kotlin/com/xpeho/yaki/MainActivity.kt | 46 +++++++++++++++- yaki_mobile/assets/translations/en.json | 4 +- yaki_mobile/assets/translations/fr.json | 4 +- .../features/profile/profile.dart | 26 ++++++++- yaki_mobile/pubspec.lock | 6 +- yaki_mobile/pubspec.yaml | 2 +- 12 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmItem.kt create mode 100644 yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmReceiver.kt create mode 100644 yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmScheduler.kt create mode 100644 yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmSchedulerImpl.kt diff --git a/yaki_mobile/android/app/build.gradle b/yaki_mobile/android/app/build.gradle index a68fccf0c..ce0818fd3 100644 --- a/yaki_mobile/android/app/build.gradle +++ b/yaki_mobile/android/app/build.gradle @@ -53,7 +53,7 @@ android { applicationId "com.xpeho.yaki" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 26 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/yaki_mobile/android/app/src/main/AndroidManifest.xml b/yaki_mobile/android/app/src/main/AndroidManifest.xml index 60ee63240..5a44b9f9c 100644 --- a/yaki_mobile/android/app/src/main/AndroidManifest.xml +++ b/yaki_mobile/android/app/src/main/AndroidManifest.xml @@ -4,12 +4,14 @@ + + + - + + diff --git a/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmItem.kt b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmItem.kt new file mode 100644 index 000000000..fb876c8f4 --- /dev/null +++ b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmItem.kt @@ -0,0 +1,8 @@ +package com.xpeho.yaki + +import java.time.LocalDateTime + +data class AlarmItem( + val time: LocalDateTime, + val message: String, +) diff --git a/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmReceiver.kt b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmReceiver.kt new file mode 100644 index 000000000..ea1b863eb --- /dev/null +++ b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmReceiver.kt @@ -0,0 +1,55 @@ +package com.xpeho.yaki + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationCompat +import java.time.LocalDateTime + +class AlarmReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + val message = intent?.getStringExtra("message") ?: return + + if (context != null) { + // Show a push notification + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as + NotificationManager + + val channel = + NotificationChannel( + "Alarm", + "Alarm", + NotificationManager.IMPORTANCE_DEFAULT + ) + notificationManager.createNotificationChannel(channel) + + val notification = + NotificationCompat.Builder(context, "Alarm") + .setContentTitle("Reminder") + .setContentText(message) + .setSmallIcon(R.mipmap.ic_launcher) + .build() + + notificationManager.notify(1, notification) + + // Define the next alarm + if (intent.getBooleanExtra("repeat", false)) { + AlarmSchedulerImpl(context = context) + .schedule( + AlarmItem( + time = + LocalDateTime.now() + .plusDays( + 1 + ), + message = + "It's time to do your daily declaration!" + ) + ) + } + } + } +} diff --git a/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmScheduler.kt b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmScheduler.kt new file mode 100644 index 000000000..af84047fa --- /dev/null +++ b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmScheduler.kt @@ -0,0 +1,6 @@ +package com.xpeho.yaki + +interface AlarmScheduler { + fun schedule(item: AlarmItem) + fun cancel(item: AlarmItem) +} \ No newline at end of file diff --git a/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmSchedulerImpl.kt b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmSchedulerImpl.kt new file mode 100644 index 000000000..f2dc0eab3 --- /dev/null +++ b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/AlarmSchedulerImpl.kt @@ -0,0 +1,55 @@ +package com.xpeho.yaki + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import java.time.ZoneId + +class AlarmSchedulerImpl(private val context: Context) : AlarmScheduler { + + private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + + override fun schedule(item: AlarmItem) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (!alarmManager.canScheduleExactAlarms()) { + val intent = + Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply { + data = Uri.parse("package:${context.packageName}") + } + context.startActivity(intent) + return + } + } + + val intent = + Intent(context, AlarmReceiver::class.java).apply { + putExtra("message", item.message) + putExtra("repeat", true) + } + alarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, + item.time.atZone(ZoneId.systemDefault()).toEpochSecond() * 1000, + PendingIntent.getBroadcast( + context, + item.hashCode(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } + + override fun cancel(item: AlarmItem) { + alarmManager.cancel( + PendingIntent.getBroadcast( + context, + item.hashCode(), + Intent(context, AlarmReceiver::class.java), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } +} diff --git a/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/MainActivity.kt b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/MainActivity.kt index 2b240f305..83eee20d3 100644 --- a/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/MainActivity.kt +++ b/yaki_mobile/android/app/src/main/kotlin/com/xpeho/yaki/MainActivity.kt @@ -1,6 +1,50 @@ package com.xpeho.yaki +import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import java.time.LocalDateTime -class MainActivity: FlutterActivity() { +class MainActivity : FlutterActivity() { + private val CHANNEL = "com.xpeho.yaki/notification" + + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { + call, + result -> + when (call.method) { + "scheduleNotifications" -> { + scheduleNotifications() + result.success(null) + } + "cancelNotifications" -> { + cancelNotifications() + result.success(null) + } + else -> result.notImplemented() + } + } + } + + private fun scheduleNotifications() { + AlarmSchedulerImpl(context = this) + .schedule( + AlarmItem( + time = LocalDateTime.now().withHour(9).withMinute(0).withSecond(0), + message = "It's time to do your daily declaration!" + ) + ) + } + + private fun cancelNotifications() { + AlarmSchedulerImpl(context = this) + .cancel( + AlarmItem( + time = LocalDateTime.now().withHour(9).withMinute(0).withSecond(0), + message = "It's time to do your daily declaration!" + ) + ) + } } diff --git a/yaki_mobile/assets/translations/en.json b/yaki_mobile/assets/translations/en.json index af135c6dd..14e1e5739 100644 --- a/yaki_mobile/assets/translations/en.json +++ b/yaki_mobile/assets/translations/en.json @@ -153,5 +153,7 @@ "zero": "Just now", "one": "Since {} minute", "other": "Since {} minutes ago" - } + }, + + "notificationStatus": "Daily notifications" } diff --git a/yaki_mobile/assets/translations/fr.json b/yaki_mobile/assets/translations/fr.json index 1a01ce419..707a8c5b4 100644 --- a/yaki_mobile/assets/translations/fr.json +++ b/yaki_mobile/assets/translations/fr.json @@ -155,5 +155,7 @@ "zero": "À l'instant", "one": "Depuis {} minute", "other": "Depuis {} minutes" - } + }, + + "notificationStatus": "Notifications journalières" } diff --git a/yaki_mobile/lib/presentation/features/profile/profile.dart b/yaki_mobile/lib/presentation/features/profile/profile.dart index 1fd821118..224348f2e 100644 --- a/yaki_mobile/lib/presentation/features/profile/profile.dart +++ b/yaki_mobile/lib/presentation/features/profile/profile.dart @@ -106,7 +106,31 @@ class Profile extends ConsumerWidget { ), ], ), - const SizedBox(height: 10), + const SizedBox(height: 20), + Padding( + padding: const EdgeInsets.only(left: 30.0), + child: Row( + children: [ + const SizedBox( + width: 48, + child: Swap( + setActivated: true, + ), + ), + const SizedBox(width: 20), + Text( + tr('notificationStatus'), + style: const TextStyle( + color: kTextColor, + fontSize: 18, + fontWeight: FontWeight.w600, + fontFamily: 'SF Pro Rounded', + ), + ), + ], + ), + ), + const SizedBox(height: 20), InputText( type: InputTextType.email, label: tr('inputLabelFirstName'), diff --git a/yaki_mobile/pubspec.lock b/yaki_mobile/pubspec.lock index a4f0bbfa5..480831d37 100644 --- a/yaki_mobile/pubspec.lock +++ b/yaki_mobile/pubspec.lock @@ -1277,11 +1277,11 @@ packages: dependency: "direct main" description: path: flutter - ref: "0.9.0" - resolved-ref: b7d6f308afc2e5e330f0a6acc71ea3c9331054a6 + ref: "0.10.0" + resolved-ref: c3b9677ecbe15840e972351b0499bd70ceeb2ef5 url: "https://github.com/XPEHO/yaki_ui.git" source: git - version: "0.9.0" + version: "0.10.0" yaml: dependency: transitive description: diff --git a/yaki_mobile/pubspec.yaml b/yaki_mobile/pubspec.yaml index a566bdad9..79360a82f 100644 --- a/yaki_mobile/pubspec.yaml +++ b/yaki_mobile/pubspec.yaml @@ -92,7 +92,7 @@ dependencies: git: url: https://github.com/XPEHO/yaki_ui.git path: flutter - ref: 0.9.0 + ref: 0.10.0 collection: ^1.17.2 image_picker: ^1.0.4 path_provider: ^2.1.1