Working comment improvements

This commit is contained in:
Matthieu 2022-08-02 19:07:06 +02:00
parent f490735113
commit 696228517d
7 changed files with 317 additions and 121 deletions

View File

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

View File

@ -1,25 +1,36 @@
package org.pixeldroid.app.posts package org.pixeldroid.app.posts
import android.content.Context import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Job
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityPostBinding import org.pixeldroid.app.databinding.ActivityPostBinding
import org.pixeldroid.app.databinding.CommentBinding import org.pixeldroid.app.databinding.CommentBinding
import org.pixeldroid.app.posts.feeds.initAdapter
import org.pixeldroid.app.posts.feeds.launch
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
import org.pixeldroid.app.posts.feeds.uncachedFeeds.comments.CommentContentRepository
import org.pixeldroid.app.profile.ProfileViewModelFactory
import org.pixeldroid.app.utils.BaseThemedWithBarActivity import org.pixeldroid.app.utils.BaseThemedWithBarActivity
import org.pixeldroid.app.utils.api.PixelfedAPI 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
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import org.pixeldroid.app.utils.displayDimensionsInPx import org.pixeldroid.app.utils.displayDimensionsInPx
import org.pixeldroid.app.utils.setProfileImageFromURL
import retrofit2.HttpException import retrofit2.HttpException
import java.io.IOException import java.io.IOException
@ -27,6 +38,10 @@ class PostActivity : BaseThemedWithBarActivity() {
lateinit var domain : String lateinit var domain : String
private lateinit var binding: ActivityPostBinding private lateinit var binding: ActivityPostBinding
private lateinit var profileAdapter: PagingDataAdapter<Status, RecyclerView.ViewHolder>
private lateinit var viewModel: FeedViewModel<Status>
private var job: Job? = null
private lateinit var status: Status private lateinit var status: Status
@ -45,7 +60,6 @@ class PostActivity : BaseThemedWithBarActivity() {
domain = user?.instance_uri.orEmpty() domain = user?.instance_uri.orEmpty()
supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName()) supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName())
val holder = StatusViewHolder(binding.postFragmentSingle) val holder = StatusViewHolder(binding.postFragmentSingle)
@ -53,6 +67,7 @@ class PostActivity : BaseThemedWithBarActivity() {
holder.bind(status, apiHolder, db, lifecycleScope, displayDimensionsInPx(), isActivity = true) holder.bind(status, apiHolder, db, lifecycleScope, displayDimensionsInPx(), isActivity = true)
activateCommenter() activateCommenter()
retrieveComments()
if(viewComments || postComment){ if(viewComments || postComment){
//Scroll already down as much as possible (since comments are not loaded yet) //Scroll already down as much as possible (since comments are not loaded yet)
@ -63,12 +78,6 @@ class PostActivity : BaseThemedWithBarActivity() {
window.setSoftInputMode(SOFT_INPUT_STATE_VISIBLE) window.setSoftInputMode(SOFT_INPUT_STATE_VISIBLE)
binding.editComment.requestFocus() 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 +101,24 @@ class PostActivity : BaseThemedWithBarActivity() {
} }
} }
private fun addComment(context: Context, commentContainer: LinearLayout, @OptIn(ExperimentalPagingApi::class)
commentUsername: String, commentContent: String, mentions: List<Mention>) { private fun retrieveComments() {
// get the view model
@Suppress("UNCHECKED_CAST")
val itemBinding = CommentBinding.inflate( viewModel = ViewModelProvider(this@PostActivity, ProfileViewModelFactory(
LayoutInflater.from(context), commentContainer, true CommentContentRepository(
apiHolder.setToCurrentUser(),
status.id
) )
itemBinding.user.text = commentUsername
itemBinding.commentText.text = parseHTMLText(
commentContent,
mentions,
apiHolder,
context,
lifecycleScope
) )
} )[FeedViewModel::class.java] as FeedViewModel<Status>
private fun retrieveComments(api: PixelfedAPI) { profileAdapter = CommentAdapter()
lifecycleScope.launchWhenCreated { initAdapter(binding.postCommentsProgressBar, binding.postRefreshLayout,
status.id.let { binding.commentRecyclerView, binding.motionLayout, binding.errorLayout,
try { profileAdapter)
val statuses = api.statusComments(it).descendants
binding.commentContainer.removeAllViews() job = launch(job, lifecycleScope, viewModel, profileAdapter)
//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()}")
}
}
}
} }
private suspend fun postComment( private suspend fun postComment(
@ -152,10 +132,7 @@ class PostActivity : BaseThemedWithBarActivity() {
binding.commentIn.visibility = View.GONE binding.commentIn.visibility = View.GONE
//Add the comment to the comment section //Add the comment to the comment section
addComment( profileAdapter.refresh()
binding.root.context, binding.commentContainer, response.account!!.username!!,
response.content!!, response.mentions.orEmpty()
)
Toast.makeText( Toast.makeText(
binding.root.context, binding.root.context,
@ -178,4 +155,79 @@ class PostActivity : BaseThemedWithBarActivity() {
} }
} }
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,
this@PostActivity,
lifecycleScope
)
if(comment.replies_count == 0 || comment.replies_count == null){
binding.replies.visibility = View.GONE
} else {
binding.replies.visibility = View.VISIBLE
binding.replies.text = 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(POST_TAG, comment)
}
itemView.context.startActivity(intent)
}
}
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)
}
}
}
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
}
} }

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,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,112 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scrollview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".posts.PostActivity" tools:context=".posts.PostActivity">
android:id="@+id/scrollview">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/postRefreshLayout"
android:layout_width="0dp"
android:layout_height="0dp" />
<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"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<include layout="@layout/post_fragment" <include
android:id="@+id/postFragmentSingle"/> android:id="@+id/postFragmentSingle"
layout="@layout/post_fragment" />
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/commentIn" android:id="@+id/commentIn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/postFragmentSingle" app:layout_constraintTop_toBottomOf="@+id/postFragmentSingle"
tools:layout_editor_absoluteX="10dp"> tools:layout_editor_absoluteX="10dp">
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:id="@+id/textInputLayout2"
android:layout_height="match_parent" android:layout_width="0dp"
android:layout_weight="3"> android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/submitComment"
app:layout_constraintStart_toStartOf="parent">
<EditText <EditText
android:id="@+id/editComment" android:id="@+id/editComment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:hint="@string/comment" android:hint="@string/comment"
android:inputType="text" android:importantForAutofill="no"
android:importantForAutofill="no" /> android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<ImageButton <Button
android:id="@+id/submitComment" android:id="@+id/submitComment"
android:layout_width="wrap_content" 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>
<LinearLayout
android:id="@+id/commentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:layout_margin="8dp"
app:layout_constraintTop_toBottomOf="@+id/commentIn"> android:contentDescription="@string/submit_comment"
android:text="@string/comment"
</LinearLayout> 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> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:visibility="visible"
app:layoutDescription="@xml/error_layout_xml_error_scene"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<include
android:id="@+id/errorLayout"
layout="@layout/error_layout"
tools:layout_editor_absoluteX="50dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/commentRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:layoutManager="LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@id/errorLayout"
tools:listitem="@layout/comment" />
<ProgressBar
android:id="@+id/postCommentsProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/errorLayout" />
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,39 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp">
<androidx.cardview.widget.CardView
android:id="@+id/comment" android:id="@+id/comment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" android:layout_margin="4dp">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical"> 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 <TextView
android:id="@+id/user" android:id="@+id/user"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="8" android:layout_marginStart="10dp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/profilePic"
app:layout_constraintTop_toTopOf="parent"
tools:text="username" /> tools:text="username" />
<TextView <TextView
android:id="@+id/commentText" android:id="@+id/commentText"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2" app:layout_constraintEnd_toEndOf="parent"
tools:text="This is a comment on this awesome post" /> app:layout_constraintHorizontal_bias="1.0"
</LinearLayout> app:layout_constraintStart_toStartOf="@+id/user"
</androidx.cardview.widget.CardView> app:layout_constraintTop_toBottomOf="@+id/user"
tools:text="This is a comment on this awesome post. Very long comment! \nMaybe with multiple lines" />
</androidx.constraintlayout.widget.ConstraintLayout> <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

@ -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="add_images_error">Error while adding images</string>
<string name="notification_thumbnail">"Thumbnail of image in this notification's post"</string> <string name="notification_thumbnail">"Thumbnail of image in this notification's post"</string>
<string name="post_preview">Preview of a 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> </resources>