Merge pull request #2156 from briancherin/1648-search-results-new-tab

#1648 Add open in new tab button to search results
This commit is contained in:
Seán Mac Gillicuddy 2020-07-20 11:30:56 +01:00 committed by GitHub
commit bd4335ea94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 154 additions and 19 deletions

View File

@ -151,6 +151,7 @@ import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_CURRENT_FILE;
import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_CURRENT_POSITIONS;
import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_CURRENT_TAB;
import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_FILE_SEARCHED;
import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_FILE_SEARCHED_NEW_TAB;
import static org.kiwix.kiwixmobile.core.utils.ConstantsKt.TAG_KIWIX;
import static org.kiwix.kiwixmobile.core.utils.LanguageUtils.getResourceString;
import static org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil.PREF_KIWIX_MOBILE;
@ -430,8 +431,10 @@ public abstract class CoreReaderFragment extends BaseFragment
private void handleIntentExtras(Intent intent) {
if (intent.hasExtra(TAG_FILE_SEARCHED)) {
boolean openInNewTab = isInTabSwitcher()
|| intent.getBooleanExtra(TAG_FILE_SEARCHED_NEW_TAB, false);
searchForTitle(intent.getStringExtra(TAG_FILE_SEARCHED),
isInTabSwitcher());
openInNewTab);
selectTab(webViewList.size() - 1);
}
if (intent.hasExtra(EXTRA_CHOSE_X_URL)) {
@ -1510,7 +1513,9 @@ public abstract class CoreReaderFragment extends BaseFragment
compatCallback.findAll();
compatCallback.showSoftInput();
} else {
searchForTitle(title, wasFromTabSwitcher);
boolean openInNewTab = wasFromTabSwitcher ||
data.getBooleanExtra(TAG_FILE_SEARCHED_NEW_TAB, false);
searchForTitle(title, openInNewTab);
}
} else if (resultCode == RESULT_CANCELED) {
Log.w(TAG_KIWIX, "Search cancelled or exited");

View File

@ -49,6 +49,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.CreatedWithIntent
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ExitedSearch
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnOpenInNewTabClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchOrigin.FromWebView
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchViewModel
@ -70,10 +71,10 @@ class SearchActivity : BaseActivity() {
private val compositeDisposable = CompositeDisposable()
private val searchAdapter: SearchAdapter by lazy {
SearchAdapter(
RecentSearchDelegate(::onItemClick) {
RecentSearchDelegate(::onItemClick, ::onItemClickNewTab) {
searchViewModel.actions.offer(OnItemLongClick(it))
},
ZimSearchResultDelegate(::onItemClick)
ZimSearchResultDelegate(::onItemClick, ::onItemClickNewTab)
)
}
@ -155,6 +156,10 @@ class SearchActivity : BaseActivity() {
searchViewModel.actions.offer(OnItemClick(it))
}
private fun onItemClickNewTab(it: SearchListItem) {
searchViewModel.actions.offer(OnOpenInNewTabClick(it))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
searchViewModel.actions.offer(ActivityResultReceived(requestCode, resultCode, data))

View File

@ -19,6 +19,7 @@
package org.kiwix.kiwixmobile.core.search.adapter
import android.view.ViewGroup
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.adapter.AbsDelegateAdapter
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.inflate
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem
@ -31,27 +32,31 @@ sealed class SearchDelegate<I : SearchListItem, out VH : SearchViewHolder<I>> :
class RecentSearchDelegate(
private val onClickListener: (SearchListItem) -> Unit,
private val onClickListenerNewTab: (SearchListItem) -> Unit,
private val onLongClickListener: (SearchListItem) -> Unit
) : SearchDelegate<RecentSearchListItem, RecentSearchViewHolder>() {
override val itemClass = RecentSearchListItem::class.java
override fun createViewHolder(parent: ViewGroup) =
RecentSearchViewHolder(
parent.inflate(android.R.layout.simple_selectable_list_item, false),
parent.inflate(R.layout.list_item_search, false),
onClickListener,
onClickListenerNewTab,
onLongClickListener
)
}
class ZimSearchResultDelegate(
private val onClickListener: (SearchListItem) -> Unit
private val onClickListener: (SearchListItem) -> Unit,
private val onClickListenerNewTab: (SearchListItem) -> Unit
) : SearchDelegate<ZimSearchResultListItem, ZimSearchResultViewHolder>() {
override val itemClass = ZimSearchResultListItem::class.java
override fun createViewHolder(parent: ViewGroup) =
ZimSearchResultViewHolder(
parent.inflate(android.R.layout.simple_selectable_list_item, false),
onClickListener
parent.inflate(R.layout.list_item_search, false),
onClickListener,
onClickListenerNewTab
)
}
}

View File

@ -19,7 +19,8 @@
package org.kiwix.kiwixmobile.core.search.adapter
import android.view.View
import android.widget.TextView
import kotlinx.android.synthetic.main.list_item_search.list_item_search_new_tab_button
import kotlinx.android.synthetic.main.list_item_search.list_item_search_text
import org.kiwix.kiwixmobile.core.base.adapter.BaseViewHolder
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.ZimSearchResultListItem
@ -30,6 +31,7 @@ sealed class SearchViewHolder<in T : SearchListItem>(containerView: View) :
class RecentSearchViewHolder(
override val containerView: View,
private val onClickListener: (SearchListItem) -> Unit,
private val onClickListenerNewTab: (SearchListItem) -> Unit,
private val onLongClickListener: (SearchListItem) -> Unit
) : SearchViewHolder<RecentSearchListItem>(containerView) {
override fun bind(item: RecentSearchListItem) {
@ -38,17 +40,20 @@ sealed class SearchViewHolder<in T : SearchListItem>(containerView: View) :
onLongClickListener(item)
true
}
(containerView as TextView).text = item.value
list_item_search_new_tab_button.setOnClickListener { onClickListenerNewTab(item) }
list_item_search_text.text = item.value
}
}
class ZimSearchResultViewHolder(
override val containerView: View,
private val onClickListener: (SearchListItem) -> Unit
private val onClickListener: (SearchListItem) -> Unit,
private val onClickListenerNewTab: (SearchListItem) -> Unit
) : SearchViewHolder<ZimSearchResultListItem>(containerView) {
override fun bind(item: ZimSearchResultListItem) {
containerView.setOnClickListener { onClickListener(item) }
(containerView as TextView).text = item.value
list_item_search_new_tab_button.setOnClickListener { onClickListenerNewTab(item) }
list_item_search_text.text = item.value
}
}
}

View File

@ -28,6 +28,8 @@ sealed class Action {
object StartSpeechInputFailed : Action()
data class OnItemClick(val searchListItem: SearchListItem) : Action()
data class OnOpenInNewTabClick(val searchListItem: SearchListItem) : Action()
data class OnItemLongClick(val searchListItem: SearchListItem) : Action()
data class Filter(val term: String) : Action()
data class ScreenWasStartedFrom(val searchOrigin: SearchOrigin) : Action()

View File

@ -38,6 +38,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.CreatedWithIntent
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ExitedSearch
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnOpenInNewTabClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ScreenWasStartedFrom
@ -89,7 +90,8 @@ class SearchViewModel @Inject constructor(
private fun actionMapper() = actions.map {
when (it) {
ExitedSearch -> effects.offer(Finish)
is OnItemClick -> saveSearchAndOpenItem(it)
is OnItemClick -> saveSearchAndOpenItem(it.searchListItem, false)
is OnOpenInNewTabClick -> saveSearchAndOpenItem(it.searchListItem, true)
is OnItemLongClick -> showDeleteDialog(it)
is Filter -> filter.offer(it.term)
ClickedSearchInText -> searchPreviousScreenWhenStateIsValid()
@ -118,12 +120,12 @@ class SearchViewModel @Inject constructor(
effects.offer(ShowDeleteSearchDialog(longClick.searchListItem, actions))
}
private fun saveSearchAndOpenItem(it: OnItemClick) {
private fun saveSearchAndOpenItem(searchListItem: SearchListItem, openInNewTab: Boolean) {
effects.offer(
SaveSearchToRecents(recentSearchDao, it.searchListItem, zimReaderContainer.id)
SaveSearchToRecents(recentSearchDao, searchListItem, zimReaderContainer.id)
)
effects.offer(
OpenSearchItem(it.searchListItem)
OpenSearchItem(searchListItem, openInNewTab)
)
}

View File

@ -24,12 +24,17 @@ import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED
import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED_NEW_TAB
data class OpenSearchItem(private val searchListItem: SearchListItem) : SideEffect<Unit> {
data class OpenSearchItem(
private val searchListItem: SearchListItem,
private val openInNewTab: Boolean = false
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
activity.setResult(
Activity.RESULT_OK,
Intent().putExtra(TAG_FILE_SEARCHED, searchListItem.value)
.putExtra(TAG_FILE_SEARCHED_NEW_TAB, openInNewTab)
)
activity.finish()
}

View File

@ -34,6 +34,7 @@ const val RESULT_HISTORY_CLEARED = 1239
// Tags
const val TAG_FILE_SEARCHED = "searchedarticle"
const val TAG_FILE_SEARCHED_NEW_TAB = "searchedarticlenewtab"
const val TAG_CURRENT_FILE = "currentzimfile"
const val TAG_CURRENT_ARTICLES = "currentarticles"
const val TAG_CURRENT_POSITIONS = "currentpositions"

View File

@ -0,0 +1,23 @@
<!--
~ Kiwix Android
~ Copyright (c) 2020 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/>.
~
-->
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#565656" android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
</vector>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Kiwix Android
~ Copyright (c) 2020 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/>.
~
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/list_item_search_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:attr/listChoiceBackgroundIndicator"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingStart="8dip"
android:paddingEnd="8dip"
android:textAppearance="?android:attr/textAppearanceListItem"
app:layout_constraintEnd_toStartOf="@+id/list_item_search_new_tab_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/list_item_search_new_tab_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:contentDescription="@string/search_open_in_new_tab"
app:srcCompat="@drawable/ic_open_in_new_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -278,6 +278,7 @@
<string name="diagnostic_report_message">Please send all the following details so we can diagnose the problem</string>
<string name="percentage">%d%%</string>
<string name="pref_text_zoom_title">Text Zoom</string>
<string name="search_open_in_new_tab">Open in new tab</string>
<string name="experimental_navigation">Experimental Navigation</string>
<string-array name="pref_night_modes_entries">
<item>@string/on</item>

View File

@ -45,6 +45,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ExitedSearch
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnOpenInNewTabClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ScreenWasStartedFrom
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpeechInputFailed
@ -226,7 +227,17 @@ internal class SearchViewModelTest {
actionResultsInEffects(
OnItemClick(searchListItem),
SaveSearchToRecents(recentSearchDao, searchListItem, "id"),
OpenSearchItem(searchListItem)
OpenSearchItem(searchListItem, false)
)
}
@Test
fun `OnOpenInNewTabClick offers Saves and Opens in new tab`() {
val searchListItem = RecentSearchListItem("")
actionResultsInEffects(
OnOpenInNewTabClick(searchListItem),
SaveSearchToRecents(recentSearchDao, searchListItem, "id"),
OpenSearchItem(searchListItem, true)
)
}

View File

@ -28,6 +28,7 @@ import io.mockk.verify
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem
import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED
import org.kiwix.kiwixmobile.core.utils.TAG_FILE_SEARCHED_NEW_TAB
internal class OpenSearchItemTest {
@ -39,8 +40,26 @@ internal class OpenSearchItemTest {
val intent = mockk<Intent>()
every {
anyConstructed<Intent>().putExtra(TAG_FILE_SEARCHED, searchListItem.value)
.putExtra(TAG_FILE_SEARCHED_NEW_TAB, false)
} returns intent
OpenSearchItem(searchListItem).invokeWith(activity)
OpenSearchItem(searchListItem, false).invokeWith(activity)
verify {
activity.setResult(Activity.RESULT_OK, intent)
activity.finish()
}
}
@Test
fun `invoke with returns an Ok Result with list item value for new tab`() {
val searchListItem = RecentSearchListItem("")
val activity: AppCompatActivity = mockk()
mockkConstructor(Intent::class)
val intent = mockk<Intent>()
every {
anyConstructed<Intent>().putExtra(TAG_FILE_SEARCHED, searchListItem.value)
.putExtra(TAG_FILE_SEARCHED_NEW_TAB, true)
} returns intent
OpenSearchItem(searchListItem, true).invokeWith(activity)
verify {
activity.setResult(Activity.RESULT_OK, intent)
activity.finish()