diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileContentRepository.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileContentRepository.kt new file mode 100644 index 00000000..292c8b36 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileContentRepository.kt @@ -0,0 +1,34 @@ +package com.h.pixeldroid.posts.feeds.uncachedFeeds.profile + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository +import com.h.pixeldroid.utils.api.PixelfedAPI +import com.h.pixeldroid.utils.api.objects.Status +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class ProfileContentRepository @ExperimentalPagingApi +@Inject constructor( + private val api: PixelfedAPI, + private val accessToken: String, + private val accountId: String +) : UncachedContentRepository { + override fun getStream(): Flow> { + return Pager( + config = PagingConfig( + initialLoadSize = NETWORK_PAGE_SIZE, + pageSize = NETWORK_PAGE_SIZE, + enablePlaceholders = false), + pagingSourceFactory = { + ProfilePagingSource(api, accessToken, accountId) + } + ).flow + } + + companion object { + private const val NETWORK_PAGE_SIZE = 20 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileFeedFragment.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileFeedFragment.kt new file mode 100644 index 00000000..670bf4b3 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileFeedFragment.kt @@ -0,0 +1,135 @@ +package com.h.pixeldroid.profile + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.appcompat.content.res.AppCompatResources +import androidx.lifecycle.ViewModelProvider +import androidx.paging.ExperimentalPagingApi +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.h.pixeldroid.R +import com.h.pixeldroid.databinding.FragmentProfilePostsBinding +import com.h.pixeldroid.posts.PostActivity +import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel +import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedFeedFragment +import com.h.pixeldroid.posts.feeds.uncachedFeeds.ViewModelFactory +import com.h.pixeldroid.posts.feeds.uncachedFeeds.profile.ProfileContentRepository +import com.h.pixeldroid.utils.ImageConverter +import com.h.pixeldroid.utils.api.objects.Account.Companion.ACCOUNT_ID_TAG +import com.h.pixeldroid.utils.api.objects.Status + +/** + * Fragment to show all the posts of a user. + */ +class ProfileFeedFragment : UncachedFeedFragment() { + + private lateinit var accountId : String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + adapter = ProfileAdapter() + + accountId = arguments?.getSerializable(ACCOUNT_ID_TAG) as String + + } + + @ExperimentalPagingApi + 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(this, ViewModelFactory( + ProfileContentRepository( + apiHolder.setDomainToCurrentUser(db), + db.userDao().getActiveUser()!!.accessToken, + accountId + ) + ) + ).get(FeedViewModel::class.java) as FeedViewModel + + launch() + initSearch() + + return view + } +} + + +class PostViewHolder(binding: FragmentProfilePostsBinding) : RecyclerView.ViewHolder(binding.root) { + private val postPreview: ImageView = binding.postPreview + private val albumIcon: ImageView = binding.albumIcon + + fun bind(post: Status) { + + if(post.sensitive!!) { + ImageConverter.setSquareImageFromDrawable( + itemView, + AppCompatResources.getDrawable(itemView.context, R.drawable.ic_sensitive), + postPreview + ) + } else { + ImageConverter.setSquareImageFromURL(itemView, post.getPostPreviewURL(), postPreview) + } + + if(post.media_attachments?.size ?: 0 > 1) { + albumIcon.visibility = View.VISIBLE + } else { + albumIcon.visibility = View.GONE + } + + postPreview.setOnClickListener { + val intent = Intent(postPreview.context, PostActivity::class.java) + intent.putExtra(Status.POST_TAG, post) + postPreview.context.startActivity(intent) + } + } + + companion object { + fun create(parent: ViewGroup): PostViewHolder { + val itemBinding = FragmentProfilePostsBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + return PostViewHolder(itemBinding) + } + } +} + + +class ProfileAdapter : PagingDataAdapter( + UIMODEL_COMPARATOR +) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return PostViewHolder.create(parent) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val post = getItem(position) + + post?.let { + (holder as PostViewHolder).bind(it) + } + } + + companion object { + private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback() { + 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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfilePagingSource.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfilePagingSource.kt new file mode 100644 index 00000000..2cefee2a --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfilePagingSource.kt @@ -0,0 +1,36 @@ +package com.h.pixeldroid.posts.feeds.uncachedFeeds.profile + +import androidx.paging.PagingSource +import com.h.pixeldroid.utils.api.PixelfedAPI +import com.h.pixeldroid.utils.api.objects.Status +import retrofit2.HttpException +import java.io.IOException + +class ProfilePagingSource( + private val api: PixelfedAPI, + private val accessToken: String, + private val accountId: String +) : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { + val position = params.key + return try { + val response = api.accountPosts("Bearer $accessToken", account_id = accountId) + + val posts = if(response.isSuccessful){ + response.body().orEmpty() + } else { + throw HttpException(response) + } + + LoadResult.Page( + data = posts, + prevKey = null, + nextKey = if(posts.isEmpty()) null else (position ?: 0) + posts.size + ) + } catch (exception: IOException) { + LoadResult.Error(exception) + } catch (exception: HttpException) { + LoadResult.Error(exception) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt b/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt index 505e3831..9dbf6c13 100644 --- a/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt @@ -15,6 +15,8 @@ import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.h.pixeldroid.R import com.h.pixeldroid.databinding.ActivityProfileBinding +import com.h.pixeldroid.databinding.FragmentProfileFeedBinding +import com.h.pixeldroid.databinding.FragmentProfilePostsBinding import com.h.pixeldroid.posts.parseHTMLText import com.h.pixeldroid.utils.BaseActivity import com.h.pixeldroid.utils.ImageConverter @@ -32,17 +34,20 @@ import java.io.IOException class ProfileActivity : BaseActivity() { private lateinit var pixelfedAPI : PixelfedAPI - private lateinit var adapter : ProfilePostsRecyclerViewAdapter +// private lateinit var adapter : ProfilePostsRecyclerViewAdapter private lateinit var accessToken : String private lateinit var domain : String - private var user: UserDatabaseEntity? = null - private lateinit var binding: ActivityProfileBinding + private var user: UserDatabaseEntity? = null + private var postsFragment = ProfileFeedFragment() + + private lateinit var activityBinding: ActivityProfileBinding + private lateinit var feedFragmentBinding: FragmentProfileFeedBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityProfileBinding.inflate(layoutInflater) - setContentView(binding.root) + activityBinding = ActivityProfileBinding.inflate(layoutInflater) + setContentView(activityBinding.root) supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -53,16 +58,16 @@ class ProfileActivity : BaseActivity() { accessToken = user?.accessToken.orEmpty() // Set posts RecyclerView as a grid with 3 columns - binding.profilePostsRecyclerView.layoutManager = GridLayoutManager(applicationContext, 3) - adapter = ProfilePostsRecyclerViewAdapter() - binding.profilePostsRecyclerView.adapter = adapter + feedFragmentBinding.profilePostsRecyclerView.layoutManager = GridLayoutManager(applicationContext, 3) +// adapter = ProfilePostsRecyclerViewAdapter() +// binding.profilePostsRecyclerView.adapter = adapter // Set profile according to given account val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account? setContent(account) - binding.profileRefreshLayout.setOnRefreshListener { + activityBinding.profileRefreshLayout.setOnRefreshListener { getAndSetAccount(account?.id ?: user!!.user_id) } } @@ -73,9 +78,10 @@ class ProfileActivity : BaseActivity() { } private fun setContent(account: Account?) { - if(account != null){ + if(account != null) { setViews(account) - setPosts(account) +// setPosts(account) + startFragment(account) } else { lifecycleScope.launchWhenResumed { val myAccount: Account = try { @@ -88,7 +94,8 @@ class ProfileActivity : BaseActivity() { } setViews(myAccount) // Populate profile page with user's posts - setPosts(myAccount) +// setPosts(myAccount) + startFragment(myAccount) } } @@ -99,9 +106,9 @@ class ProfileActivity : BaseActivity() { // On click open followers list - binding.nbFollowersTextView.setOnClickListener{ onClickFollowers(account) } + activityBinding.nbFollowersTextView.setOnClickListener{ onClickFollowers(account) } // On click open followers list - binding.nbFollowingTextView.setOnClickListener{ onClickFollowing(account) } + activityBinding.nbFollowingTextView.setOnClickListener{ onClickFollowing(account) } } private fun getAndSetAccount(id: String){ @@ -119,28 +126,28 @@ class ProfileActivity : BaseActivity() { } private fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){ - val motionLayout = binding.motionLayout + val motionLayout = activityBinding.motionLayout if(show){ motionLayout.transitionToEnd() } else { motionLayout.transitionToStart() } - binding.profileProgressBar.visibility = View.GONE - binding.profileRefreshLayout.isRefreshing = false + activityBinding.profileProgressBar.visibility = View.GONE + activityBinding.profileRefreshLayout.isRefreshing = false } /** * Populate profile page with user's data */ private fun setViews(account: Account) { - val profilePicture = binding.profilePictureImageView + val profilePicture = activityBinding.profilePictureImageView ImageConverter.setRoundImageFromURL( View(applicationContext), account.avatar, profilePicture ) - binding.descriptionTextView.text = parseHTMLText( + activityBinding.descriptionTextView.text = parseHTMLText( account.note ?: "", emptyList(), pixelfedAPI, applicationContext, "Bearer $accessToken", lifecycleScope @@ -148,49 +155,58 @@ class ProfileActivity : BaseActivity() { val displayName = account.getDisplayName() - binding.accountNameTextView.text = displayName + activityBinding.accountNameTextView.text = displayName supportActionBar?.title = displayName if(displayName != "@${account.acct}"){ supportActionBar?.subtitle = "@${account.acct}" } - binding.nbPostsTextView.text = applicationContext.getString(R.string.nb_posts) + activityBinding.nbPostsTextView.text = applicationContext.getString(R.string.nb_posts) .format(account.statuses_count.toString()) - binding.nbFollowersTextView.text = applicationContext.getString(R.string.nb_followers) + activityBinding.nbFollowersTextView.text = applicationContext.getString(R.string.nb_followers) .format(account.followers_count.toString()) - binding.nbFollowingTextView.text = applicationContext.getString(R.string.nb_following) + activityBinding.nbFollowingTextView.text = applicationContext.getString(R.string.nb_following) .format(account.following_count.toString()) } + private fun startFragment(account: Account) { + + val arguments = Bundle() + arguments.putSerializable(Account.ACCOUNT_ID_TAG, account.id) + postsFragment.arguments = arguments + + supportFragmentManager.beginTransaction().add(R.id.fragment_profile_feed, postsFragment).commit() + } + /** * Populate profile page with user's posts */ - private fun setPosts(account: Account) { - pixelfedAPI.accountPosts("Bearer $accessToken", account_id = account.id) - .enqueue(object : Callback> { - - override fun onFailure(call: Call>, t: Throwable) { - showError() - Log.e("ProfileActivity.Posts:", t.toString()) - } - - override fun onResponse( - call: Call>, - response: Response> - ) { - if (response.code() == 200) { - val statuses = response.body()!! - adapter.addPosts(statuses) - showError(show = false) - } else { - showError() - } - } - }) - } +// private fun setPosts(account: Account) { +// pixelfedAPI.accountPosts("Bearer $accessToken", account_id = account.id) +// .enqueue(object : Callback> { +// +// override fun onFailure(call: Call>, t: Throwable) { +// showError() +// Log.e("ProfileActivity.Posts:", t.toString()) +// } +// +// override fun onResponse( +// call: Call>, +// response: Response> +// ) { +// if (response.code() == 200) { +// val statuses = response.body()!! +// adapter.addPosts(statuses) +// showError(show = false) +// } else { +// showError() +// } +// } +// }) +// } private fun onClickEditButton() { val url = "$domain/settings/home" @@ -216,7 +232,7 @@ class ProfileActivity : BaseActivity() { private fun activateEditButton() { // Edit button redirects to Pixelfed's "edit account" page - binding.editButton.apply { + activityBinding.editButton.apply { visibility = View.VISIBLE setOnClickListener{ onClickEditButton() } } @@ -239,7 +255,7 @@ class ProfileActivity : BaseActivity() { } else { setOnClickFollow(account) } - binding.followButton.visibility = View.VISIBLE + activityBinding.followButton.visibility = View.VISIBLE } } catch (exception: IOException) { Log.e("FOLLOW ERROR", exception.toString()) @@ -257,7 +273,7 @@ class ProfileActivity : BaseActivity() { } private fun setOnClickFollow(account: Account) { - binding.followButton.apply { + activityBinding.followButton.apply { setText(R.string.follow) setOnClickListener { lifecycleScope.launchWhenResumed { @@ -282,7 +298,7 @@ class ProfileActivity : BaseActivity() { } private fun setOnClickUnfollow(account: Account) { - binding.followButton.apply { + activityBinding.followButton.apply { setText(R.string.unfollow) setOnClickListener { diff --git a/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt b/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt index 6a970f05..ce9a8d6e 100644 --- a/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt @@ -34,12 +34,17 @@ class ProfilePostsRecyclerViewAdapter: RecyclerView.Adapter 1){ + if(post.media_attachments?.size ?: 0 > 1) { holder.albumIcon.visibility = View.VISIBLE } else { holder.albumIcon.visibility = View.GONE @@ -55,6 +60,7 @@ class ProfilePostsRecyclerViewAdapter: RecyclerView.Adapter> + ) : Response> @GET("/api/v1/accounts/relationships") suspend fun checkRelationships( diff --git a/app/src/main/res/layout/fragment_profile_feed.xml b/app/src/main/res/layout/fragment_profile_feed.xml new file mode 100644 index 00000000..5c0249af --- /dev/null +++ b/app/src/main/res/layout/fragment_profile_feed.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be252cfc..745c905b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,5 +158,7 @@ No, cancel edit Switch to grid view Switch to carousel + + Hello blank fragment \ No newline at end of file