diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/CommonFeedFragmentUtils.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/CommonFeedFragmentUtils.kt index 30263739..0c773afd 100644 --- a/app/src/main/java/com/h/pixeldroid/posts/feeds/CommonFeedFragmentUtils.kt +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/CommonFeedFragmentUtils.kt @@ -2,25 +2,38 @@ package com.h.pixeldroid.posts.feeds import android.view.LayoutInflater import android.view.ViewGroup +import android.widget.ProgressBar +import androidx.constraintlayout.motion.widget.MotionLayout import androidx.core.view.isVisible import androidx.core.view.size +import androidx.lifecycle.LifecycleCoroutineScope import androidx.paging.LoadState import androidx.paging.LoadStateAdapter import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.h.pixeldroid.R -import com.h.pixeldroid.databinding.FragmentFeedBinding +import com.h.pixeldroid.databinding.ErrorLayoutBinding import com.h.pixeldroid.databinding.LoadStateFooterViewItemBinding +import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel +import com.h.pixeldroid.utils.api.objects.FeedContent +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch /** * Shows or hides the error in the different FeedFragments */ -private fun showError(errorText: String, show: Boolean = true, binding: FragmentFeedBinding){ - if(show){ - binding.motionLayout.transitionToEnd() - binding.errorLayout.errorText.text = errorText - } else if(binding.motionLayout.progress == 1F){ - binding.motionLayout.transitionToStart() +private fun showError( + errorText: String, show: Boolean = true, + motionLayout: MotionLayout, + errorLayout: ErrorLayoutBinding){ + + if(show) { + motionLayout.transitionToEnd() + errorLayout.errorText.text = errorText + } else if(motionLayout.progress == 1F) { + motionLayout.transitionToStart() } } @@ -29,28 +42,33 @@ private fun showError(errorText: String, show: Boolean = true, binding: Fragment * * Makes the UI respond to various [LoadState]s, including errors when an error message is shown. */ -internal fun initAdapter(binding: FragmentFeedBinding, adapter: PagingDataAdapter) { - binding.list.adapter = adapter.withLoadStateFooter( +internal fun initAdapter( + progressBar: ProgressBar, swipeRefreshLayout: SwipeRefreshLayout, + recyclerView: RecyclerView, motionLayout: MotionLayout, errorLayout: ErrorLayoutBinding, + adapter: PagingDataAdapter) { + + recyclerView.adapter = adapter.withLoadStateFooter( footer = ReposLoadStateAdapter { adapter.retry() } ) adapter.addLoadStateListener { loadState -> - if(!binding.progressBar.isVisible && binding.swipeRefreshLayout.isRefreshing) { + if(!progressBar.isVisible && swipeRefreshLayout.isRefreshing) { // Stop loading spinner when loading is done - binding.swipeRefreshLayout.isRefreshing = loadState.refresh is LoadState.Loading + swipeRefreshLayout.isRefreshing = loadState.refresh is LoadState.Loading } else { // ProgressBar should stop showing as soon as the source stops loading ("source" // meaning the database, so don't wait on the network) val sourceLoading = loadState.source.refresh is LoadState.Loading - if(!sourceLoading && binding.list.size > 0){ - binding.list.isVisible = true - binding.progressBar.isVisible = false - } else if(binding.list.size == 0 + if(!sourceLoading && recyclerView.size > 0){ + recyclerView.isVisible = true + progressBar.isVisible = false + } else if(recyclerView.size == 0 && loadState.append is LoadState.NotLoading && loadState.append.endOfPaginationReached){ - binding.progressBar.isVisible = false - showError(binding = binding, errorText = "Nothing to see here :(") + progressBar.isVisible = false + showError(motionLayout = motionLayout, errorLayout = errorLayout, + errorText = errorLayout.root.context.getString(R.string.empty_feed)) } } @@ -63,13 +81,29 @@ internal fun initAdapter(binding: FragmentFeedBinding, adapter: PagingD ?: loadState.prepend as? LoadState.Error ?: loadState.refresh as? LoadState.Error errorState?.let { - showError(binding = binding, errorText = it.error.toString()) + showError(motionLayout = motionLayout, errorLayout = errorLayout, errorText = it.error.toString()) + } + if(errorState == null) { + showError(motionLayout = motionLayout, errorLayout = errorLayout, show = false, errorText = "") + } + } +} + +fun launch( + job: Job?, lifecycleScope: LifecycleCoroutineScope, viewModel: FeedViewModel, + pagingDataAdapter: PagingDataAdapter): Job { + // Make sure we cancel the previous job before creating a new one + job?.cancel() + return lifecycleScope.launch { + viewModel.flow().collectLatest { + pagingDataAdapter.submitData(it) } - if (errorState == null) showError(binding = binding, show = false, errorText = "") } + } + /** * Adapter to the show the a [RecyclerView] item for a [LoadState], with a callback to retry if * the retry button is pressed. diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/cachedFeeds/CachedFeedFragment.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/cachedFeeds/CachedFeedFragment.kt index c02eb2f8..a9af376b 100644 --- a/app/src/main/java/com/h/pixeldroid/posts/feeds/cachedFeeds/CachedFeedFragment.kt +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/cachedFeeds/CachedFeedFragment.kt @@ -70,7 +70,8 @@ open class CachedFeedFragment : BaseFragment() { binding = FragmentFeedBinding.inflate(layoutInflater) - initAdapter(binding, adapter) + initAdapter(binding.progressBar, binding.swipeRefreshLayout, + binding.list, binding.motionLayout, binding.errorLayout, adapter) //binding.progressBar.visibility = View.GONE binding.swipeRefreshLayout.setOnRefreshListener { diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/UncachedFeedFragment.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/UncachedFeedFragment.kt index f49343d0..1e48bf16 100644 --- a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/UncachedFeedFragment.kt +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/UncachedFeedFragment.kt @@ -9,9 +9,9 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.paging.* import androidx.recyclerview.widget.RecyclerView +import com.h.pixeldroid.posts.feeds.launch import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @@ -30,20 +30,17 @@ open class UncachedFeedFragment : BaseFragment() { internal lateinit var viewModel: FeedViewModel internal lateinit var adapter: PagingDataAdapter - private lateinit var binding: FragmentFeedBinding + lateinit var binding: FragmentFeedBinding private var job: Job? = null internal fun launch() { - // Make sure we cancel the previous job before creating a new one - job?.cancel() - job = lifecycleScope.launch { - viewModel.flow().collectLatest { - adapter.submitData(it) - } - } + @Suppress("UNCHECKED_CAST") + job = launch(job, lifecycleScope, + viewModel as FeedViewModel, + adapter as PagingDataAdapter) } internal fun initSearch() { @@ -67,7 +64,8 @@ open class UncachedFeedFragment : BaseFragment() { binding = FragmentFeedBinding.inflate(layoutInflater) - initAdapter(binding, adapter) + initAdapter(binding.progressBar, binding.swipeRefreshLayout, binding.list, + binding.motionLayout, binding.errorLayout, adapter) binding.swipeRefreshLayout.setOnRefreshListener { //It shouldn't be necessary to also retry() in addition to refresh(), diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileContentRepository.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileContentRepository.kt new file mode 100644 index 00000000..292c8b36 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfileContentRepository.kt @@ -0,0 +1,34 @@ +package com.h.pixeldroid.posts.feeds.uncachedFeeds.profile + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository +import com.h.pixeldroid.utils.api.PixelfedAPI +import com.h.pixeldroid.utils.api.objects.Status +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class ProfileContentRepository @ExperimentalPagingApi +@Inject constructor( + private val api: PixelfedAPI, + private val accessToken: String, + private val accountId: String +) : UncachedContentRepository { + override fun getStream(): Flow> { + return Pager( + config = PagingConfig( + initialLoadSize = NETWORK_PAGE_SIZE, + pageSize = NETWORK_PAGE_SIZE, + enablePlaceholders = false), + pagingSourceFactory = { + ProfilePagingSource(api, accessToken, accountId) + } + ).flow + } + + companion object { + private const val NETWORK_PAGE_SIZE = 20 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfilePagingSource.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfilePagingSource.kt new file mode 100644 index 00000000..b6f6cbb4 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/profile/ProfilePagingSource.kt @@ -0,0 +1,37 @@ +package com.h.pixeldroid.posts.feeds.uncachedFeeds.profile + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.h.pixeldroid.utils.api.PixelfedAPI +import com.h.pixeldroid.utils.api.objects.Status +import retrofit2.HttpException +import java.io.IOException + +class ProfilePagingSource( + private val api: PixelfedAPI, + private val accessToken: String, + private val accountId: String +) : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { + val position = params.key + return try { + val posts = api.accountPosts("Bearer $accessToken", + account_id = accountId, + max_id = position, + limit = params.loadSize + ) + + LoadResult.Page( + data = posts, + prevKey = null, + nextKey = posts.lastOrNull()?.id + ) + } catch (exception: HttpException) { + LoadResult.Error(exception) + } catch (exception: IOException) { + LoadResult.Error(exception) + } + } + + override fun getRefreshKey(state: PagingState): String? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/search/SearchAccountFragment.kt b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/search/SearchAccountFragment.kt index c11fc275..68ace7cc 100644 --- a/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/search/SearchAccountFragment.kt +++ b/app/src/main/java/com/h/pixeldroid/posts/feeds/uncachedFeeds/search/SearchAccountFragment.kt @@ -45,8 +45,7 @@ class SearchAccountFragment : UncachedFeedFragment() { query ) ) - ) - .get(FeedViewModel::class.java) as FeedViewModel + ).get(FeedViewModel::class.java) as FeedViewModel launch() initSearch() diff --git a/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt b/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt index 753cc467..4979e4d1 100644 --- a/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/profile/ProfileActivity.kt @@ -1,44 +1,61 @@ package com.h.pixeldroid.profile import android.content.Intent -import android.graphics.Typeface import android.os.Bundle import android.util.Log +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.widget.* -import androidx.annotation.StringRes -import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat +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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.google.android.material.snackbar.Snackbar import com.h.pixeldroid.R import com.h.pixeldroid.databinding.ActivityProfileBinding +import com.h.pixeldroid.databinding.FragmentProfilePostsBinding +import com.h.pixeldroid.posts.PostActivity +import com.h.pixeldroid.posts.feeds.initAdapter +import com.h.pixeldroid.posts.feeds.launch +import com.h.pixeldroid.posts.feeds.uncachedFeeds.FeedViewModel +import com.h.pixeldroid.posts.feeds.uncachedFeeds.UncachedContentRepository +import com.h.pixeldroid.posts.feeds.uncachedFeeds.profile.ProfileContentRepository import com.h.pixeldroid.posts.parseHTMLText import com.h.pixeldroid.utils.BaseActivity import com.h.pixeldroid.utils.ImageConverter import com.h.pixeldroid.utils.api.PixelfedAPI import com.h.pixeldroid.utils.api.objects.Account +import com.h.pixeldroid.utils.api.objects.FeedContent import com.h.pixeldroid.utils.api.objects.Status import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity import com.h.pixeldroid.utils.openUrl +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import retrofit2.Call -import retrofit2.Callback import retrofit2.HttpException -import retrofit2.Response import java.io.IOException class ProfileActivity : BaseActivity() { + private lateinit var pixelfedAPI : PixelfedAPI - private lateinit var adapter : ProfilePostsRecyclerViewAdapter private lateinit var accessToken : String private lateinit var domain : String - private var user: UserDatabaseEntity? = null - + private lateinit var accountId : String private lateinit var binding: ActivityProfileBinding + private lateinit var profileAdapter: PagingDataAdapter + private lateinit var viewModel: FeedViewModel + private var user: UserDatabaseEntity? = null + private var job: Job? = null + + @ExperimentalPagingApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityProfileBinding.inflate(layoutInflater) @@ -52,19 +69,49 @@ class ProfileActivity : BaseActivity() { pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) accessToken = user?.accessToken.orEmpty() - // Set posts RecyclerView as a grid with 3 columns - binding.profilePostsRecyclerView.layoutManager = GridLayoutManager(applicationContext, 3) - adapter = ProfilePostsRecyclerViewAdapter() - binding.profilePostsRecyclerView.adapter = adapter - // Set profile according to given account val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account? + accountId = account?.id ?: user!!.user_id - setContent(account) + // get the view model + @Suppress("UNCHECKED_CAST") + viewModel = ViewModelProvider(this, ProfileViewModelFactory( + ProfileContentRepository( + apiHolder.setDomainToCurrentUser(db), + db.userDao().getActiveUser()!!.accessToken, + accountId + ) + ) + ).get(FeedViewModel::class.java) as FeedViewModel + + profileAdapter = ProfilePostsAdapter() + initAdapter(binding.profileProgressBar, binding.profileRefreshLayout, + binding.profilePostsRecyclerView, binding.motionLayout, binding.profileErrorLayout, + profileAdapter) + + binding.profilePostsRecyclerView.layoutManager = GridLayoutManager(this, 3) binding.profileRefreshLayout.setOnRefreshListener { - getAndSetAccount(account?.id ?: user!!.user_id) + profileAdapter.refresh() } + + setContent(account) + @Suppress("UNCHECKED_CAST") + job = launch(job, lifecycleScope, viewModel as FeedViewModel, + profileAdapter as PagingDataAdapter) + } + + /** + * Shows or hides the error in the different FeedFragments + */ + private fun showError(errorText: String = "Something went wrong while loading", show: Boolean = true){ + if(show){ + binding.profileProgressBar.visibility = View.GONE + binding.motionLayout.transitionToEnd() + } else if(binding.motionLayout.progress == 1F) { + binding.motionLayout.transitionToStart() + } + binding.profileRefreshLayout.isRefreshing = false } override fun onSupportNavigateUp(): Boolean { @@ -73,9 +120,8 @@ class ProfileActivity : BaseActivity() { } private fun setContent(account: Account?) { - if(account != null){ + if(account != null) { setViews(account) - setPosts(account) } else { lifecycleScope.launchWhenResumed { val myAccount: Account = try { @@ -87,16 +133,16 @@ class ProfileActivity : BaseActivity() { return@launchWhenResumed showError() } setViews(myAccount) - // Populate profile page with user's posts - setPosts(myAccount) } } - //if we aren't viewing our own account, activate follow button - if(account != null && account.id != user?.user_id) activateFollow(account) - //if we *are* viewing our own account, activate the edit button - else activateEditButton() - + if(account != null && account.id != user?.user_id) { + //if we aren't viewing our own account, activate follow button + activateFollow(account) + } else { + //if we *are* viewing our own account, activate the edit button + activateEditButton() + } // On click open followers list binding.nbFollowersTextView.setOnClickListener{ onClickFollowers(account) } @@ -104,31 +150,6 @@ class ProfileActivity : BaseActivity() { binding.nbFollowingTextView.setOnClickListener{ onClickFollowing(account) } } - private fun getAndSetAccount(id: String){ - lifecycleScope.launchWhenCreated { - val account = try{ - pixelfedAPI.getAccount("Bearer $accessToken", id) - } catch (exception: IOException) { - Log.e("ProfileActivity:", exception.toString()) - return@launchWhenCreated showError() - } catch (exception: HttpException) { - return@launchWhenCreated showError() - } - setContent(account) - } - } - - private fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){ - val motionLayout = binding.motionLayout - if(show){ - motionLayout.transitionToEnd() - } else { - motionLayout.transitionToStart() - } - binding.profileProgressBar.visibility = View.GONE - binding.profileRefreshLayout.isRefreshing = false - } - /** * Populate profile page with user's data */ @@ -151,7 +172,7 @@ class ProfileActivity : BaseActivity() { binding.accountNameTextView.text = displayName supportActionBar?.title = displayName - if(displayName != "@${account.acct}"){ + if(displayName != "@${account.acct}") { supportActionBar?.subtitle = "@${account.acct}" } @@ -174,37 +195,13 @@ class ProfileActivity : BaseActivity() { ) } - /** - * Populate profile page with user's posts - */ - private fun setPosts(account: Account) { - pixelfedAPI.accountPosts("Bearer $accessToken", account_id = account.id) - .enqueue(object : Callback> { - - override fun onFailure(call: Call>, t: Throwable) { - showError() - Log.e("ProfileActivity.Posts:", t.toString()) - } - - override fun onResponse( - call: Call>, - response: Response> - ) { - if (response.code() == 200) { - val statuses = response.body()!! - adapter.addPosts(statuses) - showError(show = false) - } else { - showError() - } - } - }) - } - private fun onClickEditButton() { val url = "$domain/settings/home" - if (!openUrl(url)) Log.e("ProfileActivity", "Cannot open this link") + if(!openUrl(url)) { + Snackbar.make(binding.root, getString(R.string.edit_link_failed), + Snackbar.LENGTH_LONG).show() + } } private fun onClickFollowers(account: Account?) { @@ -315,4 +312,89 @@ class ProfileActivity : BaseActivity() { } } } -} \ No newline at end of file +} + + +class ProfileViewModelFactory @ExperimentalPagingApi constructor( + private val searchContentRepository: UncachedContentRepository +) : ViewModelProvider.Factory { + + @ExperimentalPagingApi + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(FeedViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return FeedViewModel(searchContentRepository) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} + + +class ProfilePostsViewHolder(binding: FragmentProfilePostsBinding) : RecyclerView.ViewHolder(binding.root) { + private val postPreview: ImageView = binding.postPreview + private val albumIcon: ImageView = binding.albumIcon + + fun bind(post: Status) { + + if(post.sensitive!!) { + ImageConverter.setSquareImageFromDrawable( + itemView, + AppCompatResources.getDrawable(itemView.context, R.drawable.ic_sensitive), + postPreview + ) + } else { + ImageConverter.setSquareImageFromURL(itemView, post.getPostPreviewURL(), postPreview) + } + + if(post.media_attachments?.size ?: 0 > 1) { + albumIcon.visibility = View.VISIBLE + } else { + albumIcon.visibility = View.GONE + } + + postPreview.setOnClickListener { + val intent = Intent(postPreview.context, PostActivity::class.java) + intent.putExtra(Status.POST_TAG, post) + postPreview.context.startActivity(intent) + } + } + + companion object { + fun create(parent: ViewGroup): ProfilePostsViewHolder { + val itemBinding = FragmentProfilePostsBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + return ProfilePostsViewHolder(itemBinding) + } + } +} + + +class ProfilePostsAdapter : PagingDataAdapter( + UIMODEL_COMPARATOR +) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return ProfilePostsViewHolder.create(parent) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val post = getItem(position) + + post?.let { + (holder as ProfilePostsViewHolder).bind(it) + } + } + + companion object { + private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean = + oldItem.content == newItem.content + } + } + +} diff --git a/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt b/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt index 6a970f05..ce9a8d6e 100644 --- a/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/h/pixeldroid/profile/ProfilePostsRecyclerViewAdapter.kt @@ -34,12 +34,17 @@ class ProfilePostsRecyclerViewAdapter: RecyclerView.Adapter 1){ + if(post.media_attachments?.size ?: 0 > 1) { holder.albumIcon.visibility = View.VISIBLE } else { holder.albumIcon.visibility = View.GONE @@ -55,6 +60,7 @@ class ProfilePostsRecyclerViewAdapter: RecyclerView.Adapter> + suspend fun accountPosts( + @Header("Authorization") authorization: String, + @Path("id") account_id: String, + @Query("min_id") min_id: String? = null, + @Query("max_id") max_id: String?, + @Query("limit") limit: Int + ) : List @GET("/api/v1/accounts/relationships") suspend fun checkRelationships( diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml index de0e7b41..2ebc9cac 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -18,27 +18,26 @@ + tools:srcCompat="@tools:sample/avatars" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/descriptionTextView" /> @@ -85,10 +86,9 @@ android:layout_marginLeft="20dp" android:layout_marginTop="5dp" android:layout_marginRight="20dp" - android:layout_marginBottom="10dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/accountNameTextView"/> + app:layout_constraintTop_toBottomOf="@id/accountNameTextView" />