Skip to content

Commit df82383

Browse files
Merge pull request #1561 from ujjwalagrawal17/search_activity
Fixes #1521 (Search activity, image search feature added.)
2 parents ae1cc86 + a5d67cc commit df82383

25 files changed

+604
-1
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@
9696
android:label="@string/title_activity_featured_images"
9797
android:parentActivityName=".contributions.ContributionsActivity" />
9898

99+
<activity
100+
android:name=".explore.SearchActivity"
101+
android:label="@string/title_activity_search"
102+
android:parentActivityName=".contributions.ContributionsActivity"
103+
/>
104+
99105
<service android:name=".upload.UploadService" />
100106

101107
<service

app/src/main/java/fr/free/nrw/commons/category/CategoryImagesActivity.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@
66
import android.os.Bundle;
77
import android.support.v4.app.FragmentManager;
88
import android.support.v4.app.FragmentTransaction;
9+
import android.view.Menu;
10+
import android.view.MenuInflater;
11+
import android.view.MenuItem;
912
import android.view.View;
1013
import android.widget.AdapterView;
1114

1215
import butterknife.ButterKnife;
1316
import fr.free.nrw.commons.Media;
1417
import fr.free.nrw.commons.R;
1518
import fr.free.nrw.commons.auth.AuthenticatedActivity;
19+
import fr.free.nrw.commons.explore.SearchActivity;
1620
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
17-
import timber.log.Timber;
21+
import fr.free.nrw.commons.theme.NavigationBaseActivity;
1822

1923
/**
2024
* This activity displays pictures of a particular category
@@ -157,4 +161,24 @@ public void registerDataSetObserver(DataSetObserver observer) {
157161
public void unregisterDataSetObserver(DataSetObserver observer) {
158162

159163
}
164+
165+
@Override
166+
public boolean onCreateOptionsMenu(Menu menu) {
167+
MenuInflater inflater = getMenuInflater();
168+
inflater.inflate(R.menu.menu_search, menu);
169+
return super.onCreateOptionsMenu(menu);
170+
}
171+
172+
@Override
173+
public boolean onOptionsItemSelected(MenuItem item) {
174+
175+
// Handle item selection
176+
switch (item.getItemId()) {
177+
case R.id.action_search:
178+
NavigationBaseActivity.startActivityWithFlags(this, SearchActivity.class);
179+
return true;
180+
default:
181+
return super.onOptionsItemSelected(item);
182+
}
183+
}
160184
}

app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import fr.free.nrw.commons.auth.SignupActivity;
99
import fr.free.nrw.commons.contributions.ContributionsActivity;
1010
import fr.free.nrw.commons.category.CategoryImagesActivity;
11+
import fr.free.nrw.commons.explore.SearchActivity;
1112
import fr.free.nrw.commons.nearby.NearbyActivity;
1213
import fr.free.nrw.commons.notification.NotificationActivity;
1314
import fr.free.nrw.commons.settings.SettingsActivity;
@@ -50,4 +51,7 @@ public abstract class ActivityBuilderModule {
5051

5152
@ContributesAndroidInjector
5253
abstract CategoryImagesActivity bindFeaturedImagesActivity();
54+
55+
@ContributesAndroidInjector
56+
abstract SearchActivity bindSearchActivity();
5357
}

app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import fr.free.nrw.commons.category.CategorizationFragment;
66
import fr.free.nrw.commons.contributions.ContributionsListFragment;
77
import fr.free.nrw.commons.category.CategoryImagesListFragment;
8+
import fr.free.nrw.commons.explore.images.SearchImageFragment;
89
import fr.free.nrw.commons.media.MediaDetailFragment;
910
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
1011
import fr.free.nrw.commons.nearby.NearbyListFragment;
@@ -51,4 +52,7 @@ public abstract class FragmentBuilderModule {
5152
@ContributesAndroidInjector
5253
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
5354

55+
@ContributesAndroidInjector
56+
abstract SearchImageFragment bindBrowseImagesListFragment();
57+
5458
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package fr.free.nrw.commons.explore;
2+
3+
import android.database.DataSetObserver;
4+
import android.os.Bundle;
5+
import android.support.v4.app.FragmentManager;
6+
import android.support.v4.app.FragmentTransaction;
7+
import android.support.v7.widget.Toolbar;
8+
import android.text.TextUtils;
9+
import android.view.View;
10+
import android.widget.EditText;
11+
12+
import com.jakewharton.rxbinding2.view.RxView;
13+
import com.jakewharton.rxbinding2.widget.RxTextView;
14+
15+
import java.util.concurrent.TimeUnit;
16+
17+
import butterknife.BindView;
18+
import butterknife.ButterKnife;
19+
import fr.free.nrw.commons.Media;
20+
import fr.free.nrw.commons.R;
21+
import fr.free.nrw.commons.explore.images.SearchImageFragment;
22+
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
23+
import fr.free.nrw.commons.theme.NavigationBaseActivity;
24+
import fr.free.nrw.commons.utils.ViewUtil;
25+
import io.reactivex.android.schedulers.AndroidSchedulers;
26+
27+
/**
28+
* Represents search screen of this app
29+
*/
30+
31+
public class SearchActivity extends NavigationBaseActivity implements MediaDetailPagerFragment.MediaDetailProvider{
32+
33+
@BindView(R.id.toolbar_search) Toolbar toolbar;
34+
@BindView(R.id.searchBox) EditText etSearchKeyword;
35+
36+
private SearchImageFragment searchImageFragment;
37+
private FragmentManager supportFragmentManager;
38+
private MediaDetailPagerFragment mediaDetails;
39+
private String query;
40+
41+
@Override
42+
protected void onCreate(Bundle savedInstanceState) {
43+
super.onCreate(savedInstanceState);
44+
setContentView(R.layout.activity_search);
45+
ButterKnife.bind(this);
46+
initDrawer();
47+
setTitle(getString(R.string.title_activity_search));
48+
toolbar.setNavigationOnClickListener(v->onBackPressed());
49+
supportFragmentManager = getSupportFragmentManager();
50+
setBrowseImagesFragment();
51+
RxTextView.textChanges(etSearchKeyword)
52+
.takeUntil(RxView.detaches(etSearchKeyword))
53+
.debounce(500, TimeUnit.MILLISECONDS)
54+
.observeOn(AndroidSchedulers.mainThread())
55+
.subscribe( query -> {
56+
//update image list
57+
if (!TextUtils.isEmpty(query)) {
58+
this.query = query.toString();
59+
searchImageFragment.updateImageList(query.toString());
60+
}else {
61+
// open search history fragment
62+
}
63+
}
64+
);
65+
}
66+
67+
68+
private void setBrowseImagesFragment() {
69+
searchImageFragment = new SearchImageFragment();
70+
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
71+
transaction.add(R.id.fragmentContainer, searchImageFragment).commit();
72+
}
73+
74+
@Override
75+
public Media getMediaAtPosition(int i) {
76+
return searchImageFragment.getImageAtPosition(i);
77+
}
78+
79+
@Override
80+
public int getTotalMediaCount() {
81+
return searchImageFragment.getTotalImagesCount();
82+
}
83+
84+
@Override
85+
public void notifyDatasetChanged() {
86+
87+
}
88+
89+
@Override
90+
public void registerDataSetObserver(DataSetObserver observer) {
91+
92+
}
93+
94+
@Override
95+
public void unregisterDataSetObserver(DataSetObserver observer) {
96+
97+
}
98+
99+
/**
100+
* Open media detail pager fragment on click of image in search results
101+
* @param index item index that should be opened
102+
*/
103+
public void onSearchImageClicked(int index) {
104+
ViewUtil.hideKeyboard(this.findViewById(R.id.searchBox));
105+
toolbar.setVisibility(View.GONE);
106+
setNavigationBaseToolbarVisibility(true);
107+
if (mediaDetails == null || !mediaDetails.isVisible()) {
108+
// set isFeaturedImage true for featured images, to include author field on media detail
109+
mediaDetails = new MediaDetailPagerFragment(false, true);
110+
FragmentManager supportFragmentManager = getSupportFragmentManager();
111+
supportFragmentManager
112+
.beginTransaction()
113+
.replace(R.id.fragmentContainer, mediaDetails)
114+
.addToBackStack(null)
115+
.commit();
116+
supportFragmentManager.executePendingTransactions();
117+
}
118+
mediaDetails.showImage(index);
119+
}
120+
121+
@Override
122+
public void onBackPressed() {
123+
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
124+
// back to search so show search toolbar and hide navigation toolbar
125+
toolbar.setVisibility(View.VISIBLE);
126+
setNavigationBaseToolbarVisibility(false);
127+
if (!TextUtils.isEmpty(query)) {
128+
searchImageFragment.updateImageList(query);
129+
}
130+
}else {
131+
toolbar.setVisibility(View.GONE);
132+
setNavigationBaseToolbarVisibility(true);
133+
}
134+
super.onBackPressed();
135+
}
136+
137+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package fr.free.nrw.commons.explore.images;
2+
3+
4+
import android.content.SharedPreferences;
5+
import android.os.Bundle;
6+
import android.support.v7.widget.LinearLayoutManager;
7+
import android.support.v7.widget.RecyclerView;
8+
import android.view.LayoutInflater;
9+
import android.view.View;
10+
import android.view.ViewGroup;
11+
import android.widget.ProgressBar;
12+
import android.widget.TextView;
13+
import com.pedrogomez.renderers.RVRendererAdapter;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.concurrent.TimeUnit;
17+
import javax.inject.Inject;
18+
import javax.inject.Named;
19+
import butterknife.BindView;
20+
import butterknife.ButterKnife;
21+
import fr.free.nrw.commons.Media;
22+
import fr.free.nrw.commons.R;
23+
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
24+
import fr.free.nrw.commons.explore.SearchActivity;
25+
import fr.free.nrw.commons.mwapi.MediaWikiApi;
26+
import fr.free.nrw.commons.utils.NetworkUtils;
27+
import fr.free.nrw.commons.utils.ViewUtil;
28+
import io.reactivex.Observable;
29+
import io.reactivex.android.schedulers.AndroidSchedulers;
30+
import io.reactivex.schedulers.Schedulers;
31+
import timber.log.Timber;
32+
33+
import static android.view.View.GONE;
34+
import static android.view.View.VISIBLE;
35+
36+
/**
37+
* Displays the image search screen.
38+
*/
39+
40+
public class SearchImageFragment extends CommonsDaggerSupportFragment {
41+
42+
private static int TIMEOUT_SECONDS = 15;
43+
44+
@BindView(R.id.imagesListBox)
45+
RecyclerView imagesRecyclerView;
46+
@BindView(R.id.imageSearchInProgress)
47+
ProgressBar progressBar;
48+
@BindView(R.id.imagesNotFound)
49+
TextView imagesNotFoundView;
50+
String query;
51+
52+
@Inject
53+
MediaWikiApi mwApi;
54+
@Inject @Named("default_preferences") SharedPreferences prefs;
55+
56+
private RVRendererAdapter<Media> imagesAdapter;
57+
private List<Media> queryList = new ArrayList<>();
58+
59+
private final SearchImagesAdapterFactory adapterFactory = new SearchImagesAdapterFactory(item -> {
60+
int index = queryList.indexOf(item);
61+
((SearchActivity)getContext()).onSearchImageClicked(index);
62+
//TODO : Add images to recently searched images db table
63+
});
64+
65+
@Override
66+
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
67+
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
68+
ButterKnife.bind(this, rootView);
69+
imagesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
70+
ArrayList<Media> items = new ArrayList<>();
71+
imagesAdapter = adapterFactory.create(items);
72+
imagesRecyclerView.setAdapter(imagesAdapter);
73+
return rootView;
74+
}
75+
76+
/**
77+
* Checks for internet connection and then initializes the recycler view with 25 images of the searched query
78+
* Clearing imageAdapter every time new keyword is searched so that user can see only new results
79+
*/
80+
public void updateImageList(String query) {
81+
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
82+
handleNoInternet();
83+
return;
84+
}
85+
progressBar.setVisibility(View.VISIBLE);
86+
queryList.clear();
87+
imagesAdapter.clear();
88+
Observable.fromCallable(() -> mwApi.searchImages(query))
89+
.subscribeOn(Schedulers.io())
90+
.observeOn(AndroidSchedulers.mainThread())
91+
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
92+
.subscribe(this::handleSuccess, this::handleError);
93+
}
94+
95+
/**
96+
* Handles the success scenario
97+
* it initializes the recycler view by adding items to the adapter
98+
* @param mediaList
99+
*/
100+
private void handleSuccess(List<Media> mediaList) {
101+
queryList = mediaList;
102+
if(mediaList == null || mediaList.isEmpty()) {
103+
initErrorView();
104+
}else {
105+
106+
progressBar.setVisibility(View.GONE);
107+
imagesAdapter.addAll(mediaList);
108+
imagesAdapter.notifyDataSetChanged();
109+
}
110+
}
111+
112+
/**
113+
* Logs and handles API error scenario
114+
* @param throwable
115+
*/
116+
private void handleError(Throwable throwable) {
117+
Timber.e(throwable, "Error occurred while loading queried images");
118+
initErrorView();
119+
}
120+
121+
/**
122+
* Handles the UI updates for a error scenario
123+
*/
124+
private void initErrorView() {
125+
ViewUtil.showSnackbar(imagesRecyclerView, R.string.error_loading_images);
126+
progressBar.setVisibility(GONE);
127+
imagesNotFoundView.setVisibility(VISIBLE);
128+
imagesNotFoundView.setText(getString(R.string.images_not_found, query));
129+
}
130+
131+
/**
132+
* Handles the UI updates for no internet scenario
133+
*/
134+
private void handleNoInternet() {
135+
progressBar.setVisibility(GONE);
136+
ViewUtil.showSnackbar(imagesRecyclerView, R.string.no_internet);
137+
}
138+
139+
/**
140+
* returns total number of images present in the recyclerview adapter.
141+
*/
142+
public int getTotalImagesCount(){
143+
if (imagesAdapter == null) {
144+
return 0;
145+
}else {
146+
return imagesAdapter.getItemCount();
147+
}
148+
}
149+
150+
/**
151+
* returns Media Object at position
152+
* @param i position of Media in the recyclerview adapter.
153+
*/
154+
public Media getImageAtPosition(int i) {
155+
if (imagesAdapter.getItem(i).getFilename() == null) {
156+
// not yet ready to return data
157+
return null;
158+
} else {
159+
return new Media(imagesAdapter.getItem(i).getFilename());
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)