Skip to content

Commit

Permalink
Implemented new Notification Preference.
Browse files Browse the repository at this point in the history
New Notification Preference will show the list of decks as per my GSoC Proposal and dissucced on issue (ankidroid#4944)
  • Loading branch information
prateek-singh-3212 committed Oct 20, 2022
1 parent 16c6c52 commit 5c73d65
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,64 +15,138 @@
*/
package com.ichi2.anki.preferences

import android.app.AlarmManager
import android.content.Context.ALARM_SERVICE
import android.content.Intent
import androidx.preference.ListPreference
import androidx.preference.SwitchPreference
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ichi2.anki.CollectionHelper
import com.ichi2.anki.R
import com.ichi2.anki.services.BootService.Companion.scheduleNotification
import com.ichi2.anki.services.NotificationService
import com.ichi2.compat.CompatHelper
import com.ichi2.libanki.utils.TimeManager
import com.ichi2.utils.AdaptionUtil
import com.ichi2.anki.UIUtils
import com.ichi2.anki.widgets.NotificationPreferenceAdapter
import com.ichi2.async.CollectionTask
import com.ichi2.async.TaskListenerWithContext
import com.ichi2.async.TaskManager
import com.ichi2.libanki.Collection
import com.ichi2.libanki.DeckId
import com.ichi2.libanki.sched.AbstractDeckTreeNode
import com.ichi2.libanki.sched.DeckDueTreeNode
import com.ichi2.libanki.sched.TreeNode
import com.ichi2.libanki.sched.findInDeckTree
import timber.log.Timber

/**
* Fragment with preferences related to notifications
*/
class NotificationsSettingsFragment : SettingsFragment() {
override val preferenceResource: Int
get() = R.xml.preferences_notifications
override val analyticsScreenNameConstant: String
get() = "prefs.notifications"

override fun initSubscreen() {
if (AdaptionUtil.isXiaomiRestrictedLearningDevice) {
/** These preferences should be searchable or not based
* on this same condition at [Preferences.configureSearchBar] */
preferenceScreen.removePreference(requirePreference<SwitchPreference>(R.string.pref_notifications_vibrate_key))
preferenceScreen.removePreference(requirePreference<SwitchPreference>(R.string.pref_notifications_blink_key))
class NotificationsSettingsFragment : Fragment() {

private lateinit var mDeckListAdapter: NotificationPreferenceAdapter
private lateinit var mRecyclerView: RecyclerView
private lateinit var mProgressBar: ProgressBar
var dueTree: List<TreeNode<AbstractDeckTreeNode>>? = null
val col: Collection
get() = CollectionHelper.instance.getCol(requireContext())!!

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_preference_notification, container, false)
initViews(view)

// create and set an adapter for the RecyclerView
mDeckListAdapter = NotificationPreferenceAdapter(layoutInflater, requireContext()).apply {
setTimeClickListener(mOnTimeClickListener)
setDeckExpanderClickListener(mDeckExpanderClickListener)
}
mRecyclerView.apply {
adapter = mDeckListAdapter
layoutManager = LinearLayoutManager(context)
}
// Minimum cards due
// The number of cards that should be due today in a deck to justify adding a notification.
requirePreference<ListPreference>(R.string.pref_notifications_minimum_cards_due_key).apply {
updateNotificationPreference(this)
setOnPreferenceChangeListener { preference, newValue ->
updateNotificationPreference(preference as ListPreference)
if ((newValue as String).toInt() < Preferences.PENDING_NOTIFICATIONS_ONLY) {
scheduleNotification(TimeManager.time, requireContext())
} else {
val intent = CompatHelper.compat.getImmutableBroadcastIntent(
requireContext(), 0,
Intent(requireContext(), NotificationService::class.java), 0
)
val alarmManager = requireActivity().getSystemService(ALARM_SERVICE) as AlarmManager
alarmManager.cancel(intent)
}
true

// Fetch Deck Data
TaskManager.launchCollectionTask(
CollectionTask.LoadDeckCounts(),
UpdateDeckListListener(this)
)

return view
}

private val mOnTimeClickListener = View.OnClickListener {
TODO("Open Time picker bottom sheet.")
}

private val mDeckExpanderClickListener = View.OnClickListener { view ->
toggleDeckExpand(view.tag as Long)
}

@Suppress("UNCHECKED_CAST")
fun toggleDeckExpand(did: DeckId) {
if (!col.decks.children(did).isEmpty()) {
// update DB
col.decks.collapse(did)
// update stored state
val deck: List<TreeNode<DeckDueTreeNode>> = dueTree!! as List<TreeNode<DeckDueTreeNode>>
Timber.d(dueTree!!.toString() + " " + did)
findInDeckTree(deck, did)?.run {
collapsed = !collapsed
}
mDeckListAdapter.buildDeckList(dueTree!!, col)
}
}

private fun initViews(view: View) {
mRecyclerView = view.findViewById(R.id.preference_notification_rv)
mProgressBar = view.findViewById(R.id.preference_notification_progressbar)
}

fun <T : AbstractDeckTreeNode> onDecksLoaded(result: List<TreeNode<T>>?) {
Timber.i("Updating deck list UI")
// Make sure the fragment is visible

if (result == null) {
Timber.e("null result loading deck counts")
showCollectionErrorMessage()
return
}
dueTree = result.map { x -> x.unsafeCastToType() }
mDeckListAdapter.buildDeckList(dueTree!!, col)
hideProgressBar()
Timber.d("Startup - Deck List UI Completed")
}

private fun showProgressBar() {
mRecyclerView.visibility = View.GONE
mProgressBar.visibility = View.VISIBLE
}

private fun updateNotificationPreference(listPreference: ListPreference) {
val entries = listPreference.entries
val values = listPreference.entryValues
for (i in entries.indices) {
val value = values[i].toString().toInt()
if (entries[i].toString().contains("%d")) {
entries[i] = String.format(entries[i].toString(), value)
private fun hideProgressBar() {
mRecyclerView.visibility = View.VISIBLE
mProgressBar.visibility = View.GONE
}

private fun showCollectionErrorMessage() {
UIUtils.showThemedToast(context, "Unable to Access Collection", true)
}

private class UpdateDeckListListener<T : AbstractDeckTreeNode>(context: NotificationsSettingsFragment) :
TaskListenerWithContext<NotificationsSettingsFragment, Void, List<TreeNode<T>>?>(context) {
override fun actualOnPreExecute(context: NotificationsSettingsFragment) {

if (!CollectionHelper.instance.colIsOpen()) {
context.showProgressBar()
}
Timber.d("Refreshing deck list")
}
listPreference.entries = entries

override fun actualOnPostExecute(
context: NotificationsSettingsFragment,
result: List<TreeNode<T>>?
) = context.onDecksLoaded(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ class Preferences : AnkiActivity(), SearchPreferenceResultListener {
index(R.xml.preferences_sync)
index(R.xml.preferences_custom_sync_server)
.addBreadcrumb(R.string.pref_cat_sync)
index(R.xml.preferences_notifications)
index(R.xml.preferences_appearance)
index(R.xml.preferences_custom_buttons)
.addBreadcrumb(R.string.pref_cat_appearance)
Expand Down Expand Up @@ -335,7 +334,6 @@ class Preferences : AnkiActivity(), SearchPreferenceResultListener {
R.xml.preferences_reviewing -> ReviewingSettingsFragment()
R.xml.preferences_sync -> SyncSettingsFragment()
R.xml.preferences_custom_sync_server -> CustomSyncServerSettingsFragment()
R.xml.preferences_notifications -> NotificationsSettingsFragment()
R.xml.preferences_appearance -> AppearanceSettingsFragment()
R.xml.preferences_controls -> ControlsSettingsFragment()
R.xml.preferences_advanced -> AdvancedSettingsFragment()
Expand Down
38 changes: 38 additions & 0 deletions AnkiDroid/src/main/res/layout/fragment_preference_notification.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2022 Prateek Singh <[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/>.
-->

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ProgressBar
android:id="@+id/preference_notification_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:progressTint="?attr/notificationPreferenceProgressbarColor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/preference_notification_rv" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/preference_notification_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />

</androidx.constraintlayout.widget.ConstraintLayout>

0 comments on commit 5c73d65

Please sign in to comment.