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