mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 08:21:32 +02:00
Android - Home screen has search. First implementation that retrieves 10 pages of concurrency from the user_stories API and allows filtering
This commit is contained in:
parent
9c44be3557
commit
0ae564b9b6
6 changed files with 200 additions and 27 deletions
|
@ -12,10 +12,12 @@ import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.GridLayoutManager;
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.SearchView;
|
import android.widget.EditText;
|
||||||
import android.widget.TabHost;
|
import android.widget.TabHost;
|
||||||
import android.widget.TabWidget;
|
import android.widget.TabWidget;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -97,7 +99,7 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
|
||||||
});
|
});
|
||||||
domainsView.setAdapter(domainAdapter);
|
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);
|
int searchPlateId = searchView.getContext().getResources().getIdentifier("android:id/search_plate", null, null);
|
||||||
View searchPlate = searchView.findViewById(searchPlateId);
|
View searchPlate = searchView.findViewById(searchPlateId);
|
||||||
if (searchPlate!=null) {
|
if (searchPlate!=null) {
|
||||||
|
@ -106,7 +108,18 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
|
||||||
TextView searchTextView = searchView.findViewById(searchTextId);
|
TextView searchTextView = searchView.findViewById(searchTextId);
|
||||||
searchTextView.setTextAppearance(R.style.SearchText);
|
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();
|
updateLoginMenu();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package io.highfidelity.hifiinterface.provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by cduarte on 4/18/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface Callback<T> {
|
||||||
|
public void callback(T t);
|
||||||
|
}
|
|
@ -10,9 +10,9 @@ import io.highfidelity.hifiinterface.view.DomainAdapter;
|
||||||
|
|
||||||
public interface DomainProvider {
|
public interface DomainProvider {
|
||||||
|
|
||||||
void retrieve(Callback callback);
|
void retrieve(String filterText, DomainCallback domainCallback);
|
||||||
|
|
||||||
interface Callback {
|
interface DomainCallback {
|
||||||
void retrieveOk(List<DomainAdapter.Domain> domain);
|
void retrieveOk(List<DomainAdapter.Domain> domain);
|
||||||
void retrieveError(Exception e, String message);
|
void retrieveError(Exception e, String message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package io.highfidelity.hifiinterface.provider;
|
package io.highfidelity.hifiinterface.provider;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.MutableInt;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import io.highfidelity.hifiinterface.view.DomainAdapter;
|
import io.highfidelity.hifiinterface.view.DomainAdapter;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
@ -21,10 +25,18 @@ public class UserStoryDomainProvider implements DomainProvider {
|
||||||
|
|
||||||
public static final String BASE_URL = "https://metaverse.highfidelity.com/";
|
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 String mProtocol;
|
||||||
private Retrofit mRetrofit;
|
private Retrofit mRetrofit;
|
||||||
private UserStoryDomainProviderService mUserStoryDomainProviderService;
|
private UserStoryDomainProviderService mUserStoryDomainProviderService;
|
||||||
|
|
||||||
|
private boolean startedToGetFromAPI = false;
|
||||||
|
private List<UserStory> allStories; // All retrieved stories from the API
|
||||||
|
private List<DomainAdapter.Domain> suggestions; // Filtered places to show
|
||||||
|
|
||||||
public UserStoryDomainProvider(String protocol) {
|
public UserStoryDomainProvider(String protocol) {
|
||||||
mRetrofit = new Retrofit.Builder()
|
mRetrofit = new Retrofit.Builder()
|
||||||
.baseUrl(BASE_URL)
|
.baseUrl(BASE_URL)
|
||||||
|
@ -32,36 +44,144 @@ public class UserStoryDomainProvider implements DomainProvider {
|
||||||
.build();
|
.build();
|
||||||
mUserStoryDomainProviderService = mRetrofit.create(UserStoryDomainProviderService.class);
|
mUserStoryDomainProviderService = mRetrofit.create(UserStoryDomainProviderService.class);
|
||||||
mProtocol = protocol;
|
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<Exception> restOfPagesCallback) {
|
||||||
|
restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getUserStoryPage(int pageNumber, Callback<Exception> restOfPagesCallback, Callback<Void> firstPageCallback) {
|
||||||
|
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
|
||||||
|
INCLUDE_ACTIONS_FOR_PLACES,
|
||||||
|
"open",
|
||||||
|
true,
|
||||||
|
mProtocol,
|
||||||
|
pageNumber);
|
||||||
|
userStories.enqueue(new retrofit2.Callback<UserStories>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<UserStories> call, Response<UserStories> 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<UserStories> 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
|
@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
|
// TODO Call multiple pages
|
||||||
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(mProtocol);
|
Call<UserStories> 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<UserStories>() {
|
userStories.enqueue(new retrofit2.Callback<UserStories>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
|
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
|
||||||
UserStories userStories = response.body();
|
UserStories userStories = response.body();
|
||||||
if (userStories == null) {
|
if (userStories == null) {
|
||||||
callback.retrieveOk(new ArrayList<>(0));
|
domainCallback.retrieveOk(new ArrayList<>(0));
|
||||||
}
|
}
|
||||||
List<DomainAdapter.Domain> domains = new ArrayList<>(userStories.total_entries);
|
List<DomainAdapter.Domain> domains = new ArrayList<>(userStories.total_entries);
|
||||||
userStories.user_stories.forEach(userStory -> {
|
userStories.user_stories.forEach(userStory -> {
|
||||||
// TODO Proper url creation (it can or can't have hifi
|
domains.add(userStory.toDomain());
|
||||||
// TODO Or use host value from api?
|
|
||||||
domains.add(new DomainAdapter.Domain(
|
|
||||||
userStory.place_name,
|
|
||||||
"hifi://" + userStory.place_name + "/" + userStory.path,
|
|
||||||
userStory.thumbnail_url
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
callback.retrieveOk(domains);
|
domainCallback.retrieveOk(domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<UserStories> call, Throwable t) {
|
public void onFailure(Call<UserStories> 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 {
|
public interface UserStoryDomainProviderService {
|
||||||
@GET("api/v1/user_stories")
|
@GET("api/v1/user_stories")
|
||||||
Call<UserStories> getUserStories(@Query("protocol") String protocol);
|
Call<UserStories> 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 {
|
class UserStory {
|
||||||
|
@ -77,6 +201,28 @@ public class UserStoryDomainProvider implements DomainProvider {
|
||||||
String place_name;
|
String place_name;
|
||||||
String path;
|
String path;
|
||||||
String thumbnail_url;
|
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 {
|
class UserStories {
|
||||||
|
@ -86,4 +232,5 @@ public class UserStoryDomainProvider implements DomainProvider {
|
||||||
int total_entries;
|
int total_entries;
|
||||||
List<UserStory> user_stories;
|
List<UserStory> user_stories;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
private ItemClickListener mClickListener;
|
private ItemClickListener mClickListener;
|
||||||
private String mProtocol;
|
private String mProtocol;
|
||||||
|
private UserStoryDomainProvider domainProvider;
|
||||||
|
|
||||||
public static class Domain {
|
public static class Domain {
|
||||||
public String name;
|
public String name;
|
||||||
|
@ -52,14 +53,17 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
|
||||||
mContext = c;
|
mContext = c;
|
||||||
this.mInflater = LayoutInflater.from(mContext);
|
this.mInflater = LayoutInflater.from(mContext);
|
||||||
mProtocol = protocol;
|
mProtocol = protocol;
|
||||||
loadDomains();
|
domainProvider = new UserStoryDomainProvider(mProtocol);
|
||||||
|
loadDomains("");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDomains() {
|
|
||||||
DomainProvider domainProvider = new UserStoryDomainProvider(mProtocol);
|
|
||||||
domainProvider.retrieve(new DomainProvider.Callback() {
|
public void loadDomains(String filterText) {
|
||||||
|
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void retrieveOk(List<Domain> domain) {
|
public void retrieveOk(List<Domain> domain) {
|
||||||
|
mDomains = new Domain[domain.size()];
|
||||||
mDomains = domain.toArray(mDomains);
|
mDomains = domain.toArray(mDomains);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,14 +28,14 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/backgroundDark"/>
|
android:background="@color/backgroundDark"/>
|
||||||
|
|
||||||
<SearchView
|
<EditText
|
||||||
android:id="@+id/searchView"
|
android:id="@+id/searchView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="42dp"
|
android:layout_height="42dp"
|
||||||
android:background="@drawable/search_bg"
|
android:background="@drawable/search_bg"
|
||||||
android:gravity="right"
|
android:gravity="center_vertical|end"
|
||||||
android:layout_gravity="right"
|
android:paddingStart="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
/>
|
/>
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@android:id/tabcontent"
|
android:id="@android:id/tabcontent"
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
<android.support.v7.widget.RecyclerView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/rvDomains"
|
android:id="@+id/rvDomains"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue