Discover feature (#143)

* Discover feature

* Add refresh handler

* Add json to mockserver

* singleton mock server, fix null items

* Add test to open discover post
This commit is contained in:
Wv5twkFEKh54vo4tta9yu7dHa3 2020-05-03 14:30:01 +02:00 committed by GitHub
parent 0038cd8c9e
commit 663d0e2599
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 267 additions and 50 deletions

View File

@ -76,6 +76,17 @@ class MockedServerTest {
Thread.sleep(3000)
onView(first(withId(R.id.tag_name))).check(matches(withText("#caturday")))
}
@Test
fun openDiscoverPost(){
activityScenario.onActivity{
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(1)?.select()
}
Thread.sleep(1000)
onView(withId(R.id.discoverList)).perform(click())
Thread.sleep(1000)
onView(withId(R.id.username)).check(matches(withText("machintuck")))
}
@Test

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,25 @@
package com.h.pixeldroid
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.PostFragment
import com.h.pixeldroid.objects.DiscoverPost
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.objects.Status.Companion.DISCOVER_TAG
import com.h.pixeldroid.objects.Status.Companion.DOMAIN_TAG
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
import kotlinx.android.synthetic.main.activity_post.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class PostActivity : AppCompatActivity() {
private lateinit var preferences: SharedPreferences
lateinit var postFragment : PostFragment
lateinit var domain : String
@ -17,18 +28,53 @@ class PostActivity : AppCompatActivity() {
setContentView(R.layout.activity_post)
val status = intent.getSerializableExtra(POST_TAG) as Status?
val discoverPost: DiscoverPost? = intent.getSerializableExtra(DISCOVER_TAG) as DiscoverPost?
domain = getSharedPreferences(
preferences = getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
).getString("domain", "")!!
)
domain = preferences.getString("domain", "")!!
postFragment = PostFragment()
val arguments = Bundle()
arguments.putSerializable(POST_TAG, status)
arguments.putString(DOMAIN_TAG, domain)
postFragment.arguments = arguments
if (discoverPost != null) {
postProgressBar.visibility = View.VISIBLE
getDiscoverPost(arguments, discoverPost)
} else {
initializeFragment(arguments, status)
}
}
private fun getDiscoverPost(
arguments: Bundle,
discoverPost: DiscoverPost
) {
val api = PixelfedAPI.create(domain)
val accessToken = preferences.getString("accessToken", "") ?: ""
val id = discoverPost.url?.substringAfterLast('/') ?: ""
api.getStatus("Bearer $accessToken", id).enqueue(object : Callback<Status> {
override fun onFailure(call: Call<Status>, t: Throwable) {
Log.e("PostActivity:", t.toString())
}
override fun onResponse(call: Call<Status>, response: Response<Status>) {
if(response.code() == 200) {
val status = response.body()!!
postProgressBar.visibility = View.GONE
initializeFragment(arguments, status)
}
}
})
}
private fun initializeFragment(arguments: Bundle, status: Status?){
arguments.putSerializable(POST_TAG, status)
postFragment.arguments = arguments
supportFragmentManager.beginTransaction()
.add(R.id.postFragmentSingle, postFragment).commit()
postFragmentSingle.visibility = View.VISIBLE
}
}

View File

@ -47,7 +47,7 @@ class ProfileActivity : AppCompatActivity() {
// Set posts RecyclerView as a grid with 3 columns
recycler = findViewById(R.id.profilePostsRecyclerView)
recycler.layoutManager = GridLayoutManager(applicationContext, 3)
adapter = ProfilePostsRecyclerViewAdapter(this)
adapter = ProfilePostsRecyclerViewAdapter()
recycler.adapter = adapter
setContent()
@ -131,12 +131,8 @@ class ProfileActivity : AppCompatActivity() {
override fun onResponse(call: Call<List<Status>>, response: Response<List<Status>>) {
if(response.code() == 200) {
val posts = ArrayList<Status>()
val statuses = response.body()!!
for(status in statuses) {
posts.add(status)
}
adapter.addPosts(posts)
adapter.addPosts(statuses)
}
}
})

View File

@ -18,6 +18,17 @@ import retrofit2.http.Field
interface PixelfedAPI {
companion object {
fun create(baseUrl: String): PixelfedAPI {
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(PixelfedAPI::class.java)
}
}
@FormUrlEncoded
@POST("/api/v1/apps")
fun registerApplication(
@ -210,15 +221,11 @@ interface PixelfedAPI {
@Path("id") accountId : String
): Call<Account>
companion object {
fun create(baseUrl: String): PixelfedAPI {
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(PixelfedAPI::class.java)
}
}
@GET("/api/v1/statuses/{id}")
fun getStatus(
@Header("Authorization") authorization: String,
@Path("id") accountId : String
): Call<Status>
@Multipart
@POST("/api/v1/media")
@ -231,5 +238,11 @@ interface PixelfedAPI {
// get instance configuration
@GET("/api/v1/instance")
fun instance() : Call<Instance>
// get discover
@GET("/api/v2/discover/posts")
fun discover(
@Header("Authorization") authorization: String
) : Call<DiscoverPosts>
}

View File

@ -45,7 +45,7 @@ class ProfilePostsFragment : Fragment() {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
adapter = ProfilePostsRecyclerViewAdapter(requireContext())
adapter = ProfilePostsRecyclerViewAdapter()
}
}
return view

View File

@ -1,6 +1,5 @@
package com.h.pixeldroid.fragments
import android.content.Context
import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
@ -16,9 +15,7 @@ import com.h.pixeldroid.utils.ImageConverter.Companion.setSquareImageFromURL
* [RecyclerView.Adapter] that can display a list of [PostMiniature]s and makes a call to the
* specified [OnListFragmentInteractionListener].
*/
class ProfilePostsRecyclerViewAdapter(
private val context: Context
) : RecyclerView.Adapter<ProfilePostsRecyclerViewAdapter.ViewHolder>() {
class ProfilePostsRecyclerViewAdapter: RecyclerView.Adapter<ProfilePostsRecyclerViewAdapter.ViewHolder>() {
private val posts: ArrayList<Status> = ArrayList()
fun addPosts(newPosts : List<Status>) {
@ -37,9 +34,9 @@ class ProfilePostsRecyclerViewAdapter(
val post = posts[position]
setSquareImageFromURL(holder.postView, post.getPostPreviewURL(), holder.postPreview)
holder.postPreview.setOnClickListener {
val intent = Intent(context, PostActivity::class.java)
val intent = Intent(holder.postPreview.context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)
context.startActivity(intent)
holder.postPreview.context.startActivity(intent)
}
}

View File

@ -4,25 +4,44 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.PostActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.SearchActivity
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.DiscoverPost
import com.h.pixeldroid.objects.DiscoverPosts
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.ImageConverter
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
/**
* This fragment lets you search and use PixelFed's Discover feature
* This fragment lets you search and use Pixelfed's Discover feature
*/
class SearchDiscoverFragment : Fragment() {
lateinit var api: PixelfedAPI
private lateinit var api: PixelfedAPI
private lateinit var preferences: SharedPreferences
private lateinit var recycler : RecyclerView
private lateinit var adapter : DiscoverRecyclerViewAdapter
private lateinit var accessToken: String
private lateinit var discoverProgressBar: ProgressBar
private lateinit var discoverRefreshLayout: SwipeRefreshLayout
override fun onCreateView(
@ -37,6 +56,11 @@ class SearchDiscoverFragment : Fragment() {
intent.putExtra("searchFeed", search.text.toString())
startActivity(intent)
}
// Set posts RecyclerView as a grid with 3 columns
recycler = view.findViewById(R.id.discoverList)
recycler.layoutManager = GridLayoutManager(requireContext(), 3)
adapter = DiscoverRecyclerViewAdapter()
recycler.adapter = adapter
return view
}
@ -49,5 +73,67 @@ class SearchDiscoverFragment : Fragment() {
api = PixelfedAPI.create("${preferences.getString("domain", "")}")
accessToken = preferences.getString("accessToken", "") ?: ""
discoverProgressBar = view.findViewById(R.id.discoverProgressBar)
discoverRefreshLayout = view.findViewById(R.id.discoverRefreshLayout)
getDiscover()
discoverRefreshLayout.setOnRefreshListener {
getDiscover()
}
}
private fun getDiscover() {
api.discover("Bearer $accessToken")
.enqueue(object : Callback<DiscoverPosts> {
override fun onFailure(call: Call<DiscoverPosts>, t: Throwable) {
Log.e("SearchDiscoverFragment:", t.toString())
}
override fun onResponse(call: Call<DiscoverPosts>, response: Response<DiscoverPosts>) {
if(response.code() == 200) {
val discoverPosts = response.body()!!
adapter.addPosts(discoverPosts.posts)
discoverProgressBar.visibility = View.GONE
discoverRefreshLayout.isRefreshing = false
}
}
})
}
/**
* [RecyclerView.Adapter] that can display a list of [DiscoverPost]s
*/
class DiscoverRecyclerViewAdapter: RecyclerView.Adapter<DiscoverRecyclerViewAdapter.ViewHolder>() {
private val posts: ArrayList<DiscoverPost> = ArrayList()
fun addPosts(newPosts : List<DiscoverPost>) {
posts.clear()
posts.addAll(newPosts)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_profile_posts, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val post = posts[position]
ImageConverter.setSquareImageFromURL(holder.postView, post.thumb, holder.postPreview)
holder.postPreview.setOnClickListener {
val intent = Intent(holder.postView.context, PostActivity::class.java)
intent.putExtra(Status.DISCOVER_TAG, post)
holder.postView.context.startActivity(intent)
}
}
override fun getItemCount(): Int = posts.size
inner class ViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) {
val postPreview: ImageView = postView.findViewById(R.id.postPreview)
}
}
}

View File

@ -20,12 +20,10 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.bumptech.glide.ListPreloader.PreloadModelProvider
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.FeedContent
import com.h.pixeldroid.objects.Status
import kotlinx.android.synthetic.main.fragment_feed.view.*
import retrofit2.Call
import retrofit2.Callback

View File

@ -0,0 +1,13 @@
package com.h.pixeldroid.objects
import java.io.Serializable
/*
NOT DOCUMENTED, USE WITH CAUTION
*/
data class DiscoverPost(
val type: String?, //This is probably an enum, with these values: https://github.com/pixelfed/pixelfed/blob/700c7805cecc364b68b9cfe20df00608e0f6c465/app/Status.php#L31
val url: String?, //URL to post
val thumb: String? //URL to thumbnail
) : Serializable

View File

@ -0,0 +1,8 @@
package com.h.pixeldroid.objects
import java.io.Serializable
data class DiscoverPosts(
//Required attributes
val posts: List<DiscoverPost>
) : Serializable

View File

@ -94,6 +94,7 @@ data class Status(
const val POST_TAG = "postTag"
const val POST_FRAG_TAG = "postFragTag"
const val DOMAIN_TAG = "domainTag"
const val DISCOVER_TAG = "discoverTag"
}
fun getPostUrl() : String? = media_attachments?.getOrNull(0)?.url

View File

@ -6,12 +6,26 @@
android:layout_height="match_parent"
tools:context=".PostActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/postFragmentSingle"
android:layout_width="0dp"
android:layout_height="0dp"
<ProgressBar
android:id="@+id/postProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/postFragmentSingle"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
</androidx.fragment.app.FragmentContainerView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -16,7 +16,7 @@
app:errorEnabled="true"
app:layout_constraintEnd_toStartOf="@+id/searchButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" >
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchEditText"
@ -28,17 +28,6 @@
</com.google.android.material.textfield.TextInputLayout>
<ProgressBar
android:id="@+id/searchProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search" />
<Button
android:id="@+id/searchButton"
android:layout_width="wrap_content"
@ -49,4 +38,35 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/search" />
<ProgressBar
android:id="@+id/discoverProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/discoverRefreshLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/discoverList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@+id/search" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>