Skip to content

Commit

Permalink
feature: Allow changing decks in stats screen via spinner
Browse files Browse the repository at this point in the history
  • Loading branch information
criticalAY committed Feb 28, 2024
1 parent 722ccc2 commit 39f5818
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 9 deletions.
3 changes: 3 additions & 0 deletions AnkiDroid/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@
android:exported="false"
android:configChanges="orientation|screenSize"
/>
<activity android:name="com.ichi2.anki.StatisticsActivity"
android:exported="false"
android:configChanges="orientation|screenSize" />
<activity
android:name="com.ichi2.anki.previewer.PreviewerActivity"
android:exported="false"
Expand Down
11 changes: 11 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckSpinnerSelection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ class DeckSpinnerSelection(
setSpinnerListener()
}

@MainThread // spinner.adapter
suspend fun initializeStatsBarDeckSpinner(actionBar: ActionBar) {
actionBar.setDisplayShowTitleEnabled(false)

// Add drop-down menu to select deck to action bar.
dropDownDecks = computeDropDownDecks(includeFiltered = showFilteredDecks).toMutableList()
deckDropDownAdapter = DeckDropDownAdapter(context, dropDownDecks)
spinner.adapter = deckDropDownAdapter
setSpinnerListener()
}

@MainThread // spinner.adapter
fun initializeNoteEditorDeckSpinner(col: Collection) {
dropDownDecks = computeDropDownDecks(col, includeFiltered = false).toMutableList()
Expand Down
113 changes: 113 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/StatisticsActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2024 Ashish Yadav <mailtoashish693@gmail.com>
*
* 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.anki

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.widget.Spinner
import androidx.activity.viewModels
import androidx.appcompat.app.ActionBar
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import com.ichi2.anki.dialogs.DeckSelectionDialog
import com.ichi2.anki.widgets.DeckDropDownAdapter
import com.ichi2.libanki.DeckId
import kotlinx.coroutines.launch
import kotlin.reflect.KClass
import kotlin.reflect.jvm.jvmName

/**
* Handles changing of deck in Statistics webview
*
* Based in [SingleFragmentActivity], but with `configChanges="orientation|screenSize"`
* to avoid unwanted activity recreations
*/
class StatisticsActivity :
SingleFragmentActivity(),
DeckSelectionDialog.DeckSelectionListener,
DeckDropDownAdapter.SubtitleListener {

override val subtitleText: String
get() = resources.getString(R.string.statistics)

private val statisticsViewModel: StatisticsViewModel by viewModels()

private var deckSpinnerSelection: DeckSpinnerSelection? = null
var deckId: DeckId = 0

// menu wouldn't be visible in fragment's toolbar unless we override here
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.statistics, menu)
return super.onCreateOptionsMenu(menu)
}

fun setupDeckSelector(spinner: Spinner, supportActionBar: ActionBar) {
startLoadingCollection()
deckSpinnerSelection = DeckSpinnerSelection(
this,
spinner,
showAllDecks = true,
alwaysShowDefault = false,
showFilteredDecks = false
).apply {
lifecycleScope.launch {
initializeStatsBarDeckSpinner(supportActionBar)
}
launchCatchingTask {
selectDeckById(deckId, true)
}
}
}

override fun onDeckSelected(deck: DeckSelectionDialog.SelectableDeck?) {
if (deck == null) {
return
}
statisticsViewModel.setDeckName(deck.name)
deckId = deck.deckId
launchCatchingTask {
deckSpinnerSelection!!.selectDeckById(deckId, true)
}
}

companion object {

fun getIntent(context: Context, fragmentClass: KClass<out Fragment>, arguments: Bundle? = null): Intent {
return Intent(context, StatisticsActivity::class.java).apply {
putExtra(FRAGMENT_NAME_EXTRA, fragmentClass.jvmName)
putExtra(FRAGMENT_ARGS_EXTRA, arguments)
}
}
}
}

/**
* ViewModel for the StatisticsActivity, storing and managing the current deck name.
*/
class StatisticsViewModel : ViewModel() {
private val _deckName = MutableLiveData<String>()
val deckName: LiveData<String> = _deckName

fun setDeckName(value: String) {
_deckName.value = value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import timber.log.Timber
*/
@Suppress("LeakingThis")
abstract class PageFragment : Fragment(R.layout.page_fragment), PostRequestHandler {
abstract val title: String
abstract val title: String?
abstract val pageName: String

lateinit var webView: WebView
Expand Down
67 changes: 62 additions & 5 deletions AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,90 @@ import android.content.Intent
import android.os.Bundle
import android.print.PrintAttributes
import android.print.PrintManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.getSystemService
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.google.android.material.appbar.MaterialToolbar
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.R
import com.ichi2.anki.SingleFragmentActivity
import com.ichi2.anki.StatisticsActivity
import com.ichi2.anki.StatisticsViewModel
import com.ichi2.anki.utils.getTimestamp
import com.ichi2.libanki.utils.TimeManager
import timber.log.Timber

class Statistics : PageFragment() {
override val title: String
get() = resources.getString(R.string.statistics)
override val title: String?
get() = null

override val pageName = "graphs"

private val statisticsViewModel: StatisticsViewModel by activityViewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(
R.layout.statistics_fragment_layout,
container,
false
)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<MaterialToolbar>(R.id.toolbar)?.apply {

val toolbar = view.findViewById<MaterialToolbar>(R.id.toolbar)
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)

toolbar.apply {
inflateMenu(R.menu.statistics)
menu.findItem(R.id.action_export_stats).title = CollectionManager.TR.statisticsSavePdf()

setOnMenuItemClickListener { item ->
if (item.itemId == R.id.action_export_stats) {
exportWebViewContentAsPDF()
}
true
}
val toolbarSpinner = findViewById<Spinner>(R.id.stats_toolbar_spinner)
toolbarSpinner.isVisible = true
val supportActionBar = (activity as? StatisticsActivity)?.supportActionBar

if (supportActionBar != null) {
(activity as? StatisticsActivity)?.setupDeckSelector(toolbarSpinner, supportActionBar)
} else {
Timber.w("SupportActionBar is null")
}
}
statisticsViewModel.deckName.observe(
viewLifecycleOwner,
Observer { deck ->
changeDeck(deck)
}
)
}

/**
* This method is a workaround to change the deck in the webview by finding the text box and
* replacing the deck name with the selected deck name from the dialog and updating the stats
**/
private fun changeDeck(selectedDeck: String) {
val javascriptCode = """
var textBox = [].slice.call(document.getElementsByTagName('input'), 0).filter(x => x.type == "text")[0];
textBox.value = "deck:$selectedDeck";
textBox.dispatchEvent(new Event("input", { bubbles: true }));
textBox.dispatchEvent(new Event("change"));
""".trimIndent()
webView.evaluateJavascript(javascriptCode, null)
}

/**Prepares and initiates a printing task for the content(stats) displayed in the WebView.
Expand All @@ -66,7 +123,7 @@ class Statistics : PageFragment() {

companion object {
fun getIntent(context: Context): Intent {
return SingleFragmentActivity.getIntent(context, Statistics::class)
return StatisticsActivity.getIntent(context, Statistics::class)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import com.ichi2.anki.R
import com.ichi2.anki.StatisticsActivity
import com.ichi2.libanki.DeckNameId

class DeckDropDownAdapter(private val context: Context, decks: List<DeckNameId>) : BaseAdapter() {
Expand Down Expand Up @@ -69,6 +70,10 @@ class DeckDropDownAdapter(private val context: Context, decks: List<DeckNameId>)
convertView = LayoutInflater.from(context).inflate(R.layout.dropdown_deck_selected_item, parent, false)
deckNameView = convertView.findViewById(R.id.dropdown_deck_name)
deckCountsView = convertView.findViewById(R.id.dropdown_deck_counts)
if (context is StatisticsActivity) {
deckNameView.setTextAppearance(R.style.SpinnerCustomTextStyle)
deckCountsView.setTextAppearance(R.style.SpinnerCustomTextStyle)
}
viewHolder = DeckDropDownViewHolder()
viewHolder.deckNameView = deckNameView
viewHolder.deckCountsView = deckCountsView
Expand Down
6 changes: 3 additions & 3 deletions AnkiDroid/src/main/res/layout/page_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:navigationContentDescription="@string/abc_action_bar_up_description"
app:navigationIcon="?attr/homeAsUpIndicator"
/>
app:navigationIcon="?attr/homeAsUpIndicator"/>

<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="invisible"/>

</LinearLayout>
48 changes: 48 additions & 0 deletions AnkiDroid/src/main/res/layout/statistics_fragment_layout.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2024 Ashish Yadav <mailtoashish693@gmail.com>
~
~ 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/>.
-->
<LinearLayout
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"
android:orientation="vertical">

<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:navigationContentDescription="@string/abc_action_bar_up_description"
app:navigationIcon="?attr/homeAsUpIndicator">

<Spinner
android:id="@+id/stats_toolbar_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
app:popupTheme="@style/ActionBar.Popup" />

</com.google.android.material.appbar.MaterialToolbar>

<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="invisible"/>

</LinearLayout>
4 changes: 4 additions & 0 deletions AnkiDroid/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,8 @@
<item name="buttonTint">@color/material_blue_500</item>
</style>

<style name="SpinnerCustomTextStyle">
<item name="android:textColor">?android:attr/textColor</item>
</style>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ object ActivityList {
get(PermissionsActivity::class.java),
get(SingleFragmentActivity::class.java),
get(ImageOcclusionActivity::class.java),
get(StatisticsActivity::class.java),
get(PreviewerActivity::class.java)
)
}
Expand Down

0 comments on commit 39f5818

Please sign in to comment.