Skip to content

Feature: Home Widgets in UAC for Add Alarm (1x1) and Next Alarm (4x2) in Android #834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ android {
signingConfig signingConfigs.debug
}
}
buildFeatures {
viewBinding true
}
}

flutter {
Expand Down
22 changes: 22 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@
android:exported="true"></service>
<service android:name="com.pravera.flutter_foreground_task.service.ForegroundService" />

<receiver
android:name=".NextAlarmHomeWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/next_alarm_home_widget_info" />
</receiver>
<receiver
android:name=".AddAlarmHomeWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/add_alarm_home_widget_info" />
</receiver>
<receiver
android:name=".BootReceiver"
android:enabled="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.TypedValue
import android.widget.RemoteViews
import androidx.core.net.toUri

class AddAlarmHomeWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
for (appWidgetId in appWidgetIds) {
setupAddAlarmButton(context, appWidgetManager, appWidgetId)
}
}

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
}

override fun onAppWidgetOptionsChanged(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
newOptions: Bundle?
) {
setupAddAlarmButton(context, appWidgetManager, appWidgetId)
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
}

private fun setupAddAlarmButton(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
) {
val views = RemoteViews(context.packageName, R.layout.add_alarm_home_widget)

// Intent to open the Add or Update Alarm screen
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra("initialRoute", "/add-update-alarm")
}

val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

views.setOnClickPendingIntent(R.id.add_alarm_button, pendingIntent)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ fun getLatestAlarm(db: SQLiteDatabase, wantNextAlarm: Boolean, profile: String,c
"location" to setAlarm.location,
"isWeather" to setAlarm.isWeatherEnabled,
"weatherTypes" to setAlarm.weatherTypes,
"alarmID" to setAlarm.alarmId
"alarmID" to setAlarm.alarmId,
"days" to setAlarm.days,
"alarmTime" to setAlarm.alarmTime,
"alarmDate" to setAlarm.alarmDate,
)
Log.d("s", "sdsd ${a}")
return a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,30 @@ import android.app.ActivityManager
import android.app.AlarmManager
import android.app.NotificationManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.Ringtone
import android.media.RingtoneManager
import android.net.Uri
import android.os.Bundle
import android.os.SystemClock
import android.provider.Settings
import android.util.Log
import android.view.WindowManager
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.RemoteViews
import java.sql.Time
import java.util.Calendar
import java.util.Date
import java.util.concurrent.TimeUnit

class MainActivity : FlutterActivity() {
companion object {
Expand All @@ -40,6 +48,8 @@ class MainActivity : FlutterActivity() {
intentFilter.addAction("com.ccextractor.ultimate_alarm_clock.START_TIMERNOTIF")
intentFilter.addAction("com.ccextractor.ultimate_alarm_clock.STOP_TIMERNOTIF")
context.registerReceiver(TimerNotification(), intentFilter, Context.RECEIVER_EXPORTED)
// Start periodic updates of rings_in
handler.post(updateWidgetRunnable)
}


Expand Down Expand Up @@ -139,6 +149,15 @@ class MainActivity : FlutterActivity() {
result.notImplemented()
}
}

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "flutter/platform").setMethodCallHandler { call, result ->
if (call.method == "getInitialRoute") {
val initialRoute = intent.getStringExtra("initialRoute")
result.success(mapOf("initialRoute" to initialRoute))
} else {
result.notImplemented()
}
}
}


Expand Down Expand Up @@ -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<Boolean>, 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)
}
}
Loading