0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-20 03:52:15 +02:00

Refactor CardAnalysisWidget to handle single deck selection and update logic; adjust DeckNameAndStats to support changes

This commit is contained in:
Anoop 2024-08-31 15:42:17 +05:30 committed by David Allison
parent fc0f4fe03a
commit 9d3cb64a5b
6 changed files with 120 additions and 101 deletions

View File

@ -30,9 +30,11 @@ import com.ichi2.anki.R
import com.ichi2.anki.Reviewer import com.ichi2.anki.Reviewer
import com.ichi2.anki.analytics.UsageAnalytics import com.ichi2.anki.analytics.UsageAnalytics
import com.ichi2.anki.pages.DeckOptions import com.ichi2.anki.pages.DeckOptions
import com.ichi2.libanki.DeckId
import com.ichi2.widget.ACTION_UPDATE_WIDGET import com.ichi2.widget.ACTION_UPDATE_WIDGET
import com.ichi2.widget.AnalyticsWidgetProvider import com.ichi2.widget.AnalyticsWidgetProvider
import com.ichi2.widget.cancelRecurringAlarm import com.ichi2.widget.cancelRecurringAlarm
import com.ichi2.widget.deckpicker.DeckWidgetData
import com.ichi2.widget.deckpicker.getDeckNameAndStats import com.ichi2.widget.deckpicker.getDeckNameAndStats
import com.ichi2.widget.setRecurringAlarm import com.ichi2.widget.setRecurringAlarm
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -68,19 +70,32 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
context: Context, context: Context,
appWidgetManager: AppWidgetManager, appWidgetManager: AppWidgetManager,
appWidgetId: Int, appWidgetId: Int,
deckId: LongArray deckId: DeckId?
) { ) {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_card_analysis) val remoteViews = RemoteViews(context.packageName, R.layout.widget_card_analysis)
if (deckId == null) {
showMissingDeck(context, appWidgetManager, appWidgetId, remoteViews)
return
}
AnkiDroidApp.applicationScope.launch { AnkiDroidApp.applicationScope.launch {
val deckData = getDeckNameAndStats(deckId.toList()) val deckData = getDeckNameAndStats(deckId)
if (deckData.isEmpty()) { if (deckData == null) {
// If the deck was deleted, clear the stored deck ID // If the deck was deleted, clear the stored deck ID
val widgetPreferences = CardAnalysisWidgetPreferences(context) CardAnalysisWidgetPreferences(context).saveSelectedDeck(appWidgetId, null)
val selectedDeck = longArrayOf().map { it.toString() } showMissingDeck(context, appWidgetManager, appWidgetId, remoteViews)
widgetPreferences.saveSelectedDeck(appWidgetId, selectedDeck) return@launch
}
showDeck(context, appWidgetManager, appWidgetId, remoteViews, deckData)
}
}
private fun showMissingDeck(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int,
remoteViews: RemoteViews
) {
// Show empty_widget and set click listener to open configuration // Show empty_widget and set click listener to open configuration
remoteViews.setViewVisibility(R.id.empty_widget, View.VISIBLE) remoteViews.setViewVisibility(R.id.empty_widget, View.VISIBLE)
remoteViews.setViewVisibility(R.id.cardAnalysisDataHolder, View.GONE) remoteViews.setViewVisibility(R.id.cardAnalysisDataHolder, View.GONE)
@ -99,33 +114,38 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
remoteViews.setOnClickPendingIntent(R.id.empty_widget, configPendingIntent) remoteViews.setOnClickPendingIntent(R.id.empty_widget, configPendingIntent)
appWidgetManager.updateAppWidget(appWidgetId, remoteViews) appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
return@launch
} }
val deck = deckData[0] private fun showDeck(
remoteViews.setTextViewText(R.id.deckNameCardAnalysis, deck.name) context: Context,
remoteViews.setTextViewText(R.id.deckNew_card_analysis_widget, deck.newCount.toString()) appWidgetManager: AppWidgetManager,
remoteViews.setTextViewText(R.id.deckDue_card_analysis_widget, deck.reviewCount.toString()) appWidgetId: Int,
remoteViews.setTextViewText(R.id.deckLearn_card_analysis_widget, deck.learnCount.toString()) remoteViews: RemoteViews,
deckData: DeckWidgetData
) {
remoteViews.setTextViewText(R.id.deckNameCardAnalysis, deckData.name)
remoteViews.setTextViewText(R.id.deckNew_card_analysis_widget, deckData.newCount.toString())
remoteViews.setTextViewText(R.id.deckDue_card_analysis_widget, deckData.reviewCount.toString())
remoteViews.setTextViewText(R.id.deckLearn_card_analysis_widget, deckData.learnCount.toString())
// Hide empty_widget and show the actual widget content // Hide empty_widget and show the actual widget content
remoteViews.setViewVisibility(R.id.empty_widget, View.GONE) remoteViews.setViewVisibility(R.id.empty_widget, View.GONE)
remoteViews.setViewVisibility(R.id.cardAnalysisDataHolder, View.VISIBLE) remoteViews.setViewVisibility(R.id.cardAnalysisDataHolder, View.VISIBLE)
remoteViews.setViewVisibility(R.id.deckNameCardAnalysis, View.VISIBLE) remoteViews.setViewVisibility(R.id.deckNameCardAnalysis, View.VISIBLE)
val isEmptyDeck = deck.newCount == 0 && deck.reviewCount == 0 && deck.learnCount == 0 val isEmptyDeck = deckData.newCount == 0 && deckData.reviewCount == 0 && deckData.learnCount == 0
val intent = if (!isEmptyDeck) { val intent = if (!isEmptyDeck) {
Intent(context, Reviewer::class.java).apply { Intent(context, Reviewer::class.java).apply {
action = Intent.ACTION_VIEW action = Intent.ACTION_VIEW
putExtra("deckId", deck.deckId) putExtra("deckId", deckData.deckId)
} }
} else { } else {
DeckOptions.getIntent(context, deck.deckId) DeckOptions.getIntent(context, deckData.deckId)
} }
val pendingIntent = PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
context, context,
deck.deckId.toInt(), deckData.deckId.toInt(),
intent, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
) )
@ -133,7 +153,6 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
appWidgetManager.updateAppWidget(appWidgetId, remoteViews) appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
} }
}
/** /**
* Updates the Card Analysis Widgets based on the current state of the application. * Updates the Card Analysis Widgets based on the current state of the application.
@ -207,7 +226,7 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && selectedDeckId != -1L) { if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && selectedDeckId != -1L) {
Timber.d("Updating widget with ID: $appWidgetId") Timber.d("Updating widget with ID: $appWidgetId")
// Wrap selectedDeckId into a LongArray // Wrap selectedDeckId into a LongArray
updateWidget(context, appWidgetManager, appWidgetId, longArrayOf(selectedDeckId)) updateWidget(context, appWidgetManager, appWidgetId, selectedDeckId)
Timber.d("Widget update process completed for widget ID: $appWidgetId") Timber.d("Widget update process completed for widget ID: $appWidgetId")
} }
} }

View File

@ -104,8 +104,8 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac
initializeUIComponents() initializeUIComponents()
// Show the Deck selection dialog only when there are no decks selected while opening the configuration screen. // Show the Deck selection dialog only when there are no decks selected while opening the configuration screen.
val selectedDeckIds = cardAnalysisWidgetPreferences.getSelectedDeckIdFromPreferences(appWidgetId) val selectedDeckId = cardAnalysisWidgetPreferences.getSelectedDeckIdFromPreferences(appWidgetId)
if (selectedDeckIds.isEmpty()) { if (selectedDeckId == null) {
showDeckSelectionDialog() showDeckSelectionDialog()
} }
} }
@ -123,7 +123,7 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac
} }
fun initializeUIComponents() { fun initializeUIComponents() {
deckAdapter = WidgetConfigScreenAdapter { deck, position -> deckAdapter = WidgetConfigScreenAdapter { deck, _ ->
deckAdapter.removeDeck(deck.deckId) deckAdapter.removeDeck(deck.deckId)
showSnackbar(R.string.deck_removed_from_widget) showSnackbar(R.string.deck_removed_from_widget)
updateViewVisibility() updateViewVisibility()
@ -220,17 +220,15 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac
/** Updates the view according to the saved preference for appWidgetId.*/ /** Updates the view according to the saved preference for appWidgetId.*/
fun updateViewWithSavedPreferences() { fun updateViewWithSavedPreferences() {
val selectedDeckIds = cardAnalysisWidgetPreferences.getSelectedDeckIdFromPreferences(appWidgetId) val selectedDeckId = cardAnalysisWidgetPreferences.getSelectedDeckIdFromPreferences(appWidgetId) ?: return
if (selectedDeckIds.isNotEmpty()) {
lifecycleScope.launch { lifecycleScope.launch {
val decks = fetchDecks() val decks = fetchDecks()
val selectedDecks = decks.filter { it.deckId in selectedDeckIds } val selectedDecks = decks.filter { it.deckId == selectedDeckId }
selectedDecks.forEach { deckAdapter.addDeck(it) } selectedDecks.forEach { deckAdapter.addDeck(it) }
updateViewVisibility() updateViewVisibility()
updateFabVisibility() updateFabVisibility()
} }
} }
}
/** Asynchronously displays the list of deck in the selection dialog. */ /** Asynchronously displays the list of deck in the selection dialog. */
private fun showDeckSelectionDialog() { private fun showDeckSelectionDialog() {
@ -287,9 +285,9 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac
hasUnsavedChanges = false hasUnsavedChanges = false
setUnsavedChanges(false) setUnsavedChanges(false)
val selectedDeckIds = cardAnalysisWidgetPreferences.getSelectedDeckIdFromPreferences(appWidgetId) val selectedDeckId = cardAnalysisWidgetPreferences.getSelectedDeckIdFromPreferences(appWidgetId)
val appWidgetManager = AppWidgetManager.getInstance(this) val appWidgetManager = AppWidgetManager.getInstance(this)
CardAnalysisWidget.updateWidget(this, appWidgetManager, appWidgetId, selectedDeckIds) CardAnalysisWidget.updateWidget(this, appWidgetManager, appWidgetId, selectedDeckId)
val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) val resultValue = Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
setResult(RESULT_OK, resultValue) setResult(RESULT_OK, resultValue)
@ -310,14 +308,14 @@ class CardAnalysisWidgetConfig : AnkiActivity(), DeckSelectionListener, BaseSnac
} }
fun saveSelectedDecksToPreferencesCardAnalysisWidget() { fun saveSelectedDecksToPreferencesCardAnalysisWidget() {
val selectedDecks = deckAdapter.deckIds.map { it } val selectedDeck = deckAdapter.deckIds.getOrNull(0)
cardAnalysisWidgetPreferences.saveSelectedDeck(appWidgetId, selectedDecks.map { it.toString() }) cardAnalysisWidgetPreferences.saveSelectedDeck(appWidgetId, selectedDeck)
val updateIntent = Intent(this, CardAnalysisWidget::class.java).apply { val updateIntent = Intent(this, CardAnalysisWidget::class.java).apply {
action = AppWidgetManager.ACTION_APPWIDGET_UPDATE action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId)) putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId))
putExtra("card_analysis_widget_selected_deck_ids", selectedDecks.toList().toLongArray()) putExtra("card_analysis_widget_selected_deck_ids", selectedDeck)
} }
sendBroadcast(updateIntent) sendBroadcast(updateIntent)

View File

@ -18,6 +18,8 @@ package com.ichi2.widget.cardanalysis
import android.content.Context import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import com.ichi2.libanki.DeckId
import com.ichi2.libanki.Decks.Companion.NOT_FOUND_DECK_ID
class CardAnalysisWidgetPreferences(context: Context) { class CardAnalysisWidgetPreferences(context: Context) {
@ -39,21 +41,17 @@ class CardAnalysisWidgetPreferences(context: Context) {
} }
} }
fun getSelectedDeckIdFromPreferences(appWidgetId: Int): LongArray { fun getSelectedDeckIdFromPreferences(appWidgetId: Int): DeckId? {
val selectedDeckString = cardAnalysisWidgetSharedPreferences.getString( val selectedDeckString = cardAnalysisWidgetSharedPreferences.getLong(
getCardAnalysisExtraWidgetKey(appWidgetId), getCardAnalysisExtraWidgetKey(appWidgetId),
"" NOT_FOUND_DECK_ID
) )
return if (!selectedDeckString.isNullOrEmpty()) { return selectedDeckString.takeIf { it != NOT_FOUND_DECK_ID }
selectedDeckString.split(",").map { it.toLong() }.toLongArray()
} else {
longArrayOf()
}
} }
fun saveSelectedDeck(appWidgetId: Int, selectedDeck: List<String>) { fun saveSelectedDeck(appWidgetId: Int, selectedDeck: DeckId?) {
cardAnalysisWidgetSharedPreferences.edit { cardAnalysisWidgetSharedPreferences.edit {
putString(getCardAnalysisExtraWidgetKey(appWidgetId), selectedDeck.joinToString(",")) putLong(getCardAnalysisExtraWidgetKey(appWidgetId), selectedDeck ?: NOT_FOUND_DECK_ID)
} }
} }
} }

View File

@ -30,6 +30,7 @@ import com.ichi2.anki.R
import com.ichi2.anki.Reviewer import com.ichi2.anki.Reviewer
import com.ichi2.anki.analytics.UsageAnalytics import com.ichi2.anki.analytics.UsageAnalytics
import com.ichi2.anki.pages.DeckOptions import com.ichi2.anki.pages.DeckOptions
import com.ichi2.libanki.DeckId
import com.ichi2.widget.ACTION_UPDATE_WIDGET import com.ichi2.widget.ACTION_UPDATE_WIDGET
import com.ichi2.widget.AnalyticsWidgetProvider import com.ichi2.widget.AnalyticsWidgetProvider
import com.ichi2.widget.cancelRecurringAlarm import com.ichi2.widget.cancelRecurringAlarm
@ -37,7 +38,6 @@ import com.ichi2.widget.setRecurringAlarm
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
typealias DeckId = Long
typealias AppWidgetId = Int typealias AppWidgetId = Int
/** /**
@ -49,7 +49,7 @@ typealias AppWidgetId = Int
* @property learnCount The number of cards in the learning phase. * @property learnCount The number of cards in the learning phase.
* @property newCount The number of new cards. * @property newCount The number of new cards.
*/ */
data class DeckPickerWidgetData( data class DeckWidgetData(
val deckId: DeckId, val deckId: DeckId,
val name: String, val name: String,
val reviewCount: Int, val reviewCount: Int,
@ -97,7 +97,7 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_deck_picker_large) val remoteViews = RemoteViews(context.packageName, R.layout.widget_deck_picker_large)
AnkiDroidApp.applicationScope.launch { AnkiDroidApp.applicationScope.launch {
val deckData = getDeckNameAndStats(deckIds.toList()) val deckData = getDeckNamesAndStats(deckIds.toList())
remoteViews.removeAllViews(R.id.deckCollection) remoteViews.removeAllViews(R.id.deckCollection)
@ -274,18 +274,22 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
* *
* Note: This operation may be slow, as it involves processing the entire deck collection. * Note: This operation may be slow, as it involves processing the entire deck collection.
* *
* @param deckIds the list of deck IDs to retrieve data for * @param deckId the list of deck ID to retrieve data for
* @return a list of DeckPickerWidgetData objects containing deck names and statistics * @return a list of DeckPickerWidgetData objects containing deck names and statistics
*/ */
suspend fun getDeckNameAndStats(deckIds: List<DeckId>): List<DeckPickerWidgetData> { suspend fun getDeckNameAndStats(deckId: DeckId): DeckWidgetData? {
val result = mutableListOf<DeckPickerWidgetData>() return getDeckNamesAndStats(listOf(deckId)).getOrNull(0)
}
suspend fun getDeckNamesAndStats(deckIds: List<DeckId>): List<DeckWidgetData> {
val result = mutableListOf<DeckWidgetData>()
val deckTree = withCol { sched.deckDueTree() } val deckTree = withCol { sched.deckDueTree() }
deckTree.forEach { node -> deckTree.forEach { node ->
if (node.did !in deckIds) return@forEach if (node.did !in deckIds) return@forEach
result.add( result.add(
DeckPickerWidgetData( DeckWidgetData(
deckId = node.did, deckId = node.did,
name = node.lastDeckNameComponent, name = node.lastDeckNameComponent,
reviewCount = node.revCount, reviewCount = node.revCount,

View File

@ -82,8 +82,8 @@ class CardAnalysisWidgetConfigTest : RobolectricTest() {
activity.saveSelectedDecksToPreferencesCardAnalysisWidget() activity.saveSelectedDecksToPreferencesCardAnalysisWidget()
// Verify saved decks // Verify saved decks
val selectedDeckIds = widgetPreferences.getSelectedDeckIdFromPreferences(1).toList() val selectedDeckId = widgetPreferences.getSelectedDeckIdFromPreferences(1)
assertThat(selectedDeckIds.contains(deck1.deckId), equalTo(true)) assertThat(selectedDeckId, equalTo(deck1.deckId))
} }
/** /**
@ -95,8 +95,8 @@ class CardAnalysisWidgetConfigTest : RobolectricTest() {
@Test @Test
fun testLoadSavedPreferences() { fun testLoadSavedPreferences() {
// Save decks to preferences // Save decks to preferences
val deckIds = listOf(1L) val deckId = 1L
widgetPreferences.saveSelectedDeck(1, deckIds.map { it.toString() }) widgetPreferences.saveSelectedDeck(1, deckId)
// Load preferences // Load preferences
activity.updateViewWithSavedPreferences() activity.updateViewWithSavedPreferences()
@ -109,7 +109,7 @@ class CardAnalysisWidgetConfigTest : RobolectricTest() {
val adapter = recyclerView.adapter val adapter = recyclerView.adapter
// Verify the adapter has the correct item count // Verify the adapter has the correct item count
assertThat(adapter?.itemCount, equalTo(deckIds.size)) assertThat(adapter?.itemCount, equalTo(1))
} }
/** /**

View File

@ -18,7 +18,7 @@ package com.ichi2.anki.widget.deckpicker
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.RobolectricTest import com.ichi2.anki.RobolectricTest
import com.ichi2.widget.deckpicker.getDeckNameAndStats import com.ichi2.widget.deckpicker.getDeckNamesAndStats
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -32,7 +32,7 @@ class DeckNameAndStatsTest : RobolectricTest() {
val deck2Id = addDeck("Deck 2") val deck2Id = addDeck("Deck 2")
val deckIds = listOf(deck1Id, deck2Id) val deckIds = listOf(deck1Id, deck2Id)
val result = getDeckNameAndStats(deckIds) val result = getDeckNamesAndStats(deckIds)
assertEquals(2, result.size) assertEquals(2, result.size)
assertEquals("Deck 1", result[0].name) assertEquals("Deck 1", result[0].name)
@ -48,7 +48,7 @@ class DeckNameAndStatsTest : RobolectricTest() {
val deckCId = addDeck("Deck C") val deckCId = addDeck("Deck C")
val deckIds = listOf(deckCId, deckAId, deckBId) val deckIds = listOf(deckCId, deckAId, deckBId)
val result = getDeckNameAndStats(deckIds) val result = getDeckNamesAndStats(deckIds)
assertEquals(3, result.size) assertEquals(3, result.size)
assertEquals("Deck C", result[0].name) assertEquals("Deck C", result[0].name)
@ -65,7 +65,7 @@ class DeckNameAndStatsTest : RobolectricTest() {
val child1Id = addDeck("Deck 1::Child 1") val child1Id = addDeck("Deck 1::Child 1")
val deckIds = listOf(deck1Id, child1Id) val deckIds = listOf(deck1Id, child1Id)
val result = getDeckNameAndStats(deckIds) val result = getDeckNamesAndStats(deckIds)
assertEquals(2, result.size) assertEquals(2, result.size)
assertEquals("Deck 1", result[0].name) assertEquals("Deck 1", result[0].name)