From 0ae564b9b6866939355f6fe5941c1ae9fb4bb521 Mon Sep 17 00:00:00 2001 From: Cristian Luis Duarte Date: Wed, 18 Apr 2018 21:24:58 -0300 Subject: [PATCH] Android - Home screen has search. First implementation that retrieves 10 pages of concurrency from the user_stories API and allows filtering --- .../hifiinterface/HomeActivity.java | 17 +- .../hifiinterface/provider/Callback.java | 9 + .../provider/DomainProvider.java | 4 +- .../provider/UserStoryDomainProvider.java | 175 ++++++++++++++++-- .../hifiinterface/view/DomainAdapter.java | 12 +- .../app/src/main/res/layout/content_home.xml | 10 +- 6 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index 4a75eef98f..943c4cfaec 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -12,10 +12,12 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.SearchView; +import android.widget.EditText; import android.widget.TabHost; import android.widget.TabWidget; import android.widget.TextView; @@ -97,7 +99,7 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On }); domainsView.setAdapter(domainAdapter); - SearchView searchView = findViewById(R.id.searchView); + EditText searchView = findViewById(R.id.searchView); int searchPlateId = searchView.getContext().getResources().getIdentifier("android:id/search_plate", null, null); View searchPlate = searchView.findViewById(searchPlateId); if (searchPlate!=null) { @@ -106,7 +108,18 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On TextView searchTextView = searchView.findViewById(searchTextId); searchTextView.setTextAppearance(R.style.SearchText); } + searchView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + + @Override + public void afterTextChanged(Editable editable) { + domainAdapter.loadDomains(editable.toString()); + } + }); updateLoginMenu(); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java new file mode 100644 index 0000000000..64ca6da816 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java @@ -0,0 +1,9 @@ +package io.highfidelity.hifiinterface.provider; + +/** + * Created by cduarte on 4/18/18. + */ + +public interface Callback { + public void callback(T t); +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java index 108adfbbe4..7a2101a229 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java @@ -10,9 +10,9 @@ import io.highfidelity.hifiinterface.view.DomainAdapter; public interface DomainProvider { - void retrieve(Callback callback); + void retrieve(String filterText, DomainCallback domainCallback); - interface Callback { + interface DomainCallback { void retrieveOk(List domain); void retrieveError(Exception e, String message); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java index d96f20ac15..0e2c190a14 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java @@ -1,12 +1,16 @@ package io.highfidelity.hifiinterface.provider; +import android.util.Log; +import android.util.MutableInt; + import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.function.BinaryOperator; import java.util.function.Consumer; import io.highfidelity.hifiinterface.view.DomainAdapter; import retrofit2.Call; -import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; @@ -21,10 +25,18 @@ public class UserStoryDomainProvider implements DomainProvider { public static final String BASE_URL = "https://metaverse.highfidelity.com/"; + private static final String INCLUDE_ACTIONS_FOR_PLACES = "concurrency"; + private static final String INCLUDE_ACTIONS_FOR_FULL_SEARCH = "concurrency,announcements,snapshot"; + private static final int MAX_PAGES_TO_GET = 10; + private String mProtocol; private Retrofit mRetrofit; private UserStoryDomainProviderService mUserStoryDomainProviderService; + private boolean startedToGetFromAPI = false; + private List allStories; // All retrieved stories from the API + private List suggestions; // Filtered places to show + public UserStoryDomainProvider(String protocol) { mRetrofit = new Retrofit.Builder() .baseUrl(BASE_URL) @@ -32,36 +44,144 @@ public class UserStoryDomainProvider implements DomainProvider { .build(); mUserStoryDomainProviderService = mRetrofit.create(UserStoryDomainProviderService.class); mProtocol = protocol; + allStories = new ArrayList<>(); + suggestions = new ArrayList<>(); + } + + private void fillDestinations(String filterText, DomainCallback domainCallback) { + StoriesFilter filter = new StoriesFilter(filterText); + final MutableInt counter = new MutableInt(0); + allStories.clear(); + getUserStoryPage(1, + e -> { + allStories.subList(counter.value, allStories.size()).forEach(userStory -> { + // TODO Report error? e + filter.filterOrAdd(userStory); + // TODO Visibility stuff according to size of suggestions? + }); + if (domainCallback != null) { + domainCallback.retrieveOk(suggestions); //ended + } + }, + a -> { + allStories.forEach(userStory -> { + counter.value++; + filter.filterOrAdd(userStory); + // TODO Visibility stuff according to size of suggestions? + }); + } + ); + } + + private void handleError(String url, Throwable t, Callback restOfPagesCallback) { + restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t)); + } + + private void getUserStoryPage(int pageNumber, Callback restOfPagesCallback, Callback firstPageCallback) { + Call userStories = mUserStoryDomainProviderService.getUserStories( + INCLUDE_ACTIONS_FOR_PLACES, + "open", + true, + mProtocol, + pageNumber); + userStories.enqueue(new retrofit2.Callback() { + @Override + public void onResponse(Call call, Response response) { + UserStories data = response.body(); + allStories.addAll(data.user_stories); + if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) { + if (pageNumber == 1 && firstPageCallback!=null) { + firstPageCallback.callback(null); + } + getUserStoryPage(pageNumber + 1, restOfPagesCallback, null); + return; + } + restOfPagesCallback.callback(null); + } + + @Override + public void onFailure(Call call, Throwable t) { + handleError(call.request().url().toString(), t, restOfPagesCallback); + } + }); + } + + private class StoriesFilter { + String[] mWords = new String[]{}; + public StoriesFilter(String filterText) { + mWords = filterText.toUpperCase().split("\\s+"); + } + + private boolean matches(UserStory story) { + if (mWords.length<=0) return true; + + boolean res = true; + for (String word: mWords) { + res = res && story.searchText().contains(word); + if (!res) break; + } + + return res; + } + + private void addToSuggestions(UserStory story) { + suggestions.add(story.toDomain()); + } + + public void filterOrAdd(UserStory story) { + if (matches(story)) { + addToSuggestions(story); + } + } + } + + private void filterChoicesByText(String filterText, DomainCallback domainCallback) { + suggestions.clear(); + StoriesFilter storiesFilter = new StoriesFilter(filterText); + allStories.forEach(story -> { + storiesFilter.filterOrAdd(story); + }); + domainCallback.retrieveOk(suggestions); } @Override - public void retrieve(Callback callback) { + public synchronized void retrieve(String filterText, DomainCallback domainCallback) { + if (!startedToGetFromAPI) { + startedToGetFromAPI = true; + fillDestinations(filterText, domainCallback); + } else { + filterChoicesByText(filterText, domainCallback); + } + } + + public void retrieveNot(DomainCallback domainCallback) { // TODO Call multiple pages - Call userStories = mUserStoryDomainProviderService.getUserStories(mProtocol); + Call userStories = mUserStoryDomainProviderService.getUserStories( + INCLUDE_ACTIONS_FOR_PLACES, + "open", + true, + mProtocol, + 1); + + Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]"); userStories.enqueue(new retrofit2.Callback() { @Override public void onResponse(Call call, Response response) { UserStories userStories = response.body(); if (userStories == null) { - callback.retrieveOk(new ArrayList<>(0)); + domainCallback.retrieveOk(new ArrayList<>(0)); } List domains = new ArrayList<>(userStories.total_entries); userStories.user_stories.forEach(userStory -> { - // TODO Proper url creation (it can or can't have hifi - // TODO Or use host value from api? - domains.add(new DomainAdapter.Domain( - userStory.place_name, - "hifi://" + userStory.place_name + "/" + userStory.path, - userStory.thumbnail_url - )); + domains.add(userStory.toDomain()); }); - callback.retrieveOk(domains); + domainCallback.retrieveOk(domains); } @Override public void onFailure(Call call, Throwable t) { - callback.retrieveError(new Exception(t), t.getMessage()); + domainCallback.retrieveError(new Exception(t), t.getMessage()); } }); @@ -69,7 +189,11 @@ public class UserStoryDomainProvider implements DomainProvider { public interface UserStoryDomainProviderService { @GET("api/v1/user_stories") - Call getUserStories(@Query("protocol") String protocol); + Call getUserStories(@Query("include_actions") String includeActions, + @Query("restriction") String restriction, + @Query("require_online") boolean requireOnline, + @Query("protocol") String protocol, + @Query("page") int pageNumber); } class UserStory { @@ -77,6 +201,28 @@ public class UserStoryDomainProvider implements DomainProvider { String place_name; String path; String thumbnail_url; + String place_id; + String domain_id; + private String searchText; + + // New fields? tags, description + + String searchText() { + if (searchText==null) { + searchText = place_name == null? "" : place_name.toUpperCase(); + } + return searchText; + } + DomainAdapter.Domain toDomain() { + // TODO Proper url creation (it can or can't have hifi + // TODO Or use host value from api? + DomainAdapter.Domain domain = new DomainAdapter.Domain( + place_name, + "hifi://" + place_name + "/" + path, + thumbnail_url + ); + return domain; + } } class UserStories { @@ -86,4 +232,5 @@ public class UserStoryDomainProvider implements DomainProvider { int total_entries; List user_stories; } + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java index f8b4e73cd1..13e757db94 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java @@ -33,6 +33,7 @@ public class DomainAdapter extends RecyclerView.Adapter domain) { + mDomains = new Domain[domain.size()]; mDomains = domain.toArray(mDomains); notifyDataSetChanged(); } diff --git a/android/app/src/main/res/layout/content_home.xml b/android/app/src/main/res/layout/content_home.xml index f25d9d8f7b..217e922a8d 100644 --- a/android/app/src/main/res/layout/content_home.xml +++ b/android/app/src/main/res/layout/content_home.xml @@ -28,14 +28,14 @@ android:layout_height="wrap_content" android:background="@color/backgroundDark"/> - + android:layout_height="match_parent" />