Merge branch 'bookmarks' into 'master'
Bookmarks and Profile tabs See merge request pixeldroid/PixelDroid!480
This commit is contained in:
commit
96c14ef289
|
@ -11,6 +11,7 @@ import android.net.Uri
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
@ -317,7 +318,41 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun bookmarkPost(api: PixelfedAPI, db: AppDatabase, menu: Menu, bookmarked: Boolean) : Boolean? {
|
||||||
|
//Call the api function
|
||||||
|
status?.id?.let { id ->
|
||||||
|
try {
|
||||||
|
if(bookmarked) {
|
||||||
|
api.bookmarkStatus(id)
|
||||||
|
} else {
|
||||||
|
api.undoBookmarkStatus(id)
|
||||||
|
}
|
||||||
|
val user = db.userDao().getActiveUser()!!
|
||||||
|
db.homePostDao().bookmarkStatus(id, user.user_id, user.instance_uri, bookmarked)
|
||||||
|
db.publicPostDao().bookmarkStatus(id, user.user_id, user.instance_uri, bookmarked)
|
||||||
|
|
||||||
|
menu.setGroupVisible(R.id.post_more_menu_group_bookmark, !bookmarked)
|
||||||
|
menu.setGroupVisible(R.id.post_more_menu_group_unbookmark, bookmarked)
|
||||||
|
return bookmarked
|
||||||
|
} catch (exception: HttpException) {
|
||||||
|
Toast.makeText(
|
||||||
|
binding.root.context,
|
||||||
|
binding.root.context.getString(R.string.bookmark_post_failed_error, exception.code()),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
Toast.makeText(
|
||||||
|
binding.root.context,
|
||||||
|
binding.root.context.getString(R.string.bookmark_post_failed_io_except),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun activateMoreButton(apiHolder: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
|
private fun activateMoreButton(apiHolder: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
|
||||||
|
var bookmarked: Boolean? = null
|
||||||
binding.statusMore.setOnClickListener {
|
binding.statusMore.setOnClickListener {
|
||||||
PopupMenu(it.context, it).apply {
|
PopupMenu(it.context, it).apply {
|
||||||
setOnMenuItemClickListener { item ->
|
setOnMenuItemClickListener { item ->
|
||||||
|
@ -341,6 +376,18 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.post_more_menu_bookmark -> {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
bookmarked = bookmarkPost(apiHolder.api ?: apiHolder.setToCurrentUser(), db, menu, true)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.post_more_menu_unbookmark -> {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
bookmarked = bookmarkPost(apiHolder.api ?: apiHolder.setToCurrentUser(), db, menu, false)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
R.id.post_more_menu_save_to_gallery -> {
|
R.id.post_more_menu_save_to_gallery -> {
|
||||||
Dexter.withContext(binding.root.context)
|
Dexter.withContext(binding.root.context)
|
||||||
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
@ -429,6 +476,11 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inflate(R.menu.post_more_menu)
|
inflate(R.menu.post_more_menu)
|
||||||
|
if(bookmarked == true || status?.bookmarked == true) {
|
||||||
|
menu.setGroupVisible(R.id.post_more_menu_group_bookmark, false)
|
||||||
|
} else if(bookmarked == false || status?.bookmarked != true) {
|
||||||
|
menu.setGroupVisible(R.id.post_more_menu_group_unbookmark, false)
|
||||||
|
}
|
||||||
if(status?.media_attachments.isNullOrEmpty()) {
|
if(status?.media_attachments.isNullOrEmpty()) {
|
||||||
//make sure to disable image-related things if there aren't any
|
//make sure to disable image-related things if there aren't any
|
||||||
menu.setGroupVisible(R.id.post_more_group_picture, false)
|
menu.setGroupVisible(R.id.post_more_group_picture, false)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.LoadStateAdapter
|
import androidx.paging.LoadStateAdapter
|
||||||
import androidx.paging.PagingDataAdapter
|
import androidx.paging.PagingDataAdapter
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
@ -21,6 +22,7 @@ import org.pixeldroid.app.databinding.ErrorLayoutBinding
|
||||||
import org.pixeldroid.app.databinding.LoadStateFooterViewItemBinding
|
import org.pixeldroid.app.databinding.LoadStateFooterViewItemBinding
|
||||||
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
|
import org.pixeldroid.app.posts.feeds.uncachedFeeds.FeedViewModel
|
||||||
import org.pixeldroid.app.utils.api.objects.FeedContent
|
import org.pixeldroid.app.utils.api.objects.FeedContent
|
||||||
|
import org.pixeldroid.app.utils.api.objects.Status
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,3 +171,12 @@ class ReposLoadStateViewHolder(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val UIMODEL_STATUS_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.id == newItem.id
|
||||||
|
}
|
|
@ -8,10 +8,10 @@ import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.paging.ExperimentalPagingApi
|
import androidx.paging.ExperimentalPagingApi
|
||||||
import androidx.paging.PagingDataAdapter
|
import androidx.paging.PagingDataAdapter
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.posts.StatusViewHolder
|
import org.pixeldroid.app.posts.StatusViewHolder
|
||||||
|
import org.pixeldroid.app.posts.feeds.UIMODEL_STATUS_COMPARATOR
|
||||||
import org.pixeldroid.app.posts.feeds.uncachedFeeds.hashtags.HashTagContentRepository
|
import org.pixeldroid.app.posts.feeds.uncachedFeeds.hashtags.HashTagContentRepository
|
||||||
import org.pixeldroid.app.posts.feeds.uncachedFeeds.search.SearchContentRepository
|
import org.pixeldroid.app.posts.feeds.uncachedFeeds.search.SearchContentRepository
|
||||||
import org.pixeldroid.app.utils.api.objects.Results
|
import org.pixeldroid.app.utils.api.objects.Results
|
||||||
|
@ -74,16 +74,8 @@ class UncachedPostsFragment : UncachedFeedFragment<Status>() {
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>) : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
|
inner class PostsAdapter(private val displayDimensionsInPx: Pair<Int, Int>)
|
||||||
object : DiffUtil.ItemCallback<Status>() {
|
: PagingDataAdapter<Status, RecyclerView.ViewHolder>(UIMODEL_STATUS_COMPARATOR) {
|
||||||
override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean {
|
|
||||||
return oldItem.id == newItem.id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean =
|
|
||||||
oldItem.id == newItem.id
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return StatusViewHolder.create(parent)
|
return StatusViewHolder.create(parent)
|
||||||
|
|
|
@ -13,7 +13,8 @@ import javax.inject.Inject
|
||||||
class ProfileContentRepository @ExperimentalPagingApi
|
class ProfileContentRepository @ExperimentalPagingApi
|
||||||
@Inject constructor(
|
@Inject constructor(
|
||||||
private val api: PixelfedAPI,
|
private val api: PixelfedAPI,
|
||||||
private val accountId: String
|
private val accountId: String,
|
||||||
|
private val bookmarks: Boolean
|
||||||
) : UncachedContentRepository<Status> {
|
) : UncachedContentRepository<Status> {
|
||||||
override fun getStream(): Flow<PagingData<Status>> {
|
override fun getStream(): Flow<PagingData<Status>> {
|
||||||
return Pager(
|
return Pager(
|
||||||
|
@ -21,7 +22,7 @@ class ProfileContentRepository @ExperimentalPagingApi
|
||||||
initialLoadSize = NETWORK_PAGE_SIZE,
|
initialLoadSize = NETWORK_PAGE_SIZE,
|
||||||
pageSize = NETWORK_PAGE_SIZE),
|
pageSize = NETWORK_PAGE_SIZE),
|
||||||
pagingSourceFactory = {
|
pagingSourceFactory = {
|
||||||
ProfilePagingSource(api, accountId)
|
ProfilePagingSource(api, accountId, bookmarks)
|
||||||
}
|
}
|
||||||
).flow
|
).flow
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,16 +9,25 @@ import java.io.IOException
|
||||||
|
|
||||||
class ProfilePagingSource(
|
class ProfilePagingSource(
|
||||||
private val api: PixelfedAPI,
|
private val api: PixelfedAPI,
|
||||||
private val accountId: String
|
private val accountId: String,
|
||||||
|
private val bookmarks: Boolean
|
||||||
) : PagingSource<String, Status>() {
|
) : PagingSource<String, Status>() {
|
||||||
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
|
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
|
||||||
val position = params.key
|
val position = params.key
|
||||||
return try {
|
return try {
|
||||||
val posts = api.accountPosts(
|
val posts =
|
||||||
account_id = accountId,
|
if(bookmarks) {
|
||||||
max_id = position,
|
api.bookmarks(
|
||||||
limit = params.loadSize
|
limit = params.loadSize,
|
||||||
)
|
max_id = position
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
api.accountPosts(
|
||||||
|
account_id = accountId,
|
||||||
|
max_id = position,
|
||||||
|
limit = params.loadSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val nextKey = posts.lastOrNull()?.id
|
val nextKey = posts.lastOrNull()?.id
|
||||||
|
|
||||||
|
|
|
@ -10,28 +10,20 @@ import android.widget.ImageView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.fragment.app.Fragment
|
||||||
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.GridLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.Job
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.pixeldroid.app.R
|
import org.pixeldroid.app.R
|
||||||
import org.pixeldroid.app.databinding.ActivityProfileBinding
|
import org.pixeldroid.app.databinding.ActivityProfileBinding
|
||||||
import org.pixeldroid.app.databinding.FragmentProfilePostsBinding
|
import org.pixeldroid.app.databinding.FragmentProfilePostsBinding
|
||||||
import org.pixeldroid.app.posts.PostActivity
|
import org.pixeldroid.app.posts.PostActivity
|
||||||
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.UncachedContentRepository
|
|
||||||
import org.pixeldroid.app.posts.feeds.uncachedFeeds.profile.ProfileContentRepository
|
|
||||||
import org.pixeldroid.app.posts.parseHTMLText
|
import org.pixeldroid.app.posts.parseHTMLText
|
||||||
import org.pixeldroid.app.utils.*
|
import org.pixeldroid.app.utils.*
|
||||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||||
|
@ -47,13 +39,9 @@ class ProfileActivity : BaseThemedWithBarActivity() {
|
||||||
private lateinit var domain : String
|
private lateinit var domain : String
|
||||||
private lateinit var accountId : String
|
private lateinit var accountId : String
|
||||||
private lateinit var binding: ActivityProfileBinding
|
private lateinit var binding: ActivityProfileBinding
|
||||||
private lateinit var profileAdapter: PagingDataAdapter<Status, RecyclerView.ViewHolder>
|
|
||||||
private lateinit var viewModel: FeedViewModel<Status>
|
|
||||||
|
|
||||||
private var user: UserDatabaseEntity? = null
|
private var user: UserDatabaseEntity? = null
|
||||||
private var job: Job? = null
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPagingApi::class)
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityProfileBinding.inflate(layoutInflater)
|
binding = ActivityProfileBinding.inflate(layoutInflater)
|
||||||
|
@ -69,46 +57,82 @@ class ProfileActivity : BaseThemedWithBarActivity() {
|
||||||
val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account?
|
val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account?
|
||||||
accountId = account?.id ?: user!!.user_id
|
accountId = account?.id ?: user!!.user_id
|
||||||
|
|
||||||
// get the view model
|
val tabs = createProfileTabs(account)
|
||||||
@Suppress("UNCHECKED_CAST")
|
setupTabs(tabs)
|
||||||
viewModel = ViewModelProvider(this, ProfileViewModelFactory(
|
|
||||||
ProfileContentRepository(
|
|
||||||
apiHolder.setToCurrentUser(),
|
|
||||||
accountId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)[FeedViewModel::class.java] as FeedViewModel<Status>
|
|
||||||
|
|
||||||
profileAdapter = ProfilePostsAdapter()
|
|
||||||
initAdapter(binding.profileProgressBar, binding.profileRefreshLayout,
|
|
||||||
binding.profilePostsRecyclerView, binding.motionLayout, binding.errorLayout,
|
|
||||||
profileAdapter)
|
|
||||||
|
|
||||||
binding.profilePostsRecyclerView.layoutManager = GridLayoutManager(this, 3)
|
|
||||||
|
|
||||||
binding.profileRefreshLayout.setOnRefreshListener {
|
|
||||||
setContent(account)
|
|
||||||
profileAdapter.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent(account)
|
setContent(account)
|
||||||
job = launch(job, lifecycleScope, viewModel, profileAdapter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun createProfileTabs(account: Account?): Array<Fragment>{
|
||||||
* Shows or hides the error in the profile
|
|
||||||
*/
|
val profileFeedFragment = ProfileFeedFragment()
|
||||||
private fun showError(errorText: String = getString(R.string.profile_error), show: Boolean = true){
|
val argumentsFeed = Bundle().apply {
|
||||||
if(show){
|
putSerializable(Account.ACCOUNT_TAG, account)
|
||||||
binding.profileProgressBar.visibility = View.GONE
|
putSerializable(ProfileFeedFragment.PROFILE_GRID, false)
|
||||||
binding.motionLayout.transitionToEnd()
|
putSerializable(ProfileFeedFragment.BOOKMARKS, false)
|
||||||
binding.errorLayout.errorText.text = errorText
|
|
||||||
} else if(binding.motionLayout.progress == 1F) {
|
|
||||||
binding.motionLayout.transitionToStart()
|
|
||||||
}
|
}
|
||||||
binding.profileRefreshLayout.isRefreshing = false
|
profileFeedFragment.arguments = argumentsFeed
|
||||||
|
|
||||||
|
val profileGridFragment = ProfileFeedFragment()
|
||||||
|
val argumentsGrid = Bundle().apply {
|
||||||
|
putSerializable(Account.ACCOUNT_TAG, account)
|
||||||
|
putSerializable(ProfileFeedFragment.PROFILE_GRID, true)
|
||||||
|
putSerializable(ProfileFeedFragment.BOOKMARKS, false)
|
||||||
|
}
|
||||||
|
profileGridFragment.arguments = argumentsGrid
|
||||||
|
|
||||||
|
// If we are viewing our own account, show bookmarks
|
||||||
|
if(account == null || account.id == user?.user_id) {
|
||||||
|
val profileBookmarksFragment = ProfileFeedFragment()
|
||||||
|
val argumentsBookmarks = Bundle().apply {
|
||||||
|
putSerializable(Account.ACCOUNT_TAG, account)
|
||||||
|
putSerializable(ProfileFeedFragment.PROFILE_GRID, true)
|
||||||
|
putSerializable(ProfileFeedFragment.BOOKMARKS, true)
|
||||||
|
}
|
||||||
|
profileBookmarksFragment.arguments = argumentsBookmarks
|
||||||
|
return arrayOf(
|
||||||
|
profileGridFragment,
|
||||||
|
profileFeedFragment,
|
||||||
|
profileBookmarksFragment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return arrayOf(
|
||||||
|
profileGridFragment,
|
||||||
|
profileFeedFragment
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupTabs(
|
||||||
|
tabs: Array<Fragment>
|
||||||
|
){
|
||||||
|
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
return tabs[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return tabs.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TabLayoutMediator(binding.profileTabs, binding.viewPager) { tab, position ->
|
||||||
|
tab.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_UNLABELED
|
||||||
|
when (position) {
|
||||||
|
0 -> {
|
||||||
|
tab.setText("Grid view")
|
||||||
|
tab.setIcon(R.drawable.grid_on_black_24dp)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
tab.setText("Feed view")
|
||||||
|
tab.setIcon(R.drawable.feed_view)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
tab.setText("Bookmarks")
|
||||||
|
tab.setIcon(R.drawable.bookmark)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.attach()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun setContent(account: Account?) {
|
private fun setContent(account: Account?) {
|
||||||
if(account != null) {
|
if(account != null) {
|
||||||
setViews(account)
|
setViews(account)
|
||||||
|
@ -120,9 +144,17 @@ class ProfileActivity : BaseThemedWithBarActivity() {
|
||||||
api.verifyCredentials()
|
api.verifyCredentials()
|
||||||
} catch (exception: IOException) {
|
} catch (exception: IOException) {
|
||||||
Log.e("ProfileActivity:", exception.toString())
|
Log.e("ProfileActivity:", exception.toString())
|
||||||
return@launchWhenResumed showError()
|
Toast.makeText(
|
||||||
|
applicationContext, "Could not get your profile",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
return@launchWhenResumed
|
||||||
} catch (exception: HttpException) {
|
} catch (exception: HttpException) {
|
||||||
return@launchWhenResumed showError()
|
Toast.makeText(
|
||||||
|
applicationContext, "Could not get your profile",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
return@launchWhenResumed
|
||||||
}
|
}
|
||||||
setViews(myAccount)
|
setViews(myAccount)
|
||||||
}
|
}
|
||||||
|
@ -325,107 +357,3 @@ class ProfileActivity : BaseThemedWithBarActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ProfileViewModelFactory @ExperimentalPagingApi constructor(
|
|
||||||
private val searchContentRepository: UncachedContentRepository<Status>
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
if (modelClass.isAssignableFrom(FeedViewModel::class.java)) {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
return FeedViewModel(searchContentRepository) as T
|
|
||||||
}
|
|
||||||
throw IllegalArgumentException("Unknown ViewModel class")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerView.ViewHolder(binding.root) {
|
|
||||||
private val postPreview: ImageView = binding.postPreview
|
|
||||||
private val albumIcon: ImageView = binding.albumIcon
|
|
||||||
private val videoIcon: ImageView = binding.videoIcon
|
|
||||||
|
|
||||||
fun bind(post: Status) {
|
|
||||||
|
|
||||||
if ((post.media_attachments?.size ?: 0) == 0){
|
|
||||||
//No media in this post, so put a little icon there
|
|
||||||
postPreview.scaleX = 0.3f
|
|
||||||
postPreview.scaleY = 0.3f
|
|
||||||
Glide.with(postPreview).load(R.drawable.ic_comment_empty).into(postPreview)
|
|
||||||
albumIcon.visibility = View.GONE
|
|
||||||
videoIcon.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
postPreview.scaleX = 1f
|
|
||||||
postPreview.scaleY = 1f
|
|
||||||
if (post.sensitive != false) {
|
|
||||||
Glide.with(postPreview)
|
|
||||||
.load(post.media_attachments?.firstOrNull()?.blurhash?.let {
|
|
||||||
BlurHashDecoder.blurHashBitmap(itemView.resources, it, 32, 32)
|
|
||||||
}
|
|
||||||
).placeholder(R.drawable.ic_sensitive).apply(RequestOptions().centerCrop())
|
|
||||||
.into(postPreview)
|
|
||||||
} else {
|
|
||||||
setSquareImageFromURL(postPreview,
|
|
||||||
post.getPostPreviewURL(),
|
|
||||||
postPreview,
|
|
||||||
post.media_attachments?.firstOrNull()?.blurhash)
|
|
||||||
}
|
|
||||||
if ((post.media_attachments?.size ?: 0) > 1) {
|
|
||||||
albumIcon.visibility = View.VISIBLE
|
|
||||||
videoIcon.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
albumIcon.visibility = View.GONE
|
|
||||||
if (post.media_attachments?.getOrNull(0)?.type == Attachment.AttachmentType.video) {
|
|
||||||
videoIcon.visibility = View.VISIBLE
|
|
||||||
} else videoIcon.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): ProfilePostsViewHolder {
|
|
||||||
val itemBinding = FragmentProfilePostsBinding.inflate(
|
|
||||||
LayoutInflater.from(parent.context), parent, false
|
|
||||||
)
|
|
||||||
return ProfilePostsViewHolder(itemBinding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ProfilePostsAdapter : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
|
|
||||||
UIMODEL_COMPARATOR
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return ProfilePostsViewHolder.create(parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
val post = getItem(position)
|
|
||||||
|
|
||||||
post?.let {
|
|
||||||
(holder as ProfilePostsViewHolder).bind(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
package org.pixeldroid.app.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.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.paging.ExperimentalPagingApi
|
||||||
|
import androidx.paging.PagingDataAdapter
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import org.pixeldroid.app.R
|
||||||
|
import org.pixeldroid.app.databinding.FragmentProfilePostsBinding
|
||||||
|
import org.pixeldroid.app.posts.PostActivity
|
||||||
|
import org.pixeldroid.app.posts.StatusViewHolder
|
||||||
|
import org.pixeldroid.app.posts.feeds.UIMODEL_STATUS_COMPARATOR
|
||||||
|
import org.pixeldroid.app.posts.feeds.uncachedFeeds.*
|
||||||
|
import org.pixeldroid.app.posts.feeds.uncachedFeeds.profile.ProfileContentRepository
|
||||||
|
import org.pixeldroid.app.utils.BlurHashDecoder
|
||||||
|
import org.pixeldroid.app.utils.api.objects.Account
|
||||||
|
import org.pixeldroid.app.utils.api.objects.Attachment
|
||||||
|
import org.pixeldroid.app.utils.api.objects.Status
|
||||||
|
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||||
|
import org.pixeldroid.app.utils.displayDimensionsInPx
|
||||||
|
import org.pixeldroid.app.utils.setSquareImageFromURL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment to show a list of [Account]s, as a result of a search.
|
||||||
|
*/
|
||||||
|
class ProfileFeedFragment : UncachedFeedFragment<Status>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PROFILE_GRID = "ProfileGrid"
|
||||||
|
const val BOOKMARKS = "Bookmarks"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var accountId : String
|
||||||
|
private var user: UserDatabaseEntity? = null
|
||||||
|
private var grid: Boolean = true
|
||||||
|
private var bookmarks: Boolean = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
grid = arguments?.getSerializable(PROFILE_GRID) as Boolean
|
||||||
|
bookmarks = arguments?.getSerializable(BOOKMARKS) as Boolean
|
||||||
|
adapter = ProfilePostsAdapter()
|
||||||
|
|
||||||
|
//get the currently active user
|
||||||
|
user = db.userDao().getActiveUser()
|
||||||
|
// Set profile according to given account
|
||||||
|
val account = arguments?.getSerializable(Account.ACCOUNT_TAG) as Account?
|
||||||
|
accountId = account?.id ?: user!!.user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
|
||||||
|
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
|
||||||
|
if(grid || bookmarks) {
|
||||||
|
binding.list.layoutManager = GridLayoutManager(context, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the view model
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
viewModel = ViewModelProvider(requireActivity(), ProfileViewModelFactory(
|
||||||
|
ProfileContentRepository(
|
||||||
|
apiHolder.setToCurrentUser(),
|
||||||
|
accountId,
|
||||||
|
bookmarks
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)[if(bookmarks) "Bookmarks" else "Profile", FeedViewModel::class.java] as FeedViewModel<Status>
|
||||||
|
|
||||||
|
launch()
|
||||||
|
initSearch()
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ProfilePostsAdapter() : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
|
||||||
|
UIMODEL_STATUS_COMPARATOR
|
||||||
|
) {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
return if(grid || bookmarks) {
|
||||||
|
ProfilePostsViewHolder.create(parent)
|
||||||
|
} else {
|
||||||
|
StatusViewHolder.create(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
val post = getItem(position)
|
||||||
|
|
||||||
|
post?.let {
|
||||||
|
if(grid || bookmarks) {
|
||||||
|
(holder as ProfilePostsViewHolder).bind(it)
|
||||||
|
} else {
|
||||||
|
(holder as StatusViewHolder).bind(it, apiHolder, db,
|
||||||
|
lifecycleScope, requireContext().displayDimensionsInPx())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
private val postPreview: ImageView = binding.postPreview
|
||||||
|
private val albumIcon: ImageView = binding.albumIcon
|
||||||
|
private val videoIcon: ImageView = binding.videoIcon
|
||||||
|
|
||||||
|
fun bind(post: Status) {
|
||||||
|
|
||||||
|
if ((post.media_attachments?.size ?: 0) == 0){
|
||||||
|
//No media in this post, so put a little icon there
|
||||||
|
postPreview.scaleX = 0.3f
|
||||||
|
postPreview.scaleY = 0.3f
|
||||||
|
Glide.with(postPreview).load(R.drawable.ic_comment_empty).into(postPreview)
|
||||||
|
albumIcon.visibility = View.GONE
|
||||||
|
videoIcon.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
postPreview.scaleX = 1f
|
||||||
|
postPreview.scaleY = 1f
|
||||||
|
if (post.sensitive != false) {
|
||||||
|
Glide.with(postPreview)
|
||||||
|
.load(post.media_attachments?.firstOrNull()?.blurhash?.let {
|
||||||
|
BlurHashDecoder.blurHashBitmap(itemView.resources, it, 32, 32)
|
||||||
|
}
|
||||||
|
).placeholder(R.drawable.ic_sensitive).apply(RequestOptions().centerCrop())
|
||||||
|
.into(postPreview)
|
||||||
|
} else {
|
||||||
|
setSquareImageFromURL(postPreview,
|
||||||
|
post.getPostPreviewURL(),
|
||||||
|
postPreview,
|
||||||
|
post.media_attachments?.firstOrNull()?.blurhash)
|
||||||
|
}
|
||||||
|
if ((post.media_attachments?.size ?: 0) > 1) {
|
||||||
|
albumIcon.visibility = View.VISIBLE
|
||||||
|
videoIcon.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
albumIcon.visibility = View.GONE
|
||||||
|
if (post.media_attachments?.getOrNull(0)?.type == Attachment.AttachmentType.video) {
|
||||||
|
videoIcon.visibility = View.VISIBLE
|
||||||
|
} else videoIcon.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): ProfilePostsViewHolder {
|
||||||
|
val itemBinding = FragmentProfilePostsBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context), parent, false
|
||||||
|
)
|
||||||
|
return ProfilePostsViewHolder(itemBinding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileViewModelFactory @ExperimentalPagingApi constructor(
|
||||||
|
private val searchContentRepository: UncachedContentRepository<Status>
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(FeedViewModel::class.java)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return FeedViewModel(searchContentRepository) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
}
|
|
@ -183,6 +183,25 @@ interface PixelfedAPI {
|
||||||
@Path("id") statusId: String,
|
@Path("id") statusId: String,
|
||||||
) : Status
|
) : Status
|
||||||
|
|
||||||
|
@GET("/api/v1/bookmarks")
|
||||||
|
suspend fun bookmarks(
|
||||||
|
@Query("limit") limit: Number? = null,
|
||||||
|
@Query("max_id") max_id: String? = null,
|
||||||
|
@Query("since_id") since_id: String? = null,
|
||||||
|
@Query("min_id") min_id: String? = null
|
||||||
|
) : List<Status>
|
||||||
|
|
||||||
|
@POST("/api/v1/statuses/{id}/bookmark")
|
||||||
|
suspend fun bookmarkStatus(
|
||||||
|
@Path("id") statusId: String
|
||||||
|
) : Status
|
||||||
|
|
||||||
|
@POST("/api/v1/statuses/{id}/unbookmark")
|
||||||
|
suspend fun undoBookmarkStatus(
|
||||||
|
@Path("id") statusId: String
|
||||||
|
) : Status
|
||||||
|
|
||||||
|
|
||||||
//Used in our case to retrieve comments for a given status
|
//Used in our case to retrieve comments for a given status
|
||||||
@GET("/api/v1/statuses/{id}/context")
|
@GET("/api/v1/statuses/{id}/context")
|
||||||
suspend fun statusComments(
|
suspend fun statusComments(
|
||||||
|
|
|
@ -21,8 +21,8 @@ interface UserDao {
|
||||||
@Update
|
@Update
|
||||||
fun updateUser(user: UserDatabaseEntity)
|
fun updateUser(user: UserDatabaseEntity)
|
||||||
|
|
||||||
@Query("UPDATE users SET accessToken = :accessToken, refreshToken = :refreshToken WHERE user_id = :id and instance_uri = :instance_uri")
|
@Query("UPDATE users SET accessToken = :accessToken, refreshToken = :refreshToken WHERE user_id = :id and instance_uri = :instanceUri")
|
||||||
fun updateAccessToken(accessToken: String, refreshToken: String, id: String, instance_uri: String)
|
fun updateAccessToken(accessToken: String, refreshToken: String, id: String, instanceUri: String)
|
||||||
|
|
||||||
@Query("SELECT * FROM users")
|
@Query("SELECT * FROM users")
|
||||||
fun getAll(): List<UserDatabaseEntity>
|
fun getAll(): List<UserDatabaseEntity>
|
||||||
|
@ -33,12 +33,12 @@ interface UserDao {
|
||||||
@Query("UPDATE users SET isActive=0")
|
@Query("UPDATE users SET isActive=0")
|
||||||
fun deActivateActiveUsers()
|
fun deActivateActiveUsers()
|
||||||
|
|
||||||
@Query("UPDATE users SET isActive=1 WHERE user_id=:id AND instance_uri=:instance_uri")
|
@Query("UPDATE users SET isActive=1 WHERE user_id=:id AND instance_uri=:instanceUri")
|
||||||
fun activateUser(id: String, instance_uri: String)
|
fun activateUser(id: String, instanceUri: String)
|
||||||
|
|
||||||
@Query("DELETE FROM users WHERE isActive=1")
|
@Query("DELETE FROM users WHERE isActive=1")
|
||||||
fun deleteActiveUsers()
|
fun deleteActiveUsers()
|
||||||
|
|
||||||
@Query("SELECT * FROM users WHERE user_id=:id AND instance_uri=:instance_uri")
|
@Query("SELECT * FROM users WHERE user_id=:id AND instance_uri=:instanceUri")
|
||||||
fun getUserWithId(id: String, instance_uri: String): UserDatabaseEntity
|
fun getUserWithId(id: String, instanceUri: String): UserDatabaseEntity
|
||||||
}
|
}
|
|
@ -18,4 +18,7 @@ interface HomePostDao: FeedContentDao<HomeStatusDatabaseEntity> {
|
||||||
@Query("DELETE FROM homePosts WHERE user_id=:userId AND instance_uri=:instanceUri AND id=:id")
|
@Query("DELETE FROM homePosts WHERE user_id=:userId AND instance_uri=:instanceUri AND id=:id")
|
||||||
override suspend fun delete(id: String, userId: String, instanceUri: String)
|
override suspend fun delete(id: String, userId: String, instanceUri: String)
|
||||||
|
|
||||||
|
@Query("UPDATE homePosts SET bookmarked=:bookmarked WHERE user_id=:id AND instance_uri=:instanceUri AND id=:statusId")
|
||||||
|
fun bookmarkStatus(id: String, instanceUri: String, statusId: String, bookmarked: Boolean)
|
||||||
|
|
||||||
}
|
}
|
|
@ -18,4 +18,7 @@ interface PublicPostDao: FeedContentDao<PublicFeedStatusDatabaseEntity> {
|
||||||
@Query("DELETE FROM publicPosts WHERE user_id=:userId AND instance_uri=:instanceUri AND id=:id")
|
@Query("DELETE FROM publicPosts WHERE user_id=:userId AND instance_uri=:instanceUri AND id=:id")
|
||||||
override suspend fun delete(id: String, userId: String, instanceUri: String)
|
override suspend fun delete(id: String, userId: String, instanceUri: String)
|
||||||
|
|
||||||
|
@Query("UPDATE homePosts SET bookmarked=:bookmarked WHERE user_id=:id AND instance_uri=:instanceUri AND id=:statusId")
|
||||||
|
fun bookmarkStatus(id: String, instanceUri: String, statusId: String, bookmarked: Boolean)
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M2,21h19v-3H2v3zM20,8H3c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h17c0.55,0 1,-0.45 1,-1V9c0,-0.55 -0.45,-1 -1,-1zM2,3v3h19V3H2z"/>
|
||||||
|
</vector>
|
|
@ -46,7 +46,6 @@
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/default_nposts"
|
android:text="@string/default_nposts"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/descriptionTextView" />
|
app:layout_constraintTop_toBottomOf="@+id/descriptionTextView" />
|
||||||
|
|
||||||
|
@ -89,7 +88,6 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/profilePictureImageView" />
|
app:layout_constraintTop_toBottomOf="@id/profilePictureImageView" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/descriptionTextView"
|
android:id="@+id/descriptionTextView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -123,49 +121,26 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
|
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
|
||||||
app:layout_constraintTop_toTopOf="@+id/profilePictureImageView" />
|
app:layout_constraintTop_toTopOf="@+id/profilePictureImageView" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/profileTabs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/nbPostsTextView"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/profileProgressBar"
|
|
||||||
style="?android:attr/progressBarStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginTop="16dp" />
|
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/profileRefreshLayout"
|
android:id="@+id/view_pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
<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">
|
|
||||||
|
|
||||||
<include
|
|
||||||
android:id="@+id/errorLayout"
|
|
||||||
layout="@layout/error_layout"
|
|
||||||
tools:layout_editor_absoluteX="50dp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/profilePostsRecyclerView"
|
|
||||||
android:visibility="visible"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layoutManager="LinearLayoutManager"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/errorLayout"
|
|
||||||
tools:listitem="@layout/fragment_profile_posts" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -9,6 +9,15 @@
|
||||||
<item android:id="@+id/post_more_menu_share_link"
|
<item android:id="@+id/post_more_menu_share_link"
|
||||||
android:title="@string/share_link" />
|
android:title="@string/share_link" />
|
||||||
|
|
||||||
|
<group android:id="@+id/post_more_menu_group_bookmark">
|
||||||
|
<item android:id="@+id/post_more_menu_bookmark"
|
||||||
|
android:title="@string/bookmark" />
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group android:id="@+id/post_more_menu_group_unbookmark">
|
||||||
|
<item android:id="@+id/post_more_menu_unbookmark"
|
||||||
|
android:title="@string/unbookmark" />
|
||||||
|
</group>
|
||||||
|
|
||||||
<!-- Group that should only be shown if there are pictures in the post -->
|
<!-- Group that should only be shown if there are pictures in the post -->
|
||||||
<group android:id="@+id/post_more_group_picture">
|
<group android:id="@+id/post_more_group_picture">
|
||||||
|
|
|
@ -232,6 +232,8 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||||
<string name="search_empty_error">Search query can\'t be empty</string>
|
<string name="search_empty_error">Search query can\'t be empty</string>
|
||||||
<string name="status_more_options">More options</string>
|
<string name="status_more_options">More options</string>
|
||||||
<string name="report">Report</string>
|
<string name="report">Report</string>
|
||||||
|
<string name="bookmark">Bookmark</string>
|
||||||
|
<string name="unbookmark">Unbookmark</string>
|
||||||
<string name="share_link">Share Link</string>
|
<string name="share_link">Share Link</string>
|
||||||
<string name="optional_report_comment">Optional message for mods/admins</string>
|
<string name="optional_report_comment">Optional message for mods/admins</string>
|
||||||
<string name="report_target">Report @%1$s\'s post</string>
|
<string name="report_target">Report @%1$s\'s post</string>
|
||||||
|
@ -252,6 +254,8 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||||
<string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string>
|
<string name="mascot_description">Image showing a red panda, Pixelfed\'s mascot, using a phone</string>
|
||||||
<string name="delete_post_failed_error">Could not delete the post, error %1$d</string>
|
<string name="delete_post_failed_error">Could not delete the post, error %1$d</string>
|
||||||
<string name="delete_post_failed_io_except">Could not delete the post, check your connection?</string>
|
<string name="delete_post_failed_io_except">Could not delete the post, check your connection?</string>
|
||||||
|
<string name="bookmark_post_failed_error">Could not (un)bookmark the post, error %1$d</string>
|
||||||
|
<string name="bookmark_post_failed_io_except">Could not (un)bookmark the post, check your connection?</string>
|
||||||
|
|
||||||
<!-- Error message when a selected file can not be found -->
|
<!-- Error message when a selected file can not be found -->
|
||||||
<string name="file_not_found">File %1$s was not found</string>
|
<string name="file_not_found">File %1$s was not found</string>
|
||||||
|
|
Loading…
Reference in New Issue