diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/PagesActivityTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/PagesTest.kt similarity index 72% rename from AnkiDroid/src/androidTest/java/com/ichi2/anki/PagesActivityTest.kt rename to AnkiDroid/src/androidTest/java/com/ichi2/anki/PagesTest.kt index 144f48e59281..9293bf0cfc00 100644 --- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/PagesActivityTest.kt +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/PagesTest.kt @@ -26,7 +26,6 @@ import com.ichi2.anki.pages.CardInfo.Companion.toIntent import com.ichi2.anki.pages.CardInfoDestination import com.ichi2.anki.pages.DeckOptions import com.ichi2.anki.pages.PageFragment -import com.ichi2.anki.pages.PagesActivity import com.ichi2.anki.pages.Statistics import com.ichi2.anki.tests.InstrumentedTest import com.ichi2.annotations.NeedsTest @@ -40,10 +39,10 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @NeedsTest("extend this for all activities - Issue 15009") -class PagesActivityTest : InstrumentedTest() { +class PagesTest : InstrumentedTest() { @JvmField // required for Parameter @Parameterized.Parameter - var intentBuilder: (PagesActivityTest.(Context) -> Intent)? = null + var intentBuilder: (PagesTest.(Context) -> Intent)? = null @JvmField // required for Parameter @Parameterized.Parameter(1) @@ -54,7 +53,7 @@ class PagesActivityTest : InstrumentedTest() { @Test fun activityOpens() { val intent = intentBuilder!!.invoke(this, testContext) - ActivityScenario.launch(intent).use { activity -> + ActivityScenario.launch(intent).use { activity -> // this can fail on a real device if the screen is off assertThat("state is RESUMED", activity.state == Lifecycle.State.RESUMED) } @@ -68,15 +67,15 @@ class PagesActivityTest : InstrumentedTest() { @JvmStatic // required for initParameters fun initParameters(): Collection> { /** See [PageFragment] */ - val intents = listOf Intent, String>>( - Pair(PagesActivityTest::getStatistics, "Statistics"), - Pair(PagesActivityTest::getCardInfo, "CardInfo"), - Pair(PagesActivityTest::getCongratsPage, "CongratsPage"), - Pair(PagesActivityTest::getDeckOptions, "DeckOptions"), + val intents = listOf Intent, String>>( + Pair(PagesTest::getStatistics, "Statistics"), + Pair(PagesTest::getCardInfo, "CardInfo"), + Pair(PagesTest::getCongratsPage, "CongratsPage"), + Pair(PagesTest::getDeckOptions, "DeckOptions"), // the following need a file path - Pair(PagesActivityTest::needsPath, "AnkiPackageImporterFragment"), - Pair(PagesActivityTest::needsPath, "CsvImporter"), - Pair(PagesActivityTest::needsPath, "ImageOcclusion") + Pair(PagesTest::needsPath, "AnkiPackageImporterFragment"), + Pair(PagesTest::needsPath, "CsvImporter"), + Pair(PagesTest::needsPath, "ImageOcclusion") ) return intents.map { arrayOf(it.first, it.second) } @@ -84,28 +83,28 @@ class PagesActivityTest : InstrumentedTest() { } } -fun PagesActivityTest.getStatistics(context: Context): Intent { +fun PagesTest.getStatistics(context: Context): Intent { return Statistics.getIntent(context) } -fun PagesActivityTest.getCardInfo(context: Context): Intent { +fun PagesTest.getCardInfo(context: Context): Intent { return addNoteUsingBasicModel().firstCard().let { card -> this.card = card CardInfoDestination(card.id).toIntent(context) } } -fun PagesActivityTest.getCongratsPage(context: Context): Intent { +fun PagesTest.getCongratsPage(context: Context): Intent { return addNoteUsingBasicModel().firstCard().let { card -> this.card = card CardInfoDestination(card.id).toIntent(context) } } -fun PagesActivityTest.getDeckOptions(context: Context): Intent { +fun PagesTest.getDeckOptions(context: Context): Intent { return DeckOptions.getIntent(context, col.decks.allNamesAndIds().first().id) } -fun PagesActivityTest.needsPath(@Suppress("UNUSED_PARAMETER") context: Context): Intent { +fun PagesTest.needsPath(@Suppress("UNUSED_PARAMETER") context: Context): Intent { assumeThat("not implemented: path needed", false, equalTo(true)) TODO() } diff --git a/AnkiDroid/src/main/AndroidManifest.xml b/AnkiDroid/src/main/AndroidManifest.xml index 5ff5c5d5166f..8692328c4382 100644 --- a/AnkiDroid/src/main/AndroidManifest.xml +++ b/AnkiDroid/src/main/AndroidManifest.xml @@ -105,10 +105,6 @@ android:exported="false" android:configChanges="keyboardHidden|orientation|screenSize" /> - (R.id.pagesWebview).apply { + webView = view.findViewById(R.id.webview).apply { settings.javaScriptEnabled = true webViewClient = this@PageFragment.webViewClient webChromeClient = this@PageFragment.webChromeClient } val nightMode = if (Themes.currentTheme.isNightMode) "#night" else "" - val url = (requireActivity() as PagesActivity).baseUrl() + "$pageName.html$nightMode" + val url = server.baseUrl() + "$pageName.html$nightMode" Timber.i("Loading $url") webView.loadUrl(url) + view.findViewById(R.id.toolbar).apply { + title = this@PageFragment.title + setNavigationOnClickListener { + requireActivity().onBackPressedDispatcher.onBackPressed() + } + } return view } + + override suspend fun handlePostRequest(uri: String, bytes: ByteArray): ByteArray { + val methodName = if (uri.startsWith(AnkiServer.ANKI_PREFIX)) { + uri.substring(AnkiServer.ANKI_PREFIX.length) + } else { + throw IllegalArgumentException("unhandled request: $uri") + } + return requireActivity().handleUiPostRequest(methodName, bytes) + ?: handleCollectionPostRequest(methodName, bytes) + ?: throw IllegalArgumentException("unhandled method: $methodName") + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageWebViewClient.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageWebViewClient.kt index 5c9b8403a9a0..02ba0b475e6f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageWebViewClient.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageWebViewClient.kt @@ -34,7 +34,7 @@ open class PageWebViewClient : WebViewClient() { /** [PageFragment.webView] is invisible by default to avoid flashes while * the page is loaded, and can be made visible again after it finishes loading */ - view.isVisible = true + webView.isVisible = true } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PagesActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PagesActivity.kt deleted file mode 100644 index 22d19d53cfb7..000000000000 --- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PagesActivity.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2022 Brayan Oliveira - * - * 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 . - */ -package com.ichi2.anki.pages - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.webkit.WebView -import androidx.fragment.app.commit -import com.ichi2.anki.* -import com.ichi2.utils.getInstanceFromClassName -import kotlin.reflect.KClass -import kotlin.reflect.jvm.jvmName - -/** - * Container activity to host Anki HTML pages - * Responsibilities: - * * Serve as parent activity of the [PageFragment] that holds the page - * * Host an [AnkiServer] to intercept any requests made by an Anki page and resolve them - * * Operate UI requests by the [AnkiServer] - */ -class PagesActivity : AnkiActivity(), PostRequestHandler { - private lateinit var ankiServer: AnkiServer - - fun baseUrl(): String { - return ankiServer.baseUrl() - } - - override fun onCreate(savedInstanceState: Bundle?) { - if (showedActivityFailedScreen(savedInstanceState)) { - return - } - super.onCreate(savedInstanceState) - setContentView(R.layout.page_activity) - enableToolbar() - - // Enable debugging on DEBUG builds - WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) - - // Load server - ankiServer = AnkiServer(this) - ankiServer.start() - - // Launch page - val pageClassName = intent.extras?.getString(EXTRA_PAGE_CLASS) - ?: throw Exception("PageActivity's intent should have a '$EXTRA_PAGE_CLASS' extra") - - val pageFragment = getInstanceFromClassName(pageClassName).apply { - arguments = intent.getBundleExtra(EXTRA_PAGE_ARGS) - } - supportFragmentManager.addFragmentOnAttachListener { _, _ -> this.title = pageFragment.title } - supportFragmentManager.commit { - replace(R.id.page_container, pageFragment) - } - } - override fun onDestroy() { - super.onDestroy() - /** Stop running the server if the activity is destroyed. - * The initialization check is for the case [showedActivityFailedScreen] is true */ - if (this::ankiServer.isInitialized && ankiServer.isAlive) { - ankiServer.stop() - } - } - - override suspend fun handlePostRequest(uri: String, bytes: ByteArray): ByteArray { - val methodName = if (uri.startsWith(AnkiServer.ANKI_PREFIX)) { - uri.substring(AnkiServer.ANKI_PREFIX.length) - } else { - throw IllegalArgumentException("unhandled request: $uri") - } - return handleUiPostRequest(methodName, bytes) - ?: handleCollectionPostRequest(methodName, bytes) - ?: throw IllegalArgumentException("unhandled method: $methodName") - } - - companion object { - /** - * Extra key of [PagesActivity]'s intent that can be used to pass a [Bundle] - * as arguments of the [PageFragment] that will be opened - */ - const val EXTRA_PAGE_ARGS = "pageArgs" - - /** - * Extra key of [PagesActivity]'s intent that must be included and - * hold the name of an [Anki HTML page](https://github.com/ankitects/anki/tree/main/ts) - */ - const val EXTRA_PAGE_CLASS = "pageClass" - - /** - * @param fragmentClass class of the [PageFragment] to be created - * @param arguments to be passed to the created [PageFragment] - */ - fun getIntent(context: Context, fragmentClass: KClass, arguments: Bundle? = null): Intent { - return Intent(context, PagesActivity::class.java).apply { - putExtra(EXTRA_PAGE_CLASS, fragmentClass.jvmName) - putExtra(EXTRA_PAGE_ARGS, arguments) - } - } - } -} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt index 28acc8e0c056..452ad5e86e36 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt @@ -20,16 +20,13 @@ import android.content.Intent import android.os.Bundle import android.print.PrintAttributes import android.print.PrintManager -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.core.content.ContextCompat.getSystemService -import androidx.core.view.MenuHost -import androidx.core.view.MenuProvider -import androidx.lifecycle.Lifecycle -import com.ichi2.anki.CollectionManager +import com.google.android.material.appbar.MaterialToolbar import com.ichi2.anki.R +import com.ichi2.anki.SingleFragmentActivity import com.ichi2.anki.utils.getTimestamp import com.ichi2.libanki.utils.TimeManager @@ -41,37 +38,30 @@ class Statistics : PageFragment() { override var webViewClient = PageWebViewClient() override var webChromeClient = PageChromeClient() - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - requireActivity().invalidateOptionsMenu() - val menuHost: MenuHost = requireActivity() - menuHost.addMenuProvider( - object : MenuProvider { - override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { - menuInflater.inflate(R.menu.statistics, menu) - val exportStats = menu.findItem(R.id.action_export_stats) - exportStats?.title = CollectionManager.TR.statisticsSavePdf() - } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = super.onCreateView(inflater, container, savedInstanceState) - override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - return when (menuItem.itemId) { - R.id.action_export_stats -> { - exportWebViewContentAsPDF() - true - } - else -> false - } + view?.findViewById(R.id.toolbar)?.apply { + inflateMenu(R.menu.statistics) + setOnMenuItemClickListener { item -> + if (item.itemId == R.id.action_export_stats) { + exportWebViewContentAsPDF() } - }, - viewLifecycleOwner, - Lifecycle.State.RESUMED - ) + true + } + } + + return view } /**Prepares and initiates a printing task for the content(stats) displayed in the WebView. * It uses the Android PrintManager service to create a print job, based on the content of the WebView. * The resulting output is a PDF document. **/ - fun exportWebViewContentAsPDF() { + private fun exportWebViewContentAsPDF() { val printManager = getSystemService(requireContext(), PrintManager::class.java) val currentDateTime = getTimestamp(TimeManager.time) val jobName = "${getString(R.string.app_name)}-stats-$currentDateTime" @@ -85,7 +75,7 @@ class Statistics : PageFragment() { companion object { fun getIntent(context: Context): Intent { - return PagesActivity.getIntent(context, Statistics::class) + return SingleFragmentActivity.getIntent(context, Statistics::class) } } } diff --git a/AnkiDroid/src/main/res/layout/page_activity.xml b/AnkiDroid/src/main/res/layout/page_activity.xml deleted file mode 100644 index 525a9bfd749c..000000000000 --- a/AnkiDroid/src/main/res/layout/page_activity.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/AnkiDroid/src/main/res/layout/page_fragment.xml b/AnkiDroid/src/main/res/layout/page_fragment.xml index 4f8c0867f159..abde86eb07ac 100644 --- a/AnkiDroid/src/main/res/layout/page_fragment.xml +++ b/AnkiDroid/src/main/res/layout/page_fragment.xml @@ -1,11 +1,22 @@ + android:layout_height="match_parent" + android:orientation="vertical"> + + diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/ActivityList.kt b/AnkiDroid/src/test/java/com/ichi2/testutils/ActivityList.kt index 4f651cb55beb..7bc941865426 100644 --- a/AnkiDroid/src/test/java/com/ichi2/testutils/ActivityList.kt +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/ActivityList.kt @@ -25,7 +25,6 @@ import com.ichi2.anki.CardTemplateBrowserAppearanceEditor.Companion.INTENT_ANSWE import com.ichi2.anki.CardTemplateBrowserAppearanceEditor.Companion.INTENT_QUESTION_FORMAT import com.ichi2.anki.multimediacard.activity.MultimediaEditFieldActivity import com.ichi2.anki.notetype.ManageNotetypes -import com.ichi2.anki.pages.PagesActivity import com.ichi2.anki.preferences.Preferences import com.ichi2.anki.services.ReminderService.Companion.getReviewDeckIntent import com.ichi2.anki.ui.windows.managespace.ManageSpaceActivity @@ -69,7 +68,6 @@ object ActivityList { get(CardTemplateEditor::class.java) { intentForCardTemplateEditor() }, get(CardTemplateBrowserAppearanceEditor::class.java) { intentForCardTemplateBrowserAppearanceEditor() }, get(SharedDecksActivity::class.java), - get(PagesActivity::class.java), get(LoginActivity::class.java), get(IntroductionActivity::class.java), get(ManageNotetypes::class.java),