User profile #79 (#90)

* Refactored myProfile page

* Total refactor of profile posts

* Merged with my-profile

* Posts displayed on profile page

* Added links to profile activity where needed

* Removed MyProfileTest with swipes

* Tests ProfileActivity from Notifications

* Add test, fix progressbar being null
This commit is contained in:
mjaillot 2020-04-09 22:36:59 +02:00 committed by GitHub
parent 5372e7e9ee
commit 1b6753d119
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 917 additions and 164 deletions

View File

@ -75,6 +75,7 @@ dependencies {
implementation("com.github.bumptech.glide:glide:4.11.0") { implementation("com.github.bumptech.glide:glide:4.11.0") {
exclude group: "com.android.support" exclude group: "com.android.support"
} }
implementation "com.github.bumptech.glide:okhttp-integration:4.11.0" implementation "com.github.bumptech.glide:okhttp-integration:4.11.0"
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
// Excludes the support library because it's already included by Glide. // Excludes the support library because it's already included by Glide.
@ -104,6 +105,7 @@ tasks.withType(Test) {
jacoco.excludes = ['jdk.internal.*'] jacoco.excludes = ['jdk.internal.*']
} }
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) { task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
reports { reports {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,12 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".PostActivity"></activity> <activity android:name=".PostActivity">
</activity>
<activity android:name=".ProfileActivity">
</activity>
<activity <activity
android:name=".SettingsActivity" android:name=".SettingsActivity"
android:label="@string/title_activity_settings2"> android:label="@string/title_activity_settings2">

View File

@ -6,7 +6,6 @@ import com.h.pixeldroid.fragments.PostFragment
import com.h.pixeldroid.objects.Status.Companion.POST_TAG import com.h.pixeldroid.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.objects.Status import com.h.pixeldroid.objects.Status
class PostActivity : AppCompatActivity() { class PostActivity : AppCompatActivity() {
lateinit var postFragment : PostFragment lateinit var postFragment : PostFragment

View File

@ -0,0 +1,104 @@
package com.h.pixeldroid
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Typeface
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.ProfilePostsRecyclerViewAdapter
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_TAG
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.ImageConverter.Companion.setRoundImageFromURL
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class ProfileActivity : AppCompatActivity() {
private lateinit var adapter : ProfilePostsRecyclerViewAdapter
private lateinit var recycler : RecyclerView
private lateinit var preferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile)
// Set RecyclerView as a grid with 3 columns
recycler = findViewById(R.id.profilePostsRecyclerView)
recycler.layoutManager = GridLayoutManager(this, 3)
adapter = ProfilePostsRecyclerViewAdapter(this)
recycler.adapter = adapter
preferences = getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
// Set profile according to given account
val account = intent.getSerializableExtra(ACCOUNT_TAG) as Account
setContent(account)
// Set profile picture
val profilePicture = findViewById<ImageView>(R.id.profilePictureImageView)
setRoundImageFromURL(View(this), account.avatar, profilePicture)
setPosts(account)
}
private fun setContent(account: Account) {
val profilePicture = findViewById<ImageView>(R.id.profilePictureImageView)
setRoundImageFromURL(View(this), account.avatar, profilePicture)
val description = findViewById<TextView>(R.id.descriptionTextView)
description.text = account.note
val accountName = findViewById<TextView>(R.id.accountNameTextView)
accountName.text = account.username
val nbPosts = findViewById<TextView>(R.id.nbPostsTextView)
nbPosts.text = "${account.statuses_count}\nPosts"
nbPosts.setTypeface(null, Typeface.BOLD)
val nbFollowers = findViewById<TextView>(R.id.nbFollowersTextView)
nbFollowers.text = "${account.followers_count}\nFollowers"
nbFollowers.setTypeface(null, Typeface.BOLD)
val nbFollowing = findViewById<TextView>(R.id.nbFollowingTextView)
nbFollowing.text = "${account.following_count}\nFollowing"
nbFollowing.setTypeface(null, Typeface.BOLD)
}
// Populate profile page with user's posts
private fun setPosts(account: Account) {
val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
val accessToken = preferences.getString("accessToken", "")
pixelfedAPI.accountPosts("Bearer $accessToken", account_id = account.id).enqueue(object :
Callback<List<Status>> {
override fun onFailure(call: Call<List<Status>>, t: Throwable) {
Log.e("ProfileFragment.Posts:", t.toString())
}
override fun onResponse(
call: Call<List<Status>>,
response: Response<List<Status>>
) {
if(response.code() == 200) {
val posts = ArrayList<Status>()
val statuses = response.body()!!
for(status in statuses) {
posts.add(status)
}
adapter.addPosts(posts)
}
}
})
}
}

View File

@ -38,6 +38,52 @@ interface PixelfedAPI {
@Field("grant_type") grant_type: String? = null @Field("grant_type") grant_type: String? = null
): Call<Token> ): Call<Token>
@POST("api/v1/statuses/{id}/favourite")
fun likePost(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Path("id") statusId: String
) : Call<Status>
@POST("/api/v1/statuses/{id}/unfavourite")
fun unlikePost(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Path("id") statusId: String
) : Call<Status>
@POST("/api/v1/statuses/{id}/favourited_by")
fun postLikedBy(
@Path("id") statusId: String
) : Call<List<Account>>
//Used in our case to post a comment
@FormUrlEncoded
@POST("/api/v1/statuses")
fun commentStatus(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Field("status") statusText : String,
@Field("in_reply_to_id") in_reply_to_id : String,
@Field("media_ids[]") media_ids : List<String> = emptyList(),
@Field("poll[options][]") poll_options : List<String>? = null,
@Field("poll[expires_in]") poll_expires : List<String>? = null,
@Field("poll[multiple]") poll_multiple : List<String>? = null,
@Field("poll[hide_totals]") poll_hideTotals : List<String>? = null,
@Field("sensitive") sensitive : Boolean? = null,
@Field("spoiler_text") spoiler_text : String? = null,
@Field("visibility") visibility : String = "public",
@Field("scheduled_at") scheduled_at : String? = null,
@Field("language") language : String? = null
) : Call<Status>
//Used in our case to retrieve comments for a given status
@GET("/api/v1/statuses/{id}/context")
fun statusComments(
@Path("id") statusId: String,
@Header("Authorization") authorization: String? = null
) : Call<Context>
@GET("/api/v1/timelines/public") @GET("/api/v1/timelines/public")
fun timelinePublic( fun timelinePublic(
@Query("local") local: Boolean? = null, @Query("local") local: Boolean? = null,
@ -47,7 +93,6 @@ interface PixelfedAPI {
@Query("limit") limit: String? = null @Query("limit") limit: String? = null
): Call<List<Status>> ): Call<List<Status>>
@GET("/api/v1/timelines/home") @GET("/api/v1/timelines/home")
fun timelineHome( fun timelineHome(
//The authorization header needs to be of the form "Bearer <token>" //The authorization header needs to be of the form "Bearer <token>"
@ -82,6 +127,12 @@ interface PixelfedAPI {
@Header("Authorization") authorization: String @Header("Authorization") authorization: String
): Call<Account> ): Call<Account>
@GET("/api/v1/accounts/{id}/statuses")
fun accountPosts(
@Header("Authorization") authorization: String,
@Path("id") account_id: String? = null
): Call<List<Status>>
companion object { companion object {
fun create(baseUrl: String): PixelfedAPI { fun create(baseUrl: String): PixelfedAPI {
return Retrofit.Builder() return Retrofit.Builder()

View File

@ -14,18 +14,22 @@ import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.bumptech.glide.Glide import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.BuildConfig import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.Account import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.ImageConverter.Companion.setRoundImageFromURL
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
class MyProfileFragment : Fragment() { class MyProfileFragment : Fragment() {
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
private lateinit var adapter : ProfilePostsRecyclerViewAdapter
private lateinit var recycler : RecyclerView
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -36,9 +40,16 @@ class MyProfileFragment : Fragment() {
) )
val view = inflater.inflate(R.layout.fragment_my_profile, container, false) val view = inflater.inflate(R.layout.fragment_my_profile, container, false)
// Edit button redirects to pixelfed's "edit account" page
val editButton: Button = view.findViewById(R.id.editButton) val editButton: Button = view.findViewById(R.id.editButton)
editButton.setOnClickListener((View.OnClickListener { onClickEditButton() })) editButton.setOnClickListener((View.OnClickListener { onClickEditButton() }))
// Set RecyclerView as a grid with 3 columns
recycler = view.findViewById(R.id.myProfilePostsRecyclerView)
recycler.layoutManager = GridLayoutManager(context, 3)
adapter = ProfilePostsRecyclerViewAdapter(context!!)
recycler.adapter = adapter
return view return view
} }
@ -55,6 +66,27 @@ class MyProfileFragment : Fragment() {
val account = response.body()!! val account = response.body()!!
setContent(view, account) setContent(view, account)
// Populate profile page with user's posts
pixelfedAPI.accountPosts("Bearer $accessToken", account_id = account.id).enqueue(object : Callback<List<Status>> {
override fun onFailure(call: Call<List<Status>>, t: Throwable) {
Log.e("ProfileFragment.Posts:", t.toString())
}
override fun onResponse(
call: Call<List<Status>>,
response: Response<List<Status>>
) {
if(response.code() == 200) {
val posts = ArrayList<Status>()
val statuses = response.body()!!
for (status in statuses) {
posts.add(status)
}
adapter.addPosts(posts)
}
}
})
} }
} }
@ -64,32 +96,28 @@ class MyProfileFragment : Fragment() {
}) })
} }
// Populate myProfile page with user's data
private fun setContent(view: View, account: Account) { private fun setContent(view: View, account: Account) {
// ImageView : profile picture
val profilePicture = view.findViewById<ImageView>(R.id.profilePictureImageView) val profilePicture = view.findViewById<ImageView>(R.id.profilePictureImageView)
Glide.with(view.context).load(account.avatar).into(profilePicture) setRoundImageFromURL(view, account.avatar, profilePicture)
// TextView : description / bio
val description = view.findViewById<TextView>(R.id.descriptionTextView) val description = view.findViewById<TextView>(R.id.descriptionTextView)
description.text = account.note description.text = account.note
// TextView : account name
val accountName = view.findViewById<TextView>(R.id.accountNameTextView) val accountName = view.findViewById<TextView>(R.id.accountNameTextView)
accountName.text = account.username accountName.text = account.username
accountName.setTypeface(null, Typeface.BOLD)
// TextView : number of posts
val nbPosts = view.findViewById<TextView>(R.id.nbPostsTextView) val nbPosts = view.findViewById<TextView>(R.id.nbPostsTextView)
nbPosts.text = account.statuses_count.toString() + "\nPosts" nbPosts.text = "${account.statuses_count}\nPosts"
nbPosts.setTypeface(null, Typeface.BOLD) nbPosts.setTypeface(null, Typeface.BOLD)
// TextView : number of followers
val nbFollowers = view.findViewById<TextView>(R.id.nbFollowersTextView) val nbFollowers = view.findViewById<TextView>(R.id.nbFollowersTextView)
nbFollowers.text = account.followers_count.toString() + "\nFollowers" nbFollowers.text = "${account.followers_count}\nFollowers"
nbFollowers.setTypeface(null, Typeface.BOLD) nbFollowers.setTypeface(null, Typeface.BOLD)
// TextView : number of following
val nbFollowing = view.findViewById<TextView>(R.id.nbFollowingTextView) val nbFollowing = view.findViewById<TextView>(R.id.nbFollowingTextView)
nbFollowing.text = account.following_count.toString() + "\nFollowing" nbFollowing.text = "${account.following_count}\nFollowing"
nbFollowing.setTypeface(null, Typeface.BOLD) nbFollowing.setTypeface(null, Typeface.BOLD)
} }

View File

@ -1,10 +1,13 @@
package com.h.pixeldroid.fragments package com.h.pixeldroid.fragments
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.h.pixeldroid.R import com.h.pixeldroid.R
import com.h.pixeldroid.objects.Status.Companion.POST_TAG import com.h.pixeldroid.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.objects.Status import com.h.pixeldroid.objects.Status
@ -20,19 +23,11 @@ class PostFragment : Fragment() {
): View? { ): View? {
val status = arguments?.getSerializable(POST_TAG) as Status? val status = arguments?.getSerializable(POST_TAG) as Status?
val root = inflater.inflate(R.layout.post_fragment, container, false) val root = inflater.inflate(R.layout.post_fragment, container, false)
status?.setupPost(root) val picRequest = Glide.with(this)
//Setup post and profile images .asDrawable().fitCenter()
ImageConverter.setImageViewFromURL( .placeholder(ColorDrawable(Color.GRAY))
this,
status?.getPostUrl(), status?.setupPost(root, picRequest, root.postPicture, root.profilePic)
root.postPicture
)
ImageConverter.setImageViewFromURL(
this,
status?.getProfilePicUrl(),
root.profilePic
)
return root return root
} }
} }

View File

@ -0,0 +1,57 @@
package com.h.pixeldroid.fragments
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
/**
* A fragment representing a list of Items.
* Activities containing this fragment MUST implement the
* [ProfilePostsFragment.OnListFragmentInteractionListener] interface.
*/
class ProfilePostsFragment : Fragment() {
private lateinit var preferences: SharedPreferences
private lateinit var pixelfedAPI: PixelfedAPI
private var accessToken: String? = null
private var columnCount = 3
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_profile_posts_list, container, false)
preferences = activity!!.getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
accessToken = preferences.getString("accessToken", "")
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
adapter = ProfilePostsRecyclerViewAdapter(context!!)
}
}
return view
}
}

View File

@ -0,0 +1,51 @@
package com.h.pixeldroid.fragments
import android.content.Context
import android.content.Intent
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.h.pixeldroid.PostActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.ImageConverter.Companion.setSquareImageFromURL
/**
* [RecyclerView.Adapter] that can display a list of [PostMiniature]s and makes a call to the
* specified [OnListFragmentInteractionListener].
*/
class ProfilePostsRecyclerViewAdapter(
private val context: Context
) : RecyclerView.Adapter<ProfilePostsRecyclerViewAdapter.ViewHolder>() {
private val posts: ArrayList<Status> = ArrayList()
fun addPosts(newPosts : List<Status>) {
val size = posts.size
posts.addAll(newPosts)
notifyItemRangeInserted(size, newPosts.size)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_profile_posts, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val post = posts[position]
setSquareImageFromURL(holder.postView, post.getPostPreviewURL(), holder.postPreview)
holder.postPreview.setOnClickListener {
val intent = Intent(context, PostActivity::class.java)
intent.putExtra(Status.POST_TAG, post)
context.startActivity(intent)
}
}
override fun getItemCount(): Int = posts.size
inner class ViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) {
val postPreview: ImageView = postView.findViewById(R.id.postPreview)
}
}

View File

@ -7,6 +7,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@ -42,6 +43,7 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
protected lateinit var list : RecyclerView protected lateinit var list : RecyclerView
protected lateinit var adapter : FeedsRecyclerViewAdapter<T, VH> protected lateinit var adapter : FeedsRecyclerViewAdapter<T, VH>
private lateinit var swipeRefreshLayout: SwipeRefreshLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var loadingIndicator: ProgressBar
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -51,6 +53,7 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
val view = inflater.inflate(R.layout.fragment_feed, container, false) val view = inflater.inflate(R.layout.fragment_feed, container, false)
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout) swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout)
loadingIndicator = view.findViewById(R.id.progressBar)
list = swipeRefreshLayout.list list = swipeRefreshLayout.list
// Set the adapter // Set the adapter
list.layoutManager = LinearLayoutManager(context) list.layoutManager = LinearLayoutManager(context)
@ -111,7 +114,7 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
Toast.makeText(context,"Something went wrong while loading", Toast.LENGTH_SHORT).show() Toast.makeText(context,"Something went wrong while loading", Toast.LENGTH_SHORT).show()
} }
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
progressBar.visibility = View.GONE loadingIndicator.visibility = View.GONE
} }
override fun onFailure(call: Call<List<T>>, t: Throwable) { override fun onFailure(call: Call<List<T>>, t: Throwable) {

View File

@ -97,32 +97,23 @@ class HomeFragment : FeedFragment<Status, HomeFragment.HomeRecyclerViewAdapter.V
val post = getItem(position) ?: return val post = getItem(position) ?: return
val metrics = context.resources.displayMetrics val metrics = context.resources.displayMetrics
//Limit the height of the different images //Limit the height of the different images
holder.profilePic?.maxHeight = metrics.heightPixels holder.profilePic.maxHeight = metrics.heightPixels
holder.postPic.maxHeight = metrics.heightPixels holder.postPic.maxHeight = metrics.heightPixels
//Set the two images //Set up the the post
ImageConverter.setRoundImageFromURL( post.setupPost(holder.postView, picRequest, holder.postPic, holder.profilePic)
holder.postView,
post.getProfilePicUrl(),
holder.profilePic!!
)
picRequest.load(post.getPostUrl()).into(holder.postPic)
//Set the image back to a placeholder if the original is too big //Set the image back to a placeholder if the original is too big
if(holder.postPic.height > metrics.heightPixels) { if(holder.postPic.height > metrics.heightPixels) {
ImageConverter.setDefaultImage(holder.postView, holder.postPic) ImageConverter.setDefaultImage(holder.postView, holder.postPic)
} }
//Set the the text views
post.setupPost(holder.postView)
} }
/** /**
* Represents the posts that will be contained within the feed * Represents the posts that will be contained within the feed
*/ */
inner class ViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) { inner class ViewHolder(val postView: View) : RecyclerView.ViewHolder(postView) {
val profilePic : ImageView? = postView.findViewById(R.id.profilePic) val profilePic : ImageView = postView.findViewById(R.id.profilePic)
val postPic : ImageView = postView.findViewById(R.id.postPicture) val postPic : ImageView = postView.findViewById(R.id.postPicture)
val username : TextView = postView.findViewById(R.id.username) val username : TextView = postView.findViewById(R.id.username)
val usernameDesc: TextView = postView.findViewById(R.id.usernameDesc) val usernameDesc: TextView = postView.findViewById(R.id.usernameDesc)

View File

@ -23,7 +23,9 @@ import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.util.ViewPreloadSizeProvider import com.bumptech.glide.util.ViewPreloadSizeProvider
import com.h.pixeldroid.PostActivity import com.h.pixeldroid.PostActivity
import com.h.pixeldroid.ProfileActivity
import com.h.pixeldroid.R import com.h.pixeldroid.R
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Notification import com.h.pixeldroid.objects.Notification
import com.h.pixeldroid.objects.Status import com.h.pixeldroid.objects.Status
import kotlinx.android.synthetic.main.fragment_feed.* import kotlinx.android.synthetic.main.fragment_feed.*
@ -113,8 +115,8 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
return return
} }
Notification.NotificationType.follow -> { Notification.NotificationType.follow -> {
val url = notification.status?.url ?: notification.account.url intent = Intent(context, ProfileActivity::class.java)
intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) intent.putExtra(Account.ACCOUNT_TAG, notification.account)
} }
} }
context.startActivity(intent) context.startActivity(intent)

View File

@ -1,5 +1,9 @@
package com.h.pixeldroid.objects package com.h.pixeldroid.objects
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat.startActivity
import com.h.pixeldroid.ProfileActivity
import java.io.Serializable import java.io.Serializable
/* /*
@ -33,5 +37,16 @@ data class Account(
val fields: List<Field>? = emptyList(), val fields: List<Field>? = emptyList(),
val bot: Boolean = false, val bot: Boolean = false,
val source: Source? = null val source: Source? = null
) : Serializable ) : Serializable {
companion object {
const val ACCOUNT_TAG = "AccountTag"
}
// Open profile activity with given account
fun openProfile(context: Context) {
val intent = Intent(context, ProfileActivity::class.java)
intent.putExtra(Account.ACCOUNT_TAG, this)
startActivity(context, intent, null)
}
}

View File

@ -0,0 +1,8 @@
package com.h.pixeldroid.objects
import java.io.Serializable
data class Context(
val ancestors : List<Status>,
val descendants : List<Status>
) : Serializable

View File

@ -1,10 +1,10 @@
package com.h.pixeldroid.objects package com.h.pixeldroid.objects
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.widget.TextView import android.widget.ImageView
import androidx.fragment.app.Fragment import com.bumptech.glide.RequestBuilder
import com.h.pixeldroid.R
import com.h.pixeldroid.utils.ImageConverter import com.h.pixeldroid.utils.ImageConverter
import kotlinx.android.synthetic.main.post_fragment.view.* import kotlinx.android.synthetic.main.post_fragment.view.*
import java.io.Serializable import java.io.Serializable
@ -56,8 +56,9 @@ data class Status(
const val POST_FRAG_TAG = "postFragTag" const val POST_FRAG_TAG = "postFragTag"
} }
fun getPostUrl() : String? = media_attachments?.getOrNull(0)?.url fun getPostUrl() : String? = media_attachments.getOrNull(0)?.url
fun getProfilePicUrl() : String? = account?.avatar fun getProfilePicUrl() : String? = account.avatar
fun getPostPreviewURL() : String? = media_attachments.getOrNull(0)?.preview_url
fun getDescription() : CharSequence { fun getDescription() : CharSequence {
val description = content as CharSequence val description = content as CharSequence
@ -85,10 +86,16 @@ data class Status(
return "$nShares Shares" return "$nShares Shares"
} }
fun setupPost(rootView : View) { fun setupPost(
rootView: View,
request: RequestBuilder<Drawable>,
postPic: ImageView,
profilePic: ImageView
) {
//Setup username as a button that opens the profile //Setup username as a button that opens the profile
rootView.username.text = this.getUsername() rootView.username.text = this.getUsername()
rootView.username.setTypeface(null, Typeface.BOLD) rootView.username.setTypeface(null, Typeface.BOLD)
rootView.username.setOnClickListener { account.openProfile(rootView.context) }
rootView.usernameDesc.text = this.getUsername() rootView.usernameDesc.text = this.getUsername()
rootView.usernameDesc.setTypeface(null, Typeface.BOLD) rootView.usernameDesc.setTypeface(null, Typeface.BOLD)
@ -101,7 +108,15 @@ data class Status(
rootView.nshares.text = this.getNShares() rootView.nshares.text = this.getNShares()
rootView.nshares.setTypeface(null, Typeface.BOLD) rootView.nshares.setTypeface(null, Typeface.BOLD)
request.load(this.getPostUrl()).into(postPic)
ImageConverter.setRoundImageFromURL(
rootView,
this.getProfilePicUrl(),
profilePic
)
profilePic.setOnClickListener { account.openProfile(rootView.context) }
} }
enum class Visibility : Serializable { enum class Visibility : Serializable {
public, unlisted, private, direct public, unlisted, private, direct
} }

View File

@ -62,6 +62,16 @@ class ImageConverter {
.placeholder(R.drawable.ic_default_user).into(image) .placeholder(R.drawable.ic_default_user).into(image)
} }
/**
* @brief Loads a given image (via url) as a square image into a given image view
* @param view, the view in which this is happening
* @param url, the url of the image that will be loaded
* @param image, the imageView into which we will load the image
*/
fun setSquareImageFromURL(view : View, url : String?, image : ImageView) {
Glide.with(view).load(url).apply(RequestOptions().centerCrop()).into(image)
}
/** /**
* @brief Loads a default image into a given image view * @brief Loads a default image into a given image view
* @param view, the view in which this is happening * @param view, the view in which this is happening

View File

@ -0,0 +1,161 @@
<?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=".ProfileActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="20dp">
<ImageView
android:id="@+id/profilePictureImageView"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_weight="1"
tools:srcCompat="@tools:sample/avatars" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="10"
android:orientation="horizontal">
<TextView
android:id="@+id/nbPostsTextView"
android:layout_width="15dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="-\nPosts" />
<TextView
android:id="@+id/nbFollowersTextView"
android:layout_width="15dp"
android:layout_height="120dp"
android:layout_weight="1"
android:gravity="center"
android:text="-\nFollowers" />
<TextView
android:id="@+id/nbFollowingTextView"
android:layout_width="15dp"
android:layout_height="120dp"
android:layout_weight="1"
android:gravity="center"
android:text="-\nFollowing" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp">
<TextView
android:id="@+id/accountNameTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="No Username"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="20dp"
android:layout_marginLeft="20dp">
<TextView
android:id="@+id/descriptionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="50dp"
android:layout_marginLeft="50dp"
android:layout_marginBottom="15dp">
<Button
android:id="@+id/followButton"
android:layout_width="100dp"
android:layout_height="30dp"
android:background="@color/browser_actions_divider_color"
android:text="Follow"
android:textColor="@color/colorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/postsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:src="@android:drawable/ic_dialog_dialer" />
<ImageButton
android:id="@+id/collectionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:src="@android:drawable/ic_menu_gallery" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/profilePostsRecyclerView"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".fragments.MyProfileFragment"
tools:listitem="@layout/fragment_profile_posts"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -16,8 +16,6 @@
android:name="com.h.pixeldroid.FeedFragment" android:name="com.h.pixeldroid.FeedFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager" app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/post_fragment" /> tools:listitem="@layout/post_fragment" />

View File

@ -10,114 +10,164 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView
android:id="@+id/profilePictureImageView"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<LinearLayout <LinearLayout
android:id="@+id/linearLayout" android:layout_width="match_parent"
android:layout_width="230dp" android:layout_height="match_parent"
android:layout_height="120dp" android:orientation="vertical">
android:layout_marginTop="24dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/nbPostsTextView" <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="15dp" android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="20dp">
<ImageView
android:id="@+id/profilePictureImageView"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_weight="1"
tools:srcCompat="@tools:sample/avatars" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="10"
android:orientation="horizontal">
<TextView
android:id="@+id/nbPostsTextView"
android:layout_width="15dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="-\nPosts" />
<TextView
android:id="@+id/nbFollowersTextView"
android:layout_width="15dp"
android:layout_height="120dp"
android:layout_weight="1"
android:gravity="center"
android:text="-\nFollowers" />
<TextView
android:id="@+id/nbFollowingTextView"
android:layout_width="15dp"
android:layout_height="120dp"
android:layout_weight="1"
android:gravity="center"
android:text="-\nFollowing" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp">
<TextView
android:id="@+id/accountNameTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="No Username"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="20dp"
android:layout_marginLeft="20dp">
<TextView
android:id="@+id/descriptionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="50dp"
android:layout_marginLeft="50dp"
android:layout_marginBottom="15dp">
<Button
android:id="@+id/editButton"
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="@color/colorPrimary"
android:text="Edit profile"
android:textColor="@color/cardview_light_background"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/postsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:src="@android:drawable/ic_dialog_dialer" />
<ImageButton
android:id="@+id/collectionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:src="@android:drawable/ic_menu_gallery" />
<ImageButton
android:id="@+id/bookmarkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:src="@android:drawable/ic_input_get" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:id="@+id/myProfilePostsRecyclerView"
android:gravity="center" android:layout_marginLeft="16dp"
android:text="-\nPosts" /> android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
<TextView app:layoutManager="LinearLayoutManager"
android:id="@+id/nbFollowersTextView" tools:context=".fragments.MyProfileFragment"
android:layout_width="15dp" tools:listitem="@layout/fragment_profile_posts"/>
android:layout_height="120dp"
android:layout_weight="1"
android:gravity="center"
android:text="-\nFollowers" />
<TextView
android:id="@+id/nbFollowingTextView"
android:layout_width="15dp"
android:layout_height="120dp"
android:layout_weight="1"
android:gravity="center"
android:text="-\nFollowing" />
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/accountNameTextView"
android:layout_width="156dp"
android:layout_height="22dp"
android:layout_marginTop="15dp"
android:text="No Username"
app:layout_constraintStart_toStartOf="@+id/profilePictureImageView"
app:layout_constraintTop_toBottomOf="@+id/profilePictureImageView" />
<TextView
android:id="@+id/descriptionTextView"
android:layout_width="348dp"
android:layout_height="85dp"
android:layout_marginTop="14dp"
android:autoSizeMaxTextSize="300sp"
android:autoSizeMinTextSize="2sp"
android:autoSizeStepGranularity="2sp"
android:autoSizeTextType="uniform"
app:layout_constraintStart_toStartOf="@+id/accountNameTextView"
app:layout_constraintTop_toBottomOf="@+id/accountNameTextView" />
<Button
android:id="@+id/editButton"
android:layout_width="350dp"
android:layout_height="37dp"
android:layout_marginTop="14dp"
android:gravity="center"
android:text="Edit profile"
android:background="@color/colorPrimary"
android:textColor="@color/cardview_light_background"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/descriptionTextView" />
<LinearLayout
android:layout_width="409dp"
android:layout_height="50dp"
android:layout_marginTop="340dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/postsButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@color/cardview_shadow_end_color"
android:gravity="right"
app:srcCompat="@android:drawable/ic_dialog_dialer" />
<ImageButton
android:id="@+id/collectionButton"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@color/cardview_shadow_end_color"
android:gravity="left"
app:srcCompat="@android:drawable/ic_menu_gallery" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:id="@+id/postPreview"
android:layout_width="100dp"
android:layout_height="100dp" />
</androidx.cardview.widget.CardView>
</GridLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:name="com.h.pixeldroid.fragments.ProfilePostsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".fragments.ProfilePostsFragment"
tools:listitem="@layout/fragment_profile_posts" />

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:context=".fragments.PostFragment"> tools:context=".fragments.PostFragment">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"