make search find statuses (#613)
parent
de209e0652
commit
c85f7a5cd2
@ -0,0 +1,231 @@ |
||||
/* Copyright 2018 Conny Duck |
||||
* |
||||
* 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.fragment |
||||
|
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.preference.PreferenceManager |
||||
import android.support.v7.widget.DividerItemDecoration |
||||
import android.support.v7.widget.LinearLayoutManager |
||||
import android.util.Log |
||||
import android.view.LayoutInflater |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import com.keylesspalace.tusky.AccountActivity |
||||
import com.keylesspalace.tusky.R |
||||
import com.keylesspalace.tusky.ViewTagActivity |
||||
import com.keylesspalace.tusky.adapter.SearchResultsAdapter |
||||
import com.keylesspalace.tusky.di.Injectable |
||||
import com.keylesspalace.tusky.entity.Attachment |
||||
import com.keylesspalace.tusky.entity.SearchResults |
||||
import com.keylesspalace.tusky.entity.Status |
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener |
||||
import com.keylesspalace.tusky.network.TimelineCases |
||||
import com.keylesspalace.tusky.util.ViewDataUtils |
||||
import com.keylesspalace.tusky.viewdata.StatusViewData |
||||
import kotlinx.android.synthetic.main.fragment_search.* |
||||
import retrofit2.Call |
||||
import retrofit2.Callback |
||||
import retrofit2.Response |
||||
import javax.inject.Inject |
||||
|
||||
class SearchFragment : SFragment(), StatusActionListener, Injectable { |
||||
|
||||
@Inject |
||||
lateinit var timelineCases: TimelineCases |
||||
|
||||
private lateinit var searchAdapter: SearchResultsAdapter |
||||
|
||||
private var alwaysShowSensitiveMedia = false |
||||
private var mediaPreviewEnabled = true |
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||
return inflater.inflate(R.layout.fragment_search, container, false) |
||||
} |
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(activity) |
||||
alwaysShowSensitiveMedia = preferences.getBoolean("alwaysShowSensitiveMedia", false) |
||||
mediaPreviewEnabled = preferences.getBoolean("mediaPreviewEnabled", true) |
||||
|
||||
searchRecyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL)) |
||||
searchRecyclerView.layoutManager = LinearLayoutManager(view.context) |
||||
searchAdapter = SearchResultsAdapter(mediaPreviewEnabled, alwaysShowSensitiveMedia, this, this) |
||||
searchRecyclerView.adapter = searchAdapter |
||||
|
||||
} |
||||
|
||||
fun search(query: String) { |
||||
clearResults() |
||||
val callback = object : Callback<SearchResults> { |
||||
override fun onResponse(call: Call<SearchResults>, response: Response<SearchResults>) { |
||||
if (response.isSuccessful) { |
||||
val results = response.body() |
||||
if (results != null && (results.accounts.isNotEmpty() || results.statuses.isNotEmpty() || results.hashtags.isNotEmpty())) { |
||||
searchAdapter.updateSearchResults(results) |
||||
hideFeedback() |
||||
} else { |
||||
displayNoResults() |
||||
} |
||||
} else { |
||||
onSearchFailure() |
||||
} |
||||
} |
||||
|
||||
override fun onFailure(call: Call<SearchResults>, t: Throwable) { |
||||
onSearchFailure() |
||||
} |
||||
} |
||||
mastodonApi.search(query, true) |
||||
.enqueue(callback) |
||||
} |
||||
|
||||
private fun onSearchFailure() { |
||||
displayNoResults() |
||||
Log.e(TAG, "Search request failed.") |
||||
} |
||||
|
||||
private fun clearResults() { |
||||
searchAdapter.updateSearchResults(null) |
||||
searchProgressBar.visibility = View.VISIBLE |
||||
searchNoResultsText.visibility = View.GONE |
||||
} |
||||
|
||||
private fun displayNoResults() { |
||||
searchProgressBar.visibility = View.GONE |
||||
searchNoResultsText.visibility = View.VISIBLE |
||||
} |
||||
|
||||
private fun hideFeedback() { |
||||
searchProgressBar.visibility = View.GONE |
||||
searchNoResultsText.visibility = View.GONE |
||||
} |
||||
|
||||
override fun timelineCases(): TimelineCases { |
||||
return timelineCases |
||||
} |
||||
|
||||
override fun removeItem(position: Int) { |
||||
searchAdapter.removeStatusAtPosition(position) |
||||
} |
||||
|
||||
override fun removeAllByAccountId(accountId: String?) { |
||||
// not supported |
||||
} |
||||
|
||||
override fun onReply(position: Int) { |
||||
val status = searchAdapter.getStatusAtPosition(position) |
||||
if(status != null) { |
||||
super.reply(status) |
||||
} |
||||
} |
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) { |
||||
val status = searchAdapter.getStatusAtPosition(position) |
||||
if(status != null) { |
||||
timelineCases.reblogWithCallback(status, reblog, object: Callback<Status> { |
||||
override fun onResponse(call: Call<Status>?, response: Response<Status>?) { |
||||
status.reblogged = true |
||||
searchAdapter.updateStatusAtPosition(ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia), position) |
||||
} |
||||
|
||||
override fun onFailure(call: Call<Status>?, t: Throwable?) { |
||||
Log.d(TAG, "Failed to reblog status " + status.id, t) |
||||
} |
||||
|
||||
}) |
||||
} |
||||
} |
||||
|
||||
override fun onFavourite(favourite: Boolean, position: Int) { |
||||
val status = searchAdapter.getStatusAtPosition(position) |
||||
if(status != null) { |
||||
timelineCases.favouriteWithCallback(status, favourite, object: Callback<Status> { |
||||
override fun onResponse(call: Call<Status>?, response: Response<Status>?) { |
||||
status.favourited = true |
||||
searchAdapter.updateStatusAtPosition(ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia), position) |
||||
} |
||||
|
||||
override fun onFailure(call: Call<Status>?, t: Throwable?) { |
||||
Log.d(TAG, "Failed to favourite status " + status.id, t) |
||||
} |
||||
|
||||
}) |
||||
} |
||||
} |
||||
|
||||
override fun onMore(view: View?, position: Int) { |
||||
val status = searchAdapter.getStatusAtPosition(position) |
||||
if(status != null) { |
||||
more(status, view, position) |
||||
} |
||||
} |
||||
|
||||
override fun onViewMedia(urls: Array<out String>?, index: Int, type: Attachment.Type?, view: View?) { |
||||
viewMedia(urls, index, type, view) |
||||
} |
||||
|
||||
override fun onViewThread(position: Int) { |
||||
val status = searchAdapter.getStatusAtPosition(position) |
||||
if(status != null) { |
||||
viewThread(status) |
||||
} |
||||
} |
||||
|
||||
override fun onOpenReblog(position: Int) { |
||||
// there are no reblogs in search results |
||||
} |
||||
|
||||
override fun onExpandedChange(expanded: Boolean, position: Int) { |
||||
val status = searchAdapter.getConcreteStatusAtPosition(position) |
||||
if(status != null) { |
||||
val newStatus = StatusViewData.Builder(status) |
||||
.setIsExpanded(expanded).createStatusViewData() |
||||
searchAdapter.updateStatusAtPosition(newStatus, position) |
||||
} |
||||
} |
||||
|
||||
override fun onContentHiddenChange(isShowing: Boolean, position: Int) { |
||||
val status = searchAdapter.getConcreteStatusAtPosition(position) |
||||
if(status != null) { |
||||
val newStatus = StatusViewData.Builder(status) |
||||
.setIsShowingSensitiveContent(isShowing).createStatusViewData() |
||||
searchAdapter.updateStatusAtPosition(newStatus, position) |
||||
} |
||||
} |
||||
|
||||
override fun onLoadMore(position: Int) { |
||||
// not needed here, search is not paginated |
||||
} |
||||
|
||||
companion object { |
||||
const val TAG = "SearchFragment" |
||||
} |
||||
|
||||
override fun onViewAccount(id: String) { |
||||
val intent = Intent(context, AccountActivity::class.java) |
||||
intent.putExtra("id", id) |
||||
startActivity(intent) |
||||
} |
||||
|
||||
override fun onViewTag(tag: String) { |
||||
val intent = Intent(context, ViewTagActivity::class.java) |
||||
intent.putExtra("hashtag", tag) |
||||
startActivity(intent) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,31 @@ |
||||
<?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"> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/searchRecyclerView" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
tools:listitem="@layout/item_account" /> |
||||
|
||||
<ProgressBar |
||||
android:id="@+id/searchProgressBar" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="center" |
||||
android:indeterminate="true" |
||||
android:visibility="gone" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/searchNoResultsText" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="center" |
||||
android:text="@string/search_no_results" |
||||
android:visibility="gone" /> |
||||
|
||||
<include layout="@layout/item_status_bottom_sheet" /> |
||||
|
||||
</android.support.design.widget.CoordinatorLayout> |
Loading…
Reference in new issue