Merge branch 'commentImprovements' into 'master'

Comment improvements

See merge request pixeldroid/PixelDroid!460
This commit is contained in:
Matthieu 2022-08-03 18:34:26 +00:00
commit 5cc5d0d9d7
14 changed files with 360 additions and 140 deletions

View File

@ -45,6 +45,10 @@ android {
testBuildType "staging"
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix "-debug"
}
staging {
initWith debug
testCoverageEnabled true
@ -67,10 +71,6 @@ android {
buildConfigField "String", "CLIENT_ID", System.getenv("CLIENT_ID") ?: localProperties['CLIENT_ID'] ?: '""'
buildConfigField "String", "CLIENT_SECRET", System.getenv("CLIENT_SECRET") ?: localProperties['CLIENT_SECRET'] ?: '""'
}
debug {
applicationIdSuffix '.debug'
versionNameSuffix "-debug"
}
release {
minifyEnabled true
shrinkResources true

View File

@ -136,7 +136,7 @@ class LoginActivity : BaseThemedWithoutBarActivity() {
val credentialsDeferred: Deferred<Application?> = async {
try {
pixelfedAPI.registerApplication(
appName, "$oauthScheme://$PACKAGE_ID", SCOPE
appName, "$oauthScheme://$PACKAGE_ID", SCOPE, "https://pixeldroid.org"
)
} catch (exception: IOException) {
return@async null

View File

@ -1,20 +1,18 @@
package org.pixeldroid.app.posts
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
import android.widget.LinearLayout
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityPostBinding
import org.pixeldroid.app.databinding.CommentBinding
import org.pixeldroid.app.posts.feeds.uncachedFeeds.comments.CommentFragment
import org.pixeldroid.app.posts.feeds.uncachedFeeds.comments.CommentFragment.Companion.COMMENT_DOMAIN
import org.pixeldroid.app.posts.feeds.uncachedFeeds.comments.CommentFragment.Companion.COMMENT_STATUS_ID
import org.pixeldroid.app.utils.BaseThemedWithBarActivity
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Mention
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
@ -24,10 +22,10 @@ import retrofit2.HttpException
import java.io.IOException
class PostActivity : BaseThemedWithBarActivity() {
lateinit var domain : String
private lateinit var binding: ActivityPostBinding
private var commentFragment = CommentFragment()
private lateinit var status: Status
override fun onCreate(savedInstanceState: Bundle?) {
@ -43,9 +41,6 @@ class PostActivity : BaseThemedWithBarActivity() {
val user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty()
supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName())
val holder = StatusViewHolder(binding.postFragmentSingle)
@ -53,6 +48,7 @@ class PostActivity : BaseThemedWithBarActivity() {
holder.bind(status, apiHolder, db, lifecycleScope, displayDimensionsInPx(), isActivity = true)
activateCommenter()
initCommentsFragment(domain = user?.instance_uri.orEmpty())
if(viewComments || postComment){
//Scroll already down as much as possible (since comments are not loaded yet)
@ -63,12 +59,6 @@ class PostActivity : BaseThemedWithBarActivity() {
window.setSoftInputMode(SOFT_INPUT_STATE_VISIBLE)
binding.editComment.requestFocus()
}
// also retrieve comments if we're not posting the comment
if(!postComment) retrieveComments(apiHolder.api!!)
}
binding.postFragmentSingle.viewComments.setOnClickListener {
retrieveComments(apiHolder.api!!)
}
}
@ -92,53 +82,15 @@ class PostActivity : BaseThemedWithBarActivity() {
}
}
private fun addComment(context: Context, commentContainer: LinearLayout,
commentUsername: String, commentContent: String, mentions: List<Mention>) {
private fun initCommentsFragment(domain: String) {
val arguments = Bundle()
arguments.putSerializable(COMMENT_STATUS_ID, status.id)
arguments.putSerializable(COMMENT_DOMAIN, domain)
commentFragment.arguments = arguments
val itemBinding = CommentBinding.inflate(
LayoutInflater.from(context), commentContainer, true
)
itemBinding.user.text = commentUsername
itemBinding.commentText.text = parseHTMLText(
commentContent,
mentions,
apiHolder,
context,
lifecycleScope
)
}
private fun retrieveComments(api: PixelfedAPI) {
lifecycleScope.launchWhenCreated {
status.id.let {
try {
val statuses = api.statusComments(it).descendants
binding.commentContainer.removeAllViews()
//Create the new views for each comment
for (status in statuses) {
addComment(
binding.root.context,
binding.commentContainer,
status.account!!.username!!,
status.content!!,
status.mentions.orEmpty()
)
}
binding.commentContainer.visibility = View.VISIBLE
//Focus the comments
binding.scrollview.requestChildFocus(binding.commentContainer, binding.commentContainer)
} catch (exception: IOException) {
Log.e("COMMENT FETCH ERROR", exception.toString())
} catch (exception: HttpException) {
Log.e("COMMENT ERROR", "${exception.code()} with body ${exception.response()?.errorBody()}")
}
}
}
supportFragmentManager.beginTransaction()
.add(R.id.commentFragment, commentFragment).commit()
}
private suspend fun postComment(
@ -151,11 +103,8 @@ class PostActivity : BaseThemedWithBarActivity() {
val response = api.postStatus(nonNullText, it)
binding.commentIn.visibility = View.GONE
//Add the comment to the comment section
addComment(
binding.root.context, binding.commentContainer, response.account!!.username!!,
response.content!!, response.mentions.orEmpty()
)
//Reload to add the comment to the comment section
commentFragment.adapter.refresh()
Toast.makeText(
binding.root.context,
@ -177,5 +126,4 @@ class PostActivity : BaseThemedWithBarActivity() {
}
}
}
}

View File

@ -55,6 +55,10 @@ internal fun <T: Any> initAdapter(
footer = ReposLoadStateAdapter { adapter.retry() }
)
swipeRefreshLayout.setOnRefreshListener {
adapter.refresh()
}
adapter.addLoadStateListener { loadState ->
if(!progressBar.isVisible && swipeRefreshLayout.isRefreshing) {

View File

@ -75,10 +75,6 @@ open class CachedFeedFragment<T: FeedContentDatabase> : BaseFragment() {
initAdapter(binding.progressBar, binding.swipeRefreshLayout,
binding.list, binding.motionLayout, binding.errorLayout, adapter)
binding.swipeRefreshLayout.setOnRefreshListener {
adapter.refresh()
}
return binding.root
}

View File

@ -64,10 +64,6 @@ open class UncachedFeedFragment<T: FeedContent> : BaseFragment() {
initAdapter(binding.progressBar, binding.swipeRefreshLayout, binding.list,
binding.motionLayout, binding.errorLayout, adapter)
binding.swipeRefreshLayout.setOnRefreshListener {
adapter.refresh()
}
return binding.root
}
}

View File

@ -0,0 +1,33 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds.comments
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
import org.pixeldroid.app.posts.feeds.uncachedFeeds.profile.ProfilePagingSource
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Status
import javax.inject.Inject
class CommentContentRepository @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val statusId: String
) : UncachedContentRepository<Status> {
override fun getStream(): Flow<PagingData<Status>> {
return Pager(
config = PagingConfig(
initialLoadSize = NETWORK_PAGE_SIZE,
pageSize = NETWORK_PAGE_SIZE),
pagingSourceFactory = {
CommentPagingSource(api, statusId)
}
).flow
}
companion object {
private const val NETWORK_PAGE_SIZE = 20
}
}

View File

@ -0,0 +1,151 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds.comments
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.CommentBinding
import org.pixeldroid.app.posts.PostActivity
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedFeedFragment
import org.pixeldroid.app.posts.feeds.uncachedFeeds.ViewModelFactory
import org.pixeldroid.app.posts.parseHTMLText
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.setProfileImageFromURL
/**
* Fragment to show a list of [Status]s, in form of comments
*/
class CommentFragment : UncachedFeedFragment<Status>() {
private lateinit var id: String
private lateinit var domain: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
id = arguments?.getSerializable(COMMENT_STATUS_ID) as String
domain = arguments?.getSerializable(COMMENT_DOMAIN) as String
adapter = CommentAdapter()
}
@OptIn(ExperimentalPagingApi::class)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = super.onCreateView(inflater, container, savedInstanceState)
// get the view model
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(
requireActivity(), ViewModelFactory(
CommentContentRepository(
apiHolder.setToCurrentUser(),
id
)
)
)["commentFragment", FeedViewModel::class.java] as FeedViewModel<Status>
launch()
initSearch()
return view
}
companion object {
const val COMMENT_STATUS_ID = "PostActivityCommentsId"
const val COMMENT_DOMAIN = "PostActivityCommentsDomain"
}
private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback<Status>() {
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
oldItem.content == newItem.content
}
inner class CommentAdapter : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
UIMODEL_COMPARATOR
) {
fun create(parent: ViewGroup): CommentViewHolder {
val itemBinding = CommentBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return CommentViewHolder(itemBinding)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return create(parent)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val post = getItem(position)
post?.let {
(holder as CommentViewHolder).bind(it)
}
}
}
inner class CommentViewHolder(val binding: CommentBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(comment: Status) {
setProfileImageFromURL(
binding.profilePic,
comment.account!!.anyAvatar(),
binding.profilePic
)
binding.user.text = comment.account.username
binding.commentText.text = parseHTMLText(
comment.content!!,
comment.mentions.orEmpty(),
apiHolder,
itemView.context,
lifecycleScope
)
binding.postDomain.text =
comment.getStatusDomain(domain, binding.postDomain.context)
if (comment.replies_count == 0 || comment.replies_count == null) {
binding.replies.visibility = View.GONE
} else {
binding.replies.visibility = View.VISIBLE
binding.replies.text = itemView.context.resources.getQuantityString(
R.plurals.replies_count,
comment.replies_count,
comment.replies_count
)
}
binding.comment.setOnClickListener { openComment(comment) }
binding.profilePic.setOnClickListener { comment.account.openProfile(itemView.context) }
binding.user.setOnClickListener { comment.account.openProfile(itemView.context) }
}
private fun openComment(comment: Status) {
val intent = Intent(itemView.context, PostActivity::class.java).apply {
putExtra(Status.POST_TAG, comment)
}
itemView.context.startActivity(intent)
}
}
}

View File

@ -0,0 +1,34 @@
package org.pixeldroid.app.posts.feeds.uncachedFeeds.comments
import androidx.paging.PagingSource
import androidx.paging.PagingState
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Status
import retrofit2.HttpException
import java.io.IOException
class CommentPagingSource(
private val api: PixelfedAPI,
private val statusId: String
) : PagingSource<String, Status>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
//val position = params.key
return try {
val comments = api.statusComments(statusId).descendants
// TODO use pagination to have many comments also work
// For now, don't paginate (nextKey and prevKey null)
LoadResult.Page(
data = comments,
prevKey = null,
nextKey = null
)
} catch (exception: HttpException) {
LoadResult.Error(exception)
} catch (exception: IOException) {
LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<String, Status>): String? = null
}

View File

@ -1,59 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".posts.PostActivity"
android:id="@+id/scrollview">
tools:context=".posts.PostActivity">
<androidx.constraintlayout.widget.ConstraintLayout
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<include layout="@layout/post_fragment"
android:id="@+id/postFragmentSingle"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
<include
android:id="@+id/postFragmentSingle"
layout="@layout/post_fragment" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/commentIn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/postFragmentSingle"
tools:layout_editor_absoluteX="10dp">
<com.google.android.material.textfield.TextInputLayout
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout2"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/submitComment"
app:layout_constraintStart_toStartOf="parent">
<EditText
android:id="@+id/editComment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="3">
android:layout_height="wrap_content"
android:hint="@string/comment"
android:importantForAutofill="no"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<EditText
android:id="@+id/editComment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/comment"
android:inputType="text"
android:importantForAutofill="no" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/submitComment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:contentDescription="@string/submit_comment"
android:text="@string/comment"
app:layout_constraintBottom_toBottomOf="@+id/textInputLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textInputLayout2"
app:layout_constraintTop_toTopOf="@+id/textInputLayout2" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageButton
android:id="@+id/submitComment"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:contentDescription="@string/submit_comment"
android:src="@drawable/ic_send_blue" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/commentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@+id/commentIn">
</com.google.android.material.appbar.CollapsingToolbarLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>
</ScrollView>
<androidx.fragment.app.FragmentContainerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/commentFragment"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,39 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp">
android:layout_height="wrap_content"
android:layout_margin="4dp">
<androidx.cardview.widget.CardView
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical">
<ImageView
android:id="@+id/profilePic"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="10dp"
android:contentDescription="@string/profile_picture"
tools:src="@drawable/ic_default_user"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/user"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="8"
android:layout_marginStart="10dp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/profilePic"
app:layout_constraintTop_toTopOf="parent"
tools:text="username" />
<TextView
android:id="@+id/commentText"
android:layout_width="match_parent"
android:id="@+id/postDomain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
tools:text="This is a comment on this awesome post" />
</LinearLayout>
</androidx.cardview.widget.CardView>
android:layout_marginStart="8dp"
android:textColor="@color/domainGray"
app:layout_constraintBottom_toBottomOf="@+id/user"
app:layout_constraintStart_toEndOf="@+id/user"
app:layout_constraintTop_toTopOf="@+id/user"
tools:text="from domain.tld" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/commentText"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/user"
app:layout_constraintTop_toBottomOf="@+id/user"
tools:text="This is a comment on this awesome post. Very long comment! \nMaybe with multiple lines" />
<TextView
android:id="@+id/replies"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="@id/commentText"
app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintStart_toStartOf="@id/commentText"
app:layout_constraintTop_toBottomOf="@id/commentText"
tools:text="3 replies" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -47,8 +47,9 @@
android:id="@+id/list"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/errorLayout"
app:layout_constraintBottom_toBottomOf="parent"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/post_fragment"/>

View File

@ -6,6 +6,8 @@
<color name="white">#FFFFFF</color>
<color name="domainGray">#b3b3b3</color>
<!-- > Following are theme color values </-->
<color name="seed">#f4a261</color>
<color name="md_theme_light_primary">#924C00</color>

View File

@ -291,4 +291,8 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
<string name="add_images_error">Error while adding images</string>
<string name="notification_thumbnail">"Thumbnail of image in this notification's post"</string>
<string name="post_preview">Preview of a post</string>
<plurals name="replies_count">
<item quantity="one">%d reply</item>
<item quantity="other">%d replies</item>
</plurals>
</resources>