Merge branch 'collections' into 'master'
Implement collections See merge request pixeldroid/PixelDroid!492
This commit is contained in:
commit
5b3870b80d
|
@ -86,6 +86,7 @@
|
|||
android:name=".profile.ProfileActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
tools:ignore="LockedOrientationActivity" />
|
||||
<activity android:name=".profile.CollectionActivity"/>
|
||||
<activity
|
||||
android:name=".settings.SettingsActivity"
|
||||
android:label="@string/title_activity_settings2"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package org.pixeldroid.app.posts.feeds.uncachedFeeds.profile
|
||||
|
||||
import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedContentRepository
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.pixeldroid.app.utils.api.objects.Collection
|
||||
import javax.inject.Inject
|
||||
|
||||
class CollectionsContentRepository @ExperimentalPagingApi
|
||||
@Inject constructor(
|
||||
private val api: PixelfedAPI,
|
||||
private val accountId: String,
|
||||
) : UncachedContentRepository<Collection> {
|
||||
override fun getStream(): Flow<PagingData<Collection>> {
|
||||
return Pager(
|
||||
config = PagingConfig(
|
||||
initialLoadSize = NETWORK_PAGE_SIZE,
|
||||
pageSize = NETWORK_PAGE_SIZE),
|
||||
pagingSourceFactory = {
|
||||
CollectionsPagingSource(api, accountId)
|
||||
}
|
||||
).flow
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NETWORK_PAGE_SIZE = 20
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.pixeldroid.app.posts.feeds.uncachedFeeds.profile
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import org.pixeldroid.app.utils.api.objects.Collection
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
class CollectionsPagingSource(
|
||||
private val api: PixelfedAPI,
|
||||
private val accountId: String,
|
||||
) : PagingSource<String, Collection>() {
|
||||
override suspend fun load(params: LoadParams<String>): LoadResult<String, Collection> {
|
||||
return try {
|
||||
val posts = api.accountCollections(accountId)
|
||||
|
||||
LoadResult.Page(
|
||||
data = posts,
|
||||
prevKey = null,
|
||||
//TODO pagination. For now, don't paginate
|
||||
nextKey = null
|
||||
)
|
||||
} catch (exception: HttpException) {
|
||||
LoadResult.Error(exception)
|
||||
} catch (exception: IOException) {
|
||||
LoadResult.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRefreshKey(state: PagingState<String, Collection>): String? = null
|
||||
}
|
|
@ -14,7 +14,8 @@ class ProfileContentRepository @ExperimentalPagingApi
|
|||
@Inject constructor(
|
||||
private val api: PixelfedAPI,
|
||||
private val accountId: String,
|
||||
private val bookmarks: Boolean
|
||||
private val bookmarks: Boolean,
|
||||
private val collectionId: String?,
|
||||
) : UncachedContentRepository<Status> {
|
||||
override fun getStream(): Flow<PagingData<Status>> {
|
||||
return Pager(
|
||||
|
@ -22,8 +23,9 @@ class ProfileContentRepository @ExperimentalPagingApi
|
|||
initialLoadSize = NETWORK_PAGE_SIZE,
|
||||
pageSize = NETWORK_PAGE_SIZE),
|
||||
pagingSourceFactory = {
|
||||
ProfilePagingSource(api, accountId, bookmarks)
|
||||
}
|
||||
ProfilePagingSource(api, accountId, bookmarks, collectionId)
|
||||
},
|
||||
initialKey = if(collectionId != null) "1" else null
|
||||
).flow
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,20 @@ import java.io.IOException
|
|||
class ProfilePagingSource(
|
||||
private val api: PixelfedAPI,
|
||||
private val accountId: String,
|
||||
private val bookmarks: Boolean
|
||||
private val bookmarks: Boolean,
|
||||
private val collectionId: String?,
|
||||
) : PagingSource<String, Status>() {
|
||||
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
|
||||
val position = params.key
|
||||
return try {
|
||||
val posts =
|
||||
if(bookmarks) {
|
||||
if(collectionId != null){
|
||||
api.collectionItems(
|
||||
collectionId,
|
||||
page = position
|
||||
)
|
||||
}
|
||||
else if(bookmarks) {
|
||||
api.bookmarks(
|
||||
limit = params.loadSize,
|
||||
max_id = position
|
||||
|
@ -34,7 +41,9 @@ class ProfilePagingSource(
|
|||
LoadResult.Page(
|
||||
data = posts,
|
||||
prevKey = null,
|
||||
nextKey = if(nextKey == position) null else nextKey
|
||||
nextKey = if(collectionId != null ) {
|
||||
if(posts.isEmpty()) null else (params.key?.toIntOrNull()?.plus(1))?.toString()
|
||||
} else if(nextKey == position) null else nextKey
|
||||
)
|
||||
} catch (exception: HttpException) {
|
||||
LoadResult.Error(exception)
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
package org.pixeldroid.app.profile
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.launch
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.databinding.ActivityCollectionBinding
|
||||
import org.pixeldroid.app.profile.ProfileFeedFragment.Companion.COLLECTION
|
||||
import org.pixeldroid.app.profile.ProfileFeedFragment.Companion.COLLECTION_ID
|
||||
import org.pixeldroid.app.utils.BaseThemedWithBarActivity
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import org.pixeldroid.app.utils.api.objects.Collection
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
class CollectionActivity : BaseThemedWithBarActivity() {
|
||||
private lateinit var binding: ActivityCollectionBinding
|
||||
|
||||
private lateinit var collection: Collection
|
||||
private var addCollection: Boolean = false
|
||||
private var deleteFromCollection: Boolean = false
|
||||
|
||||
companion object {
|
||||
const val COLLECTION_TAG = "Collection"
|
||||
const val ADD_COLLECTION_TAG = "AddCollection"
|
||||
const val DELETE_FROM_COLLECTION_TAG = "DeleteFromCollection"
|
||||
const val DELETE_FROM_COLLECTION_RESULT = "DeleteFromCollectionResult"
|
||||
const val ADD_TO_COLLECTION_RESULT = "AddToCollectionResult"
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityCollectionBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
collection = intent.getSerializableExtra(COLLECTION_TAG) as Collection
|
||||
|
||||
|
||||
addCollection = intent.getBooleanExtra(ADD_COLLECTION_TAG, false)
|
||||
deleteFromCollection = intent.getBooleanExtra(DELETE_FROM_COLLECTION_TAG, false)
|
||||
|
||||
val addedResult = intent.getBooleanExtra(ADD_TO_COLLECTION_RESULT, false)
|
||||
val deletedResult = intent.getBooleanExtra(DELETE_FROM_COLLECTION_RESULT, false)
|
||||
|
||||
if(addedResult)
|
||||
Snackbar.make(
|
||||
binding.root, getString(R.string.added_post_to_collection),
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
else if (deletedResult) Snackbar.make(
|
||||
binding.root, getString(R.string.removed_post_from_collection),
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
|
||||
supportActionBar?.title = if(addCollection) getString(R.string.add_to_collection)
|
||||
else if(deleteFromCollection) getString(R.string.delete_from_collection)
|
||||
else getString(R.string.collection_title).format(collection.username)
|
||||
|
||||
val collectionFragment = ProfileFeedFragment()
|
||||
collectionFragment.arguments = Bundle().apply {
|
||||
putBoolean(COLLECTION, true)
|
||||
putString(COLLECTION_ID, collection.id)
|
||||
putSerializable(COLLECTION, collection)
|
||||
if(addCollection) putBoolean(ADD_COLLECTION_TAG, true)
|
||||
else if (deleteFromCollection) putBoolean(DELETE_FROM_COLLECTION_TAG, true)
|
||||
}
|
||||
|
||||
supportFragmentManager.beginTransaction()
|
||||
.add(R.id.collectionFragment, collectionFragment).commit()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val userId = db.userDao().getActiveUser()?.user_id
|
||||
|
||||
// Only show options for editing a collection if it's the user's collection
|
||||
if(!(addCollection || deleteFromCollection) && userId != null && collection.pid == userId) {
|
||||
val inflater: MenuInflater = menuInflater
|
||||
inflater.inflate(R.menu.collection_menu, menu)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
// Relaunch same activity, to avoid duplicates in history
|
||||
super.onNewIntent(intent);
|
||||
finish();
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.delete_collection -> {
|
||||
AlertDialog.Builder(this).apply {
|
||||
setMessage(R.string.delete_collection_warning)
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
// Delete collection
|
||||
lifecycleScope.launch {
|
||||
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
try {
|
||||
api.deleteCollection(collection.id)
|
||||
// Deleted, exit activity
|
||||
finish()
|
||||
} catch (exception: IOException) {
|
||||
TODO("Error")
|
||||
} catch (exception: HttpException) {
|
||||
TODO("Error")
|
||||
}
|
||||
}
|
||||
}
|
||||
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
}.show()
|
||||
true
|
||||
}
|
||||
R.id.add_post_collection -> {
|
||||
val intent = Intent(this, CollectionActivity::class.java)
|
||||
intent.putExtra(COLLECTION_TAG, collection)
|
||||
intent.putExtra(ADD_COLLECTION_TAG, true)
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
R.id.remove_post_collection -> {
|
||||
val intent = Intent(this, CollectionActivity::class.java)
|
||||
intent.putExtra(COLLECTION_TAG, collection)
|
||||
intent.putExtra(DELETE_FROM_COLLECTION_TAG, true)
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -65,40 +65,44 @@ class ProfileActivity : BaseThemedWithBarActivity() {
|
|||
private fun createProfileTabs(account: Account?): Array<Fragment>{
|
||||
|
||||
val profileFeedFragment = ProfileFeedFragment()
|
||||
val argumentsFeed = Bundle().apply {
|
||||
profileFeedFragment.arguments = Bundle().apply {
|
||||
putSerializable(Account.ACCOUNT_TAG, account)
|
||||
putSerializable(ProfileFeedFragment.PROFILE_GRID, false)
|
||||
putSerializable(ProfileFeedFragment.BOOKMARKS, false)
|
||||
}
|
||||
profileFeedFragment.arguments = argumentsFeed
|
||||
|
||||
val profileGridFragment = ProfileFeedFragment()
|
||||
val argumentsGrid = Bundle().apply {
|
||||
profileGridFragment.arguments = Bundle().apply {
|
||||
putSerializable(Account.ACCOUNT_TAG, account)
|
||||
putSerializable(ProfileFeedFragment.PROFILE_GRID, true)
|
||||
putSerializable(ProfileFeedFragment.BOOKMARKS, false)
|
||||
}
|
||||
profileGridFragment.arguments = argumentsGrid
|
||||
|
||||
val profileCollectionsFragment = ProfileFeedFragment()
|
||||
profileCollectionsFragment.arguments = Bundle().apply {
|
||||
putSerializable(Account.ACCOUNT_TAG, account)
|
||||
putSerializable(ProfileFeedFragment.PROFILE_GRID, true)
|
||||
putSerializable(ProfileFeedFragment.BOOKMARKS, false)
|
||||
putSerializable(ProfileFeedFragment.COLLECTIONS, true)
|
||||
}
|
||||
|
||||
val returnArray: Array<Fragment> = arrayOf(
|
||||
profileGridFragment,
|
||||
profileFeedFragment,
|
||||
profileCollectionsFragment
|
||||
)
|
||||
|
||||
// If we are viewing our own account, show bookmarks
|
||||
if(account == null || account.id == user?.user_id) {
|
||||
val profileBookmarksFragment = ProfileFeedFragment()
|
||||
val argumentsBookmarks = Bundle().apply {
|
||||
profileBookmarksFragment.arguments = 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 returnArray + profileBookmarksFragment
|
||||
}
|
||||
return arrayOf(
|
||||
profileGridFragment,
|
||||
profileFeedFragment
|
||||
)
|
||||
return returnArray
|
||||
}
|
||||
|
||||
private fun setupTabs(
|
||||
|
@ -117,15 +121,19 @@ class ProfileActivity : BaseThemedWithBarActivity() {
|
|||
tab.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_UNLABELED
|
||||
when (position) {
|
||||
0 -> {
|
||||
tab.setText("Grid view")
|
||||
tab.setText(R.string.grid_view)
|
||||
tab.setIcon(R.drawable.grid_on_black_24dp)
|
||||
}
|
||||
1 -> {
|
||||
tab.setText("Feed view")
|
||||
tab.setText(R.string.feed_view)
|
||||
tab.setIcon(R.drawable.feed_view)
|
||||
}
|
||||
2 -> {
|
||||
tab.setText("Bookmarks")
|
||||
tab.setText(R.string.collections)
|
||||
tab.setIcon(R.drawable.collections)
|
||||
}
|
||||
3 -> {
|
||||
tab.setText(R.string.bookmarks)
|
||||
tab.setIcon(R.drawable.bookmark)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,36 +6,55 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.launch
|
||||
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.CollectionsContentRepository
|
||||
import org.pixeldroid.app.posts.feeds.uncachedFeeds.profile.ProfileContentRepository
|
||||
import org.pixeldroid.app.profile.CollectionActivity.Companion.ADD_COLLECTION_TAG
|
||||
import org.pixeldroid.app.profile.CollectionActivity.Companion.ADD_TO_COLLECTION_RESULT
|
||||
import org.pixeldroid.app.profile.CollectionActivity.Companion.DELETE_FROM_COLLECTION_RESULT
|
||||
import org.pixeldroid.app.profile.CollectionActivity.Companion.DELETE_FROM_COLLECTION_TAG
|
||||
import org.pixeldroid.app.utils.BlurHashDecoder
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import org.pixeldroid.app.utils.api.objects.Account
|
||||
import org.pixeldroid.app.utils.api.objects.Attachment
|
||||
import org.pixeldroid.app.utils.api.objects.Collection
|
||||
import org.pixeldroid.app.utils.api.objects.FeedContent
|
||||
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.openUrl
|
||||
import org.pixeldroid.app.utils.setSquareImageFromURL
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Fragment to show a list of [Account]s, as a result of a search.
|
||||
*/
|
||||
class ProfileFeedFragment : UncachedFeedFragment<Status>() {
|
||||
class ProfileFeedFragment : UncachedFeedFragment<FeedContent>() {
|
||||
|
||||
companion object {
|
||||
// List of collections
|
||||
const val COLLECTIONS = "Collections"
|
||||
// Content of collection
|
||||
const val COLLECTION = "Collection"
|
||||
const val COLLECTION_ID = "CollectionId"
|
||||
const val PROFILE_GRID = "ProfileGrid"
|
||||
const val BOOKMARKS = "Bookmarks"
|
||||
}
|
||||
|
@ -44,12 +63,27 @@ class ProfileFeedFragment : UncachedFeedFragment<Status>() {
|
|||
private var user: UserDatabaseEntity? = null
|
||||
private var grid: Boolean = true
|
||||
private var bookmarks: Boolean = false
|
||||
private var collections: Boolean = false
|
||||
private var collection: Collection? = null
|
||||
private var addCollection: Boolean = false
|
||||
private var deleteFromCollection: Boolean = false
|
||||
private var collectionId: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
grid = arguments?.getSerializable(PROFILE_GRID) as Boolean
|
||||
bookmarks = arguments?.getSerializable(BOOKMARKS) as Boolean
|
||||
grid = arguments?.getBoolean(PROFILE_GRID, true) ?: true
|
||||
bookmarks = arguments?.getBoolean(BOOKMARKS) ?: false
|
||||
collections = arguments?.getBoolean(COLLECTIONS) ?: false
|
||||
collection = arguments?.getSerializable(COLLECTION) as? Collection
|
||||
addCollection = arguments?.getBoolean(ADD_COLLECTION_TAG) ?: false
|
||||
deleteFromCollection = arguments?.getBoolean(DELETE_FROM_COLLECTION_TAG) ?: false
|
||||
collectionId = arguments?.getString(COLLECTION_ID)
|
||||
if(addCollection){
|
||||
// We want the user's profile, set all the rest to false to be sure
|
||||
collections = false
|
||||
bookmarks = false
|
||||
}
|
||||
adapter = ProfilePostsAdapter()
|
||||
|
||||
//get the currently active user
|
||||
|
@ -67,20 +101,23 @@ class ProfileFeedFragment : UncachedFeedFragment<Status>() {
|
|||
|
||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
if(grid || bookmarks) {
|
||||
if(grid || bookmarks || collections || addCollection) {
|
||||
binding.list.layoutManager = GridLayoutManager(context, 3)
|
||||
}
|
||||
|
||||
// Get the view model
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
viewModel = ViewModelProvider(requireActivity(), ProfileViewModelFactory(
|
||||
ProfileContentRepository(
|
||||
(if(!collections) ProfileContentRepository(
|
||||
apiHolder.setToCurrentUser(),
|
||||
accountId,
|
||||
bookmarks
|
||||
bookmarks,
|
||||
if (addCollection) null else collectionId
|
||||
)
|
||||
else CollectionsContentRepository(apiHolder.setToCurrentUser(), accountId)) as UncachedContentRepository<FeedContent>
|
||||
)
|
||||
)[if(bookmarks) "Bookmarks" else "Profile", FeedViewModel::class.java] as FeedViewModel<Status>
|
||||
)[if (addCollection) "AddCollection" else if (collections) "Collections" else if(bookmarks) "Bookmarks" else "Profile",
|
||||
FeedViewModel::class.java] as FeedViewModel<FeedContent>
|
||||
|
||||
launch()
|
||||
initSearch()
|
||||
|
@ -88,29 +125,122 @@ class ProfileFeedFragment : UncachedFeedFragment<Status>() {
|
|||
return view
|
||||
}
|
||||
|
||||
inner class ProfilePostsAdapter() : PagingDataAdapter<Status, RecyclerView.ViewHolder>(
|
||||
UIMODEL_STATUS_COMPARATOR
|
||||
inner class ProfilePostsAdapter : PagingDataAdapter<FeedContent, RecyclerView.ViewHolder>(
|
||||
object : DiffUtil.ItemCallback<FeedContent>() {
|
||||
override fun areItemsTheSame(oldItem: FeedContent, newItem: FeedContent): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: FeedContent, newItem: FeedContent): Boolean =
|
||||
oldItem.id == newItem.id
|
||||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return if(grid || bookmarks) {
|
||||
ProfilePostsViewHolder.create(parent)
|
||||
} else {
|
||||
StatusViewHolder.create(parent)
|
||||
}
|
||||
return if(collections) {
|
||||
if (viewType == 1) {
|
||||
val view =
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.create_new_collection, parent, false)
|
||||
AddCollectionViewHolder(view)
|
||||
} else CollectionsViewHolder.create(parent)
|
||||
}
|
||||
else if(grid || bookmarks) {
|
||||
ProfilePostsViewHolder.create(parent)
|
||||
} else {
|
||||
StatusViewHolder.create(parent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if(position == 0 && user?.user_id == accountId) 1
|
||||
else 0
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if (collections && user?.user_id == accountId) {
|
||||
super.getItemCount() + 1
|
||||
} else super.getItemCount()
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val post = getItem(position)
|
||||
val post = if(collections && user?.user_id == accountId && position == 0) null else getItem(if(collections && user?.user_id == accountId) position - 1 else position)
|
||||
|
||||
post?.let {
|
||||
if(grid || bookmarks) {
|
||||
(holder as ProfilePostsViewHolder).bind(it)
|
||||
if(collections) {
|
||||
(holder as CollectionsViewHolder).bind(it as Collection)
|
||||
} else if(grid || bookmarks || addCollection) {
|
||||
(holder as ProfilePostsViewHolder).bind(
|
||||
it as Status,
|
||||
lifecycleScope,
|
||||
apiHolder.api ?: apiHolder.setToCurrentUser(),
|
||||
addCollection,
|
||||
collection,
|
||||
deleteFromCollection
|
||||
)
|
||||
} else {
|
||||
(holder as StatusViewHolder).bind(it, apiHolder, db,
|
||||
(holder as StatusViewHolder).bind(it as Status, apiHolder, db,
|
||||
lifecycleScope, requireContext().displayDimensionsInPx())
|
||||
}
|
||||
}
|
||||
|
||||
if(collections && post == null){
|
||||
(holder as AddCollectionViewHolder).itemView.setOnClickListener {
|
||||
val domain = user?.instance_uri
|
||||
val url = "$domain/i/collections/create"
|
||||
|
||||
if(domain.isNullOrEmpty() || !requireContext().openUrl(url)) {
|
||||
Snackbar.make(binding.root, getString(R.string.new_collection_link_failed),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
class AddCollectionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
}
|
||||
|
||||
|
||||
class CollectionsViewHolder(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(collection: Collection) {
|
||||
|
||||
if (collection.post_count == 0){
|
||||
//No media in this collection, 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
|
||||
setSquareImageFromURL(postPreview, collection.thumb, postPreview)
|
||||
if (collection.post_count > 1) {
|
||||
albumIcon.visibility = View.VISIBLE
|
||||
} else {
|
||||
albumIcon.visibility = View.GONE
|
||||
}
|
||||
videoIcon.visibility = View.GONE
|
||||
}
|
||||
|
||||
postPreview.setOnClickListener {
|
||||
val intent = Intent(postPreview.context, CollectionActivity::class.java)
|
||||
intent.putExtra(CollectionActivity.COLLECTION_TAG, collection)
|
||||
postPreview.context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup): CollectionsViewHolder {
|
||||
val itemBinding = FragmentProfilePostsBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
)
|
||||
return CollectionsViewHolder(itemBinding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +250,9 @@ class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerVie
|
|||
private val albumIcon: ImageView = binding.albumIcon
|
||||
private val videoIcon: ImageView = binding.videoIcon
|
||||
|
||||
fun bind(post: Status) {
|
||||
fun bind(post: Status, lifecycleScope: LifecycleCoroutineScope, api: PixelfedAPI,
|
||||
addCollection: Boolean = false, collection: Collection? = null, deleteFromCollection: Boolean = false
|
||||
) {
|
||||
|
||||
if ((post.media_attachments?.size ?: 0) == 0){
|
||||
//No media in this post, so put a little icon there
|
||||
|
@ -158,9 +290,52 @@ class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerVie
|
|||
}
|
||||
|
||||
postPreview.setOnClickListener {
|
||||
val intent = Intent(postPreview.context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
postPreview.context.startActivity(intent)
|
||||
if(addCollection && collection != null){
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
api.addToCollection(collection.id, post.id)
|
||||
val intent = Intent(postPreview.context, CollectionActivity::class.java)
|
||||
.apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
putExtra(ADD_TO_COLLECTION_RESULT, true)
|
||||
putExtra(CollectionActivity.COLLECTION_TAG, collection)
|
||||
}
|
||||
postPreview.context.startActivity(intent)
|
||||
} catch (exception: IOException) {
|
||||
Snackbar.make(postPreview, postPreview.context.getString(R.string.error_add_post_to_collection),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
} catch (exception: HttpException) {
|
||||
Snackbar.make(postPreview, postPreview.context.getString(R.string.error_add_post_to_collection),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
} else if (deleteFromCollection && (collection != null)){
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
api.removeFromCollection(collection.id, post.id)
|
||||
val intent = Intent(postPreview.context, CollectionActivity::class.java)
|
||||
.apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
putExtra(DELETE_FROM_COLLECTION_RESULT, true)
|
||||
putExtra(CollectionActivity.COLLECTION_TAG, collection)
|
||||
}
|
||||
postPreview.context.startActivity(intent)
|
||||
} catch (exception: IOException) {
|
||||
Snackbar.make(postPreview, postPreview.context.getString(R.string.error_remove_post_from_collection),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
} catch (exception: HttpException) {
|
||||
Snackbar.make(postPreview, postPreview.context.getString(R.string.error_remove_post_from_collection),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
val intent = Intent(postPreview.context, PostActivity::class.java)
|
||||
intent.putExtra(Status.POST_TAG, post)
|
||||
postPreview.context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,7 +351,7 @@ class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerVie
|
|||
|
||||
|
||||
class ProfileViewModelFactory @ExperimentalPagingApi constructor(
|
||||
private val searchContentRepository: UncachedContentRepository<Status>
|
||||
private val searchContentRepository: UncachedContentRepository<FeedContent>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
|
|
|
@ -95,7 +95,7 @@ fun normalizeDomain(domain: String): String {
|
|||
.trim(Char::isWhitespace)
|
||||
}
|
||||
|
||||
fun BaseActivity.openUrl(url: String): Boolean {
|
||||
fun Context.openUrl(url: String): Boolean {
|
||||
|
||||
val intent = CustomTabsIntent.Builder().build()
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import okhttp3.Interceptor
|
|||
import org.pixeldroid.app.utils.api.objects.*
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.OkHttpClient
|
||||
import org.pixeldroid.app.utils.api.objects.Collection
|
||||
import org.pixeldroid.app.utils.api.objects.Tag
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
|
@ -201,6 +202,33 @@ interface PixelfedAPI {
|
|||
@Path("id") statusId: String
|
||||
) : Status
|
||||
|
||||
@GET("/api/v1.1/collections/accounts/{id}")
|
||||
suspend fun accountCollections(
|
||||
@Path("id") account_id: String? = null
|
||||
): List<Collection>
|
||||
|
||||
@GET("/api/v1.1/collections/items/{id}")
|
||||
suspend fun collectionItems(
|
||||
@Path("id") id: String,
|
||||
@Query("page") page: String? = null
|
||||
): List<Status>
|
||||
|
||||
@DELETE("/api/v1.1/collections/delete/{id}")
|
||||
suspend fun deleteCollection(
|
||||
@Path("id") id: String,
|
||||
)
|
||||
|
||||
@POST("/api/v1.1/collections/add")
|
||||
suspend fun addToCollection(
|
||||
@Query("collection_id") collection_id: String,
|
||||
@Query("post_id") post_id: String,
|
||||
): Status
|
||||
|
||||
@POST("/api/v1.1/collections/remove")
|
||||
suspend fun removeFromCollection(
|
||||
@Query("collection_id") collection_id: String,
|
||||
@Query("post_id") post_id: String,
|
||||
)
|
||||
|
||||
//Used in our case to retrieve comments for a given status
|
||||
@GET("/api/v1/statuses/{id}/context")
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package org.pixeldroid.app.utils.api.objects
|
||||
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
data class Collection(
|
||||
override val id: String, // Id of the profile
|
||||
val pid: String, // Account id
|
||||
val visibility: Visibility, // Public or private, or draft for your own collections
|
||||
val title: String,
|
||||
val description: String,
|
||||
val thumb: String, // URL to the thumbnail of this collection
|
||||
val updated_at: Instant,
|
||||
val published_at: Instant,
|
||||
val avatar: String, // URL to the avatar of the author of this collection
|
||||
val username: String, // Username of author
|
||||
val post_count: Int, //Number of posts in collection
|
||||
): FeedContent, Serializable {
|
||||
enum class Visibility: Serializable {
|
||||
public, private, draft
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
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="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11h-4v4h-2v-4L9,11L9,9h4L13,5h2v4h4v2z"/>
|
||||
</vector>
|
|
@ -0,0 +1,6 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
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="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,12l-2.5,-1.5L15,12L15,4h5v8z"/>
|
||||
</vector>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".searchDiscover.TrendingActivity">
|
||||
|
||||
<FrameLayout
|
||||
android:id = "@+id/collectionFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.pixeldroid.app.postCreation.SquareLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/addPhotoSquare"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:backgroundTint="?attr/colorOnBackground"
|
||||
android:background="@drawable/collection_add"
|
||||
android:contentDescription="@string/add_photo" />
|
||||
</org.pixeldroid.app.postCreation.SquareLayout>
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/delete_collection"
|
||||
android:enabled="true"
|
||||
android:title="@string/delete_collection"/>
|
||||
<item
|
||||
android:id="@+id/add_post_collection"
|
||||
android:enabled="true"
|
||||
android:title="@string/collection_add_post"/>
|
||||
<item
|
||||
android:id="@+id/remove_post_collection"
|
||||
android:enabled="true"
|
||||
android:title="@string/collection_remove_post"/>
|
||||
</menu>
|
|
@ -177,6 +177,7 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
|||
<string name="save_image_success">Image successfully saved</string>
|
||||
<string name="follow_status_failed">Could not get follow status</string>
|
||||
<string name="edit_link_failed">Failed to open edit page</string>
|
||||
<string name="new_collection_link_failed">Failed to open collection creation page</string>
|
||||
<string name="empty_feed">Nothing to see here :(</string>
|
||||
<string name="follow_button_failed">Could not display follow button</string>
|
||||
<string name="follow_error">Could not follow</string>
|
||||
|
@ -212,6 +213,7 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
|||
<string name="license_info">PixelDroid is free and open source software, licensed under the GNU General Public License (version 3 or later)</string>
|
||||
<string name="about">About</string>
|
||||
<string name="post_title">%1$s\'s post</string>
|
||||
<string name="collection_title">%1$s\'s collection</string>
|
||||
<string name="followers_title">%1$s\'s followers</string>
|
||||
<string name="hashtag_title">#%1$s</string>
|
||||
<string name="follows_title">%1$s\'s follows</string>
|
||||
|
@ -289,6 +291,20 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
|||
<string name="daily_trending">View daily trending posts</string>
|
||||
<string name="trending_posts">Trending Posts</string>
|
||||
<string name="explore_posts">Explore random posts of the day</string>
|
||||
<string name="grid_view">Grid view</string>
|
||||
<string name="feed_view">Feed view</string>
|
||||
<string name="bookmarks">Bookmarks</string>
|
||||
<string name="collections">Collections</string>
|
||||
<string name="delete_collection">Delete collection</string>
|
||||
<string name="collection_add_post">Add post</string>
|
||||
<string name="collection_remove_post">Remove post</string>
|
||||
<string name="delete_collection_warning">Are you sure you want to delete this collection?</string>
|
||||
<string name="add_to_collection">Choose a post to add</string>
|
||||
<string name="delete_from_collection">Choose a post to remove</string>
|
||||
<string name="added_post_to_collection">Added post to the collection</string>
|
||||
<string name="error_add_post_to_collection">Failed to add post to the collection</string>
|
||||
<string name="error_remove_post_from_collection">Failed to remove post from the collection</string>
|
||||
<string name="removed_post_from_collection">Removed post from collection</string>
|
||||
<plurals name="replies_count">
|
||||
<item quantity="one">%d reply</item>
|
||||
<item quantity="other">%d replies</item>
|
||||
|
|
Loading…
Reference in New Issue