My profile from drawer (#125)

* Refactor profile as Activity

* Profile info in drawer + tests

* Fixed usernames and post scroll

* Updated username test

* Default avatar drawer

* Fixed avatar drawer

* Correction getUserName

* Fixed get username

* Small refactors for codeclimate

* Uncomment test

* adapted test to new modifications made on this branch
This commit is contained in:
mjaillot 2020-05-01 11:26:18 +02:00 committed by GitHub
parent 600813baaa
commit 0df369d88c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 548 additions and 545 deletions

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.view.Gravity
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.contrib.DrawerMatchers
@ -45,14 +46,6 @@ class DrawerMenuTest {
.perform(DrawerActions.open()) // Open Drawer
}
@Test
fun testDrawerProfileButton() {
// Start the screen of your activity.
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
// Check that settings activity was opened.
onView(withId(R.id.profilePictureImageView)).check(matches(isDisplayed()))
}
@Test
fun testDrawerSettingsButton() {
// Start the screen of your activity.
@ -68,4 +61,59 @@ class DrawerMenuTest {
// Check that settings activity was opened.
onView(withId(R.id.connect_instance_button)).check(matches(isDisplayed()))
}
@Test
fun testDrawerProfileButton() {
// Start the screen of your activity.
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
// Check that profile activity was opened.
onView(withId(R.id.profilePictureImageView)).check(matches(isDisplayed()))
}
@Test
fun testDrawerAvatarClick() {
// Start the screen of your activity.
onView(withId(R.id.drawer_avatar)).perform(ViewActions.click())
// Check that profile activity was opened.
onView(withId(R.id.profilePictureImageView)).check(matches(isDisplayed()))
}
@Test
fun testDrawerAccountNameClick() {
// Start the screen of your activity.
onView(withId(R.id.drawer_account_name)).perform(ViewActions.click())
// Check that profile activity was opened.
onView(withId(R.id.profilePictureImageView)).check(matches(isDisplayed()))
}
@Test
fun clickFollowers() {
// Open My Profile from drawer
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
Thread.sleep(1000)
// Open followers list
onView(withId(R.id.nbFollowersTextView)).perform(ViewActions.click())
Thread.sleep(1000)
// Open follower's profile
onView(withText("ete2")).perform(ViewActions.click())
Thread.sleep(1000)
onView(withId(R.id.accountNameTextView)).check(matches(withText("Christian")))
}
@Test
fun clickFollowing() {
// Open My Profile from drawer
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
Thread.sleep(1000)
// Open followers list
onView(withId(R.id.nbFollowingTextView)).perform(ViewActions.click())
Thread.sleep(1000)
// Open following's profile
onView(withText("Dobios")).perform(ViewActions.click())
Thread.sleep(1000)
onView(withId(R.id.accountNameTextView)).check(matches(withText("Andrew Dobis")))
}
}

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.text.SpannableString
import android.text.style.ClickableSpan
import android.view.Gravity
import android.view.View
import android.widget.TextView
import androidx.test.core.app.ActivityScenario
@ -12,6 +13,10 @@ import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.contrib.DrawerMatchers
import androidx.test.espresso.contrib.NavigationViewActions
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
@ -142,9 +147,14 @@ class IntentTest {
@Test
fun launchesIntent() {
ActivityScenario.launch(MainActivity::class.java).onActivity { a ->
a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
}
// Open Drawer to click on navigation.
ActivityScenario.launch(MainActivity::class.java)
Espresso.onView(ViewMatchers.withId(R.id.drawer_layout))
.check(ViewAssertions.matches(DrawerMatchers.isClosed(Gravity.LEFT))) // Left Drawer should be closed.
.perform(DrawerActions.open()) // Open Drawer
Espresso.onView(ViewMatchers.withId(R.id.nav_view))
.perform(NavigationViewActions.navigateTo(R.id.nav_account))
val expectedIntent: Matcher<Intent> = CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasDataString(CoreMatchers.containsString("settings/home"))

View File

@ -60,8 +60,7 @@ class MockedServerTest {
onView(withId(R.id.searchButton)).perform(click())
Thread.sleep(3000)
onView(first(withId(R.id.username))).check(matches(withText("Memo")))
onView(first(withId(R.id.username))).check(matches(withText("memo")))
}
@Test
@ -94,18 +93,6 @@ class MockedServerTest {
}
@Test
fun testFollowersTextView() {
activityScenario.onActivity{
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
}
Thread.sleep(1000)
onView(withId(R.id.nbFollowersTextView)).check(matches(withText("68\nFollowers")))
onView(withId(R.id.accountNameTextView)).check(matches(withText("deerbard_photo")))
}
@Test
fun clickFollowButton() {
ActivityScenario.launch(MainActivity::class.java)
@ -129,22 +116,6 @@ class MockedServerTest {
onView(withId(R.id.followButton)).check(matches(withText("Unfollow")))
}
@Test
fun clickFollowers() {
ActivityScenario.launch(MainActivity::class.java).onActivity{
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
}
Thread.sleep(1000)
// Open followers list
onView(withId(R.id.nbFollowersTextView)).perform((ViewActions.click()))
Thread.sleep(1000)
// Open follower's profile
onView(withText("ete2")).perform((ViewActions.click()))
Thread.sleep(1000)
onView(withId(R.id.accountNameTextView)).check(matches(withText("ete2")))
}
@Test
fun clickOtherUserFollowers() {
ActivityScenario.launch(MainActivity::class.java)
@ -164,23 +135,7 @@ class MockedServerTest {
onView(withText("ete2")).perform((ViewActions.click()))
Thread.sleep(1000)
onView(withId(R.id.accountNameTextView)).check(matches(withText("ete2")))
}
@Test
fun clickFollowing() {
ActivityScenario.launch(MainActivity::class.java).onActivity{
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
}
Thread.sleep(1000)
// Open followers list
onView(withId(R.id.nbFollowingTextView)).perform((ViewActions.click()))
Thread.sleep(1000)
// Open following's profile
onView(withText("Dobios")).perform((ViewActions.click()))
Thread.sleep(1000)
onView(withId(R.id.accountNameTextView)).check(matches(withText("Dobios")))
onView(withId(R.id.accountNameTextView)).check(matches(withText("Christian")))
}
@Test
@ -226,7 +181,7 @@ class MockedServerTest {
onView(withText("Dobios followed you")).perform(ViewActions.click())
Thread.sleep(1000)
onView(withText("Dobios")).check(matches(withId(R.id.accountNameTextView)))
onView(withText("Andrew Dobis")).check(matches(withId(R.id.accountNameTextView)))
}
@Test
@ -260,18 +215,7 @@ class MockedServerTest {
onView(withText("Clement shared your post")).perform(ViewActions.click())
Thread.sleep(1000)
onView(first(withText("Andrea"))).check(matches(withId(R.id.username)))
}
@Test
fun swipingLeftStopsAtProfile() {
onView(withId(R.id.main_activity_main_linear_layout))
.perform(ViewActions.swipeLeft()) // search
.perform(ViewActions.swipeLeft()) // camera
.perform(ViewActions.swipeLeft()) // notifications
.perform(ViewActions.swipeLeft()) // profile
.perform(ViewActions.swipeLeft()) // should stop at profile
onView(withId(R.id.nbFollowersTextView)).check(matches(isDisplayed()))
onView(first(withText("Clement"))).check(matches(withId(R.id.username)))
}
@Test
@ -292,7 +236,7 @@ class MockedServerTest {
@Test
fun clickingTabOnAlbumShowsNextPhoto() {
ActivityScenario.launch(MainActivity::class.java).onActivity {
ActivityScenario.launch(MainActivity::class.java).onActivity {
a -> run {
//Wait for the feed to load
Thread.sleep(1000)
@ -514,10 +458,5 @@ class MockedServerTest {
onView(first(withId(R.id.commentContainer)))
.check(matches(hasDescendant(withId(R.id.comment))))
}
@Test
fun instanceConfigurationTest() {
}
}

View File

@ -4,7 +4,11 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.NonNull
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
@ -15,12 +19,16 @@ import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.navigation.NavigationView
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.fragments.NewPostFragment
import com.h.pixeldroid.fragments.ProfileFragment
import com.h.pixeldroid.fragments.SearchDiscoverFragment
import com.h.pixeldroid.fragments.feeds.PostsFeedFragment
import com.h.pixeldroid.fragments.feeds.NotificationsFragment
import kotlinx.android.synthetic.main.activity_main.*
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.utils.ImageConverter
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
@ -44,26 +52,58 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
if(!preferences.contains("accessToken")){
launchActivity(LoginActivity())
} else {
// Setup the drawer
drawerLayout = findViewById(R.id.drawer_layout)
val navigationView: NavigationView = findViewById(R.id.nav_view)
navigationView.setNavigationItemSelectedListener(this)
setupDrawer()
val tabs = arrayOf(
PostsFeedFragment(),
searchDiscoverFragment,
NewPostFragment(),
NotificationsFragment(),
ProfileFragment()
Fragment()
)
setupTabs(tabs)
}
}
private fun setupDrawer() {
drawerLayout = findViewById(R.id.drawer_layout)
val navigationView: NavigationView = findViewById(R.id.nav_view)
navigationView.setNavigationItemSelectedListener(this)
// Setup views
val accessToken = preferences.getString("accessToken", "")
val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
val drawerHeader = navigationView.getHeaderView(0)
val accountName = drawerHeader.findViewById<TextView>(R.id.drawer_account_name)
val avatar = drawerHeader.findViewById<ImageView>(R.id.drawer_avatar)
pixelfedAPI.verifyCredentials("Bearer $accessToken")
.enqueue(object : Callback<Account> {
override fun onResponse(call: Call<Account>, response: Response<Account>) {
if (response.code() == 200) {
val account = response.body()!!
// Set profile picture
ImageConverter.setRoundImageFromURL(
View(applicationContext), account.avatar_static, avatar)
avatar.setOnClickListener{ launchActivity(ProfileActivity()) }
// Set account name
accountName.text = account.display_name
accountName.setOnClickListener{ launchActivity(ProfileActivity()) }
}
}
override fun onFailure(call: Call<Account>, t: Throwable) {
Log.e("DRAWER ACCOUNT:", t.toString())
}
})
}
private fun setupTabs(tabs: Array<Fragment>){
viewPager = findViewById(R.id.view_pager)
viewPager.adapter = object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): Fragment {
@ -91,7 +131,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
*/
override fun onNavigationItemSelected(@NonNull item: MenuItem): Boolean {
when (item.itemId){
R.id.nav_account -> tabs.getTabAt(4)!!.select()
R.id.nav_account -> launchActivity(ProfileActivity())
R.id.nav_settings -> launchActivity(SettingsActivity())
R.id.nav_logout -> launchActivity(LoginActivity())
}

View File

@ -1,27 +1,262 @@
package com.h.pixeldroid
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Typeface
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.h.pixeldroid.fragments.ProfileFragment
import androidx.core.content.ContextCompat
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.Relationship
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.utils.ImageConverter
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class ProfileActivity : AppCompatActivity() {
lateinit var profileFragment : ProfileFragment
private lateinit var preferences : SharedPreferences
private lateinit var pixelfedAPI : PixelfedAPI
private lateinit var adapter : ProfilePostsRecyclerViewAdapter
private lateinit var recycler : RecyclerView
private var accessToken : String? = null
private var account: Account? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile)
preferences = this.getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE)
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
accessToken = preferences.getString("accessToken", "")
// Set posts RecyclerView as a grid with 3 columns
recycler = findViewById(R.id.profilePostsRecyclerView)
recycler.layoutManager = GridLayoutManager(applicationContext, 3)
adapter = ProfilePostsRecyclerViewAdapter(this)
recycler.adapter = adapter
setContent()
}
private fun setContent() {
// Set profile according to given account
val account = intent.getSerializableExtra(ACCOUNT_TAG) as Account
account = intent.getSerializableExtra(ACCOUNT_TAG) as Account?
profileFragment = ProfileFragment()
val arguments = Bundle()
arguments.putSerializable(ACCOUNT_TAG, account)
profileFragment.arguments = arguments
account?.let {
setViews()
activateFollow()
setPosts()
} ?: run {
pixelfedAPI.verifyCredentials("Bearer $accessToken")
.enqueue(object : Callback<Account> {
override fun onResponse(call: Call<Account>, response: Response<Account>) {
if (response.code() == 200) {
account = response.body()!!
supportFragmentManager.beginTransaction()
.add(R.id.profileFragmentSingle, profileFragment).commit()
setViews()
// Populate profile page with user's posts
setPosts()
}
}
override fun onFailure(call: Call<Account>, t: Throwable) {
Log.e("ProfileActivity:", t.toString())
}
})
// Edit button redirects to Pixelfed's "edit account" page
val editButton = findViewById<Button>(R.id.editButton)
editButton.visibility = View.VISIBLE
editButton.setOnClickListener{ onClickEditButton() }
}
// On click open followers list
findViewById<TextView>(R.id.nbFollowersTextView).setOnClickListener{ onClickFollowers() }
// On click open followers list
findViewById<TextView>(R.id.nbFollowingTextView).setOnClickListener{ onClickFollowing() }
}
/**
* Populate myProfile page with user's data
*/
private fun setViews() {
val profilePicture = findViewById<ImageView>(R.id.profilePictureImageView)
ImageConverter.setRoundImageFromURL(View(applicationContext), account!!.avatar, profilePicture)
val description = findViewById<TextView>(R.id.descriptionTextView)
description.text = account!!.note
val accountName = findViewById<TextView>(R.id.accountNameTextView)
accountName.text = account!!.display_name
accountName.setTypeface(null, Typeface.BOLD)
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() {
pixelfedAPI.accountPosts("Bearer $accessToken", account_id = account!!.id)
.enqueue(object : Callback<List<Status>> {
override fun onFailure(call: Call<List<Status>>, t: Throwable) {
Log.e("ProfileActivity.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)
}
}
})
}
private fun onClickEditButton() {
val url = "${preferences.getString("domain", "")}/settings/home"
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
if(browserIntent.resolveActivity(packageManager) != null) {
startActivity(browserIntent)
} else {
val text = "Cannot open this link"
Log.e("ProfileActivity", text)
}
}
private fun onClickFollowers() {
val intent = Intent(this, FollowsActivity::class.java)
intent.putExtra(Account.FOLLOWING_TAG, true)
intent.putExtra(Account.ACCOUNT_ID_TAG, account?.id)
ContextCompat.startActivity(this, intent, null)
}
private fun onClickFollowing() {
val intent = Intent(this, FollowsActivity::class.java)
intent.putExtra(Account.FOLLOWING_TAG, false)
intent.putExtra(Account.ACCOUNT_ID_TAG, account?.id)
ContextCompat.startActivity(this, intent, null)
}
/**
* Set up follow button
*/
private fun activateFollow() {
// Get relationship between the two users (credential and this) and set followButton accordingly
pixelfedAPI.checkRelationships("Bearer $accessToken", listOf(account!!.id))
.enqueue(object : Callback<List<Relationship>> {
override fun onFailure(call: Call<List<Relationship>>, t: Throwable) {
Log.e("FOLLOW ERROR", t.toString())
Toast.makeText(applicationContext,"Could not get follow status", Toast.LENGTH_SHORT).show()
}
override fun onResponse(call: Call<List<Relationship>>, response: Response<List<Relationship>>) {
if(response.code() == 200) {
if(response.body()!!.isNotEmpty()) {
val followButton = findViewById<Button>(R.id.followButton)
if (response.body()!![0].following) {
followButton.text = "Unfollow"
setOnClickUnfollow()
} else {
followButton.text = "Follow"
setOnClickFollow()
}
followButton.visibility = View.VISIBLE
}
} else {
Toast.makeText(applicationContext, "Could not display follow button", Toast.LENGTH_SHORT)
.show()
}
}
})
}
private fun setOnClickFollow() {
val followButton = findViewById<Button>(R.id.followButton)
followButton.setOnClickListener {
pixelfedAPI.follow(account!!.id, "Bearer $accessToken")
.enqueue(object : Callback<Relationship> {
override fun onFailure(call: Call<Relationship>, t: Throwable) {
Log.e("FOLLOW ERROR", t.toString())
Toast.makeText(applicationContext, "Could not follow", Toast.LENGTH_SHORT)
.show()
}
override fun onResponse(
call: Call<Relationship>,
response: Response<Relationship>
) {
if (response.code() == 200) {
followButton.text = "Unfollow"
setOnClickUnfollow()
} else if (response.code() == 403) {
Toast.makeText(applicationContext, "This action is not allowed", Toast.LENGTH_SHORT)
.show()
}
}
})
}
}
private fun setOnClickUnfollow() {
val followButton = findViewById<Button>(R.id.followButton)
followButton.setOnClickListener {
pixelfedAPI.unfollow(account!!.id, "Bearer $accessToken")
.enqueue(object : Callback<Relationship> {
override fun onFailure(call: Call<Relationship>, t: Throwable) {
Log.e("UNFOLLOW ERROR", t.toString())
Toast.makeText(applicationContext, "Could not unfollow", Toast.LENGTH_SHORT)
.show()
}
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
if (response.code() == 200) {
followButton.text = "Follow"
setOnClickFollow()
} else if (response.code() == 401) {
Toast.makeText(applicationContext, "The access token is invalid", Toast.LENGTH_SHORT)
.show()
}
}
})
}
}
}

View File

@ -1,150 +0,0 @@
package com.h.pixeldroid.fragments
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.FollowsActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_ID_TAG
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_TAG
import com.h.pixeldroid.objects.Account.Companion.FOLLOWING_TAG
import com.h.pixeldroid.objects.Status
import kotlinx.android.synthetic.main.profile_fragment.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class ProfileFragment : Fragment() {
private lateinit var preferences : SharedPreferences
private lateinit var pixelfedAPI : PixelfedAPI
private lateinit var adapter : ProfilePostsRecyclerViewAdapter
private lateinit var recycler : RecyclerView
private var accessToken : String? = null
private var account: Account? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
preferences = this.requireActivity().getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
account = arguments?.getSerializable(ACCOUNT_TAG) as Account?
val view = inflater.inflate(R.layout.profile_fragment, container, false)
// Set RecyclerView as a grid with 3 columns
recycler = view.findViewById(R.id.profilePostsRecyclerView)
recycler.layoutManager = GridLayoutManager(context, 3)
adapter = ProfilePostsRecyclerViewAdapter(requireContext())
recycler.adapter = adapter
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
accessToken = preferences.getString("accessToken", "")
// On click open followers list
view.nbFollowersTextView.setOnClickListener{ onClickFollowers() }
// On click open followers list
view.nbFollowingTextView.setOnClickListener{ onClickFollowing() }
if(account == null) {
pixelfedAPI.verifyCredentials("Bearer $accessToken")
.enqueue(object : Callback<Account> {
override fun onResponse(call: Call<Account>, response: Response<Account>) {
if (response.code() == 200) {
val account = response.body()!!
account.setContent(view)
// Populate profile page with user's posts
setPosts(account)
}
}
override fun onFailure(call: Call<Account>, t: Throwable) {
Log.e("ProfileFragment:", t.toString())
}
})
// Edit button redirects to Pixelfed's "edit account" page
val editButton: Button = view.findViewById(R.id.editButton)
editButton.visibility = View.VISIBLE
editButton.setOnClickListener{ onClickEditButton() }
} else {
account!!.activateFollow(view, requireContext(), pixelfedAPI, accessToken)
account!!.setContent(view)
setPosts(account!!)
}
}
// Populate profile page with user's posts
private fun setPosts(account: Account) {
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)
}
}
})
}
private fun onClickEditButton() {
val url = "${preferences.getString("domain", "")}/settings/home"
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
if(activity != null && browserIntent.resolveActivity(requireActivity().packageManager) != null) {
startActivity(browserIntent)
} else {
val text = "Cannot open this link"
Log.e("ProfileFragment", text)
}
}
private fun onClickFollowers() {
val intent = Intent(context, FollowsActivity::class.java)
intent.putExtra(FOLLOWING_TAG, true)
intent.putExtra(ACCOUNT_ID_TAG, account?.id)
ContextCompat.startActivity(requireContext(), intent, null)
}
private fun onClickFollowing() {
val intent = Intent(context, FollowsActivity::class.java)
intent.putExtra(FOLLOWING_TAG, false)
intent.putExtra(ACCOUNT_ID_TAG, account?.id)
ContextCompat.startActivity(requireContext(), intent, null)
}
}

View File

@ -2,18 +2,10 @@ package com.h.pixeldroid.objects
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.util.Log
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
import com.h.pixeldroid.ProfileActivity
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.utils.ImageConverter
import kotlinx.android.synthetic.main.profile_fragment.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
@ -84,117 +76,12 @@ data class Account(
}
}
// Open profile activity with given account
/**
* @brief Open profile activity with given account
*/
fun openProfile(context: Context) {
val intent = Intent(context, ProfileActivity::class.java)
intent.putExtra(ACCOUNT_TAG, this)
intent.putExtra(ACCOUNT_TAG, this as Serializable)
startActivity(context, intent, null)
}
// Populate myProfile page with user's data
fun setContent(view: View) {
val profilePicture = view.findViewById<ImageView>(R.id.profilePictureImageView)
ImageConverter.setRoundImageFromURL(view, this.avatar, profilePicture)
val description = view.findViewById<TextView>(R.id.descriptionTextView)
description.text = this.note
val accountName = view.findViewById<TextView>(R.id.accountNameTextView)
accountName.text = this.username
accountName.setTypeface(null, Typeface.BOLD)
val nbPosts = view.findViewById<TextView>(R.id.nbPostsTextView)
nbPosts.text = "${this.statuses_count}\nPosts"
nbPosts.setTypeface(null, Typeface.BOLD)
val nbFollowers = view.findViewById<TextView>(R.id.nbFollowersTextView)
nbFollowers.text = "${this.followers_count}\nFollowers"
nbFollowers.setTypeface(null, Typeface.BOLD)
val nbFollowing = view.findViewById<TextView>(R.id.nbFollowingTextView)
nbFollowing.text = "${this.following_count}\nFollowing"
nbFollowing.setTypeface(null, Typeface.BOLD)
}
// Activate follow button
fun activateFollow(
view : View,
context : Context,
api : PixelfedAPI,
credential : String?
) {
// Get relationship between the two users (credential and this) and set followButton accordingly
api.checkRelationships("Bearer $credential", listOf(id)).enqueue(object : Callback<List<Relationship>> {
override fun onFailure(call: Call<List<Relationship>>, t: Throwable) {
Log.e("FOLLOW ERROR", t.toString())
Toast.makeText(context,"Could not get follow status", Toast.LENGTH_SHORT).show()
}
override fun onResponse(call: Call<List<Relationship>>, response: Response<List<Relationship>>) {
if(response.code() == 200) {
if(response.body()!!.isNotEmpty()) {
if (response.body()!![0].following) {
view.followButton.text = "Unfollow"
setOnClickUnfollow(view, api, context, credential)
} else {
view.followButton.text = "Follow"
setOnClickFollow(view, api, context, credential)
}
view.followButton.visibility = View.VISIBLE
}
} else {
Toast.makeText(context, "Could not display follow button", Toast.LENGTH_SHORT)
.show()
}
}
})
}
private fun setOnClickFollow(view: View, api: PixelfedAPI, context: Context, credential: String?) {
view.followButton.setOnClickListener {
api.follow(id, "Bearer $credential").enqueue(object : Callback<Relationship> {
override fun onFailure(call: Call<Relationship>, t: Throwable) {
Log.e("FOLLOW ERROR", t.toString())
Toast.makeText(context, "Could not follow", Toast.LENGTH_SHORT).show()
}
override fun onResponse(
call: Call<Relationship>,
response: Response<Relationship>
) {
if (response.code() == 200) {
view.followButton.text = "Unfollow"
setOnClickUnfollow(view, api, context, credential)
} else if (response.code() == 403) {
Toast.makeText(context, "This action is not allowed", Toast.LENGTH_SHORT)
.show()
}
}
})
}
}
private fun setOnClickUnfollow(view: View, api: PixelfedAPI, context: Context, credential: String?) {
view.followButton.setOnClickListener {
api.unfollow(id, "Bearer $credential").enqueue(object : Callback<Relationship> {
override fun onFailure(call: Call<Relationship>, t: Throwable) {
Log.e("UNFOLLOW ERROR", t.toString())
Toast.makeText(context, "Could not unfollow", Toast.LENGTH_SHORT).show()
}
override fun onResponse(
call: Call<Relationship>,
response: Response<Relationship>
) {
if (response.code() == 200) {
view.followButton.text = "Follow"
setOnClickFollow(view, api, context, credential)
} else if (response.code() == 401) {
Toast.makeText(context, "The access token is invalid", Toast.LENGTH_SHORT)
.show()
}
}
})
}
}
}

View File

@ -113,9 +113,9 @@ data class Status(
}
fun getUsername() : CharSequence {
var name = account.display_name
var name = account.username
if (name.isNullOrEmpty()) {
name = account.username
name = account.display_name?: "NoName"
}
return name
}

View File

@ -6,12 +6,166 @@
android:layout_height="match_parent"
tools:context=".ProfileActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/profileFragmentSingle"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
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:layout_margin="20dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/profilePictureImageView"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_weight="1"
tools:srcCompat="@tools:sample/avatars" />
<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_marginLeft="20dp"
android:layout_marginTop="5dp"
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_marginLeft="20dp"
android:layout_marginTop="5dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp">
<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_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginBottom="15dp">
<Button
android:id="@+id/followButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Follow"
android:textColor="@color/colorPrimary"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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"
android:visibility="gone"
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: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:id="@+id/profilePostsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
app:layoutManager="LinearLayoutManager"
tools:context=".fragments.ProfileFragment"
tools:listitem="@layout/fragment_profile_posts" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -13,11 +13,13 @@
android:theme="@style/ThemeOverlay.AppCompat.Dark">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@mipmap/ic_launcher_round" />
android:id="@+id/drawer_avatar"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_default_user" />
<TextView
android:id="@+id/drawer_account_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
@ -25,6 +27,7 @@
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<TextView
android:id="@+id/drawer_account_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_subtitle" />

View File

@ -12,6 +12,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -251,6 +255,8 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.cardview.widget.CardView>

View File

@ -1,169 +0,0 @@
<?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">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
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:layout_margin="20dp"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/profilePictureImageView"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_weight="1"
tools:srcCompat="@tools:sample/avatars" />
<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_marginLeft="20dp"
android:layout_marginTop="5dp"
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_marginLeft="20dp"
android:layout_marginTop="5dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp">
<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_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginBottom="15dp">
<Button
android:id="@+id/followButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Follow"
android:textColor="@color/colorPrimary"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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"
android:visibility="gone"
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:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
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: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:id="@+id/profilePostsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
app:layoutManager="LinearLayoutManager"
tools:context=".fragments.ProfileFragment"
tools:listitem="@layout/fragment_profile_posts" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -33,7 +33,7 @@ class PostUnitTest {
fun getProfilePicUrlReturnsAValidURL() = Assert.assertNotNull(status.getProfilePicUrl())
@Test
fun getUsernameReturnsACorrectName() = Assert.assertEquals(status.account.display_name, status.getUsername())
fun getUsernameReturnsACorrectName() = Assert.assertEquals(status.account.username, status.getUsername())
@Test
fun getUsernameReturnsOtherNameIfUsernameIsNull() {