diff --git a/android/app/build.gradle b/android/app/build.gradle index 3da777f2..ab927f33 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -86,6 +86,9 @@ android { signingConfig signingConfigs.debug } } + buildFeatures { + viewBinding true + } } flutter { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 64507121..c0de01a3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -70,6 +70,28 @@ android:exported="true"> + + + + + + + + + + + + + + + if (call.method == "getInitialRoute") { + val initialRoute = intent.getStringExtra("initialRoute") + result.success(mapOf("initialRoute" to initialRoute)) + } else { + result.notImplemented() + } + } } @@ -294,4 +313,156 @@ class MainActivity : FlutterActivity() { startActivity(intent) } + // Update the rings_in home_widget in a loop of 1 minute + private val handler = Handler(Looper.getMainLooper()) + private val updateInterval: Long = 60000 + + private val updateWidgetRunnable = object : Runnable { + override fun run() { + val appWidgetManager = AppWidgetManager.getInstance(applicationContext) + val componentName = ComponentName(applicationContext, NextAlarmHomeWidget::class.java) + val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) + + for (appWidgetId in appWidgetIds) { + val views = RemoteViews(packageName, R.layout.next_alarm_home_widget) + val alarmTime = getAlarmTimeText() + if (alarmTime != "No upcoming alarms!") { + views.setViewVisibility(R.id.repeat_days, View.VISIBLE) + views.setTextViewText(R.id.rings_in, alarmTime) + } else { + views.setViewVisibility(R.id.repeat_days, View.GONE) + views.setTextViewText(R.id.alarm_date_n_time, "No upcoming alarms!") + views.setTextViewText(R.id.rings_in, "") + } + appWidgetManager.updateAppWidget(appWidgetId, views) + } + + handler.postDelayed(this, updateInterval) + } + } + + private fun getAlarmTimeText(): String { + val dbHelper = DatabaseHelper(applicationContext) + val db = dbHelper.readableDatabase + val sharedPreferences = getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) + val profile = sharedPreferences.getString("flutter.profile", "Default") ?: "Default" + val latestAlarm = getLatestAlarm(db, true, profile, applicationContext) + + return if (latestAlarm != null) { + val alarmTime = latestAlarm["alarmTime"] as? String ?: "" + val days = latestAlarm["days"] as? String ?: "" + val alarmDate = latestAlarm["alarmDate"] as? String ?: "" + + if (alarmTime.isEmpty() || days.isEmpty() || alarmDate.isEmpty()) { + return "No upcoming alarms!" + } + return "Rings in " + timeUntilAlarm(stringToTime(alarmTime), days.map { it == '1' }, stringToDate(alarmDate)) + } else { + "No upcoming alarms!" + } + } + + private fun timeUntilAlarm(alarmTime: Time, days: List, alarmDate: Date): String { + val now = Calendar.getInstance() + + val todayAlarm = Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, alarmTime.hours) + set(Calendar.MINUTE, alarmTime.minutes) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + + var durationMillis: Long + + // If the alarm is for a specific future date + if (alarmDate.after(now.time)) { + val specificDateAlarm = Calendar.getInstance().apply { + time = alarmDate + set(Calendar.HOUR_OF_DAY, alarmTime.hours) + set(Calendar.MINUTE, alarmTime.minutes) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + durationMillis = specificDateAlarm.timeInMillis - now.timeInMillis + return formatInterval(durationMillis) + } + + // One-time alarm (no repeat days) + if (days.all { !it }) { + durationMillis = if (now.before(todayAlarm)) { + todayAlarm.timeInMillis - now.timeInMillis + } else { + val nextAlarm = todayAlarm + nextAlarm.add(Calendar.DAY_OF_YEAR, 1) + nextAlarm.timeInMillis - now.timeInMillis + } + } else if (now.before(todayAlarm) && days[now.get(Calendar.DAY_OF_WEEK) - 1]) { + durationMillis = todayAlarm.timeInMillis - now.timeInMillis + } else { + // var daysUntilNextAlarm = 7 + var nextAlarm: Calendar? = null + + for (i in 1..7) { + val nextDayIndex = (now.get(Calendar.DAY_OF_WEEK) + i - 1) % 7; + if (days[nextDayIndex]) { + // daysUntilNextAlarm = i; + nextAlarm = Calendar.getInstance().apply { + add(Calendar.DAY_OF_YEAR, i) + set(Calendar.HOUR_OF_DAY, alarmTime.hours) + set(Calendar.MINUTE, alarmTime.minutes) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + break + } + } + + if (nextAlarm != null) { + durationMillis = nextAlarm.timeInMillis - now.timeInMillis; + } else { + return "No upcoming alarms" + } + } + return formatInterval(durationMillis) + } + + private fun formatInterval(durationMillis: Long): String { + val minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis) + val hours = TimeUnit.MILLISECONDS.toHours(durationMillis) + val days = TimeUnit.MILLISECONDS.toDays(durationMillis) + + return when { + minutes < 1 -> "less than 1 minute" + hours < 24 -> { + val remainingMinutes = minutes % 60 + when { + hours == 0L -> "$remainingMinutes minute${if (remainingMinutes == 1L) "" else "s"}" + remainingMinutes == 0L -> "$hours hour${if (hours == 1L) "" else "s"}" + else -> "$hours hour${if (hours == 1L) "" else "s"} $remainingMinutes minute${if (remainingMinutes == 1L) "" else "s"}" + } + } + days == 1L -> "1 day" + else -> "$days days" + } + } + + private fun stringToDate(date: String): Date { + val parts = date.split("-") + val year = parts[0].trim().toInt() - 1900 + val month = parts[1].trim().toInt() - 1 + val day = parts[2].trim().toInt() + return Date(year, month, day) + } + + private fun stringToTime(time: String): Time { + val parts = time.split(':') + val hour = parts[0].toInt() + val minute = parts[1].toInt() + return Time(hour, minute, 0); + } + + override fun onDestroy() { + super.onDestroy() + handler.removeCallbacks(updateWidgetRunnable) + } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/NextAlarmHomeWidget.kt b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/NextAlarmHomeWidget.kt new file mode 100644 index 00000000..5f602b3a --- /dev/null +++ b/android/app/src/main/kotlin/com/ccextractor/ultimate_alarm_clock/ultimate_alarm_clock/NextAlarmHomeWidget.kt @@ -0,0 +1,81 @@ +package com.ccextractor.ultimate_alarm_clock + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.content.Intent +import android.view.View +import android.widget.RemoteViews +import es.antonborri.home_widget.HomeWidgetPlugin + + +class NextAlarmHomeWidget : AppWidgetProvider() { + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + // There may be multiple widgets active, so update all of them + for (appWidgetId in appWidgetIds) { + // Handling next alarm content + val data = HomeWidgetPlugin.getData(context); + val views = RemoteViews(context.packageName, R.layout.next_alarm_home_widget) + val ringsIn = data.getString("rings_in", "No upcoming alarms!") + val alarmTime = data.getString("alarm_time", "") + val repeatDays = data.getString("alarm_repeat_days", "One Time") + if (ringsIn == "No upcoming alarms!") { + views.setViewVisibility(R.id.repeat_days, View.GONE) + views.setViewVisibility(R.id.rings_in, View.GONE) + views.setTextViewText(R.id.repeat_days, "") + views.setTextViewText(R.id.alarm_date_n_time, "No upcoming alarms!") + views.setTextViewText(R.id.rings_in, "") + } else { + views.setViewVisibility(R.id.repeat_days, View.VISIBLE) + views.setViewVisibility(R.id.rings_in, View.VISIBLE) + views.setTextViewText(R.id.repeat_days, repeatDays) + views.setTextViewText(R.id.alarm_date_n_time, alarmTime) + views.setTextViewText(R.id.rings_in, ringsIn) + } + + // Handle feature icons visibility + views.setViewVisibility(R.id.shared_alarm_icon, + if (data.getBoolean("isSharedAlarmEnabled", false)) View.VISIBLE else View.GONE) + views.setViewVisibility(R.id.location_icon, + if (data.getBoolean("isLocationEnabled", false)) View.VISIBLE else View.GONE) + views.setViewVisibility(R.id.activity_icon, + if (data.getBoolean("isActivityEnabled", false)) View.VISIBLE else View.GONE) + views.setViewVisibility(R.id.weather_icon, + if (data.getBoolean("isWeatherEnabled", false)) View.VISIBLE else View.GONE) + views.setViewVisibility(R.id.qr_icon, + if (data.getBoolean("isQrEnabled", false)) View.VISIBLE else View.GONE) + views.setViewVisibility(R.id.shake_icon, + if (data.getBoolean("isShakeEnabled", false)) View.VISIBLE else View.GONE) + views.setViewVisibility(R.id.maths_icon, + if (data.getBoolean("isMathsEnabled", false)) View.VISIBLE else View.GONE) + views.setViewVisibility(R.id.pedometer_icon, + if (data.getBoolean("isPedometerEnabled", false)) View.VISIBLE else View.GONE) + + // // handling on tap of add-alarm button + // val intent = Intent(context, MainActivity::class.java).apply { + // flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + // } + // val pendingIntent = PendingIntent.getActivity( + // context, + // 0, + // intent, + // PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + // ) + // views.setOnClickPendingIntent(R.id.add_alarm_button_next_alarm_widget, pendingIntent) + appWidgetManager.updateAppWidget(appWidgetId, views) + } + } + + override fun onEnabled(context: Context) { + // Enter relevant functionality for when the first widget is created + } + + override fun onDisabled(context: Context) { + // Enter relevant functionality for when the last widget is disabled + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable-night-v21/next_alarm_home_widget_background.xml b/android/app/src/main/res/drawable-night-v21/next_alarm_home_widget_background.xml new file mode 100644 index 00000000..22059bbf --- /dev/null +++ b/android/app/src/main/res/drawable-night-v21/next_alarm_home_widget_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-night/next_alarm_home_widget_background.xml b/android/app/src/main/res/drawable-night/next_alarm_home_widget_background.xml new file mode 100644 index 00000000..22059bbf --- /dev/null +++ b/android/app/src/main/res/drawable-night/next_alarm_home_widget_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable-v21/next_alarm_home_widget_background.xml b/android/app/src/main/res/drawable-v21/next_alarm_home_widget_background.xml new file mode 100644 index 00000000..968f82bf --- /dev/null +++ b/android/app/src/main/res/drawable-v21/next_alarm_home_widget_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/add_alarm_button_background.xml b/android/app/src/main/res/drawable/add_alarm_button_background.xml new file mode 100644 index 00000000..e129308b --- /dev/null +++ b/android/app/src/main/res/drawable/add_alarm_button_background.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_activity.xml b/android/app/src/main/res/drawable/ic_activity.xml new file mode 100644 index 00000000..ec7d6d2f --- /dev/null +++ b/android/app/src/main/res/drawable/ic_activity.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_location.xml b/android/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 00000000..0a8bda9b --- /dev/null +++ b/android/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_maths.xml b/android/app/src/main/res/drawable/ic_maths.xml new file mode 100644 index 00000000..797d8c11 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_maths.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_pedometer.xml b/android/app/src/main/res/drawable/ic_pedometer.xml new file mode 100644 index 00000000..b171c28f --- /dev/null +++ b/android/app/src/main/res/drawable/ic_pedometer.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_qr.xml b/android/app/src/main/res/drawable/ic_qr.xml new file mode 100644 index 00000000..1bfbb59f --- /dev/null +++ b/android/app/src/main/res/drawable/ic_qr.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_shake.xml b/android/app/src/main/res/drawable/ic_shake.xml new file mode 100644 index 00000000..1eb0d624 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_shake.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_shared_alarm.xml b/android/app/src/main/res/drawable/ic_shared_alarm.xml new file mode 100644 index 00000000..1655cd43 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_shared_alarm.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_weather.xml b/android/app/src/main/res/drawable/ic_weather.xml new file mode 100644 index 00000000..39ea6daf --- /dev/null +++ b/android/app/src/main/res/drawable/ic_weather.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/next_alarm_home_widget_background.xml b/android/app/src/main/res/drawable/next_alarm_home_widget_background.xml new file mode 100644 index 00000000..968f82bf --- /dev/null +++ b/android/app/src/main/res/drawable/next_alarm_home_widget_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/add_alarm_home_widget.xml b/android/app/src/main/res/layout/add_alarm_home_widget.xml new file mode 100644 index 00000000..56949d20 --- /dev/null +++ b/android/app/src/main/res/layout/add_alarm_home_widget.xml @@ -0,0 +1,11 @@ + + +