Skip to content

Commit

Permalink
Having a check for storage permission, and removed an extra snackbar.
Browse files Browse the repository at this point in the history
  • Loading branch information
xenonnn4w committed Aug 19, 2024
1 parent d9582f8 commit aa658a4
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 89 deletions.
1 change: 1 addition & 0 deletions AnkiDroid/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@
The way to add it depends on the phone. It usually consists in a long press on the screen, followed by finding a "widget" button"-->
<receiver
android:name="com.ichi2.widget.DeckPickerWidget"
android:label="@string/deck_picker_widget_description"
android:exported="false"
>
<intent-filter>
Expand Down
36 changes: 23 additions & 13 deletions AnkiDroid/src/main/java/com/ichi2/anki/snackbar/Snackbars.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ interface BaseSnackbarBuilderProvider {
fun Activity.showSnackbar(
@StringRes textResource: Int,
duration: Int = Snackbar.LENGTH_LONG,
anchorView: View? = null,
snackbarBuilder: SnackbarBuilder? = null
) {
val text = getText(textResource)
showSnackbar(text, duration, snackbarBuilder)
showSnackbar(text, duration, anchorView, snackbarBuilder)
}

/**
Expand Down Expand Up @@ -104,13 +105,14 @@ fun Activity.showSnackbar(
fun Activity.showSnackbar(
text: CharSequence,
duration: Int = Snackbar.LENGTH_LONG,
anchorView: View? = null,
snackbarBuilder: SnackbarBuilder? = null
) {
val view: View? = findViewById(R.id.root_layout) as? CoordinatorLayout

if (view != null) {
val baseSnackbarBuilder = (this as? BaseSnackbarBuilderProvider)?.baseSnackbarBuilder
view.showSnackbar(text, duration) {
view.showSnackbar(text, duration, anchorView = anchorView) {
baseSnackbarBuilder?.invoke(this)
snackbarBuilder?.invoke(this)
Timber.d("displayed snackbar: '%s'", text)
Expand Down Expand Up @@ -157,10 +159,11 @@ fun Activity.showSnackbar(
fun View.showSnackbar(
@StringRes textResource: Int,
duration: Int = Snackbar.LENGTH_LONG,
anchorView: View? = null,
snackbarBuilder: SnackbarBuilder? = null
) {
val text = resources.getText(textResource)
showSnackbar(text, duration, snackbarBuilder)
showSnackbar(text, duration, anchorView, snackbarBuilder)
}

/**
Expand Down Expand Up @@ -192,19 +195,24 @@ fun View.showSnackbar(
fun View.showSnackbar(
text: CharSequence,
duration: Int = Snackbar.LENGTH_LONG,
anchorView: View? = null,
snackbarBuilder: SnackbarBuilder? = null
) {
val snackbar = Snackbar.make(this, text, duration)
snackbar.setMaxLines(4)
snackbar.behavior = SwipeDismissBehaviorFix()
Snackbar.make(this, text, duration).apply {
this.anchorView = anchorView
setMaxLines(4)
behavior = SwipeDismissBehaviorFix()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
snackbar.fixMarginsWhenInsetsChange()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
fixMarginsWhenInsetsChange()
}

if (snackbarBuilder != null) { snackbar.snackbarBuilder() }
if (snackbarBuilder != null) {
snackbarBuilder()
}

snackbar.show()
show()
}
}

/**
Expand Down Expand Up @@ -236,10 +244,11 @@ fun View.showSnackbar(
fun Fragment.showSnackbar(
text: CharSequence,
duration: Int = Snackbar.LENGTH_LONG,
anchorView: View? = null,
snackbarBuilder: SnackbarBuilder? = null
) {
val baseSnackbarBuilder = (this as? BaseSnackbarBuilderProvider)?.baseSnackbarBuilder
requireActivity().showSnackbar(text, duration) {
requireActivity().showSnackbar(text, duration, anchorView = anchorView) {
baseSnackbarBuilder?.invoke(this)
snackbarBuilder?.invoke(this)
Timber.d("displayed snackbar: '%s'", text)
Expand Down Expand Up @@ -275,10 +284,11 @@ fun Fragment.showSnackbar(
fun Fragment.showSnackbar(
@StringRes textResource: Int,
duration: Int = Snackbar.LENGTH_LONG,
anchorView: View? = null,
snackbarBuilder: SnackbarBuilder? = null
) {
val text = resources.getText(textResource)
showSnackbar(text, duration, snackbarBuilder)
showSnackbar(text, duration, anchorView, snackbarBuilder)
}

/* ********************************************************************************************** */
Expand Down
11 changes: 6 additions & 5 deletions AnkiDroid/src/main/java/com/ichi2/widget/DeckPickerWidget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,8 @@ class DeckPickerWidget : AnalyticsWidgetProvider(), ChangeManager.Subscriber {
* Each ID corresponds to a specific deck, and the view will
* contain exactly the decks whose IDs are in this list.
*
* TODO: Implement the following enhancements:
* 1. Instead of doing nothing when a deck has no cards to review, open the Study Options screen
* so the user can choose to do a custom study.
* 2. If the deck is completely empty (no cards at all), display a Snackbar or Toast message
* saying "The deck is empty" instead of opening any activity.
* TODO: If the deck is completely empty (no cards at all), display a Snackbar or Toast message
* saying "The deck is empty" instead of opening any activity.
*
*/
fun updateWidget(
Expand Down Expand Up @@ -322,13 +319,17 @@ class DeckPickerWidget : AnalyticsWidgetProvider(), ChangeManager.Subscriber {
}
}
AppWidgetManager.ACTION_APPWIDGET_DELETED -> {
Timber.d("ACTION_APPWIDGET_DELETED received")
val appWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID
)
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
Timber.d("Deleting widget with ID: $appWidgetId")
cancelRecurringAlarm(context, appWidgetId)
widgetPreferences.deleteDeckPickerWidgetData(appWidgetId)
} else {
Timber.e("Invalid widget ID received in ACTION_APPWIDGET_DELETED")
}
}
AppWidgetManager.ACTION_APPWIDGET_ENABLED -> {
Expand Down
56 changes: 25 additions & 31 deletions AnkiDroid/src/main/java/com/ichi2/widget/DeckPickerWidgetConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
lateinit var deckAdapter: WidgetConfigScreenAdapter
private lateinit var deckPickerWidgetPreferences: WidgetPreferences
private val MAX_DECKS_ALLOWED = 5 // Maximum number of decks allowed in the widget
private var hasUnsavedChanges = false // Flag to track unsaved changes

/**
* Maximum number of decks allowed in the widget.
*/
private val MAX_DECKS_ALLOWED = 5
private var hasUnsavedChanges = false
private var dueTree: DeckNode? = null

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -69,6 +73,11 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {
}

super.onCreate(savedInstanceState)

if (!ensureStoragePermissions()) {
return
}

setContentView(R.layout.widget_deck_picker_config)

deckPickerWidgetPreferences = WidgetPreferences(this)
Expand All @@ -84,7 +93,7 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {
return
}

// Check if the collection is empty before proceeding
// Check if the collection is empty before proceeding and if the collection is empty, show a toast instead of the configuration view.
lifecycleScope.launch {
val tree = withCol { sched.deckDueTree() }
if (isCollectionEmpty(tree)) {
Expand All @@ -103,15 +112,17 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {
}

@SuppressLint("DirectSnackbarMakeUsage")
fun showSnackbar(message: CharSequence) {
val v: View = findViewById(R.id.widgetConfigContainer)
v.showSnackbar(
message,
Snackbar.LENGTH_LONG,
findViewById<FloatingActionButton>(R.id.fabWidgetDeckPicker)
)
}

fun showSnackbar(messageResId: Int) {
val snackbar = Snackbar.make(
findViewById(R.id.widgetConfigContainer),
getString(messageResId),
Snackbar.LENGTH_LONG
).apply {
anchorView = findViewById<FloatingActionButton>(R.id.fabWidgetDeckPicker)
}
snackbar.show()
showSnackbar(getString(messageResId))
}

// Method to initialize UI components
Expand Down Expand Up @@ -139,18 +150,7 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {

// TODO: Implement multi-select functionality so that user can select desired decks in once.
findViewById<FloatingActionButton>(R.id.fabWidgetDeckPicker).setOnClickListener {
lifecycleScope.launch {
val tree = dueTree
if (tree != null) {
// Check if the default deck is the only available deck and there are no cards
val isEmpty = isCollectionEmpty(tree)
if (isEmpty) {
showSnackbar(R.string.no_decks_available_message)
} else {
showDeckSelectionDialog()
}
}
}
showDeckSelectionDialog()
}

// Load and display saved preferences
Expand Down Expand Up @@ -341,8 +341,7 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {
if (isDeckAlreadySelected) {
// Show snackbar if the deck is already selected
// TODO: Eventually, ensure that the user can't select a deck that is already selected.
val message = getString(R.string.deck_already_selected_message)
showSnackbar(message)
showSnackbar(getString(R.string.deck_already_selected_message))
return
}

Expand Down Expand Up @@ -439,11 +438,6 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {
sendBroadcast(updateIntent)
}

/** Deletes the widget data associated with the given app widget ID. */
private fun deleteWidgetDataDeckPickerWidget(appWidgetId: Int) {
deckPickerWidgetPreferences.deleteDeckPickerWidgetData(appWidgetId)
}

/** BroadcastReceiver to handle widget removal. */
private val widgetRemovedReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
Expand All @@ -456,7 +450,7 @@ class DeckPickerWidgetConfig : AnkiActivity(), DeckSelectionListener {
return
}

context?.let { deleteWidgetDataDeckPickerWidget(appWidgetId) }
context?.let { deckPickerWidgetPreferences.deleteDeckPickerWidgetData(appWidgetId) }
}
}

Expand Down
39 changes: 26 additions & 13 deletions AnkiDroid/src/main/java/com/ichi2/widget/WidgetPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,29 @@ import androidx.core.content.edit
*/
class WidgetPreferences(context: Context) {

/**
* Prefix for the SharedPreferences key used to store the selected decks for the DeckPickerWidget.
* The full key is constructed by appending the appWidgetId to this prefix, ensuring that each
* widget instance has a unique key. This approach helps prevent typos and ensures consistency
* across the codebase when accessing or modifying the stored deck selections.
*/
private val DECK_PICKER_WIDGET_KEY = "deck_picker_widget_selected_decks_"

private val deckPickerSharedPreferences = context.getSharedPreferences("DeckPickerWidgetPrefs", Context.MODE_PRIVATE)

// Deletes the stored data for a specific widget for DeckPickerWidget
/**
* Deletes the selected deck IDs from the shared preferences for the given widget ID.
*/
fun deleteDeckPickerWidgetData(appWidgetId: Int) {
deckPickerSharedPreferences.edit {
remove("deck_picker_widget_selected_decks_$appWidgetId")
}
}

// Get selected deck IDs from shared preferences for DeckPickerWidget
/**
* Retrieves the selected deck IDs from the shared preferences for the given widget ID.
* Note: There's no guarantee that these IDs still represent decks that exist at the time of execution.
*/
fun getSelectedDeckIdsFromPreferencesDeckPickerWidget(appWidgetId: Int): LongArray {
val selectedDecksString = deckPickerSharedPreferences.getString("deck_picker_widget_selected_decks_$appWidgetId", "")
return if (!selectedDecksString.isNullOrEmpty()) {
Expand All @@ -45,19 +58,19 @@ class WidgetPreferences(context: Context) {
}
}

// Save selected deck IDs to shared preferences for DeckPickerWidget
fun saveSelectedDecks(appWidgetId: Int, selectedDecks: List<String>) {
deckPickerSharedPreferences.edit {
putString("deck_picker_widget_selected_decks_$appWidgetId", selectedDecks.joinToString(","))
}
/**
* Generates the key for the shared preferences for the given widget ID.
*/
private fun getDeckPickerWidgetKey(appWidgetId: Int): String {
return "$DECK_PICKER_WIDGET_KEY$appWidgetId"
}

fun getAllWidgetIds(): IntArray {
val widgetIdsString = deckPickerSharedPreferences.getString("all_widget_ids", "")
return if (!widgetIdsString.isNullOrEmpty()) {
widgetIdsString.split(",").map { it.toInt() }.toIntArray()
} else {
intArrayOf()
/**
* Saves the selected deck IDs to the shared preferences for the given widget ID.
*/
fun saveSelectedDecks(appWidgetId: Int, selectedDecks: List<String>) {
deckPickerSharedPreferences.edit {
putString(getDeckPickerWidgetKey(appWidgetId), selectedDecks.joinToString(","))
}
}
}
9 changes: 2 additions & 7 deletions AnkiDroid/src/main/res/layout/widget_deck_picker_config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginStart="7dp"
android:gravity="center"
android:padding="30dp"
android:text="@string/no_selected_deck_placeholder_title" />

<com.ichi2.ui.FixedTextView
style="@style/TextAppearance.AppCompat.Body1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/no_selected_deck_placeholder_description"
android:textAlignment="center" />
</LinearLayout>

<LinearLayout
Expand Down
Loading

0 comments on commit aa658a4

Please sign in to comment.