parent
00ba3e4df0
commit
cc8baac73b
@ -0,0 +1,195 @@ |
||||
/* Copyright 2017 Andrew Dawson |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* 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. |
||||
* |
||||
* Tusky 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 Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky; |
||||
|
||||
import android.app.SearchManager; |
||||
import android.app.SearchableInfo; |
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.os.Bundle; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v7.app.ActionBar; |
||||
import android.support.v7.widget.LinearLayoutManager; |
||||
import android.support.v7.widget.RecyclerView; |
||||
import android.support.v7.widget.SearchView; |
||||
import android.support.v7.widget.Toolbar; |
||||
import android.util.Log; |
||||
import android.view.Menu; |
||||
import android.view.MenuItem; |
||||
import android.view.View; |
||||
import android.widget.ProgressBar; |
||||
import android.widget.TextView; |
||||
|
||||
import com.keylesspalace.tusky.adapter.SearchResultsAdapter; |
||||
import com.keylesspalace.tusky.entity.SearchResults; |
||||
|
||||
import java.util.List; |
||||
|
||||
import butterknife.BindView; |
||||
import butterknife.ButterKnife; |
||||
import retrofit2.Call; |
||||
import retrofit2.Callback; |
||||
import retrofit2.Response; |
||||
|
||||
public class SearchActivity extends BaseActivity implements SearchView.OnQueryTextListener { |
||||
private static final String TAG = "SearchActivity"; // logging tag
|
||||
|
||||
@BindView(R.id.progress_bar) ProgressBar progressBar; |
||||
@BindView(R.id.message_no_results) TextView messageNoResults; |
||||
private SearchResultsAdapter adapter; |
||||
private String currentQuery; |
||||
|
||||
@Override |
||||
protected void onCreate(@Nullable Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
setContentView(R.layout.activity_search); |
||||
ButterKnife.bind(this); |
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); |
||||
setSupportActionBar(toolbar); |
||||
ActionBar bar = getSupportActionBar(); |
||||
if (bar != null) { |
||||
bar.setDisplayHomeAsUpEnabled(true); |
||||
bar.setDisplayShowHomeEnabled(true); |
||||
} |
||||
|
||||
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); |
||||
recyclerView.setHasFixedSize(true); |
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this)); |
||||
adapter = new SearchResultsAdapter(); |
||||
recyclerView.setAdapter(adapter); |
||||
|
||||
handleIntent(getIntent()); |
||||
} |
||||
|
||||
@Override |
||||
protected void onNewIntent(Intent intent) { |
||||
super.onNewIntent(intent); |
||||
handleIntent(intent); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onCreateOptionsMenu(Menu menu) { |
||||
super.onCreateOptionsMenu(menu); |
||||
|
||||
getMenuInflater().inflate(R.menu.search_toolbar, menu); |
||||
SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); |
||||
setupSearchView(searchView); |
||||
|
||||
if (currentQuery != null) { |
||||
searchView.setQuery(currentQuery, false); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onOptionsItemSelected(MenuItem item) { |
||||
switch (item.getItemId()) { |
||||
case android.R.id.home: { |
||||
onBackPressed(); |
||||
return true; |
||||
} |
||||
} |
||||
return super.onOptionsItemSelected(item); |
||||
} |
||||
|
||||
@Override |
||||
public boolean onQueryTextChange(String newText) { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean onQueryTextSubmit(String query) { |
||||
return false; |
||||
} |
||||
|
||||
private void handleIntent(Intent intent) { |
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) { |
||||
currentQuery = intent.getStringExtra(SearchManager.QUERY); |
||||
search(currentQuery); |
||||
} |
||||
} |
||||
|
||||
private void setupSearchView(SearchView searchView) { |
||||
searchView.setIconifiedByDefault(false); |
||||
|
||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); |
||||
if (searchManager != null) { |
||||
List<SearchableInfo> searchables = searchManager.getSearchablesInGlobalSearch(); |
||||
SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName()); |
||||
for (SearchableInfo info : searchables) { |
||||
if (info.getSuggestAuthority() != null |
||||
&& info.getSuggestAuthority().startsWith("applications")) { |
||||
searchableInfo = info; |
||||
} |
||||
} |
||||
searchView.setSearchableInfo(searchableInfo); |
||||
} |
||||
|
||||
searchView.setOnQueryTextListener(this); |
||||
searchView.setFocusable(false); |
||||
searchView.setFocusableInTouchMode(false); |
||||
} |
||||
|
||||
private void search(String query) { |
||||
clearResults(); |
||||
Callback<SearchResults> callback = new Callback<SearchResults>() { |
||||
@Override |
||||
public void onResponse(Call<SearchResults> call, Response<SearchResults> response) { |
||||
if (response.isSuccessful()) { |
||||
SearchResults results = response.body(); |
||||
if (results.accounts != null || results.hashtags != null) { |
||||
adapter.updateSearchResults(results); |
||||
hideFeedback(); |
||||
} else { |
||||
displayNoResults(); |
||||
} |
||||
} else { |
||||
onSearchFailure(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onFailure(Call<SearchResults> call, Throwable t) { |
||||
onSearchFailure(); |
||||
} |
||||
}; |
||||
mastodonAPI.search(query, false) |
||||
.enqueue(callback); |
||||
} |
||||
|
||||
private void onSearchFailure() { |
||||
displayNoResults(); |
||||
Log.e(TAG, "Search request failed."); |
||||
} |
||||
|
||||
private void clearResults() { |
||||
adapter.updateSearchResults(null); |
||||
progressBar.setVisibility(View.VISIBLE); |
||||
messageNoResults.setVisibility(View.GONE); |
||||
} |
||||
|
||||
private void displayNoResults() { |
||||
progressBar.setVisibility(View.GONE); |
||||
messageNoResults.setVisibility(View.VISIBLE); |
||||
} |
||||
|
||||
private void hideFeedback() { |
||||
progressBar.setVisibility(View.GONE); |
||||
messageNoResults.setVisibility(View.GONE); |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
package com.keylesspalace.tusky.adapter; |
||||
|
||||
import android.content.Context; |
||||
import android.support.v7.widget.RecyclerView; |
||||
import android.view.View; |
||||
import android.widget.TextView; |
||||
|
||||
import com.keylesspalace.tusky.R; |
||||
import com.keylesspalace.tusky.entity.Account; |
||||
import com.keylesspalace.tusky.interfaces.AccountActionListener; |
||||
import com.pkmmte.view.CircularImageView; |
||||
import com.squareup.picasso.Picasso; |
||||
|
||||
class AccountViewHolder extends RecyclerView.ViewHolder { |
||||
private View container; |
||||
private TextView username; |
||||
private TextView displayName; |
||||
private CircularImageView avatar; |
||||
private String id; |
||||
|
||||
AccountViewHolder(View itemView) { |
||||
super(itemView); |
||||
container = itemView.findViewById(R.id.account_container); |
||||
username = (TextView) itemView.findViewById(R.id.account_username); |
||||
displayName = (TextView) itemView.findViewById(R.id.account_display_name); |
||||
avatar = (CircularImageView) itemView.findViewById(R.id.account_avatar); |
||||
} |
||||
|
||||
void setupWithAccount(Account account) { |
||||
id = account.id; |
||||
String format = username.getContext().getString(R.string.status_username_format); |
||||
String formattedUsername = String.format(format, account.username); |
||||
username.setText(formattedUsername); |
||||
displayName.setText(account.getDisplayName()); |
||||
Context context = avatar.getContext(); |
||||
Picasso.with(context) |
||||
.load(account.avatar) |
||||
.placeholder(R.drawable.avatar_default) |
||||
.error(R.drawable.avatar_error) |
||||
.into(avatar); |
||||
} |
||||
|
||||
void setupActionListener(final AccountActionListener listener) { |
||||
container.setOnClickListener(new View.OnClickListener() { |
||||
@Override |
||||
public void onClick(View v) { |
||||
listener.onViewAccount(id); |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,115 @@ |
||||
/* Copyright 2017 Andrew Dawson |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* 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. |
||||
* |
||||
* Tusky 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 Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter; |
||||
|
||||
import android.support.v7.widget.RecyclerView; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.TextView; |
||||
|
||||
import com.keylesspalace.tusky.R; |
||||
import com.keylesspalace.tusky.entity.Account; |
||||
import com.keylesspalace.tusky.entity.SearchResults; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
public class SearchResultsAdapter extends RecyclerView.Adapter { |
||||
private static final int VIEW_TYPE_ACCOUNT = 0; |
||||
private static final int VIEW_TYPE_HASHTAG = 1; |
||||
|
||||
private List<Account> accountList; |
||||
private List<String> hashtagList; |
||||
|
||||
public SearchResultsAdapter() { |
||||
super(); |
||||
accountList = new ArrayList<>(); |
||||
hashtagList = new ArrayList<>(); |
||||
} |
||||
|
||||
@Override |
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { |
||||
switch (viewType) { |
||||
default: |
||||
case VIEW_TYPE_ACCOUNT: { |
||||
View view = LayoutInflater.from(parent.getContext()) |
||||
.inflate(R.layout.item_account, parent, false); |
||||
return new AccountViewHolder(view); |
||||
} |
||||
case VIEW_TYPE_HASHTAG: { |
||||
View view = LayoutInflater.from(parent.getContext()) |
||||
.inflate(R.layout.item_hashtag, parent, false); |
||||
return new HashtagViewHolder(view); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { |
||||
if (position < accountList.size()) { |
||||
AccountViewHolder holder = (AccountViewHolder) viewHolder; |
||||
holder.setupWithAccount(accountList.get(position)); |
||||
} else { |
||||
HashtagViewHolder holder = (HashtagViewHolder) viewHolder; |
||||
int index = position - accountList.size(); |
||||
holder.setHashtag(hashtagList.get(index)); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return accountList.size() + hashtagList.size(); |
||||
} |
||||
|
||||
@Override |
||||
public int getItemViewType(int position) { |
||||
if (position >= accountList.size()) { |
||||
return VIEW_TYPE_HASHTAG; |
||||
} else { |
||||
return VIEW_TYPE_ACCOUNT; |
||||
} |
||||
} |
||||
|
||||
public void updateSearchResults(SearchResults results) { |
||||
if (results != null) { |
||||
if (results.accounts != null) { |
||||
accountList.addAll(Arrays.asList(results.accounts)); |
||||
} |
||||
if (results.hashtags != null) { |
||||
hashtagList.addAll(Arrays.asList(results.hashtags)); |
||||
} |
||||
} else { |
||||
accountList.clear(); |
||||
hashtagList.clear(); |
||||
} |
||||
notifyDataSetChanged(); |
||||
} |
||||
|
||||
private static class HashtagViewHolder extends RecyclerView.ViewHolder { |
||||
private TextView hashtag; |
||||
|
||||
HashtagViewHolder(View itemView) { |
||||
super(itemView); |
||||
hashtag = (TextView) itemView.findViewById(R.id.hashtag); |
||||
} |
||||
|
||||
void setHashtag(String tag) { |
||||
hashtag.setText(String.format("#%s", tag)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
/* Copyright 2017 Andrew Dawson |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* 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. |
||||
* |
||||
* Tusky 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 Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.entity; |
||||
|
||||
public class SearchResults { |
||||
public Account[] accounts; |
||||
public Status[] statuses; |
||||
public String[] hashtags; |
||||
} |
@ -0,0 +1,50 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
tools:context="com.keylesspalace.tusky.SearchActivity"> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:orientation="vertical"> |
||||
|
||||
<android.support.v7.widget.Toolbar |
||||
android:id="@+id/toolbar" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="?attr/actionBarSize" |
||||
android:background="?attr/toolbar_background_color" |
||||
app:navigationIcon="?attr/homeAsUpIndicator" /> |
||||
|
||||
<RelativeLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:id="@+id/recycler_view" /> |
||||
|
||||
<ProgressBar |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:id="@+id/progress_bar" |
||||
android:layout_centerInParent="true" |
||||
android:indeterminate="true" |
||||
android:visibility="gone" /> |
||||
|
||||
<TextView |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:id="@+id/message_no_results" |
||||
android:text="@string/search_no_results" |
||||
android:layout_centerInParent="true" |
||||
android:visibility="gone" /> |
||||
|
||||
</RelativeLayout> |
||||
|
||||
</LinearLayout> |
||||
|
||||
</android.support.design.widget.CoordinatorLayout> |
@ -0,0 +1,7 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<TextView |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:id="@+id/hashtag" |
||||
android:padding="8dp" /> |
@ -0,0 +1,10 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto"> |
||||
<item |
||||
android:id="@+id/action_search" |
||||
android:title="@string/action_search" |
||||
android:icon="@android:drawable/ic_menu_search" |
||||
app:actionViewClass="android.support.v7.widget.SearchView" |
||||
app:showAsAction="always" /> |
||||
</menu> |
@ -0,0 +1,5 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<searchable xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:label="@string/app_name" |
||||
android:hint="@string/hint_search"> |
||||
</searchable> |
Loading…
Reference in new issue