Skip to content

Commit

Permalink
refacotr
Browse files Browse the repository at this point in the history
  • Loading branch information
xenonnn4w committed Aug 2, 2024
1 parent d9c785f commit 5a1f9cc
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,23 @@ This program is free software; you can redistribute it and/or modify it under

package com.ichi2.widget

import android.app.AlarmManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.os.SystemClock
import android.widget.RemoteViews
import com.ichi2.anki.AnkiDroidApp.Companion.applicationScope
import com.ichi2.anki.R
import com.ichi2.anki.analytics.UsageAnalytics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.time.Duration.Companion.seconds

/**
* AnalyticsWidgetProvider class for Card Analysis Widget that integrates
* with UsageAnalytics to send analytics events when the widget is enabled, disabled,
* or updated..
* This widget displays a decks with respective name, new, learning, and review card counts.
* It updates every minute .
* No user actions can be performed from this widget as of now; it is for display purposes only.
*/
class CardAnalysisExtraWidget : AnalyticsWidgetProvider() {
class CardAnalysisExtraWidget : WidgetAlarm() {

companion object {
const val ACTION_APPWIDGET_UPDATE = AppWidgetManager.ACTION_APPWIDGET_UPDATE
Expand Down Expand Up @@ -73,62 +66,6 @@ class CardAnalysisExtraWidget : AnalyticsWidgetProvider() {
appWidgetManager.updateAppWidget(widgetId, remoteViews)
}
}

/**
* Sets a recurring alarm to update the widget every minute.
*
* @param context the context of the application
* @param appWidgetId the ID of the widget
*/
private fun setRecurringAlarm(context: Context, appWidgetId: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, CardAnalysisExtraWidget::class.java).apply {
action = ACTION_UPDATE_WIDGET
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)

/**
* When onUpdate is called, the code checks if an existing alarm PendingIntent
* is already set for the widget .If an Alarm Already Exists: PendingIntent.getBroadcast
* returns the existing PendingIntent, and pendingIntent is not null.
* The if block is skipped, and no new alarm is set.
*/

if (pendingIntent != null) {
Timber.v("Recurring alarm PendingIntent already exists for widget ID: $appWidgetId")
return
}

Timber.v("Creating a new recurring alarm PendingIntent for widget ID: $appWidgetId")
val newPendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)

// Set alarm to trigger every minute
val ONE_MINUTE_MILLIS = 60.seconds.inWholeMilliseconds
alarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + ONE_MINUTE_MILLIS,
ONE_MINUTE_MILLIS,
newPendingIntent
)
}

/**
* Cancels the recurring alarm for the widget.
*
* @param context the context of the application
* @param appWidgetId the ID of the widget
*/
private fun cancelRecurringAlarm(context: Context, appWidgetId: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, CardAnalysisExtraWidget::class.java).apply {
action = ACTION_UPDATE_WIDGET
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
Timber.d("Canceling recurring alarm for widget ID: $appWidgetId")
alarmManager.cancel(pendingIntent)
}
}

override fun performUpdate(
Expand All @@ -140,11 +77,11 @@ class CardAnalysisExtraWidget : AnalyticsWidgetProvider() {
val widgetPreferences = WidgetPreferences(context)

for (widgetId in appWidgetIds) {
val selectedDeckIds = widgetPreferences.getSelectedDeckIdsFromPreferencesCardAnalysisExtraWidgetData(widgetId)
val selectedDeckIds = widgetPreferences.getSelectedDeckIdsFromPreferencesCardAnalysisExtraData(widgetId)
if (selectedDeckIds.isNotEmpty()) {
updateWidget(context, appWidgetManager, intArrayOf(widgetId), selectedDeckIds)
}
setRecurringAlarm(context, widgetId)
setRecurringAlarm(context, widgetId, CardAnalysisExtraWidget::class.java)
}
}

Expand All @@ -159,20 +96,24 @@ class CardAnalysisExtraWidget : AnalyticsWidgetProvider() {

when (intent.action) {
ACTION_APPWIDGET_UPDATE -> {
Timber.i("Received ACTION_APPWIDGET_UPDATE")
val appWidgetManager = AppWidgetManager.getInstance(context)
val appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS)
val selectedDeckIds = intent.getLongArrayExtra("card_analysis_extra_widget_selected_deck_ids")

if (appWidgetIds != null && selectedDeckIds != null) {
Timber.d("Updating widget with appWidgetIds: ${appWidgetIds.joinToString()} and selectedDeckIds: ${selectedDeckIds.joinToString()}")
updateWidget(context, appWidgetManager, appWidgetIds, selectedDeckIds)
}
}
ACTION_UPDATE_WIDGET -> {
Timber.i("Received ACTION_UPDATE_WIDGET")
val appWidgetManager = AppWidgetManager.getInstance(context)
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
val selectedDeckIds = widgetPreferences.getSelectedDeckIdsFromPreferencesCardAnalysisExtraWidgetData(appWidgetId)
val selectedDeckIds = widgetPreferences.getSelectedDeckIdsFromPreferencesCardAnalysisExtraData(appWidgetId)
if (selectedDeckIds.isNotEmpty()) {
Timber.d("Updating widget with appWidgetId: $appWidgetId and selectedDeckIds: ${selectedDeckIds.joinToString()}")
updateWidget(context, appWidgetManager, intArrayOf(appWidgetId), selectedDeckIds)
}
}
Expand All @@ -189,7 +130,7 @@ class CardAnalysisExtraWidget : AnalyticsWidgetProvider() {
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
super.onDeleted(context, appWidgetIds)
appWidgetIds?.forEach { widgetId ->
cancelRecurringAlarm(context!!, widgetId)
cancelRecurringAlarm(context!!, widgetId, CardAnalysisExtraWidget::class.java)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class CardAnalysisExtraWidgetConfig : FragmentActivity(), DeckSelectionListener
findViewById<Button>(R.id.done_button).setOnClickListener {
saveSelectedDecksToPreferences()

val selectedDeckIds = CardAnalysisExtraWidgetPreferences.getSelectedDeckIdsFromPreferencesCardAnalysisExtraWidgetData(appWidgetId)
val selectedDeckIds = CardAnalysisExtraWidgetPreferences.getSelectedDeckIdsFromPreferencesCardAnalysisExtraData(appWidgetId)

val appWidgetManager = AppWidgetManager.getInstance(this)
CardAnalysisExtraWidget.updateWidget(this, appWidgetManager, intArrayOf(appWidgetId), selectedDeckIds)
Expand Down Expand Up @@ -129,13 +129,15 @@ class CardAnalysisExtraWidgetConfig : FragmentActivity(), DeckSelectionListener

/** Called when a deck is selected from the deck selection dialog. */
override fun onDeckSelected(deck: SelectableDeck?) {
if (deck != null) {
if (deckAdapter.itemCount >= 1) {
showSnackbar(getString(R.string.deck_limit_reached_card_analysis))
} else {
deckAdapter.addDeck(deck)
updateViewVisibility()
}
if (deck == null) {
return
}

if (deckAdapter.itemCount >= 1) {
showSnackbar(getString(R.string.deck_limit_reached_card_analysis))
} else {
deckAdapter.addDeck(deck)
updateViewVisibility()
}
}

Expand Down Expand Up @@ -165,7 +167,7 @@ class CardAnalysisExtraWidgetConfig : FragmentActivity(), DeckSelectionListener
val selectedDecks = deckAdapter.deckIds.map { it.toString() }

// Store the selected deck IDs using WidgetPreferences
CardAnalysisExtraWidgetPreferences.saveSelectedDecksCardAnalysisExtraWidgetData(appWidgetId, selectedDecks)
CardAnalysisExtraWidgetPreferences.saveSelectedDecksCardAnalysisExtraData(appWidgetId, selectedDecks)

// Create an intent to trigger a widget update
val updateIntent = Intent(this, CardAnalysisExtraWidget::class.java).apply {
Expand All @@ -182,7 +184,7 @@ class CardAnalysisExtraWidgetConfig : FragmentActivity(), DeckSelectionListener

/** Deletes the widget data associated with the given app widget ID. */
private fun deleteWidgetData(appWidgetId: Int) {
CardAnalysisExtraWidgetPreferences.deleteCardAnalysisExtraWidgetData(appWidgetId)
CardAnalysisExtraWidgetPreferences.deleteCardAnalysisExtraData(appWidgetId)
}

/** BroadcastReceiver to handle widget removal. */
Expand Down
66 changes: 3 additions & 63 deletions AnkiDroid/src/main/java/com/ichi2/widget/DeckPickerWidget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@

package com.ichi2.widget

import android.app.AlarmManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.os.SystemClock
import android.widget.RemoteViews
import com.ichi2.anki.AnkiDroidApp.Companion.applicationScope
import com.ichi2.anki.CollectionManager.withCol
Expand All @@ -30,7 +27,6 @@ import com.ichi2.anki.analytics.UsageAnalytics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.time.Duration.Companion.seconds

/**
* Data class representing the data for a deck displayed in the widget.
Expand Down Expand Up @@ -58,7 +54,7 @@ data class DeckPickerWidgetData(
* It can be resized vertically & horizontally.
* No user actions can be performed from this widget as of now; it is for display purposes only.
*/
class DeckPickerWidget : AnalyticsWidgetProvider() {
class DeckPickerWidget : WidgetAlarm() {

companion object {
const val ACTION_APPWIDGET_UPDATE = AppWidgetManager.ACTION_APPWIDGET_UPDATE
Expand Down Expand Up @@ -98,62 +94,6 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
appWidgetManager.updateAppWidget(widgetId, remoteViews)
}
}

/**
* Sets a recurring alarm to update the widget every minute.
*
* @param context the context of the application
* @param appWidgetId the ID of the widget
*/
private fun setRecurringAlarm(context: Context, appWidgetId: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, DeckPickerWidget::class.java).apply {
action = ACTION_UPDATE_WIDGET
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)

/**
* When onUpdate is called, the code checks if an existing alarm PendingIntent
* is already set for the widget .If an Alarm Already Exists: PendingIntent.getBroadcast
* returns the existing PendingIntent, and pendingIntent is not null.
* The if block is skipped, and no new alarm is set.
*/

if (pendingIntent != null) {
Timber.v("Recurring alarm PendingIntent already exists for widget ID: $appWidgetId")
return
}

Timber.v("Creating a new recurring alarm PendingIntent for widget ID: $appWidgetId")
val newPendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)

// Set alarm to trigger every minute
val ONE_MINUTE_MILLIS = 60.seconds.inWholeMilliseconds
alarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + ONE_MINUTE_MILLIS,
ONE_MINUTE_MILLIS,
newPendingIntent
)
}

/**
* Cancels the recurring alarm for the widget.
*
* @param context the context of the application
* @param appWidgetId the ID of the widget
*/
private fun cancelRecurringAlarm(context: Context, appWidgetId: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, DeckPickerWidget::class.java).apply {
action = ACTION_UPDATE_WIDGET
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
Timber.d("Canceling recurring alarm for widget ID: $appWidgetId")
alarmManager.cancel(pendingIntent)
}
}

override fun performUpdate(
Expand All @@ -168,8 +108,8 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
val selectedDeckIds = widgetPreferences.getSelectedDeckIdsFromPreferencesDeckPickerWidgetData(widgetId)
if (selectedDeckIds.isNotEmpty()) {
updateWidget(context, appWidgetManager, intArrayOf(widgetId), selectedDeckIds)
setRecurringAlarm(context, widgetId, DeckPickerWidget::class.java)
}
setRecurringAlarm(context, widgetId)
}
}

Expand Down Expand Up @@ -214,7 +154,7 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
super.onDeleted(context, appWidgetIds)
appWidgetIds?.forEach { widgetId ->
cancelRecurringAlarm(context!!, widgetId)
cancelRecurringAlarm(context!!, widgetId, DeckPickerWidget::class.java)
}
}
}
Expand Down
86 changes: 86 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/widget/WidgetAlarm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright (c) 2024 Anoop <[email protected]>
This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.ichi2.widget

import android.app.AlarmManager
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.os.SystemClock
import timber.log.Timber
import kotlin.time.Duration.Companion.seconds

/**
* Currently used in Deck Picker Widget and Card Analysis Extra Widget.
*/
abstract class WidgetAlarm : AnalyticsWidgetProvider() {

companion object {
const val ACTION_UPDATE_WIDGET = "com.ichi2.widget.ACTION_UPDATE_WIDGET"

/**
* Sets a recurring alarm to update the widget every minute.
*
* @param context the context of the application
* @param appWidgetId the ID of the widget
* @param widgetClass the class of the widget
*/
fun setRecurringAlarm(context: Context, appWidgetId: Int, widgetClass: Class<*>) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, widgetClass).apply {
action = ACTION_UPDATE_WIDGET
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE)

if (pendingIntent != null) {
Timber.v("Recurring alarm PendingIntent already exists for widget ID: $appWidgetId")
return
}

Timber.v("Creating a new recurring alarm PendingIntent for widget ID: $appWidgetId")
val newPendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)

// Set alarm to trigger every minute
val ONE_MINUTE_MILLIS = 60.seconds.inWholeMilliseconds
alarmManager.setRepeating(
AlarmManager.ELAPSED_REALTIME,
SystemClock.elapsedRealtime() + ONE_MINUTE_MILLIS,
ONE_MINUTE_MILLIS,
newPendingIntent
)
}

/**
* Cancels the recurring alarm for the widget.
*
* @param context the context of the application
* @param appWidgetId the ID of the widget
* @param widgetClass the class of the widget
*/
fun cancelRecurringAlarm(context: Context, appWidgetId: Int, widgetClass: Class<*>) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, widgetClass).apply {
action = ACTION_UPDATE_WIDGET
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
}
val pendingIntent = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
Timber.d("Canceling recurring alarm for widget ID: $appWidgetId")
alarmManager.cancel(pendingIntent)
}
}
}
Loading

0 comments on commit 5a1f9cc

Please sign in to comment.