#1676 Search is slow and laggy - add kotlin UDF implementation

This commit is contained in:
Sean Mac Gillicuddy 2020-01-14 17:57:45 +00:00
parent 08b4e20e2a
commit e1f87b1a60
43 changed files with 1154 additions and 612 deletions

View File

@ -1,6 +1,6 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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
@ -27,7 +27,6 @@ import org.kiwix.kiwixmobile.di.KiwixScope;
@KiwixScope
public class KiwixViewModelFactory extends ViewModelFactory {
@Inject
public KiwixViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
super(creators);

View File

@ -32,6 +32,7 @@ import kotlinx.android.synthetic.main.activity_language.language_recycler_view
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
import org.kiwix.kiwixmobile.core.utils.SimpleTextListener
import org.kiwix.kiwixmobile.kiwixActivityComponent
import org.kiwix.kiwixmobile.language.adapter.LanguageAdapter
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.HeaderDelegate
@ -44,7 +45,6 @@ import org.kiwix.kiwixmobile.language.viewmodel.State
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.language.viewmodel.State.Loading
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
import org.kiwix.kiwixmobile.zim_manager.SimpleTextListener
import javax.inject.Inject
class LanguageActivity : BaseActivity() {

View File

@ -51,14 +51,17 @@ class LanguageViewModel @Inject constructor(
.subscribe(state::postValue, Throwable::printStackTrace),
languageDao.languages().filter { it.isNotEmpty() }
.subscribe(
{
actions.offer(UpdateLanguages(it))
},
{ actions.offer(UpdateLanguages(it)) },
Throwable::printStackTrace
)
)
}
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
private fun reduce(
action: Action,
currentState: State

View File

@ -32,6 +32,7 @@ import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.start
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.SimpleTextListener
import org.kiwix.kiwixmobile.kiwixActivityComponent
import org.kiwix.kiwixmobile.language.LanguageActivity
import org.kiwix.kiwixmobile.local_file_transfer.LocalFileTransferActivity

View File

@ -23,7 +23,10 @@
android:usesCleartextTraffic="true"
tools:targetApi="m">
<activity android:name=".search.SearchActivity" />
<activity
android:name=".search.SearchActivity"
android:windowSoftInputMode="adjustResize" />
<activity android:name=".bookmark.BookmarksActivity" />
<provider

View File

@ -21,11 +21,15 @@ package org.kiwix.kiwixmobile.core;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
@Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public ViewModelFactory(
Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;

View File

@ -19,6 +19,7 @@ package org.kiwix.kiwixmobile.core.dao
import io.objectbox.Box
import io.objectbox.kotlin.query
import io.objectbox.query.Query
import io.objectbox.rx.RxQuery
import io.reactivex.BackpressureStrategy
import io.reactivex.BackpressureStrategy.LATEST
@ -40,5 +41,8 @@ class NewLanguagesDao @Inject constructor(private val box: Box<LanguageEntity>)
}
}
internal fun <T> Box<T>.asFlowable(backpressureStrategy: BackpressureStrategy = LATEST) =
RxQuery.observable(query {}).toFlowable(backpressureStrategy)
internal fun <T> Box<T>.asFlowable(
query: Query<T> = query {},
backpressureStrategy: BackpressureStrategy = LATEST
) =
RxQuery.observable(query).toFlowable(backpressureStrategy)

View File

@ -22,28 +22,25 @@ import io.objectbox.kotlin.query
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity_
import org.kiwix.kiwixmobile.core.data.local.entity.RecentSearch
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem
import javax.inject.Inject
class NewRecentSearchDao @Inject constructor(
val box: Box<RecentSearchEntity>
) {
fun getRecentSearches(zimId: String?) = box
.query {
fun recentSearches(zimId: String?) = box.asFlowable(
box.query {
equal(RecentSearchEntity_.zimId, zimId ?: "")
orderDesc(RecentSearchEntity_.id)
}
.find()
.distinctBy(RecentSearchEntity::searchTerm)
.take(NUM_RECENT_RESULTS)
.map(RecentSearchEntity::searchTerm)
).map { searchEntities ->
searchEntities.distinct()
.take(NUM_RECENT_RESULTS)
.map { searchEntity -> RecentSearchListItem(searchEntity.searchTerm) }
}
fun saveSearch(title: String, id: String) {
box.put(
RecentSearchEntity(
searchTerm = title,
zimId = id
)
)
box.put(RecentSearchEntity(searchTerm = title, zimId = id))
}
fun deleteSearchString(searchTerm: String) {

View File

@ -0,0 +1,42 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.di.components
import android.app.Activity
import dagger.BindsInstance
import dagger.Subcomponent
import org.kiwix.kiwixmobile.core.di.ActivityScope
import org.kiwix.kiwixmobile.core.di.modules.ActivityModule
import org.kiwix.kiwixmobile.core.search.SearchActivity
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
@ActivityScope
@Subcomponent(modules = [ActivityModule::class])
interface CoreActivityComponent {
fun inject(searchActivity: SearchActivity)
fun inject(showDeleteSearchDialog: ShowDeleteSearchDialog)
@Subcomponent.Builder
interface Builder {
@BindsInstance
fun activity(activity: Activity): CoreActivityComponent.Builder
fun build(): CoreActivityComponent
}
}

View File

@ -29,19 +29,21 @@ import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.data.DataModule
import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.di.modules.ApplicationModule
import org.kiwix.kiwixmobile.core.di.modules.CoreViewModelModule
import org.kiwix.kiwixmobile.core.di.modules.JNIModule
import org.kiwix.kiwixmobile.core.di.modules.NetworkModule
import org.kiwix.kiwixmobile.core.di.modules.SearchModule
import org.kiwix.kiwixmobile.core.downloader.Downloader
import org.kiwix.kiwixmobile.core.main.AddNoteDialog
import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.reader.ZimContentProvider
import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.AutoCompleteAdapter
import org.kiwix.kiwixmobile.core.settings.CorePrefsFragment
import org.kiwix.kiwixmobile.core.utils.BookUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
@ -53,7 +55,10 @@ import javax.inject.Singleton
ApplicationModule::class,
NetworkModule::class,
JNIModule::class,
DataModule::class]
DataModule::class,
CoreViewModelModule::class,
SearchModule::class
]
)
interface CoreComponent {
@ -65,6 +70,7 @@ interface CoreComponent {
fun build(): CoreComponent
}
fun activityComponentBuilder(): CoreActivityComponent.Builder
fun zimReaderContainer(): ZimReaderContainer
fun sharedPrefUtil(): SharedPreferenceUtil
fun zimFileReaderFactory(): ZimFileReader.Factory
@ -76,6 +82,7 @@ interface CoreComponent {
fun fetchDownloadDao(): FetchDownloadDao
fun newBookDao(): NewBookDao
fun newLanguagesDao(): NewLanguagesDao
fun recentSearchDao(): NewRecentSearchDao
fun connectivityManager(): ConnectivityManager
fun context(): Context
fun downloader(): Downloader
@ -85,7 +92,6 @@ interface CoreComponent {
fun inject(zimContentProvider: ZimContentProvider)
fun inject(kiwixWebView: KiwixWebView)
fun inject(prefsFragment: CorePrefsFragment)
fun inject(autoCompleteAdapter: AutoCompleteAdapter)
fun inject(storageSelectDialog: StorageSelectDialog)
fun inject(addNoteDialog: AddNoteDialog)
}

View File

@ -27,7 +27,6 @@ import org.kiwix.kiwixmobile.core.error.ErrorActivity;
import org.kiwix.kiwixmobile.core.help.HelpActivity;
import org.kiwix.kiwixmobile.core.history.HistoryActivity;
import org.kiwix.kiwixmobile.core.history.HistoryModule;
import org.kiwix.kiwixmobile.core.search.SearchActivity;
/**
* Dagger.Android annotation processor will create the sub-components. We also specify the modules
@ -38,10 +37,6 @@ import org.kiwix.kiwixmobile.core.search.SearchActivity;
@Module
public abstract class ActivityBindingModule {
@ActivityScope
@ContributesAndroidInjector
public abstract SearchActivity provideSearchActivity();
@ActivityScope
@ContributesAndroidInjector(modules = BookmarksModule.class)
public abstract BookmarksActivity provideBookmarksActivity();

View File

@ -0,0 +1,42 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.di.modules
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import org.kiwix.kiwixmobile.core.ViewModelFactory
import org.kiwix.kiwixmobile.core.di.ViewModelKey
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchViewModel
import javax.inject.Singleton
@Module
abstract class CoreViewModelModule {
@Binds
@IntoMap
@ViewModelKey(SearchViewModel::class)
abstract fun bindSearchViewModel(searchViewModel: SearchViewModel): ViewModel
@Binds
@Singleton
abstract fun bindViewModelFactory(factory: ViewModelFactory):
ViewModelProvider.Factory
}

View File

@ -0,0 +1,31 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.di.modules
import dagger.Binds
import dagger.Module
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchResultGenerator
import org.kiwix.kiwixmobile.core.search.viewmodel.ZimSearchResultGenerator
@Module
abstract class SearchModule {
@Binds
abstract fun bindsSearchResultGenerator(zimSearchResultGenerator: ZimSearchResultGenerator):
SearchResultGenerator
}

View File

@ -28,6 +28,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.Intents
object ActivityExtensions {
@ -87,4 +88,7 @@ object ActivityExtensions {
) =
ViewModelProviders.of(this, viewModelFactory)
.get(T::class.java)
val Activity.coreActivityComponent
get() = CoreApp.getCoreComponent().activityComponentBuilder().activity(this).build()
}

View File

@ -110,7 +110,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES;
import static org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.DocumentSection;
import static org.kiwix.kiwixmobile.core.main.TableDrawerAdapter.TableClickListener;
import static org.kiwix.kiwixmobile.core.search.SearchActivity.EXTRA_SEARCH_IN_TEXT;
import static org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchInPreviousScreen.EXTRA_SEARCH_IN_TEXT;
import static org.kiwix.kiwixmobile.core.utils.AnimationUtils.rotate;
import static org.kiwix.kiwixmobile.core.utils.Constants.BOOKMARK_CHOSEN_REQUEST;
import static org.kiwix.kiwixmobile.core.utils.Constants.EXTRA_CHOSE_X_TITLE;

View File

@ -1,154 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 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.core.search;
import android.content.Context;
import android.text.Html;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import org.kiwix.kiwixlib.JNIKiwix;
import org.kiwix.kiwixlib.JNIKiwixSearcher;
import org.kiwix.kiwixmobile.core.CoreApp;
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil;
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer;
public class AutoCompleteAdapter extends ArrayAdapter<String> implements Filterable {
@Inject JNIKiwix currentJNIReader;
@Inject SharedPreferenceUtil sharedPreferenceUtil;
@Inject ZimReaderContainer zimReaderContainer;
private List<String> mData;
private KiwixFilter mFilter;
private Context context;
public AutoCompleteAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1);
this.context = context;
mData = new ArrayList<>();
mFilter = new KiwixFilter();
setupDagger();
}
private void setupDagger() {
CoreApp.getCoreComponent().inject(this);
}
@Override
public int getCount() {
return mData.size();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = super.getView(position, convertView, parent);
TextView tv = row.findViewById(android.R.id.text1);
tv.setText(Html.fromHtml(getItem(position)));
return row;
}
@Override
public String getItem(int index) {
String a = mData.get(index);
if (a.endsWith(".html")) {
String trim = a.substring(2);
trim = trim.substring(0, trim.length() - 5);
return trim.replace("_", " ");
} else {
return a;
}
}
@Override
public Filter getFilter() {
return mFilter;
}
class KiwixFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults filterResults = new FilterResults();
ArrayList<String> data = new ArrayList<>();
if (constraint != null) {
try {
/* Get search request */
final String query = constraint.toString();
/* Fulltext search */
if (sharedPreferenceUtil.getPrefFullTextSearch()) {
zimReaderContainer.search(query, 200);
JNIKiwixSearcher.Result result = zimReaderContainer.getNextResult();
while (result != null) {
if (!result.getTitle().trim().isEmpty()) {
data.add(result.getTitle());
}
result = zimReaderContainer.getNextResult();
}
}
/* Suggestion search if no fulltext results */
if (data.size() == 0) {
zimReaderContainer.searchSuggestions(query, 200);
String suggestion;
String suggestionUrl;
SearchSuggestion results;
List<String> alreadyAdded = new ArrayList<>();
while ((results = zimReaderContainer.getNextSuggestion()) != null) {
suggestion = results.getTitle();
suggestionUrl = results.getUrl();
if (!alreadyAdded.contains(suggestionUrl)) {
alreadyAdded.add(suggestionUrl);
data.add(suggestion);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
/* Return results */
filterResults.values = data;
filterResults.count = data.size();
}
return filterResults;
}
@Override
protected void publishResults(CharSequence contraint, FilterResults results) {
mData = (ArrayList<String>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
}

View File

@ -1,353 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 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.core.search;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.speech.RecognizerIntent;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.ViewCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.core.Intents;
import org.kiwix.kiwixmobile.core.R;
import org.kiwix.kiwixmobile.core.base.BaseActivity;
import org.kiwix.kiwixmobile.core.main.CoreMainActivity;
import static org.kiwix.kiwixmobile.core.utils.Constants.EXTRA_IS_WIDGET_VOICE;
import static org.kiwix.kiwixmobile.core.utils.Constants.EXTRA_SEARCH;
import static org.kiwix.kiwixmobile.core.utils.Constants.EXTRA_SEARCH_TEXT;
import static org.kiwix.kiwixmobile.core.utils.Constants.TAG_FILE_SEARCHED;
public class SearchActivity extends BaseActivity
implements AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener,
SearchViewCallback {
public static final String EXTRA_SEARCH_IN_TEXT = "bool_searchintext";
private final int REQ_CODE_SPEECH_INPUT = 100;
@Inject
SearchPresenter searchPresenter;
private ListView listView;
private ArrayAdapter<String> currentAdapter;
private AutoCompleteAdapter autoAdapter;
private ArrayAdapter<String> defaultAdapter;
private SearchView searchView;
private String searchText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search);
if (savedInstanceState != null) {
searchText = savedInstanceState.getString(EXTRA_SEARCH_TEXT);
}
Toolbar toolbar = findViewById(R.id.toolbar);
ViewCompat.setLayoutDirection(toolbar, ViewCompat.LAYOUT_DIRECTION_LOCALE);
setSupportActionBar(toolbar);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_action_back);
getSupportActionBar().setHomeButtonEnabled(true);
searchPresenter.attachView(this);
listView = findViewById(R.id.search_list);
defaultAdapter = getDefaultAdapter();
searchPresenter.getRecentSearches();
activateDefaultAdapter();
autoAdapter = new AutoCompleteAdapter(this);
listView.setOnItemClickListener(this);
listView.setOnItemLongClickListener(this);
boolean IS_VOICE_SEARCH_INTENT = getIntent().getBooleanExtra(EXTRA_IS_WIDGET_VOICE, false);
if (IS_VOICE_SEARCH_INTENT) {
promptSpeechInput();
}
}
public void activateDefaultAdapter() {
currentAdapter = defaultAdapter;
listView.setAdapter(currentAdapter);
}
public void activateAutoAdapter() {
currentAdapter = autoAdapter;
listView.setAdapter(currentAdapter);
}
@Override
public void addRecentSearches(List<String> recentSearches) {
defaultAdapter.addAll(recentSearches);
defaultAdapter.notifyDataSetChanged();
}
@Override
public void finish() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
int value =
Settings.System.getInt(getContentResolver(), Settings.System.ALWAYS_FINISH_ACTIVITIES, 0);
if (value == 1) {
Intent intent = Intents.internal(CoreMainActivity.class);
startActivity(intent);
} else {
super.finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_search, menu);
MenuItem searchMenuItem = menu.findItem(R.id.menu_search);
searchMenuItem.expandActionView();
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
searchView.setMaxWidth(Integer.MAX_VALUE);
if (searchText != null) {
searchView.setQuery(searchText, false);
activateAutoAdapter();
autoAdapter.getFilter().filter(searchText.toLowerCase());
}
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
return false;
}
@Override
public boolean onQueryTextChange(String s) {
if (s.equals("")) {
View item = findViewById(R.id.menu_searchintext);
item.setVisibility(View.VISIBLE);
activateDefaultAdapter();
} else {
View item = findViewById(R.id.menu_searchintext);
item.setVisibility(View.GONE);
activateAutoAdapter();
autoAdapter.getFilter().filter(s.toLowerCase());
}
return true;
}
});
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return false;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
finish();
return false;
}
});
if (getIntent().hasExtra(Intent.EXTRA_PROCESS_TEXT)) {
searchView.setQuery(getIntent().getStringExtra(Intent.EXTRA_PROCESS_TEXT), true);
}
if (getIntent().hasExtra(EXTRA_SEARCH)) {
searchView.setQuery(getIntent().getStringExtra(EXTRA_SEARCH), true);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_searchintext) {
String queryText = "";
if (searchView != null) {
queryText = searchView.getQuery().toString();
}
Intent resultIntent = Intents.internal(CoreMainActivity.class);
resultIntent.putExtra(EXTRA_SEARCH_IN_TEXT, true);
resultIntent.putExtra(TAG_FILE_SEARCHED, queryText);
if (shouldStartNewActivity() != 1) {
setResult(RESULT_OK, resultIntent);
finish();
} else {
startActivity(resultIntent);
}
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String title = currentAdapter.getItem(position);
searchPresenter.saveSearch(title);
sendMessage(title);
}
private void sendMessage(String uri) {
int value = shouldStartNewActivity();
if (value == 1) {
Intent i = Intents.internal(CoreMainActivity.class);
i.putExtra(TAG_FILE_SEARCHED, uri);
startActivity(i);
} else {
Intent i = new Intent();
i.putExtra(TAG_FILE_SEARCHED, uri);
setResult(RESULT_OK, i);
finish();
}
}
/**
* Checks if the ActivityManager is set to aggressively reclaim Activities.
*
* @return 1 if the above setting is true.
*/
private int shouldStartNewActivity() {
int value;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
//deprecated in API 17
value =
Settings.System.getInt(getContentResolver(), Settings.System.ALWAYS_FINISH_ACTIVITIES, 0);
} else {
value =
Settings.System.getInt(getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0);
}
return value;
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (parent.getAdapter() == defaultAdapter) {
String searched = listView.getItemAtPosition(position).toString();
deleteSpecificSearchDialog(searched);
}
return true;
}
private void deleteSpecificSearchDialog(final String search) {
new AlertDialog.Builder(this)
.setMessage(getString(R.string.delete_recent_search_item))
.setPositiveButton(getResources().getString(R.string.delete), (dialog, which) -> {
deleteSpecificSearchItem(search);
Toast.makeText(getBaseContext(),
getResources().getString(R.string.delete_specific_search_toast), Toast.LENGTH_SHORT)
.show();
})
.setNegativeButton(android.R.string.no, (dialog, which) -> {
// do nothing
})
.show();
}
private void deleteSpecificSearchItem(String search) {
searchPresenter.deleteSearchString(search);
resetAdapter();
}
private void resetAdapter() {
defaultAdapter = getDefaultAdapter();
activateDefaultAdapter();
searchPresenter.getRecentSearches();
}
private ArrayAdapter<String> getDefaultAdapter() {
return new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1) {
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View row;
if (convertView == null) {
row = LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_1, null);
} else {
row = convertView;
}
((TextView) row).setText(Html.fromHtml(getItem(position)));
return row;
}
};
}
private void promptSpeechInput() {
String appName = getResources().getString(R.string.app_name);
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE,
Locale.getDefault()); // TODO: choose selected lang on kiwix
intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
String.format(getString(R.string.speech_prompt_text), appName));
try {
startActivityForResult(intent, REQ_CODE_SPEECH_INPUT);
} catch (ActivityNotFoundException a) {
Toast.makeText(getApplicationContext(),
getString(R.string.speech_not_supported),
Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQ_CODE_SPEECH_INPUT: {
if (resultCode == RESULT_OK && data != null) {
ArrayList<String> result = data
.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
searchViaVoice(result.get(0));
}
break;
}
}
}
private void searchViaVoice(String search) {
searchView.setQuery(search, false);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (searchView != null && searchView.getQuery() != null) {
outState.putString(EXTRA_SEARCH_TEXT, searchView.getQuery().toString());
}
}
}

View File

@ -0,0 +1,155 @@
/*
* Kiwix Android
* Copyright (c) 2019 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.core.search
import android.R.anim
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.MenuItem.OnActionExpandListener
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.activity_search.searchViewAnimator
import kotlinx.android.synthetic.main.activity_search.search_list
import kotlinx.android.synthetic.main.layout_toolbar.toolbar
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.R.id
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.coreActivityComponent
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.viewModel
import org.kiwix.kiwixmobile.core.extensions.setDistinctDisplayedChild
import org.kiwix.kiwixmobile.core.search.adapter.SearchAdapter
import org.kiwix.kiwixmobile.core.search.adapter.SearchDelegate.RecentSearchDelegate
import org.kiwix.kiwixmobile.core.search.adapter.SearchDelegate.ZimSearchResultDelegate
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ActivityResultReceived
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ClickedSearchInText
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.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchViewModel
import org.kiwix.kiwixmobile.core.search.viewmodel.State
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.utils.SimpleTextListener
import javax.inject.Inject
class SearchActivity : BaseActivity() {
val activityComponent by lazy { coreActivityComponent }
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var searchView: SearchView
private lateinit var searchInTextMenuItem: MenuItem
private val searchViewModel by lazy { viewModel<SearchViewModel>(viewModelFactory) }
private val compositeDisposable = CompositeDisposable()
private val searchAdapter: SearchAdapter by lazy {
SearchAdapter(
RecentSearchDelegate(::onItemClick) {
searchViewModel.actions.offer(OnItemLongClick(it))
},
ZimSearchResultDelegate(::onItemClick)
)
}
override fun injection() {
activityComponent.inject(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
setSupportActionBar(toolbar)
supportActionBar!!.setHomeAsUpIndicator(R.drawable.ic_action_back)
supportActionBar!!.setHomeButtonEnabled(true)
search_list.run {
adapter = searchAdapter
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
setHasFixedSize(true)
}
compositeDisposable.add(searchViewModel.effects.subscribe { it.invokeWith(this) })
}
override fun onDestroy() {
compositeDisposable.clear()
super.onDestroy()
}
override fun finish() {
super.finish()
overridePendingTransition(anim.fade_in, anim.fade_out)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_search, menu)
val searchMenuItem = menu.findItem(id.menu_search)
searchMenuItem.expandActionView()
searchView = searchMenuItem.actionView as SearchView
searchView.setOnQueryTextListener(SimpleTextListener {
searchViewModel.actions.offer(Filter(it))
})
searchMenuItem.setOnActionExpandListener(object : OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem) = false
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
searchViewModel.actions.offer(ExitedSearch)
return false
}
})
searchInTextMenuItem = menu.findItem(R.id.menu_searchintext)
searchInTextMenuItem.setOnMenuItemClickListener {
searchViewModel.actions.offer(ClickedSearchInText)
true
}
searchViewModel.state.observe(this, Observer(::render))
searchViewModel.actions.offer(CreatedWithIntent(intent))
return true
}
private fun render(state: State) = when (state) {
is Results -> {
searchViewAnimator.setDistinctDisplayedChild(0)
searchAdapter.items = state.values
searchView.setQuery(state.searchString, false)
searchInTextMenuItem.isVisible = state.searchString.isNotBlank()
}
Empty -> searchViewAnimator.setDistinctDisplayedChild(1)
Initialising -> {
// do nothing
}
}
private fun onItemClick(it: SearchListItem) {
searchViewModel.actions.offer(OnItemClick(it))
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
searchViewModel.actions.offer(ActivityResultReceived(requestCode, resultCode, data))
}
}

View File

@ -1,56 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 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.core.search;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.core.base.BasePresenter;
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao;
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer;
/**
* Created by srv_twry on 14/2/18.
*/
public class SearchPresenter extends BasePresenter<SearchViewCallback> {
private final NewRecentSearchDao recentSearchDao;
private final ZimReaderContainer zimReaderContainer;
@Inject SearchPresenter(NewRecentSearchDao recentSearchDao,
ZimReaderContainer zimReaderContainer) {
this.recentSearchDao = recentSearchDao;
this.zimReaderContainer = zimReaderContainer;
}
@Override
public void attachView(SearchViewCallback searchViewCallback) {
super.attachView(searchViewCallback);
}
void getRecentSearches() {
view.addRecentSearches(recentSearchDao.getRecentSearches(zimReaderContainer.getId()));
}
void saveSearch(String title) {
recentSearchDao.saveSearch(title, zimReaderContainer.getId());
}
void deleteSearchString(String search) {
recentSearchDao.deleteSearchString(search);
}
}

View File

@ -0,0 +1,28 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.adapter
import org.kiwix.kiwixmobile.core.base.adapter.AdapterDelegate
import org.kiwix.kiwixmobile.core.base.adapter.BaseDelegateAdapter
class SearchAdapter(
vararg delegates: AdapterDelegate<SearchListItem>
) : BaseDelegateAdapter<SearchListItem>(*delegates) {
override fun getIdFor(item: SearchListItem) = item.value.hashCode().toLong()
}

View File

@ -0,0 +1,57 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.adapter
import android.view.ViewGroup
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
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.ZimSearchResultListItem
import org.kiwix.kiwixmobile.core.search.adapter.SearchViewHolder.RecentSearchViewHolder
import org.kiwix.kiwixmobile.core.search.adapter.SearchViewHolder.ZimSearchResultViewHolder
sealed class SearchDelegate<I : SearchListItem, out VH : SearchViewHolder<I>> :
AbsDelegateAdapter<I, SearchListItem, VH> {
class RecentSearchDelegate(
private val onClickListener: (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_list_item_1, false),
onClickListener,
onLongClickListener
)
}
class ZimSearchResultDelegate(
private val onClickListener: (SearchListItem) -> Unit
) : SearchDelegate<ZimSearchResultListItem, ZimSearchResultViewHolder>() {
override val itemClass = ZimSearchResultListItem::class.java
override fun createViewHolder(parent: ViewGroup) =
ZimSearchResultViewHolder(
parent.inflate(android.R.layout.simple_list_item_1, false),
onClickListener
)
}
}

View File

@ -0,0 +1,26 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.adapter
sealed class SearchListItem() {
abstract val value: String
data class RecentSearchListItem(override val value: String) : SearchListItem()
data class ZimSearchResultListItem constructor(override val value: String) : SearchListItem()
}

View File

@ -0,0 +1,54 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.adapter
import android.view.View
import android.widget.TextView
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
sealed class SearchViewHolder<in T : SearchListItem>(containerView: View) :
BaseViewHolder<T>(containerView) {
class RecentSearchViewHolder(
override val containerView: View,
private val onClickListener: (SearchListItem) -> Unit,
private val onLongClickListener: (SearchListItem) -> Unit
) : SearchViewHolder<RecentSearchListItem>(containerView) {
override fun bind(item: RecentSearchListItem) {
containerView.setOnClickListener { onClickListener(item) }
containerView.setOnLongClickListener {
onLongClickListener(item)
true
}
(containerView as TextView).text = item.value
}
}
class ZimSearchResultViewHolder(
override val containerView: View,
private val onClickListener: (SearchListItem) -> Unit
) : SearchViewHolder<ZimSearchResultListItem>(containerView) {
override fun bind(item: ZimSearchResultListItem) {
containerView.setOnClickListener { onClickListener(item) }
(containerView as TextView).text = item.value
}
}
}

View File

@ -0,0 +1,37 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel
import android.content.Intent
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
sealed class Action {
object ExitedSearch : Action()
object ClickedSearchInText : Action()
object ReceivedPromptForSpeechInput : Action()
object StartSpechInputFailed : Action()
data class OnItemClick(val searchListItem: SearchListItem) : Action()
data class OnItemLongClick(val searchListItem: SearchListItem) : Action()
data class Filter(val term: String) : Action()
data class ConfirmedDelete(val searchListItem: SearchListItem) : Action()
data class CreatedWithIntent(val intent: Intent?) : Action()
data class ActivityResultReceived(val requestCode: Int, val resultCode: Int, val data: Intent?) :
Action()
}

View File

@ -0,0 +1,54 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.ZimSearchResultListItem
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
interface SearchResultGenerator {
fun generateSearchResults(searchTerm: String): List<SearchListItem>
}
class ZimSearchResultGenerator @Inject constructor(
val sharedPreferenceUtil: SharedPreferenceUtil,
val zimReaderContainer: ZimReaderContainer
) : SearchResultGenerator {
override fun generateSearchResults(searchTerm: String) =
if (searchTerm.isNotEmpty()) readResultsFromZim(searchTerm)
else emptyList()
private fun readResultsFromZim(it: String) =
if (sharedPreferenceUtil.prefFullTextSearch)
zimReaderContainer.search(it, 200).run { fullTextResults() }
else
zimReaderContainer.searchSuggestions(it, 200).run { suggestionResults() }
private fun suggestionResults() = generateSequence {
zimReaderContainer.getNextSuggestion()?.let { ZimSearchResultListItem(it.title) }
}.distinct()
.toList()
private fun fullTextResults() = generateSequence {
zimReaderContainer.getNextResult()?.let { ZimSearchResultListItem(it.title) }
}.filter { it.value.isNotBlank() }
.toList()
}

View File

@ -0,0 +1,156 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.Flowable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.Function3
import io.reactivex.processors.BehaviorProcessor
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ActivityResultReceived
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ClickedSearchInText
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ConfirmedDelete
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.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpechInputFailed
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.DeleteRecentSearch
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.OpenSearchItem
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ProcessActivityResult
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SaveSearchToRecents
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchInPreviousScreen
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.SearchIntentProcessing
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowDeleteSearchDialog
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.ShowToast
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.StartSpeechInput
import java.util.concurrent.TimeUnit.MILLISECONDS
import javax.inject.Inject
class SearchViewModel @Inject constructor(
val recentSearchDao: NewRecentSearchDao,
val zimReaderContainer: ZimReaderContainer,
private val searchResultGenerator: SearchResultGenerator
) : ViewModel() {
val state = MutableLiveData<State>().apply { value = Initialising }
val effects = PublishProcessor.create<SideEffect<*>>()
val actions = PublishProcessor.create<Action>()
private val filter = BehaviorProcessor.createDefault("")
private val compositeDisposable = CompositeDisposable()
init {
compositeDisposable.addAll(
viewStateReducer(),
actionMapper()
)
}
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
private fun actionMapper() = actions.map {
when (it) {
ExitedSearch -> effects.offer(Finish)
is OnItemClick -> saveSearchAndOpenItem(it)
is OnItemLongClick -> showDeleteDialog(it)
is Filter -> filter.offer(it.term)
ClickedSearchInText -> searchPreviousScreenhenStateIsValid()
is ConfirmedDelete -> deleteItemAndShowToast(it)
is CreatedWithIntent -> effects.offer(SearchIntentProcessing(it.intent, actions))
ReceivedPromptForSpeechInput -> effects.offer(StartSpeechInput(actions))
StartSpechInputFailed -> effects.offer(ShowToast(string.speech_not_supported))
is ActivityResultReceived ->
effects.offer(ProcessActivityResult(it.requestCode, it.resultCode, it.data, actions))
}
}.subscribe(
{},
Throwable::printStackTrace
)
private fun deleteItemAndShowToast(it: ConfirmedDelete) {
effects.offer(DeleteRecentSearch(it.searchListItem, recentSearchDao))
effects.offer(ShowToast(string.delete_specific_search_toast))
}
private fun searchPreviousScreenhenStateIsValid(): Any =
when (val currentState = state.value) {
is Results -> effects.offer(SearchInPreviousScreen(currentState.searchString))
else -> Unit
}
private fun showDeleteDialog(longClick: OnItemLongClick) {
effects.offer(ShowDeleteSearchDialog(longClick.searchListItem, actions))
}
private fun saveSearchAndOpenItem(it: OnItemClick) {
effects.offer(
SaveSearchToRecents(recentSearchDao, it.searchListItem, zimReaderContainer.id)
)
effects.offer(
OpenSearchItem(it.searchListItem)
)
}
private fun viewStateReducer() =
Flowable.combineLatest(
recentSearchDao.recentSearches(zimReaderContainer.id),
searchResultsFromZimReader(),
filter,
Function3(this::reduce)
).debounce(100, MILLISECONDS)
.subscribe(state::postValue, Throwable::printStackTrace)
private fun reduce(
recentSearchResults: List<SearchListItem>,
zimSearchResults: List<SearchListItem>,
searchString: String
) = when {
searchString.isNotEmpty() && zimSearchResults.isNotEmpty() ->
Results(searchString, zimSearchResults)
searchString.isEmpty() && recentSearchResults.isNotEmpty() ->
Results(searchString, recentSearchResults)
else -> Empty
}
private fun searchResultsFromZimReader() = filter
.distinctUntilChanged()
.switchMap(::searchResults)
private fun searchResults(it: String) = Flowable.fromCallable {
searchResultGenerator.generateSearchResults(it)
}.subscribeOn(Schedulers.io())
}

View File

@ -0,0 +1,27 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
sealed class State {
data class Results(val searchString: String, val values: List<SearchListItem>) : State()
object Empty : State()
object Initialising : State()
}

View File

@ -0,0 +1,33 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
data class DeleteRecentSearch(
val searchListItem: SearchListItem,
val recentSearchDao: NewRecentSearchDao
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
recentSearchDao.deleteSearchString(searchListItem.value)
}
}

View File

@ -1,6 +1,6 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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
@ -15,15 +15,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.search;
import java.util.List;
import org.kiwix.kiwixmobile.core.base.BaseContract;
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
/**
* Created by srv_twry on 14/2/18.
*/
import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
public interface SearchViewCallback extends BaseContract.View {
void addRecentSearches(List<String> recentSearches);
object Finish : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
activity.finish()
}
}

View File

@ -0,0 +1,36 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import android.app.Activity
import android.content.Intent
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.Constants
class OpenSearchItem(private val searchListItem: SearchListItem) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
activity.setResult(
Activity.RESULT_OK,
Intent().putExtra(Constants.TAG_FILE_SEARCHED, searchListItem.value)
)
activity.finish()
}
}

View File

@ -0,0 +1,44 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import android.app.Activity
import android.content.Intent
import android.speech.RecognizerIntent
import androidx.appcompat.app.AppCompatActivity
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.search.viewmodel.Action
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.Filter
data class ProcessActivityResult(
val requestCode: Int,
val resultCode: Int,
val data: Intent?,
val actions: PublishProcessor<Action>
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
if (requestCode == StartSpeechInput.REQ_CODE_SPEECH_INPUT &&
resultCode == Activity.RESULT_OK &&
data != null
) {
actions.offer(Filter(data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)[0]))
}
}
}

View File

@ -0,0 +1,34 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewRecentSearchDao
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
class SaveSearchToRecents(
private val recentSearchDao: NewRecentSearchDao,
private val searchListItem: SearchListItem,
private val id: String?
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
id?.let { recentSearchDao.saveSearch(searchListItem.value, it) }
}
}

View File

@ -0,0 +1,42 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.utils.Constants
data class SearchInPreviousScreen(val searchString: String) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
activity.setResult(
Activity.RESULT_OK,
Intent().apply {
putExtra(EXTRA_SEARCH_IN_TEXT, true)
putExtra(Constants.TAG_FILE_SEARCHED, searchString)
}
)
activity.finish()
}
companion object {
const val EXTRA_SEARCH_IN_TEXT = "bool_searchintext"
}
}

View File

@ -0,0 +1,48 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import android.content.Intent
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.appcompat.app.AppCompatActivity
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.search.viewmodel.Action
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.core.utils.Constants
data class SearchIntentProcessing(
val intent: Intent?,
val actions: PublishProcessor<Action>
) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
if (intent != null) {
if (VERSION.SDK_INT >= VERSION_CODES.M && intent.hasExtra(Intent.EXTRA_PROCESS_TEXT)) {
actions.offer(Filter(intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT)))
}
if (intent.hasExtra(Constants.EXTRA_SEARCH)) {
actions.offer(Filter(intent.getStringExtra(Constants.EXTRA_SEARCH)))
}
if (intent.getBooleanExtra(Constants.EXTRA_IS_WIDGET_VOICE, false)) {
actions.offer(Action.ReceivedPromptForSpeechInput)
}
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import androidx.appcompat.app.AppCompatActivity
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.search.SearchActivity
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
import org.kiwix.kiwixmobile.core.search.viewmodel.Action
import org.kiwix.kiwixmobile.core.utils.DialogShower
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.DeleteSearch
import javax.inject.Inject
data class ShowDeleteSearchDialog(
val searchListItem: SearchListItem,
val actions: PublishProcessor<Action>
) : SideEffect<Unit> {
@Inject lateinit var dialogShower: DialogShower
override fun invokeWith(activity: AppCompatActivity) {
(activity as SearchActivity).activityComponent.inject(this)
dialogShower.show(DeleteSearch, { actions.offer(Action.ConfirmedDelete(searchListItem)) })
}
}

View File

@ -0,0 +1,31 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.toast
data class ShowToast(@StringRes val stringId: Int) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
activity.toast(stringId, Toast.LENGTH_SHORT)
}
}

View File

@ -0,0 +1,54 @@
/*
* 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/>.
*
*/
package org.kiwix.kiwixmobile.core.search.viewmodel.effects
import android.content.ActivityNotFoundException
import android.content.Intent
import android.speech.RecognizerIntent
import androidx.appcompat.app.AppCompatActivity
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.search.viewmodel.Action
import java.util.Locale
data class StartSpeechInput(val actions: PublishProcessor<Action>) : SideEffect<Unit> {
override fun invokeWith(activity: AppCompatActivity) {
try {
activity.startActivityForResult(
Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
putExtra(
RecognizerIntent.EXTRA_PROMPT,
activity.getString(R.string.speech_prompt_text, activity.getString(R.string.app_name))
)
},
REQ_CODE_SPEECH_INPUT
)
} catch (a: ActivityNotFoundException) {
actions.offer(Action.StartSpechInputFailed)
}
}
companion object {
const val REQ_CODE_SPEECH_INPUT = 100
}
}

View File

@ -99,6 +99,10 @@ sealed class KiwixDialog(
constructor(selectedPeerDeviceName: String) : this(listOf(selectedPeerDeviceName))
}
object DeleteSearch : KiwixDialog(
null, R.string.delete_recent_search_item, R.string.delete, R.string.no
)
open class YesNoDialog(
title: Int,
message: Int

View File

@ -1,6 +1,6 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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
@ -16,7 +16,7 @@
*
*/
package org.kiwix.kiwixmobile.zim_manager
package org.kiwix.kiwixmobile.core.utils
import androidx.appcompat.widget.SearchView.OnQueryTextListener

View File

@ -1,9 +1,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:fitsSystemWindows="true"
android:layoutDirection="ltr"
android:orientation="vertical">
<LinearLayout
@ -18,10 +16,25 @@
<include layout="@layout/layout_toolbar" />
</RelativeLayout>
<ListView
android:id="@+id/search_list"
<ViewAnimator
android:id="@+id/searchViewAnimator"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/search_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/no_results"
android:textAppearance="?textAppearanceHeadline6" />
</ViewAnimator>
</LinearLayout>

View File

@ -9,5 +9,5 @@
android:layout_height="wrap_content"
app:popupTheme="@style/KiwixTheme"
app:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
tools:showIn="@layout/search" />
tools:showIn="@layout/activity_search" />
</merge>

View File

@ -292,6 +292,7 @@
<string name="storage_permission_denied">Storage Permission Denied</string>
<string name="grant_read_storage_permission">This app requires the ability to read storage to function. Please grant the permission in your settings</string>
<string name="go_to_permissions">Go to Settings</string>
<string name="no_results">No Results</string>
<string-array name="pref_night_modes_entries">
<item>On</item>
<item>Off</item>

View File

@ -1,6 +1,6 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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
@ -27,7 +27,6 @@ import org.kiwix.kiwixmobile.custom.di.CustomScope;
@CustomScope
public class CustomViewModelFactory extends ViewModelFactory {
@Inject
public CustomViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
super(creators);