Skip to content

Commit d46b463

Browse files
committed
refactor(widgets): decouple widget files from app module using bridge interfaces
Replace direct app-module dependencies (UsageAnalytics, IntentHandler, CollectionManager, AnkiDroidApp, CrashReportService, MetaDB, Prefs) with WidgetDependencies bridge calls. Move AnalyticsWidgetProvider and WidgetAlarm to :widgets module. Change performUpdate parameter type from UsageAnalytics to WidgetAnalytics across all widget providers.
1 parent 72e313e commit d46b463

10 files changed

Lines changed: 60 additions & 73 deletions

File tree

AnkiDroid/src/main/java/com/ichi2/widget/AddNoteWidget.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ import android.content.Context
1919
import android.widget.RemoteViews
2020
import androidx.core.app.PendingIntentCompat
2121
import com.ichi2.anki.R
22-
import com.ichi2.anki.analytics.UsageAnalytics
23-
import com.ichi2.anki.noteeditor.NoteEditorLauncher
22+
import com.ichi2.widget.bridge.WidgetAnalytics
23+
import com.ichi2.widget.bridge.WidgetDependencies
2424

2525
class AddNoteWidget : AnalyticsWidgetProvider() {
2626
override fun performUpdate(
2727
context: Context,
2828
appWidgetManager: AppWidgetManager,
2929
appWidgetIds: AppWidgetIds,
30-
usageAnalytics: UsageAnalytics,
30+
usageAnalytics: WidgetAnalytics,
3131
) {
3232
updateWidgets(context, appWidgetManager, appWidgetIds)
3333
}
@@ -47,7 +47,7 @@ class AddNoteWidget : AnalyticsWidgetProvider() {
4747
appWidgetIds: AppWidgetIds,
4848
) {
4949
val remoteViews = RemoteViews(context.packageName, R.layout.widget_add_note)
50-
val intent = NoteEditorLauncher.AddNote().toIntent(context)
50+
val intent = WidgetDependencies.intentFactory.intentToOpenNoteEditor(context)
5151
val pendingIntent = PendingIntentCompat.getActivity(context, 0, intent, 0, false)
5252
remoteViews.setOnClickPendingIntent(R.id.widget_add_note_button, pendingIntent)
5353
appWidgetManager.updateAppWidget(appWidgetIds, remoteViews)

AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.kt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,9 @@ import androidx.annotation.LayoutRes
3232
import androidx.core.app.PendingIntentCompat
3333
import androidx.core.content.ContextCompat
3434
import androidx.core.content.edit
35-
import com.ichi2.anki.AnkiDroidApp
36-
import com.ichi2.anki.IntentHandler
3735
import com.ichi2.anki.R
38-
import com.ichi2.anki.analytics.UsageAnalytics
39-
import com.ichi2.anki.compat.CompatHelper.Companion.registerReceiverCompat
40-
import com.ichi2.anki.preferences.sharedPrefs
36+
import com.ichi2.widget.bridge.WidgetAnalytics
37+
import com.ichi2.widget.bridge.WidgetDependencies
4138
import timber.log.Timber
4239
import kotlin.math.sqrt
4340

@@ -52,20 +49,20 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
5249
context: Context,
5350
appWidgetManager: AppWidgetManager,
5451
appWidgetIds: AppWidgetIds,
55-
usageAnalytics: UsageAnalytics,
52+
usageAnalytics: WidgetAnalytics,
5653
) {
5754
WidgetStatus.updateInBackground(context)
5855
}
5956

6057
override fun onEnabled(context: Context) {
6158
super.onEnabled(context)
62-
val preferences = context.sharedPrefs()
59+
val preferences = WidgetDependencies.preferences.sharedPrefs(context)
6360
preferences.edit(commit = true) { putBoolean("widgetSmallEnabled", true) }
6461
}
6562

6663
override fun onDisabled(context: Context) {
6764
super.onDisabled(context)
68-
val preferences = context.sharedPrefs()
65+
val preferences = WidgetDependencies.preferences.sharedPrefs(context)
6966
preferences.edit(commit = true) { putBoolean("widgetSmallEnabled", false) }
7067
}
7168

@@ -110,7 +107,7 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
110107
private fun buildUpdate(context: Context): RemoteViews {
111108
Timber.d("updating small widget UI")
112109
val updateViews = RemoteViews(context.packageName, widgetSmallLayout)
113-
val mounted = AnkiDroidApp.isSdCardMounted
110+
val mounted = WidgetDependencies.appState.isSdCardMounted
114111
if (!mounted) {
115112
updateViews.setViewVisibility(R.id.widget_due, View.INVISIBLE)
116113
updateViews.setViewVisibility(R.id.widget_eta, View.INVISIBLE)
@@ -128,10 +125,10 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
128125
if (action != null && action == Intent.ACTION_MEDIA_MOUNTED) {
129126
Timber.d("mountReceiver - Action = Media Mounted")
130127
if (remounted) {
131-
WidgetStatus.updateInBackground(AnkiDroidApp.instance)
128+
WidgetStatus.updateInBackground(WidgetDependencies.appState.applicationInstance)
132129
remounted = false
133130
if (mountReceiver != null) {
134-
AnkiDroidApp.instance.unregisterReceiver(mountReceiver)
131+
WidgetDependencies.appState.applicationInstance.unregisterReceiver(mountReceiver)
135132
}
136133
} else {
137134
remounted = true
@@ -142,7 +139,12 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
142139
val iFilter = IntentFilter()
143140
iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED)
144141
iFilter.addDataScheme("file")
145-
AnkiDroidApp.instance.registerReceiverCompat(mountReceiver, iFilter, ContextCompat.RECEIVER_EXPORTED)
142+
ContextCompat.registerReceiver(
143+
WidgetDependencies.appState.applicationInstance,
144+
mountReceiver,
145+
iFilter,
146+
ContextCompat.RECEIVER_EXPORTED,
147+
)
146148
}
147149
} else {
148150
// Compute the total number of cards due.
@@ -176,9 +178,7 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
176178

177179
// Add a click listener to open Anki from the icon.
178180
// This should be always there, whether there are due cards or not.
179-
val ankiDroidIntent = Intent(context, IntentHandler::class.java)
180-
ankiDroidIntent.action = Intent.ACTION_MAIN
181-
ankiDroidIntent.addCategory(Intent.CATEGORY_LAUNCHER)
181+
val ankiDroidIntent = WidgetDependencies.intentFactory.intentToMainActivity(context)
182182
val pendingAnkiDroidIntent =
183183
PendingIntentCompat.getActivity(
184184
context,

AnkiDroid/src/main/java/com/ichi2/widget/WidgetConfigScreenAdapter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ package com.ichi2.widget
1919
import android.view.LayoutInflater
2020
import android.view.ViewGroup
2121
import androidx.recyclerview.widget.RecyclerView
22-
import com.ichi2.anki.CollectionManager.withCol
2322
import com.ichi2.anki.common.utils.ext.indexOfOrNull
2423
import com.ichi2.anki.databinding.ItemWidgetDeckConfigBinding
2524
import com.ichi2.anki.libanki.DeckId
2625
import com.ichi2.anki.model.SelectableDeck
26+
import com.ichi2.widget.bridge.WidgetDependencies
2727
import kotlinx.coroutines.CoroutineScope
2828
import kotlinx.coroutines.Dispatchers
2929
import kotlinx.coroutines.launch
@@ -69,7 +69,7 @@ class WidgetConfigScreenAdapter(
6969
coroutineScope.launch {
7070
val deckName =
7171
withContext(Dispatchers.IO) {
72-
withCol { decks.getLegacy(deck.deckId)!!.name }
72+
WidgetDependencies.collectionAccess.withCol { decks.getLegacy(deck.deckId)!!.name }
7373
}
7474
holder.binding.deckNameTextView.text = deckName
7575
}

AnkiDroid/src/main/java/com/ichi2/widget/WidgetPermissionReceiver.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import android.content.BroadcastReceiver
2121
import android.content.ComponentName
2222
import android.content.Context
2323
import android.content.Intent
24-
import com.ichi2.anki.IntentHandler
24+
import com.ichi2.widget.bridge.WidgetDependencies
2525

2626
/**
2727
* BroadcastReceiver to handle the scenario where storage permissions are granted,
@@ -32,7 +32,7 @@ class WidgetPermissionReceiver : BroadcastReceiver() {
3232
context: Context,
3333
intent: Intent,
3434
) {
35-
if (IntentHandler.grantedStoragePermissions(context, showToast = false)) {
35+
if (WidgetDependencies.intentFactory.grantedStoragePermissions(context, showToast = false)) {
3636
val appWidgetManager = getAppWidgetManager(context) ?: return
3737
val widgetIds = appWidgetManager.getAppWidgetIdsEx(ComponentName(context, AddNoteWidget::class.java))
3838
AddNoteWidget.updateWidgets(context, appWidgetManager, widgetIds)

AnkiDroid/src/main/java/com/ichi2/widget/WidgetStatus.kt

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,9 @@
1515
package com.ichi2.widget
1616

1717
import android.content.Context
18-
import com.ichi2.anki.AnkiDroidApp
19-
import com.ichi2.anki.CollectionManager.withCol
20-
import com.ichi2.anki.MetaDB
2118
import com.ichi2.anki.R
22-
import com.ichi2.anki.preferences.sharedPrefs
23-
import com.ichi2.anki.settings.Prefs
2419
import com.ichi2.anki.utils.ext.allDecksCounts
20+
import com.ichi2.widget.bridge.WidgetDependencies
2521
import kotlinx.coroutines.DelicateCoroutinesApi
2622
import kotlinx.coroutines.GlobalScope
2723
import kotlinx.coroutines.Job
@@ -42,11 +38,11 @@ object WidgetStatus {
4238
* https://developer.android.com/guide/topics/appwidgets/#MetaData
4339
*/
4440
fun updateInBackground(context: Context) {
45-
val preferences = context.sharedPrefs()
41+
val preferences = WidgetDependencies.preferences.sharedPrefs(context)
4642
smallWidgetEnabled = preferences.getBoolean("widgetSmallEnabled", false)
4743
val canExecuteTask = smallWidgetUpdateJob == null || smallWidgetUpdateJob?.isActive == false
4844

49-
if (Prefs.newReviewRemindersEnabled) {
45+
if (WidgetDependencies.preferences.newReviewRemindersEnabled) {
5046
if (smallWidgetEnabled && canExecuteTask) {
5147
Timber.d("WidgetStatus.update(): updating")
5248
smallWidgetUpdateJob = launchSmallWidgetUpdateJob(context)
@@ -79,28 +75,28 @@ object WidgetStatus {
7975
}
8076

8177
suspend fun updateSmallWidgetStatus(context: Context) {
82-
if (!AnkiDroidApp.isSdCardMounted) {
78+
if (!WidgetDependencies.appState.isSdCardMounted) {
8379
Timber.w("updateStatus failed: no SD Card")
8480
return
8581
}
8682
val status = querySmallWidgetStatus()
87-
MetaDB.storeSmallWidgetStatus(context, status)
83+
WidgetDependencies.metaStorage.storeSmallWidgetStatus(context, status)
8884
if (smallWidgetEnabled) {
8985
Timber.i("triggering small widget UI update")
9086
AnkiDroidWidgetSmall.UpdateService().doUpdate(context)
9187
}
92-
if (!Prefs.newReviewRemindersEnabled) {
93-
(context.applicationContext as AnkiDroidApp).scheduleNotification()
88+
if (!WidgetDependencies.preferences.newReviewRemindersEnabled) {
89+
WidgetDependencies.appState.scheduleNotification(context)
9490
}
9591
}
9692

9793
/** Returns the status of each of the decks. */
98-
fun fetchSmall(context: Context): SmallWidgetStatus = MetaDB.getWidgetSmallStatus(context)
94+
fun fetchSmall(context: Context): SmallWidgetStatus = WidgetDependencies.metaStorage.getWidgetSmallStatus(context)
9995

100-
fun fetchDue(context: Context): Int = MetaDB.getNotificationStatus(context)
96+
fun fetchDue(context: Context): Int = WidgetDependencies.metaStorage.getNotificationStatus(context)
10197

10298
private suspend fun querySmallWidgetStatus(): SmallWidgetStatus =
103-
withCol {
99+
WidgetDependencies.collectionAccess.withCol {
104100
val total = sched.allDecksCounts()
105101
val eta = sched.eta(total, false)
106102
SmallWidgetStatus(total.count(), eta)

AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidget.kt

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,17 @@ import android.content.Context
2424
import android.content.Intent
2525
import android.view.View
2626
import android.widget.RemoteViews
27-
import com.ichi2.anki.AnkiDroidApp
28-
import com.ichi2.anki.CrashReportService
29-
import com.ichi2.anki.IntentHandler.Companion.intentToReviewDeckFromShortcuts
3027
import com.ichi2.anki.R
31-
import com.ichi2.anki.analytics.UsageAnalytics
32-
import com.ichi2.anki.isCollectionEmpty
3328
import com.ichi2.anki.libanki.DeckId
3429
import com.ichi2.anki.libanki.Decks.Companion.NOT_FOUND_DECK_ID
35-
import com.ichi2.anki.pages.DeckOptionsDestination
3630
import com.ichi2.widget.ACTION_UPDATE_WIDGET
3731
import com.ichi2.widget.AnalyticsWidgetProvider
3832
import com.ichi2.widget.AppWidgetId
3933
import com.ichi2.widget.AppWidgetId.Companion.INVALID_APPWIDGET_ID
4034
import com.ichi2.widget.AppWidgetId.Companion.getAppWidgetId
4135
import com.ichi2.widget.AppWidgetIds
36+
import com.ichi2.widget.bridge.WidgetAnalytics
37+
import com.ichi2.widget.bridge.WidgetDependencies
4238
import com.ichi2.widget.cancelRecurringAlarm
4339
import com.ichi2.widget.deckpicker.DeckWidgetData
4440
import com.ichi2.widget.deckpicker.getDeckNameAndStats
@@ -88,8 +84,8 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
8884
return
8985
}
9086

91-
AnkiDroidApp.applicationScope.launch {
92-
val isCollectionEmpty = isCollectionEmpty()
87+
WidgetDependencies.appState.applicationScope.launch {
88+
val isCollectionEmpty = WidgetDependencies.collectionAccess.isCollectionEmpty()
9389
if (isCollectionEmpty) {
9490
showCollectionDeck(context, appWidgetManager, appWidgetId, remoteViews)
9591
return@launch
@@ -193,9 +189,9 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
193189

194190
val intent =
195191
if (!isEmptyDeck) {
196-
intentToReviewDeckFromShortcuts(context, deckData.deckId)
192+
WidgetDependencies.intentFactory.intentToReviewDeck(context, deckData.deckId)
197193
} else {
198-
DeckOptionsDestination.fromDeckId(deckData.deckId).toIntent(context)
194+
WidgetDependencies.intentFactory.intentToDeckOptions(context, deckData.deckId)
199195
}
200196
val pendingIntent =
201197
PendingIntent.getActivity(
@@ -233,7 +229,7 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
233229
context: Context,
234230
appWidgetManager: AppWidgetManager,
235231
appWidgetIds: AppWidgetIds,
236-
usageAnalytics: UsageAnalytics,
232+
usageAnalytics: WidgetAnalytics,
237233
) {
238234
Timber.d("Performing widget update for appWidgetIds: %s", appWidgetIds)
239235

@@ -321,7 +317,7 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
321317
}
322318
else -> {
323319
Timber.e("Unexpected action received: ${intent.action}")
324-
CrashReportService.sendExceptionReport(
320+
WidgetDependencies.crashReporter.sendExceptionReport(
325321
Exception("Unexpected action received: ${intent.action}"),
326322
"CardAnalysisWidget - onReceive",
327323
onlyIfSilent = true,

AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidget.kt

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,16 @@ import android.content.Context
2424
import android.content.Intent
2525
import android.view.View
2626
import android.widget.RemoteViews
27-
import com.ichi2.anki.AnkiDroidApp
28-
import com.ichi2.anki.CollectionManager.withCol
29-
import com.ichi2.anki.CrashReportService
30-
import com.ichi2.anki.IntentHandler.Companion.intentToReviewDeckFromShortcuts
3127
import com.ichi2.anki.R
32-
import com.ichi2.anki.analytics.UsageAnalytics
33-
import com.ichi2.anki.isCollectionEmpty
3428
import com.ichi2.anki.libanki.DeckId
35-
import com.ichi2.anki.pages.DeckOptionsDestination
3629
import com.ichi2.widget.ACTION_UPDATE_WIDGET
3730
import com.ichi2.widget.AnalyticsWidgetProvider
3831
import com.ichi2.widget.AppWidgetId
3932
import com.ichi2.widget.AppWidgetId.Companion.INVALID_APPWIDGET_ID
4033
import com.ichi2.widget.AppWidgetId.Companion.getAppWidgetId
4134
import com.ichi2.widget.AppWidgetIds
35+
import com.ichi2.widget.bridge.WidgetAnalytics
36+
import com.ichi2.widget.bridge.WidgetDependencies
4237
import com.ichi2.widget.cancelRecurringAlarm
4338
import com.ichi2.widget.getAppWidgetIdsEx
4439
import com.ichi2.widget.setRecurringAlarm
@@ -103,8 +98,8 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
10398
showEmptyWidget(context, appWidgetManager, appWidgetId, remoteViews)
10499
return
105100
}
106-
AnkiDroidApp.applicationScope.launch {
107-
val isCollectionEmpty = isCollectionEmpty()
101+
WidgetDependencies.appState.applicationScope.launch {
102+
val isCollectionEmpty = WidgetDependencies.collectionAccess.isCollectionEmpty()
108103
if (isCollectionEmpty) {
109104
showEmptyCollection(context, appWidgetManager, appWidgetId, remoteViews)
110105
return@launch
@@ -144,9 +139,9 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
144139

145140
val intent =
146141
if (!isEmptyDeck) {
147-
intentToReviewDeckFromShortcuts(context, deck.deckId)
142+
WidgetDependencies.intentFactory.intentToReviewDeck(context, deck.deckId)
148143
} else {
149-
DeckOptionsDestination.fromDeckId(deck.deckId).toIntent(context)
144+
WidgetDependencies.intentFactory.intentToDeckOptions(context, deck.deckId)
150145
}
151146

152147
val pendingIntent =
@@ -243,7 +238,7 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
243238
context: Context,
244239
appWidgetManager: AppWidgetManager,
245240
appWidgetIds: AppWidgetIds,
246-
usageAnalytics: UsageAnalytics,
241+
usageAnalytics: WidgetAnalytics,
247242
) {
248243
Timber.d("Performing widget update for appWidgetIds: %s", appWidgetIds)
249244

@@ -347,7 +342,7 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
347342
}
348343
else -> {
349344
Timber.e("Unexpected action received: ${intent.action}")
350-
CrashReportService.sendExceptionReport(
345+
WidgetDependencies.crashReporter.sendExceptionReport(
351346
Exception("Unexpected action received: ${intent.action}"),
352347
"DeckPickerWidget - onReceive",
353348
onlyIfSilent = true,
@@ -387,7 +382,7 @@ suspend fun getDeckNameAndStats(deckId: DeckId): DeckWidgetData? = getDeckNamesA
387382
suspend fun getDeckNamesAndStats(deckIds: List<DeckId>): List<DeckWidgetData> {
388383
val result = mutableListOf<DeckWidgetData>()
389384

390-
val deckTree = withCol { sched.deckDueTree() }
385+
val deckTree = WidgetDependencies.collectionAccess.withCol { sched.deckDueTree() }
391386

392387
deckTree.forEach { node ->
393388
if (node.did !in deckIds) return@forEach

AnkiDroid/src/test/java/com/ichi2/anki/widget/AnalyticalWidgetProviderTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.ichi2.anki.RobolectricTest
2121
import com.ichi2.anki.analytics.UsageAnalytics
2222
import com.ichi2.widget.AnalyticsWidgetProvider
2323
import com.ichi2.widget.AppWidgetIds
24+
import com.ichi2.widget.bridge.WidgetAnalytics
2425
import io.mockk.every
2526
import io.mockk.mockkObject
2627
import io.mockk.unmockkObject
@@ -59,7 +60,7 @@ class AnalyticalWidgetProviderTest : RobolectricTest() {
5960
context: android.content.Context,
6061
appWidgetManager: AppWidgetManager,
6162
appWidgetIds: AppWidgetIds,
62-
usageAnalytics: UsageAnalytics,
63+
usageAnalytics: WidgetAnalytics,
6364
) {
6465
// Do nothing
6566
}

0 commit comments

Comments
 (0)