Merge pull request #3218 from kiwix/Issue#2537

Fixed Hosted Books don't update on Application
This commit is contained in:
Kelson 2023-11-12 18:00:20 +01:00 committed by GitHub
commit f53809abc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 538 additions and 101 deletions

View File

@ -20,7 +20,6 @@ package org.kiwix.kiwixmobile
import android.Manifest.permission
import android.content.Context
import android.os.Build
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
@ -35,18 +34,10 @@ import org.kiwix.kiwixmobile.main.KiwixMainActivity
abstract class BaseActivityTest {
open lateinit var activityScenario: ActivityScenario<KiwixMainActivity>
private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
arrayOf(
permission.READ_EXTERNAL_STORAGE,
permission.WRITE_EXTERNAL_STORAGE,
permission.SYSTEM_ALERT_WINDOW
)
} else {
arrayOf(
permission.READ_EXTERNAL_STORAGE,
permission.WRITE_EXTERNAL_STORAGE
)
}
private val permissions = arrayOf(
permission.READ_EXTERNAL_STORAGE,
permission.WRITE_EXTERNAL_STORAGE
)
@get:Rule
var permissionRules: GrantPermissionRule =

View File

@ -18,16 +18,15 @@
package org.kiwix.kiwixmobile
import android.Manifest
import android.os.Build
import android.util.Log
import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.espresso.Espresso
import androidx.test.espresso.IdlingPolicies
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import com.adevinta.android.barista.interaction.BaristaDialogInteractions
@ -58,18 +57,10 @@ class NetworkTest {
// @Inject
// MockWebServer mockWebServer
private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.SYSTEM_ALERT_WINDOW
)
} else {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
private val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
@Rule
@JvmField

View File

@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.language
import android.Manifest
import android.app.Instrumentation
import android.os.Build
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import androidx.test.ext.junit.rules.ActivityScenarioRule
@ -50,18 +49,10 @@ class LanguageFragmentTest {
@get:Rule
var activityScenarioRule = ActivityScenarioRule(KiwixMainActivity::class.java)
private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.SYSTEM_ALERT_WINDOW
)
} else {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
private val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
@Rule
@JvmField

View File

@ -19,20 +19,25 @@
package org.kiwix.kiwixmobile.nav.destination.library
import android.util.Log
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import applyWithViewHierarchyPrinting
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.Text
import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferRobot
import org.kiwix.kiwixmobile.localFileTransfer.localFileTransfer
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.utils.RecyclerViewItemCount
fun library(func: LibraryRobot.() -> Unit) = LibraryRobot().applyWithViewHierarchyPrinting(func)
@ -60,7 +65,17 @@ class LibraryRobot : BaseRobot() {
fun deleteZimIfExists() {
try {
longClickOnZimFile()
val recyclerViewId: Int = R.id.zimfilelist
val recyclerViewItemsCount = RecyclerViewItemCount(recyclerViewId).checkRecyclerViewCount()
// Scroll to the end of the RecyclerView to ensure all items are visible
onView(withId(recyclerViewId))
.perform(scrollToPosition<ViewHolder>(recyclerViewItemsCount - 1))
for (position in 0 until recyclerViewItemsCount) {
// Long-click the item to select it
onView(withId(recyclerViewId))
.perform(actionOnItemAtPosition<ViewHolder>(position, longClick()))
}
clickOnFileDeleteIcon()
assertDeleteDialogDisplayed()
clickOnDeleteZimFile()
@ -75,6 +90,7 @@ class LibraryRobot : BaseRobot() {
}
private fun clickOnFileDeleteIcon() {
pauseForBetterTestPerformance()
clickOn(ViewId(R.id.zim_file_delete_item))
}
@ -84,10 +100,6 @@ class LibraryRobot : BaseRobot() {
.check(ViewAssertions.matches(isDisplayed()))
}
private fun longClickOnZimFile() {
longClickOn(Text(zimFileTitle))
}
private fun clickOnDeleteZimFile() {
pauseForBetterTestPerformance()
onView(withText("DELETE")).perform(click())

View File

@ -18,7 +18,6 @@
package org.kiwix.kiwixmobile.settings
import android.Manifest
import android.os.Build
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import androidx.test.platform.app.InstrumentationRegistry
@ -46,18 +45,10 @@ class KiwixSettingsFragmentTest {
@get:Rule
var activityScenarioRule = ActivityScenarioRule(KiwixMainActivity::class.java)
private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.SYSTEM_ALERT_WINDOW
)
} else {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
private val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
@Rule
@JvmField

View File

@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.splash
import android.Manifest
import android.content.Context
import android.os.Build
import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso
@ -59,18 +58,10 @@ class KiwixSplashActivityTest {
private val activityScenario: ActivityScenario<KiwixMainActivity> =
ActivityScenario.launch(KiwixMainActivity::class.java)
private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.SYSTEM_ALERT_WINDOW
)
} else {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
private val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
@Rule
@JvmField

View File

@ -0,0 +1,41 @@
/*
* Kiwix Android
* Copyright (c) 2023 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.utils
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.matcher.ViewMatchers.withId
class RecyclerViewItemCount(private val recyclerViewId: Int) {
fun checkRecyclerViewCount(): Int {
var recyclerViewItemCount = 0
onView(withId(recyclerViewId))
.check { view: View, noViewFoundException: NoMatchingViewException? ->
if (noViewFoundException != null) {
throw noViewFoundException
}
val recyclerView = view as RecyclerView
// Get the item count from the RecyclerView
recyclerViewItemCount = recyclerView.adapter?.itemCount ?: 0
}
return recyclerViewItemCount
}
}

View File

@ -0,0 +1,75 @@
/*
* Kiwix Android
* Copyright (c) 2023 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.utils
import android.view.View
import android.widget.CheckBox
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.core.AllOf.allOf
class RecyclerViewSelectedCheckBoxCountAssertion(
private val recyclerViewId: Int,
private val checkBoxId: Int
) {
fun countCheckedCheckboxes(): Int {
var checkedCount = 0
// Find the RecyclerView
val recyclerViewMatcher: Matcher<View> = allOf(
isAssignableFrom(RecyclerView::class.java),
isDisplayed(),
withId(recyclerViewId)
)
// Use a custom ViewMatcher to find checkboxes that are checked
val checkBoxMatcher: Matcher<View> = object : TypeSafeMatcher<View>() {
override fun matchesSafely(view: View): Boolean =
view is CheckBox && view.isChecked
override fun describeTo(description: Description) {
description.appendText("is checked")
}
}
// Count the checked checkboxes
onView(recyclerViewMatcher).check { view, noViewFoundException ->
if (noViewFoundException != null) {
throw noViewFoundException
}
val recyclerView = view as RecyclerView
(0 until recyclerView.childCount)
.asSequence()
.map {
// Check the checkbox directly without using inRoot
recyclerView.getChildAt(it).findViewById<CheckBox>(checkBoxId)
}
.filter { it != null && checkBoxMatcher.matches(it) }
.forEach { _ -> checkedCount++ }
}
return checkedCount
}
}

View File

@ -0,0 +1,175 @@
/*
* Kiwix Android
* Copyright (c) 2023 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.webserver
import android.Manifest
import android.content.Context
import android.os.Build
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import androidx.test.uiautomator.UiDevice
import leakcanary.LeakAssertions
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
class ZimHostFragmentTest {
@Rule
@JvmField
var retryRule = RetryRule()
private lateinit var sharedPreferenceUtil: SharedPreferenceUtil
private lateinit var activityScenario: ActivityScenario<KiwixMainActivity>
private val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.NEARBY_WIFI_DEVICES
)
} else {
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
}
@Rule
@JvmField
var permissionRules: GrantPermissionRule =
GrantPermissionRule.grant(*permissions)
private var context: Context? = null
@Before
fun waitForIdle() {
context = InstrumentationRegistry.getInstrumentation().targetContext
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
if (TestUtils.isSystemUINotRespondingDialogVisible(this)) {
TestUtils.closeSystemDialogs(context)
}
waitForIdle()
}
context?.let {
sharedPreferenceUtil = SharedPreferenceUtil(it).apply {
setIntroShown()
putPrefWifiOnly(false)
setIsPlayStoreBuildType(true)
prefIsTest = true
}
}
activityScenario = ActivityScenario.launch(KiwixMainActivity::class.java).apply {
moveToState(Lifecycle.State.RESUMED)
}
}
@Test
fun testZimHostFragment() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
activityScenario.onActivity {
it.navigate(R.id.libraryFragment)
}
loadZimFileInApplication("testzim.zim")
loadZimFileInApplication("small.zim")
zimHost {
refreshLibraryList()
assertZimFilesLoaded()
openZimHostFragment()
// Check if server is already started
stopServerIfAlreadyStarted()
// Check if both zim file are selected or not to properly run our test case
selectZimFileIfNotAlreadySelected()
clickOnTestZim()
// Start the server with one ZIM file
startServer()
assertServerStarted()
// Check that only one ZIM file is hosted on the server
assertItemHostedOnServer(1)
// Stop the server
stopServer()
assertServerStopped()
// Select the test ZIM file to host on the server
clickOnTestZim()
// Start the server with two ZIM files
startServer()
assertServerStarted()
// Check that both ZIM files are hosted on the server
assertItemHostedOnServer(2)
// Unselect the test ZIM to test restarting server functionality
clickOnTestZim()
// Check if the server is running
assertServerStarted()
// Check that only one ZIM file is hosted on the server after unselecting
assertItemHostedOnServer(1)
}
LeakAssertions.assertNoLeaks()
}
}
private fun loadZimFileInApplication(zimFileName: String) {
val loadFileStream =
ZimHostFragmentTest::class.java.classLoader.getResourceAsStream(zimFileName)
val zimFile = File(sharedPreferenceUtil.prefStorage, zimFileName)
if (zimFile.exists()) zimFile.delete()
zimFile.createNewFile()
loadFileStream.use { inputStream ->
val outputStream: OutputStream = FileOutputStream(zimFile)
outputStream.use { it ->
val buffer = ByteArray(inputStream.available())
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
it.write(buffer, 0, length)
}
}
}
}
@After
fun setIsTestPreference() {
sharedPreferenceUtil.apply {
setIsPlayStoreBuildType(false)
prefIsTest = false
}
}
}

View File

@ -18,10 +18,27 @@
package org.kiwix.kiwixmobile.webserver
import android.util.Log
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import applyWithViewHierarchyPrinting
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh
import junit.framework.AssertionFailedError
import org.hamcrest.CoreMatchers
import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.StringId.TextId
import org.kiwix.kiwixmobile.Findable.Text
import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.utils.RecyclerViewItemCount
import org.kiwix.kiwixmobile.utils.RecyclerViewMatcher
import org.kiwix.kiwixmobile.utils.RecyclerViewSelectedCheckBoxCountAssertion
import org.kiwix.kiwixmobile.utils.StandardActions.openDrawer
fun zimHost(func: ZimHostRobot.() -> Unit) = ZimHostRobot().applyWithViewHierarchyPrinting(func)
@ -30,4 +47,107 @@ class ZimHostRobot : BaseRobot() {
fun assertMenuWifiHotspotDiplayed() {
isVisible(TextId(R.string.menu_wifi_hotspot))
}
fun refreshLibraryList() {
pauseForBetterTestPerformance()
refresh(R.id.zim_swiperefresh)
}
fun assertZimFilesLoaded() {
pauseForBetterTestPerformance()
isVisible(Text("Test_Zim"))
}
fun openZimHostFragment() {
openDrawer()
clickOn(TextId(R.string.menu_wifi_hotspot))
}
fun clickOnTestZim() {
clickOn(Text("Test_Zim"))
}
fun startServer() {
clickOn(ViewId(R.id.startServerButton))
pauseForBetterTestPerformance()
isVisible(TextId(R.string.wifi_dialog_title))
clickOn(TextId(R.string.hotspot_dialog_neutral_button))
}
fun assertServerStarted() {
pauseForBetterTestPerformance()
isVisible(Text("STOP SERVER"))
}
fun stopServerIfAlreadyStarted() {
try {
assertServerStarted()
stopServer()
} catch (exception: Exception) {
Log.i(
"ZIM_HOST_FRAGMENT",
"Failed to stop the server, Probably because server is not running"
)
}
}
fun selectZimFileIfNotAlreadySelected() {
try {
// check both files are selected.
assertItemHostedOnServer(2)
} catch (assertionFailedError: AssertionFailedError) {
try {
val recyclerViewItemsCount =
RecyclerViewItemCount(R.id.recyclerViewZimHost).checkRecyclerViewCount()
(0 until recyclerViewItemsCount)
.asSequence()
.filter { it != 0 }
.forEach(::selectZimFile)
} catch (assertionFailedError: AssertionFailedError) {
Log.i("ZIM_HOST_FRAGMENT", "Failed to select the zim file, probably it is already selected")
}
}
}
private fun selectZimFile(position: Int) {
pauseForBetterTestPerformance()
try {
onView(
RecyclerViewMatcher(R.id.recyclerViewZimHost).atPositionOnView(
position,
R.id.itemBookCheckbox
)
).check(matches(ViewMatchers.isChecked()))
} catch (assertionError: AssertionFailedError) {
pauseForBetterTestPerformance()
onView(
RecyclerViewMatcher(R.id.recyclerViewZimHost).atPositionOnView(
position,
R.id.itemBookCheckbox
)
).perform(click())
}
}
fun assertItemHostedOnServer(itemCount: Int) {
val checkedCheckboxCount =
RecyclerViewSelectedCheckBoxCountAssertion(
R.id.recyclerViewZimHost,
R.id.itemBookCheckbox
).countCheckedCheckboxes()
assertThat(checkedCheckboxCount, CoreMatchers.`is`(itemCount))
}
fun stopServer() {
clickOn(ViewId(R.id.startServerButton))
}
fun assertServerStopped() {
pauseForBetterTestPerformance()
isVisible(Text("START SERVER"))
}
private fun pauseForBetterTestPerformance() {
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong())
}
}

Binary file not shown.

View File

@ -333,6 +333,7 @@ abstract class CoreReaderFragment :
private var readAloudService: ReadAloudService? = null
private var navigationHistoryList: MutableList<NavigationHistoryListItem> = ArrayList()
private var isReadSelection = false
private var isReadAloudServiceRunning = false
private var storagePermissionForNotesLauncher: ActivityResultLauncher<String>? =
registerForActivityResult(
@ -1047,8 +1048,7 @@ abstract class CoreReaderFragment :
} catch (ignore: IllegalArgumentException) {
// to handle if service is already unbounded
}
readAloudService?.registerCallBack(null)
readAloudService = null
unRegisterReadAloudService()
storagePermissionForNotesLauncher?.unregister()
storagePermissionForNotesLauncher = null
}
@ -2074,9 +2074,17 @@ abstract class CoreReaderFragment :
private fun unbindService() {
readAloudService?.let {
requireActivity().unbindService(serviceConnection)
if (!isReadAloudServiceRunning) {
unRegisterReadAloudService()
}
}
}
private fun unRegisterReadAloudService() {
readAloudService?.registerCallBack(null)
readAloudService = null
}
private fun createReadAloudIntent(action: String, isPauseTTS: Boolean): Intent =
Intent(requireActivity(), ReadAloudService::class.java).apply {
setAction(action)
@ -2086,7 +2094,11 @@ abstract class CoreReaderFragment :
}
private fun setActionAndStartTTSService(action: String, isPauseTTS: Boolean = false) {
requireActivity().startService(createReadAloudIntent(action, isPauseTTS))
requireActivity().startService(
createReadAloudIntent(action, isPauseTTS)
).also {
isReadAloudServiceRunning = action == ACTION_PAUSE_OR_RESUME_TTS
}
}
protected abstract fun restoreViewStateOnValidJSON(

View File

@ -60,8 +60,11 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
val prefIsFirstRun: Boolean
get() = sharedPreferences.getBoolean(PREF_IS_FIRST_RUN, true)
val prefIsTest: Boolean
var prefIsTest: Boolean
get() = sharedPreferences.getBoolean(PREF_IS_TEST, false)
set(prefIsTest) {
sharedPreferences.edit { putBoolean(PREF_IS_TEST, prefIsTest) }
}
val prefShowShowCaseToUser: Boolean
get() = sharedPreferences.getBoolean(PREF_SHOW_SHOWCASE, true)

View File

@ -45,12 +45,15 @@ class WebServerHelper @Inject constructor(
private var isServerStarted = false
private var validIpAddressDisposable: Disposable? = null
fun startServerHelper(selectedBooksPath: ArrayList<String>): ServerStatus {
fun startServerHelper(
selectedBooksPath: ArrayList<String>,
restartServer: Boolean
): ServerStatus? {
val ip = getIpAddress()
return if (ip.isNullOrEmpty()) {
ServerStatus(false, R.string.error_ip_address_not_found)
} else {
startAndroidWebServer(selectedBooksPath)
startAndroidWebServer(selectedBooksPath, restartServer)
}
}
@ -61,16 +64,28 @@ class WebServerHelper @Inject constructor(
}
}
private fun startAndroidWebServer(selectedBooksPath: ArrayList<String>): ServerStatus {
var errorMessage: Int? = null
private fun startAndroidWebServer(
selectedBooksPath: ArrayList<String>,
restartServer: Boolean
): ServerStatus? {
var serverStatus: ServerStatus? = null
if (!isServerStarted) {
ServerUtils.port = DEFAULT_PORT
kiwixServer = kiwixServerFactory.createKiwixServer(selectedBooksPath).also {
updateServerState(it.startServer(ServerUtils.port))
Log.d(TAG, "Server status$isServerStarted").also {
if (!isServerStarted) {
errorMessage = R.string.error_server_already_running
}
serverStatus = startKiwixServer(selectedBooksPath)
} else if (restartServer) {
kiwixServer?.stopServer()
serverStatus = startKiwixServer(selectedBooksPath)
}
return serverStatus
}
private fun startKiwixServer(selectedBooksPath: ArrayList<String>): ServerStatus {
var errorMessage: Int? = null
ServerUtils.port = DEFAULT_PORT
kiwixServer = kiwixServerFactory.createKiwixServer(selectedBooksPath).also {
updateServerState(it.startServer(ServerUtils.port))
Log.d(TAG, "Server status$isServerStarted").also {
if (!isServerStarted) {
errorMessage = R.string.error_server_already_running
}
}
}

View File

@ -94,6 +94,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
private lateinit var serviceConnection: ServiceConnection
private var dialog: Dialog? = null
private var activityZimHostBinding: ActivityZimHostBinding? = null
private var isHotspotServiceRunning = false
override val fragmentTitle: String? by lazy {
getString(R.string.menu_wifi_hotspot)
}
@ -304,7 +305,11 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
}
private fun stopServer() {
requireActivity().startService(createHotspotIntent(ACTION_STOP_SERVER))
requireActivity().startService(
createHotspotIntent(ACTION_STOP_SERVER)
).also {
isHotspotServiceRunning = false
}
}
private fun select(bookOnDisk: BooksOnDiskListItem.BookOnDisk) {
@ -316,6 +321,9 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
}
booksAdapter.items = booksList
saveHostedBooks(booksList)
if (ServerUtils.isServerStarted) {
startWifiHotspot(true)
}
}
override fun onStart() {
@ -338,6 +346,9 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
private fun unbindService() {
hotspotService?.let {
requireActivity().unbindService(serviceConnection)
if (!isHotspotServiceRunning) {
unRegisterHotspotService()
}
}
}
@ -370,7 +381,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
activityZimHostBinding?.startServerButton?.setBackgroundColor(
ContextCompat.getColor(requireActivity(), R.color.stopServerRed)
)
bookDelegate.selectionMode = SelectionMode.NORMAL
bookDelegate.selectionMode = SelectionMode.MULTI
booksAdapter.notifyDataSetChanged()
}
@ -403,11 +414,16 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
override fun onDestroyView() {
super.onDestroyView()
activityZimHostBinding?.recyclerViewZimHost?.adapter = null
hotspotService?.registerCallBack(null)
unRegisterHotspotService()
presenter.detachView()
activityZimHostBinding = null
}
private fun unRegisterHotspotService() {
hotspotService?.registerCallBack(null)
hotspotService = null
}
// Advice user to turn on hotspot manually for API<26
private fun startHotspotManuallyDialog() {
@ -487,13 +503,19 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
}
}
override fun onIpAddressValid() {
dialog?.dismiss()
private fun startWifiHotspot(restartServer: Boolean) {
requireActivity().startService(
createHotspotIntent(ACTION_START_SERVER).putStringArrayListExtra(
SELECTED_ZIM_PATHS_KEY, selectedBooksPath
)
)
).putExtra(RESTART_SERVER, restartServer)
).also {
isHotspotServiceRunning = true
}
}
override fun onIpAddressValid() {
dialog?.dismiss()
startWifiHotspot(false)
}
override fun onIpAddressInvalid() {
@ -503,6 +525,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View {
companion object {
const val SELECTED_ZIM_PATHS_KEY = "selected_zim_paths"
const val RESTART_SERVER = "restart_server"
const val PERMISSION_REQUEST_CODE_COARSE_LOCATION = 10
}
}

View File

@ -29,6 +29,7 @@ import org.kiwix.kiwixmobile.core.utils.ServerUtils.getSocketAddress
import org.kiwix.kiwixmobile.core.webserver.WebServerHelper
import org.kiwix.kiwixmobile.core.webserver.ZimHostCallbacks
import org.kiwix.kiwixmobile.core.webserver.ZimHostFragment
import org.kiwix.kiwixmobile.core.webserver.ZimHostFragment.Companion.RESTART_SERVER
import java.lang.ref.WeakReference
import javax.inject.Inject
@ -68,22 +69,27 @@ class HotspotService :
super.onDestroy()
}
@Suppress("NestedBlockDepth")
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
when (intent.action) {
ACTION_START_SERVER ->
ACTION_START_SERVER -> {
val restartServer = intent.getBooleanExtra(RESTART_SERVER, false)
intent.getStringArrayListExtra(ZimHostFragment.SELECTED_ZIM_PATHS_KEY)?.let {
val serverStatus = webServerHelper?.startServerHelper(it)
val serverStatus = webServerHelper?.startServerHelper(it, restartServer)
if (serverStatus?.isServerStarted == true) {
zimHostCallbacks?.onServerStarted(getSocketAddress())
startForegroundNotificationHelper()
Toast.makeText(
this, R.string.server_started_successfully_toast_message,
Toast.LENGTH_SHORT
).show()
if (!restartServer) {
Toast.makeText(
this, R.string.server_started_successfully_toast_message,
Toast.LENGTH_SHORT
).show()
}
} else {
onServerFailedToStart(serverStatus?.errorMessage)
}
} ?: kotlin.run { onServerFailedToStart(R.string.no_books_selected_toast_message) }
}
ACTION_STOP_SERVER -> {
Toast.makeText(