mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-23 04:33:54 -04:00
Added zim scheme deep link support.
* Refactored code to retrieve the ZIM file from `LibkiwixBookOnDisk` instead of `NewBookDao`, since `NewBookDao` no longer exists and books are now stored in `Libkiwix`. * Added a UI test case for this deep link to verify correct behavior. * Added `testZimHostDeepLink` to specifically test the new compose deep link and help prevent future regressions.
This commit is contained in:
parent
7b4afafa2d
commit
268b411f6e
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Kiwix Android
|
||||
* Copyright (c) 2025 Kiwix <android.kiwix.org>
|
||||
* 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 org.kiwix.kiwixmobile.deeplinks
|
||||
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.test.espresso.web.sugar.Web.onWebView
|
||||
import androidx.test.espresso.web.webdriver.DriverAtoms.findElement
|
||||
import androidx.test.espresso.web.webdriver.Locator
|
||||
import applyWithViewHierarchyPrinting
|
||||
import org.kiwix.kiwixmobile.BaseRobot
|
||||
import org.kiwix.kiwixmobile.core.R
|
||||
import org.kiwix.kiwixmobile.core.main.reader.READER_SCREEN_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.core.ui.components.TOOLBAR_TITLE_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
|
||||
|
||||
fun deepLink(func: DeepLinkRobot.() -> Unit) =
|
||||
DeepLinkRobot().applyWithViewHierarchyPrinting(func)
|
||||
|
||||
class DeepLinkRobot : BaseRobot() {
|
||||
fun checkZimFileLoadedSuccessful(composeTestRule: ComposeContentTestRule) {
|
||||
testFlakyView({
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(READER_SCREEN_TESTING_TAG).assertExists()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun assertZimFilePageLoaded(composeTestRule: ComposeContentTestRule) {
|
||||
testFlakyView({
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onWebView()
|
||||
.withElement(
|
||||
findElement(
|
||||
Locator.XPATH,
|
||||
"//*[contains(text(), 'History')]"
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun checkZimHostScreenVisible(composeTestRule: ComposeContentTestRule) {
|
||||
testFlakyView({
|
||||
composeTestRule.apply {
|
||||
waitForIdle()
|
||||
onNodeWithTag(TOOLBAR_TITLE_TESTING_TAG)
|
||||
.assertTextEquals(context.getString(R.string.menu_wifi_hotspot))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.accessibility.AccessibilityChecks
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
@ -44,16 +46,21 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.fail
|
||||
import org.kiwix.kiwixmobile.BaseActivityTest
|
||||
import org.kiwix.kiwixmobile.core.main.ZIM_HOST_NAV_DEEP_LINK
|
||||
import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange
|
||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||
import org.kiwix.kiwixmobile.core.utils.TestingUtils.COMPOSE_TEST_RULE_ORDER
|
||||
import org.kiwix.kiwixmobile.core.utils.TestingUtils.RETRY_RULE_ORDER
|
||||
import org.kiwix.kiwixmobile.core.utils.dialog.ALERT_DIALOG_CONFIRM_BUTTON_TESTING_TAG
|
||||
import org.kiwix.kiwixmobile.main.KiwixMainActivity
|
||||
import org.kiwix.kiwixmobile.main.OPENING_ZIM_FILE_DELAY
|
||||
import org.kiwix.kiwixmobile.nav.destination.library.library
|
||||
import org.kiwix.kiwixmobile.page.history.navigationHistory
|
||||
import org.kiwix.kiwixmobile.testutils.RetryRule
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils.TEST_PAUSE_MS_FOR_DOWNLOAD_TEST
|
||||
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
|
||||
import org.kiwix.kiwixmobile.ui.KiwixDestination
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStream
|
||||
@ -108,7 +115,9 @@ class DeepLinksTest : BaseActivityTest() {
|
||||
fun fileTypeDeepLinkTest() {
|
||||
loadZimFileInApplicationAndReturnSchemeTypeUri("file")?.let {
|
||||
// Launch the activity to test the deep link
|
||||
ActivityScenario.launch<KiwixMainActivity>(createDeepLinkIntent(it)).onActivity {}
|
||||
ActivityScenario.launch<KiwixMainActivity>(
|
||||
createDeepLinkIntent(it, "application/octet-stream")
|
||||
).onActivity {}
|
||||
clickOnCopy(composeTestRule)
|
||||
navigationHistory {
|
||||
checkZimFileLoadedSuccessful(composeTestRule)
|
||||
@ -138,7 +147,9 @@ class DeepLinksTest : BaseActivityTest() {
|
||||
fun contentTypeDeepLinkTest() {
|
||||
loadZimFileInApplicationAndReturnSchemeTypeUri("content")?.let {
|
||||
// Launch the activity to test the deep link
|
||||
ActivityScenario.launch<KiwixMainActivity>(createDeepLinkIntent(it)).onActivity {}
|
||||
ActivityScenario.launch<KiwixMainActivity>(
|
||||
createDeepLinkIntent(it, "application/octet-stream")
|
||||
).onActivity {}
|
||||
clickOnCopy(composeTestRule)
|
||||
navigationHistory {
|
||||
checkZimFileLoadedSuccessful(composeTestRule)
|
||||
@ -151,6 +162,60 @@ class DeepLinksTest : BaseActivityTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun zimUrlTypeDeepLinkTest() {
|
||||
activityScenario =
|
||||
ActivityScenario.launch(KiwixMainActivity::class.java).apply {
|
||||
moveToState(Lifecycle.State.RESUMED)
|
||||
onActivity {
|
||||
handleLocaleChange(
|
||||
it,
|
||||
"en",
|
||||
SharedPreferenceUtil(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
activityScenario.onActivity {
|
||||
it.navigate(KiwixDestination.Library.route)
|
||||
}
|
||||
library {
|
||||
refreshList(composeTestRule)
|
||||
waitUntilZimFilesRefreshing(composeTestRule)
|
||||
deleteZimIfExists(composeTestRule)
|
||||
}
|
||||
loadZimFileInApplicationAndReturnSchemeTypeUri("file")
|
||||
library {
|
||||
refreshList(composeTestRule)
|
||||
waitUntilZimFilesRefreshing(composeTestRule)
|
||||
}
|
||||
// it tests the zim deep link e.g. (zim://60094d1e-1c9a-a60b-2011-4fb02f8db6c3/A/Android_(operating_system).html)
|
||||
ActivityScenario.launch<KiwixMainActivity>(
|
||||
createDeepLinkIntent("zim://60094d1e-1c9a-a60b-2011-4fb02f8db6c3/A/Android_(operating_system).html".toUri())
|
||||
).onActivity {}
|
||||
// for a bit to properly handle the deep link.
|
||||
composeTestRule.mainClock.advanceTimeBy(OPENING_ZIM_FILE_DELAY + 500)
|
||||
composeTestRule.waitForIdle()
|
||||
deepLink {
|
||||
checkZimFileLoadedSuccessful(composeTestRule)
|
||||
assertZimFilePageLoaded(composeTestRule)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZimHostDeepLink() {
|
||||
// For testing the deep link triggers when user click on notification of the hotspot.
|
||||
// it should open the WIFI-Hotspot screen.
|
||||
ActivityScenario.launch<KiwixMainActivity>(
|
||||
createDeepLinkIntent(ZIM_HOST_NAV_DEEP_LINK.toUri())
|
||||
).onActivity {}
|
||||
// for a bit to properly handle the deep link.
|
||||
composeTestRule.mainClock.advanceTimeBy(OPENING_ZIM_FILE_DELAY + 500)
|
||||
composeTestRule.waitForIdle()
|
||||
deepLink {
|
||||
checkZimHostScreenVisible(composeTestRule)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadZimFileInApplicationAndReturnSchemeTypeUri(schemeType: String): Uri? {
|
||||
val loadFileStream =
|
||||
DeepLinksTest::class.java.classLoader.getResourceAsStream("testzim.zim")
|
||||
@ -180,14 +245,16 @@ class DeepLinksTest : BaseActivityTest() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDeepLinkIntent(uri: Uri): Intent {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
setDataAndType(uri, "application/octet-stream")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
setPackage(context.packageName)
|
||||
}
|
||||
return intent
|
||||
private fun createDeepLinkIntent(
|
||||
uri: Uri,
|
||||
mimeType: String? = null
|
||||
): Intent {
|
||||
return Intent(Intent.ACTION_VIEW).apply {
|
||||
data = uri
|
||||
mimeType?.let { setDataAndType(uri, it) }
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
setPackage(context.packageName)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -141,6 +141,14 @@
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="kiwix" android:host="zimhost" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="zim" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
|
@ -83,6 +83,7 @@ import org.kiwix.kiwixmobile.core.main.PAGE_URL_KEY
|
||||
import org.kiwix.kiwixmobile.core.main.SHOULD_OPEN_IN_NEW_TAB
|
||||
import org.kiwix.kiwixmobile.core.main.ZIM_FILE_URI_KEY
|
||||
import org.kiwix.kiwixmobile.core.main.ZIM_HOST_DEEP_LINK_SCHEME
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimFileReader.Companion.CONTENT_PREFIX
|
||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderSource
|
||||
import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange
|
||||
import org.kiwix.kiwixmobile.core.utils.dialog.DialogHost
|
||||
@ -309,6 +310,21 @@ class KiwixMainActivity : CoreMainActivity() {
|
||||
}, OPENING_ZIM_FILE_DELAY)
|
||||
}
|
||||
|
||||
"zim" -> {
|
||||
val zimId = it.host
|
||||
val page = it.encodedPath?.removePrefix("/")
|
||||
if (zimId.isNullOrEmpty() || page.isNullOrEmpty()) {
|
||||
return toast(R.string.cannot_open_file)
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
delay(OPENING_ZIM_FILE_DELAY)
|
||||
val book = libkiwixBookOnDisk.bookById(zimId)
|
||||
?: return@launch toast(R.string.cannot_open_file)
|
||||
openPage("$CONTENT_PREFIX$page", book.zimReaderSource)
|
||||
clearIntentDataAndAction()
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (it.scheme != ZIM_HOST_DEEP_LINK_SCHEME) {
|
||||
toast(R.string.cannot_open_file)
|
||||
|
@ -228,6 +228,9 @@ class LibkiwixBookOnDisk @Inject constructor(
|
||||
it.zimReaderSource.toDatabase().endsWith(downloadTitle, true)
|
||||
}
|
||||
|
||||
suspend fun bookById(bookId: String) =
|
||||
getBooks().firstOrNull { it.book.id == bookId }
|
||||
|
||||
/**
|
||||
* Asynchronously writes the library data to their respective file in a background thread
|
||||
* to prevent potential data loss and ensures that the library holds the updated ZIM file data.
|
||||
|
@ -1869,7 +1869,7 @@ abstract class CoreReaderFragment :
|
||||
}
|
||||
|
||||
private fun startIntentBasedOnAction(intent: Intent?) {
|
||||
Log.d(TAG_KIWIX, "action" + requireActivity().intent?.action)
|
||||
Log.d(TAG_KIWIX, "action: ${requireActivity().intent?.action}")
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_PROCESS_TEXT -> {
|
||||
goToSearchWithText(intent)
|
||||
@ -1903,7 +1903,7 @@ abstract class CoreReaderFragment :
|
||||
// Added condition to handle ZIM files. When opening from storage, the intent may
|
||||
// return null for the type, triggering the search unintentionally. This condition
|
||||
// prevents such occurrences.
|
||||
intent.scheme !in listOf("file", "content", ZIM_HOST_DEEP_LINK_SCHEME)
|
||||
intent.scheme !in listOf("file", "content", "zim", ZIM_HOST_DEEP_LINK_SCHEME)
|
||||
) {
|
||||
val searchString = if (intent.data == null) "" else intent.data?.lastPathSegment
|
||||
openSearch(
|
||||
|
Loading…
x
Reference in New Issue
Block a user