Account Activity enhancements (#1196)
* use the "follow" button as an "unblock" button on the profiles of blocked users * use the "follow" button as an "unblock" button on the profiles of blocked users * add an icon to the profiles that can be clicked to mute/unmute the user * add an icon to the profiles that can be clicked to mute/unmute the user * Fix view issues * Fix view issues * Implement swipe to refresh for Account layout * Implement swipe to refresh handler at the account screen * Implement swipe to refresh * Correct account refresh * Show Progress Bar * Show Progress Bar * Move "itSelf" check into the viewModel * Change methods access level * Change TimelineFragment newInstance overload * Change avatarSize type to Float * Replace ImageButton with MaterialButton * Update account activity swipe to refresh colors * Refactor code * Refactor code * Fix crash on moved account refresh * Show moved account stats * Update mute button behaviour * Show tabs and content for moved accounts * Fix crash on tablet
This commit is contained in:
parent
2cd25b6ce0
commit
ae5d8b8633
13 changed files with 890 additions and 580 deletions
|
@ -64,7 +64,7 @@ import javax.inject.Inject
|
|||
class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportFragmentInjector, LinkListener {
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<androidx.fragment.app.Fragment>
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
|
@ -72,12 +72,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
|
||||
private val accountFieldAdapter = AccountFieldAdapter(this)
|
||||
|
||||
private lateinit var accountId: String
|
||||
private var followState: FollowState = FollowState.NOT_FOLLOWING
|
||||
private var blocking: Boolean = false
|
||||
private var muting: Boolean = false
|
||||
private var showingReblogs: Boolean = false
|
||||
private var isSelf: Boolean = false
|
||||
private var loadedAccount: Account? = null
|
||||
|
||||
// fields for scroll animation
|
||||
|
@ -95,7 +93,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
private var textColorPrimary: Int = 0
|
||||
@ColorInt
|
||||
private var textColorSecondary: Int = 0
|
||||
@Px
|
||||
|
||||
private var avatarSize: Float = 0f
|
||||
@Px
|
||||
private var titleVisibleHeight: Int = 0
|
||||
|
@ -106,46 +104,118 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
REQUESTED
|
||||
}
|
||||
|
||||
private var adapter: AccountPagerAdapter? = null
|
||||
private lateinit var adapter: AccountPagerAdapter
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
makeNotificationBarTransparent()
|
||||
setContentView(R.layout.activity_account)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, viewModelFactory)[AccountViewModel::class.java]
|
||||
|
||||
viewModel.accountData.observe(this, Observer<Resource<Account>> {
|
||||
when (it) {
|
||||
is Success -> onAccountChanged(it.data)
|
||||
is Error -> {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { reload() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
viewModel.relationshipData.observe(this, Observer<Resource<Relationship>> {
|
||||
val relation = it?.data
|
||||
if (relation != null) {
|
||||
onRelationshipChanged(relation)
|
||||
// Obtain information to fill out the profile.
|
||||
viewModel.setAccountInfo(intent.getStringExtra(KEY_ACCOUNT_ID))
|
||||
|
||||
if (viewModel.isSelf) {
|
||||
updateButtons()
|
||||
}
|
||||
|
||||
if (it is Error) {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { reload() }
|
||||
.show()
|
||||
hideFab = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fabHide", false)
|
||||
|
||||
loadResources()
|
||||
setupToolbar()
|
||||
setupTabs()
|
||||
setupAccountViews()
|
||||
setupRefreshLayout()
|
||||
subscribeObservables()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load colors and dimensions from resources
|
||||
*/
|
||||
private fun loadResources() {
|
||||
toolbarColor = ThemeUtils.getColor(this, R.attr.toolbar_background_color)
|
||||
backgroundColor = ThemeUtils.getColor(this, android.R.attr.colorBackground)
|
||||
statusBarColorTransparent = ContextCompat.getColor(this, R.color.header_background_filter)
|
||||
statusBarColorOpaque = ThemeUtils.getColor(this, R.attr.colorPrimaryDark)
|
||||
textColorPrimary = ThemeUtils.getColor(this, android.R.attr.textColorPrimary)
|
||||
textColorSecondary = ThemeUtils.getColor(this, android.R.attr.textColorSecondary)
|
||||
avatarSize = resources.getDimension(R.dimen.account_activity_avatar_size)
|
||||
titleVisibleHeight = resources.getDimensionPixelSize(R.dimen.account_activity_scroll_title_visible_height)
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup account widgets visibility and actions
|
||||
*/
|
||||
private fun setupAccountViews() {
|
||||
// Initialise the default UI states.
|
||||
accountFloatingActionButton.hide()
|
||||
accountFollowButton.hide()
|
||||
accountMuteButton.hide()
|
||||
accountFollowsYouTextView.hide()
|
||||
|
||||
|
||||
// setup the RecyclerView for the account fields
|
||||
accountFieldList.isNestedScrollingEnabled = false
|
||||
accountFieldList.layoutManager = LinearLayoutManager(this)
|
||||
accountFieldList.adapter = accountFieldAdapter
|
||||
|
||||
|
||||
val accountListClickListener = { v: View ->
|
||||
val type = when (v.id) {
|
||||
R.id.accountFollowers -> AccountListActivity.Type.FOLLOWERS
|
||||
R.id.accountFollowing -> AccountListActivity.Type.FOLLOWS
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
val accountListIntent = AccountListActivity.newIntent(this, type, viewModel.accountId)
|
||||
startActivityWithSlideInAnimation(accountListIntent)
|
||||
}
|
||||
accountFollowers.setOnClickListener(accountListClickListener)
|
||||
accountFollowing.setOnClickListener(accountListClickListener)
|
||||
|
||||
accountStatuses.setOnClickListener {
|
||||
// Make nice ripple effect on tab
|
||||
accountTabLayout.getTabAt(0)!!.select()
|
||||
val poorTabView = (accountTabLayout.getChildAt(0) as ViewGroup).getChildAt(0)
|
||||
poorTabView.isPressed = true
|
||||
accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Init timeline tabs
|
||||
*/
|
||||
private fun setupTabs() {
|
||||
// Setup the tabs and timeline pager.
|
||||
adapter = AccountPagerAdapter(supportFragmentManager, viewModel.accountId)
|
||||
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media))
|
||||
adapter.setPageTitles(pageTitles)
|
||||
accountFragmentViewPager.pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
|
||||
val pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
|
||||
R.drawable.tab_page_margin_dark)
|
||||
accountFragmentViewPager.setPageMarginDrawable(pageMarginDrawable)
|
||||
accountFragmentViewPager.adapter = adapter
|
||||
accountFragmentViewPager.offscreenPageLimit = 2
|
||||
accountTabLayout.setupWithViewPager(accountFragmentViewPager)
|
||||
accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||
tab?.position?.let { position ->
|
||||
(adapter.getFragment(position) as? ReselectableFragment)?.onReselect()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
val decorView = window.decorView
|
||||
decorView.systemUiVisibility = decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
|
||||
setContentView(R.layout.activity_account)
|
||||
|
||||
val intent = intent
|
||||
accountId = intent.getStringExtra(KEY_ACCOUNT_ID)
|
||||
|
||||
/**
|
||||
* Setup toolbar
|
||||
*/
|
||||
private fun setupToolbar() {
|
||||
// set toolbar top margin according to system window insets
|
||||
accountCoordinatorLayout.setOnApplyWindowInsetsListener { _, insets ->
|
||||
val top = insets.systemWindowInsetTop
|
||||
|
@ -162,17 +232,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
hideFab = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("fabHide", false)
|
||||
|
||||
toolbarColor = ThemeUtils.getColor(this, R.attr.toolbar_background_color)
|
||||
backgroundColor = ThemeUtils.getColor(this, android.R.attr.colorBackground)
|
||||
statusBarColorTransparent = ContextCompat.getColor(this, R.color.header_background_filter)
|
||||
statusBarColorOpaque = ThemeUtils.getColor(this, R.attr.colorPrimaryDark)
|
||||
textColorPrimary = ThemeUtils.getColor(this, android.R.attr.textColorPrimary)
|
||||
textColorSecondary = ThemeUtils.getColor(this, android.R.attr.textColorSecondary)
|
||||
avatarSize = resources.getDimensionPixelSize(R.dimen.account_activity_avatar_size).toFloat()
|
||||
titleVisibleHeight = resources.getDimensionPixelSize(R.dimen.account_activity_scroll_title_visible_height)
|
||||
|
||||
ThemeUtils.setDrawableTint(this, accountToolbar.navigationIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
|
||||
ThemeUtils.setDrawableTint(this, accountToolbar.overflowIcon, R.attr.account_toolbar_icon_tint_uncollapsed)
|
||||
|
||||
|
@ -201,7 +260,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
ThemeUtils.setDrawableTint(context, accountToolbar.overflowIcon, attribute)
|
||||
}
|
||||
|
||||
if (hideFab && !isSelf && !blocking) {
|
||||
if (hideFab && !viewModel.isSelf && !blocking) {
|
||||
if (verticalOffset > oldOffset) {
|
||||
accountFloatingActionButton.show()
|
||||
}
|
||||
|
@ -228,99 +287,98 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
accountToolbar.setBackgroundColor(evaluatedToolbarColor)
|
||||
accountHeaderInfoContainer.setBackgroundColor(evaluatedTabBarColor)
|
||||
accountTabLayout.setBackgroundColor(evaluatedTabBarColor)
|
||||
swipeToRefreshLayout.isEnabled = verticalOffset == 0
|
||||
}
|
||||
})
|
||||
|
||||
// Initialise the default UI states.
|
||||
accountFloatingActionButton.hide()
|
||||
accountFollowButton.hide()
|
||||
accountFollowsYouTextView.hide()
|
||||
|
||||
// Obtain information to fill out the profile.
|
||||
viewModel.obtainAccount(accountId)
|
||||
|
||||
val activeAccount = accountManager.activeAccount
|
||||
|
||||
if (accountId == activeAccount?.accountId) {
|
||||
isSelf = true
|
||||
updateButtons()
|
||||
} else {
|
||||
isSelf = false
|
||||
viewModel.obtainRelationship(accountId)
|
||||
}
|
||||
|
||||
// setup the RecyclerView for the account fields
|
||||
accountFieldList.isNestedScrollingEnabled = false
|
||||
accountFieldList.layoutManager = LinearLayoutManager(this)
|
||||
accountFieldList.adapter = accountFieldAdapter
|
||||
|
||||
// Setup the tabs and timeline pager.
|
||||
adapter = AccountPagerAdapter(supportFragmentManager, accountId)
|
||||
val pageTitles = arrayOf(getString(R.string.title_statuses), getString(R.string.title_statuses_with_replies), getString(R.string.title_statuses_pinned), getString(R.string.title_media))
|
||||
adapter?.setPageTitles(pageTitles)
|
||||
accountFragmentViewPager.pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
|
||||
val pageMarginDrawable = ThemeUtils.getDrawable(this, R.attr.tab_page_margin_drawable,
|
||||
R.drawable.tab_page_margin_dark)
|
||||
accountFragmentViewPager.setPageMarginDrawable(pageMarginDrawable)
|
||||
accountFragmentViewPager.adapter = adapter
|
||||
accountFragmentViewPager.offscreenPageLimit = 2
|
||||
accountTabLayout.setupWithViewPager(accountFragmentViewPager)
|
||||
accountTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
||||
tab?.position?.let {
|
||||
(adapter?.getFragment(tab.position) as? ReselectableFragment)?.onReselect()
|
||||
}
|
||||
private fun makeNotificationBarTransparent() {
|
||||
val decorView = window.decorView
|
||||
decorView.systemUiVisibility = decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||
/**
|
||||
* Subscribe to data loaded at the view model
|
||||
*/
|
||||
private fun subscribeObservables() {
|
||||
viewModel.accountData.observe(this, Observer<Resource<Account>> {
|
||||
when (it) {
|
||||
is Success -> onAccountChanged(it.data)
|
||||
is Error -> {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
viewModel.relationshipData.observe(this, Observer<Resource<Relationship>> {
|
||||
val relation = it?.data
|
||||
if (relation != null) {
|
||||
onRelationshipChanged(relation)
|
||||
}
|
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {}
|
||||
if (it is Error) {
|
||||
Snackbar.make(accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
}
|
||||
|
||||
})
|
||||
val accountListClickListener = { v: View ->
|
||||
val type = when (v.id) {
|
||||
R.id.accountFollowers -> AccountListActivity.Type.FOLLOWERS
|
||||
R.id.accountFollowing -> AccountListActivity.Type.FOLLOWS
|
||||
else -> throw AssertionError()
|
||||
}
|
||||
val accountListIntent = AccountListActivity.newIntent(this, type, accountId)
|
||||
startActivityWithSlideInAnimation(accountListIntent)
|
||||
}
|
||||
accountFollowers.setOnClickListener(accountListClickListener)
|
||||
accountFollowing.setOnClickListener(accountListClickListener)
|
||||
|
||||
accountStatuses.setOnClickListener {
|
||||
// Make nice ripple effect on tab
|
||||
accountTabLayout.getTabAt(0)!!.select()
|
||||
val poorTabView = (accountTabLayout.getChildAt(0) as ViewGroup).getChildAt(0)
|
||||
poorTabView.isPressed = true
|
||||
accountTabLayout.postDelayed({ poorTabView.isPressed = false }, 300)
|
||||
/**
|
||||
* Setup swipe to refresh layout
|
||||
*/
|
||||
private fun setupRefreshLayout() {
|
||||
swipeToRefreshLayout.setOnRefreshListener {
|
||||
viewModel.refresh()
|
||||
adapter.refreshContent()
|
||||
}
|
||||
viewModel.isRefreshing.observe(this, Observer { isRefreshing ->
|
||||
swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
})
|
||||
swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
swipeToRefreshLayout.setProgressBackgroundColorSchemeColor(ThemeUtils.getColor(this,
|
||||
android.R.attr.colorBackground))
|
||||
}
|
||||
|
||||
private fun onAccountChanged(account: Account?) {
|
||||
if (account != null) {
|
||||
loadedAccount = account
|
||||
loadedAccount = account ?: return
|
||||
|
||||
val usernameFormatted = getString(R.string.status_username_format, account.username)
|
||||
accountUsernameTextView.text = usernameFormatted
|
||||
accountDisplayNameTextView.text = CustomEmojiHelper.emojifyString(account.name, account.emojis, accountDisplayNameTextView)
|
||||
if (supportActionBar != null) {
|
||||
try {
|
||||
supportActionBar?.title = EmojiCompat.get().process(account.name)
|
||||
} catch (e: IllegalStateException) {
|
||||
supportActionBar?.title = account.name
|
||||
}
|
||||
|
||||
val subtitle = String.format(getString(R.string.status_username_format),
|
||||
account.username)
|
||||
supportActionBar?.subtitle = subtitle
|
||||
}
|
||||
val emojifiedNote = CustomEmojiHelper.emojifyText(account.note, account.emojis, accountNoteTextView)
|
||||
LinkHelper.setClickableText(accountNoteTextView, emojifiedNote, null, this)
|
||||
|
||||
accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
accountFieldAdapter.emojis = account.emojis ?: emptyList()
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
|
||||
|
||||
accountLockedImageView.visible(account.locked)
|
||||
accountBadgeTextView.visible(account.bot)
|
||||
|
||||
updateAccountAvatar()
|
||||
updateToolbar()
|
||||
updateMovedAccount()
|
||||
updateRemoteAccount()
|
||||
updateAccountStats()
|
||||
|
||||
accountMuteButton.setOnClickListener {
|
||||
viewModel.changeMuteState()
|
||||
updateMuteButton()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load account's avatar and header image
|
||||
*/
|
||||
private fun updateAccountAvatar() {
|
||||
loadedAccount?.let { account ->
|
||||
Glide.with(this)
|
||||
.load(account.avatar)
|
||||
.placeholder(R.drawable.avatar_default)
|
||||
|
@ -330,6 +388,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
.centerCrop()
|
||||
.into(accountHeaderImageView)
|
||||
|
||||
|
||||
accountAvatarImageView.setOnClickListener { avatarView ->
|
||||
val intent = ViewMediaActivity.newAvatarIntent(avatarView.context, account.avatar)
|
||||
|
||||
|
@ -338,18 +397,33 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
|
||||
startActivity(intent, options.toBundle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accountFieldAdapter.fields = account.fields ?: emptyList()
|
||||
accountFieldAdapter.emojis = account.emojis ?: emptyList()
|
||||
accountFieldAdapter.notifyDataSetChanged()
|
||||
/**
|
||||
* Update toolbar views for loaded account
|
||||
*/
|
||||
private fun updateToolbar() {
|
||||
loadedAccount?.let { account ->
|
||||
try {
|
||||
supportActionBar?.title = EmojiCompat.get().process(account.name)
|
||||
} catch (e: IllegalStateException) {
|
||||
supportActionBar?.title = account.name
|
||||
}
|
||||
supportActionBar?.subtitle = String.format(getString(R.string.status_username_format), account.username)
|
||||
}
|
||||
}
|
||||
|
||||
if (account.moved != null) {
|
||||
val movedAccount = account.moved
|
||||
/**
|
||||
* Update moved account info
|
||||
*/
|
||||
private fun updateMovedAccount() {
|
||||
loadedAccount?.moved?.let { movedAccount ->
|
||||
|
||||
accountMovedView.show()
|
||||
accountMovedView?.show()
|
||||
|
||||
// necessary because accountMovedView is now replaced in layout hierachy
|
||||
findViewById<View>(R.id.accountMovedView).setOnClickListener {
|
||||
findViewById<View>(R.id.accountMovedViewLayout).setOnClickListener {
|
||||
onViewAccount(movedAccount.id)
|
||||
}
|
||||
|
||||
|
@ -369,21 +443,29 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
movedIcon?.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
||||
|
||||
accountMovedText.setCompoundDrawablesRelativeWithIntrinsicBounds(movedIcon, null, null, null)
|
||||
|
||||
accountFollowers.hide()
|
||||
accountFollowing.hide()
|
||||
accountStatuses.hide()
|
||||
accountTabLayout.hide()
|
||||
accountFragmentViewPager.hide()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is account remote and update info if so
|
||||
*/
|
||||
private fun updateRemoteAccount() {
|
||||
loadedAccount?.let { account ->
|
||||
if (account.isRemote()) {
|
||||
accountRemoveView.show()
|
||||
accountRemoveView.setOnClickListener {
|
||||
LinkHelper.openLink(account.url, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update account stat info
|
||||
*/
|
||||
private fun updateAccountStats() {
|
||||
loadedAccount?.let { account ->
|
||||
val numberFormat = NumberFormat.getNumberInstance()
|
||||
accountFollowersTextView.text = numberFormat.format(account.followersCount)
|
||||
accountFollowingTextView.text = numberFormat.format(account.followingCount)
|
||||
|
@ -392,19 +474,25 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
accountFloatingActionButton.setOnClickListener { mention() }
|
||||
|
||||
accountFollowButton.setOnClickListener {
|
||||
if (isSelf) {
|
||||
if (viewModel.isSelf) {
|
||||
val intent = Intent(this@AccountActivity, EditProfileActivity::class.java)
|
||||
startActivity(intent)
|
||||
return@setOnClickListener
|
||||
}
|
||||
when (followState) {
|
||||
AccountActivity.FollowState.NOT_FOLLOWING -> {
|
||||
viewModel.changeFollowState(accountId)
|
||||
|
||||
if (blocking) {
|
||||
viewModel.changeBlockState()
|
||||
return@setOnClickListener
|
||||
}
|
||||
AccountActivity.FollowState.REQUESTED -> {
|
||||
|
||||
when (followState) {
|
||||
FollowState.NOT_FOLLOWING -> {
|
||||
viewModel.changeFollowState()
|
||||
}
|
||||
FollowState.REQUESTED -> {
|
||||
showFollowRequestPendingDialog()
|
||||
}
|
||||
AccountActivity.FollowState.FOLLOWING -> {
|
||||
FollowState.FOLLOWING -> {
|
||||
showUnfollowWarningDialog()
|
||||
}
|
||||
}
|
||||
|
@ -413,11 +501,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putString(KEY_ACCOUNT_ID, accountId)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
private fun onRelationshipChanged(relation: Relationship) {
|
||||
followState = when {
|
||||
relation.following -> FollowState.FOLLOWING
|
||||
|
@ -433,53 +516,67 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
updateButtons()
|
||||
}
|
||||
|
||||
private fun reload() {
|
||||
viewModel.obtainAccount(accountId, true)
|
||||
viewModel.obtainRelationship(accountId)
|
||||
}
|
||||
|
||||
private fun updateFollowButton() {
|
||||
if (isSelf) {
|
||||
if (viewModel.isSelf) {
|
||||
accountFollowButton.setText(R.string.action_edit_own_profile)
|
||||
return
|
||||
}
|
||||
if (blocking) {
|
||||
accountFollowButton.setText(R.string.action_unblock)
|
||||
return
|
||||
}
|
||||
when (followState) {
|
||||
AccountActivity.FollowState.NOT_FOLLOWING -> {
|
||||
FollowState.NOT_FOLLOWING -> {
|
||||
accountFollowButton.setText(R.string.action_follow)
|
||||
}
|
||||
AccountActivity.FollowState.REQUESTED -> {
|
||||
FollowState.REQUESTED -> {
|
||||
accountFollowButton.setText(R.string.state_follow_requested)
|
||||
}
|
||||
AccountActivity.FollowState.FOLLOWING -> {
|
||||
FollowState.FOLLOWING -> {
|
||||
accountFollowButton.setText(R.string.action_unfollow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMuteButton() {
|
||||
if (muting) {
|
||||
accountMuteButton.setIconResource(R.drawable.ic_unmute_24dp)
|
||||
} else {
|
||||
accountMuteButton.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateButtons() {
|
||||
invalidateOptionsMenu()
|
||||
|
||||
if (!blocking && loadedAccount?.moved == null) {
|
||||
if (loadedAccount?.moved == null) {
|
||||
|
||||
accountFollowButton.show()
|
||||
updateFollowButton()
|
||||
|
||||
if (isSelf) {
|
||||
if (blocking || viewModel.isSelf) {
|
||||
accountFloatingActionButton.hide()
|
||||
accountMuteButton.hide()
|
||||
} else {
|
||||
accountFloatingActionButton.show()
|
||||
if (muting)
|
||||
accountMuteButton.show()
|
||||
else
|
||||
accountMuteButton.hide()
|
||||
updateMuteButton()
|
||||
}
|
||||
|
||||
} else {
|
||||
accountFloatingActionButton.hide()
|
||||
accountFollowButton.hide()
|
||||
accountMuteButton.hide()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.account_toolbar, menu)
|
||||
|
||||
if (!isSelf) {
|
||||
if (!viewModel.isSelf) {
|
||||
val follow = menu.findItem(R.id.action_follow)
|
||||
follow.title = if (followState == FollowState.NOT_FOLLOWING) {
|
||||
getString(R.string.action_follow)
|
||||
|
@ -529,7 +626,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
private fun showFollowRequestPendingDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_message_cancel_follow_request)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState(accountId) }
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
@ -537,7 +634,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
private fun showUnfollowWarningDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.dialog_unfollow_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState(accountId) }
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
@ -585,20 +682,20 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
return true
|
||||
}
|
||||
R.id.action_follow -> {
|
||||
viewModel.changeFollowState(accountId)
|
||||
viewModel.changeFollowState()
|
||||
return true
|
||||
}
|
||||
R.id.action_block -> {
|
||||
viewModel.changeBlockState(accountId)
|
||||
viewModel.changeBlockState()
|
||||
return true
|
||||
}
|
||||
R.id.action_mute -> {
|
||||
viewModel.changeMuteState(accountId)
|
||||
viewModel.changeMuteState()
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.action_show_reblogs -> {
|
||||
viewModel.changeShowReblogsState(accountId)
|
||||
viewModel.changeShowReblogsState()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -606,7 +703,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
|||
}
|
||||
|
||||
override fun getActionButton(): FloatingActionButton? {
|
||||
return if (!isSelf && !blocking) {
|
||||
return if (!viewModel.isSelf && !blocking) {
|
||||
accountFloatingActionButton
|
||||
} else null
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment;
|
||||
|
|
|
@ -30,6 +30,7 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasSu
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
|
||||
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment;
|
||||
|
|
|
@ -77,7 +77,7 @@ class NetworkModule {
|
|||
.apply {
|
||||
addInterceptor(InstanceSwitchAuthInterceptor(accountManager))
|
||||
if (BuildConfig.DEBUG) {
|
||||
addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
|
||||
addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
|
|
@ -33,6 +33,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
|
|||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.interfaces.RefreshableFragment
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
|
@ -53,22 +54,26 @@ import javax.inject.Inject
|
|||
* Fragment with multiple columns of media previews for the specified account.
|
||||
*/
|
||||
|
||||
class AccountMediaFragment : BaseFragment(), Injectable {
|
||||
|
||||
class AccountMediaFragment : BaseFragment(), RefreshableFragment, Injectable {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(accountId: String): AccountMediaFragment {
|
||||
fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment {
|
||||
val fragment = AccountMediaFragment()
|
||||
val args = Bundle()
|
||||
args.putString(ACCOUNT_ID_ARG, accountId)
|
||||
args.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,enableSwipeToRefresh)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
|
||||
private const val ACCOUNT_ID_ARG = "account_id"
|
||||
private const val TAG = "AccountMediaFragment"
|
||||
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh"
|
||||
}
|
||||
|
||||
private var isSwipeToRefreshEnabled: Boolean = true
|
||||
private var needToRefresh = false
|
||||
|
||||
@Inject
|
||||
lateinit var api: MastodonApi
|
||||
|
||||
|
@ -78,6 +83,8 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
private var fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||
private var isVisibleToUser: Boolean = false
|
||||
|
||||
private var accountId: String?=null
|
||||
|
||||
private val callback = object : Callback<List<Status>> {
|
||||
override fun onFailure(call: Call<List<Status>>?, t: Throwable?) {
|
||||
fetchingStatus = FetchingStatus.NOT_FETCHING
|
||||
|
@ -85,6 +92,7 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
if (isAdded) {
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
progressBar.visibility = View.GONE
|
||||
topProgressBar?.hide()
|
||||
statusView.show()
|
||||
if (t is IOException) {
|
||||
statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
|
@ -105,6 +113,7 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
if (isAdded) {
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
progressBar.visibility = View.GONE
|
||||
topProgressBar?.hide()
|
||||
|
||||
val body = response.body()
|
||||
body?.let { fetched ->
|
||||
|
@ -115,6 +124,8 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
result.addAll(AttachmentViewData.list(status))
|
||||
}
|
||||
adapter.addTop(result)
|
||||
if (result.isNotEmpty())
|
||||
recyclerView.scrollToPosition(0)
|
||||
|
||||
if (statuses.isEmpty()) {
|
||||
statusView.show()
|
||||
|
@ -152,6 +163,11 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
isSwipeToRefreshEnabled = arguments?.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,true)==true
|
||||
accountId = arguments?.getString(ACCOUNT_ID_ARG)
|
||||
}
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_timeline, container, false)
|
||||
|
@ -171,24 +187,15 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
val accountId = arguments?.getString(ACCOUNT_ID_ARG)
|
||||
|
||||
|
||||
if (isSwipeToRefreshEnabled) {
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
statusView.hide()
|
||||
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return@setOnRefreshListener
|
||||
currentCall = if (statuses.isEmpty()) {
|
||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||
api.accountStatuses(accountId, null, null, null, null, true, null)
|
||||
} else {
|
||||
fetchingStatus = FetchingStatus.REFRESHING
|
||||
api.accountStatuses(accountId, null, statuses[0].id, null, null, true, null)
|
||||
}
|
||||
currentCall?.enqueue(callback)
|
||||
|
||||
refresh()
|
||||
}
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ThemeUtils.getColor(view.context, android.R.attr.colorBackground))
|
||||
|
||||
}
|
||||
statusView.visibility = View.GONE
|
||||
|
||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
|
@ -212,6 +219,22 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
if (isVisibleToUser) doInitialLoadingIfNeeded()
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
statusView.hide()
|
||||
if (fetchingStatus != FetchingStatus.NOT_FETCHING) return
|
||||
currentCall = if (statuses.isEmpty()) {
|
||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||
api.accountStatuses(accountId, null, null, null, null, true, null)
|
||||
} else {
|
||||
fetchingStatus = FetchingStatus.REFRESHING
|
||||
api.accountStatuses(accountId, null, statuses[0].id, null, null, true, null)
|
||||
}
|
||||
currentCall?.enqueue(callback)
|
||||
|
||||
if (!isSwipeToRefreshEnabled)
|
||||
topProgressBar?.show()
|
||||
}
|
||||
|
||||
// That's sort of an optimization to only load media once user has opened the tab
|
||||
// Attention: can be called before *any* lifecycle method!
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
|
@ -224,12 +247,14 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
if (isAdded) {
|
||||
statusView.hide()
|
||||
}
|
||||
val accountId = arguments?.getString(ACCOUNT_ID_ARG)
|
||||
if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) {
|
||||
fetchingStatus = FetchingStatus.INITIAL_FETCHING
|
||||
currentCall = api.accountStatuses(accountId, null, null, null, null, true, null)
|
||||
currentCall?.enqueue(callback)
|
||||
}
|
||||
else if (needToRefresh)
|
||||
refresh()
|
||||
needToRefresh = false
|
||||
}
|
||||
|
||||
private fun viewMedia(items: List<AttachmentViewData>, currentIndex: Int, view: View?) {
|
||||
|
@ -321,4 +346,13 @@ class AccountMediaFragment : BaseFragment(), Injectable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun refreshContent() {
|
||||
if (isAdded)
|
||||
refresh()
|
||||
else
|
||||
needToRefresh = true
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -48,6 +48,7 @@ import com.keylesspalace.tusky.entity.Filter;
|
|||
import com.keylesspalace.tusky.entity.Poll;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity;
|
||||
import com.keylesspalace.tusky.interfaces.RefreshableFragment;
|
||||
import com.keylesspalace.tusky.interfaces.ReselectableFragment;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.network.MastodonApi;
|
||||
|
@ -82,6 +83,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.arch.core.util.Function;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.core.widget.ContentLoadingProgressBar;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig;
|
||||
import androidx.recyclerview.widget.AsyncListDiffer;
|
||||
|
@ -92,6 +94,7 @@ import androidx.recyclerview.widget.ListUpdateCallback;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import at.connyduck.sparkbutton.helpers.Utils;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
@ -108,12 +111,15 @@ import static com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvid
|
|||
public class TimelineFragment extends SFragment implements
|
||||
SwipeRefreshLayout.OnRefreshListener,
|
||||
StatusActionListener,
|
||||
Injectable, ReselectableFragment {
|
||||
Injectable, ReselectableFragment, RefreshableFragment {
|
||||
private static final String TAG = "TimelineF"; // logging tag
|
||||
private static final String KIND_ARG = "kind";
|
||||
private static final String HASHTAG_OR_ID_ARG = "hashtag_or_id";
|
||||
private static final String ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh";
|
||||
|
||||
private static final int LOAD_AT_ONCE = 30;
|
||||
private boolean isSwipeToRefreshEnabled = true;
|
||||
private boolean isNeedRefresh;
|
||||
|
||||
public enum Kind {
|
||||
HOME,
|
||||
|
@ -146,6 +152,7 @@ public class TimelineFragment extends SFragment implements
|
|||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private RecyclerView recyclerView;
|
||||
private ProgressBar progressBar;
|
||||
private ContentLoadingProgressBar topProgressBar;
|
||||
private BackgroundMessageView statusView;
|
||||
|
||||
private TimelineAdapter adapter;
|
||||
|
@ -182,18 +189,19 @@ public class TimelineFragment extends SFragment implements
|
|||
});
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind) {
|
||||
TimelineFragment fragment = new TimelineFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(KIND_ARG, kind.name());
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
return newInstance(kind, null);
|
||||
}
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind, String hashtagOrId) {
|
||||
public static TimelineFragment newInstance(Kind kind, @Nullable String hashtagOrId) {
|
||||
return newInstance(kind, hashtagOrId, true);
|
||||
}
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind, @Nullable String hashtagOrId, boolean enableSwipeToRefresh) {
|
||||
TimelineFragment fragment = new TimelineFragment();
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString(KIND_ARG, kind.name());
|
||||
arguments.putString(HASHTAG_OR_ID_ARG, hashtagOrId);
|
||||
arguments.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, enableSwipeToRefresh);
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
@ -213,6 +221,8 @@ public class TimelineFragment extends SFragment implements
|
|||
|
||||
adapter = new TimelineAdapter(dataSource, this);
|
||||
|
||||
isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -224,6 +234,7 @@ public class TimelineFragment extends SFragment implements
|
|||
swipeRefreshLayout = rootView.findViewById(R.id.swipeRefreshLayout);
|
||||
progressBar = rootView.findViewById(R.id.progressBar);
|
||||
statusView = rootView.findViewById(R.id.statusView);
|
||||
topProgressBar = rootView.findViewById(R.id.topProgressBar);
|
||||
|
||||
setupSwipeRefreshLayout();
|
||||
setupRecyclerView();
|
||||
|
@ -236,6 +247,8 @@ public class TimelineFragment extends SFragment implements
|
|||
this.sendInitialRequest();
|
||||
} else {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (isNeedRefresh)
|
||||
onRefresh();
|
||||
}
|
||||
|
||||
return rootView;
|
||||
|
@ -388,12 +401,15 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
|
||||
private void setupSwipeRefreshLayout() {
|
||||
swipeRefreshLayout.setEnabled(isSwipeToRefreshEnabled);
|
||||
if (isSwipeToRefreshEnabled) {
|
||||
Context context = swipeRefreshLayout.getContext();
|
||||
swipeRefreshLayout.setOnRefreshListener(this);
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue);
|
||||
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ThemeUtils.getColor(context,
|
||||
android.R.attr.colorBackground));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView() {
|
||||
recyclerView.setAccessibilityDelegateCompat(
|
||||
|
@ -524,8 +540,10 @@ public class TimelineFragment extends SFragment implements
|
|||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
if (isSwipeToRefreshEnabled)
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
this.statusView.setVisibility(View.GONE);
|
||||
isNeedRefresh = false;
|
||||
if (this.initialUpdateFailed) {
|
||||
updateCurrent();
|
||||
} else {
|
||||
|
@ -936,6 +954,9 @@ public class TimelineFragment extends SFragment implements
|
|||
private void sendFetchTimelineRequest(@Nullable String maxId, @Nullable String sinceId,
|
||||
@Nullable String sinceIdMinusOne,
|
||||
final FetchEnd fetchEnd, final int pos) {
|
||||
if (isAdded() && (fetchEnd == FetchEnd.TOP || fetchEnd == FetchEnd.BOTTOM && maxId == null && progressBar.getVisibility() != View.VISIBLE) && !isSwipeToRefreshEnabled)
|
||||
topProgressBar.show();
|
||||
|
||||
if (kind == Kind.HOME) {
|
||||
TimelineRequestMode mode;
|
||||
// allow getting old statuses/fallbacks for network only for for bottom loading
|
||||
|
@ -1015,6 +1036,8 @@ public class TimelineFragment extends SFragment implements
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (isAdded()) {
|
||||
topProgressBar.hide();
|
||||
updateBottomLoadingState(fetchEnd);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
|
@ -1025,10 +1048,12 @@ public class TimelineFragment extends SFragment implements
|
|||
this.statusView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onFetchTimelineFailure(Exception exception, FetchEnd fetchEnd, int position) {
|
||||
if (isAdded()) {
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
topProgressBar.hide();
|
||||
|
||||
if (fetchEnd == FetchEnd.MIDDLE && !statuses.get(position).isRight()) {
|
||||
Placeholder placeholder = statuses.get(position).asLeftOrNull();
|
||||
|
@ -1267,7 +1292,10 @@ public class TimelineFragment extends SFragment implements
|
|||
adapter.notifyItemRangeInserted(position, count);
|
||||
Context context = getContext();
|
||||
if (position == 0 && context != null) {
|
||||
if (isSwipeToRefreshEnabled)
|
||||
recyclerView.scrollBy(0, Utils.dpToPx(context, -30));
|
||||
else
|
||||
recyclerView.scrollToPosition(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1362,4 +1390,12 @@ public class TimelineFragment extends SFragment implements
|
|||
public void onReselect() {
|
||||
jumpToTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshContent() {
|
||||
if (isAdded())
|
||||
onRefresh();
|
||||
else
|
||||
isNeedRefresh = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package com.keylesspalace.tusky.interfaces
|
||||
|
||||
/**
|
||||
* Created by pandasoft (joelpyska1@gmail.com) on 04/04/2019.
|
||||
*/
|
||||
interface RefreshableFragment {
|
||||
/**
|
||||
* Call this method to refresh fragment content
|
||||
*/
|
||||
fun refreshContent()
|
||||
}
|
|
@ -20,6 +20,10 @@ import android.view.ViewGroup;
|
|||
|
||||
import com.keylesspalace.tusky.fragment.AccountMediaFragment;
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment;
|
||||
import com.keylesspalace.tusky.interfaces.RefreshableFragment;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -34,6 +38,8 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
|||
|
||||
private SparseArray<Fragment> fragments = new SparseArray<>(TAB_COUNT);
|
||||
|
||||
private final Set<Integer> pagesToRefresh = new HashSet<>();
|
||||
|
||||
public AccountPagerAdapter(FragmentManager manager, String accountId) {
|
||||
super(manager);
|
||||
this.accountId = accountId;
|
||||
|
@ -48,16 +54,16 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
|||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
case 0: {
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId);
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER, accountId,false);
|
||||
}
|
||||
case 1: {
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER_WITH_REPLIES, accountId);
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER_WITH_REPLIES, accountId,false);
|
||||
}
|
||||
case 2: {
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER_PINNED, accountId);
|
||||
return TimelineFragment.newInstance(TimelineFragment.Kind.USER_PINNED, accountId,false);
|
||||
}
|
||||
case 3: {
|
||||
return AccountMediaFragment.newInstance(accountId);
|
||||
return AccountMediaFragment.newInstance(accountId,false);
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError("Page " + position + " is out of AccountPagerAdapter bounds");
|
||||
|
@ -76,6 +82,11 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
|||
Object fragment = super.instantiateItem(container, position);
|
||||
if (fragment instanceof Fragment)
|
||||
fragments.put(position, (Fragment) fragment);
|
||||
if (pagesToRefresh.contains(position)) {
|
||||
if (fragment instanceof RefreshableFragment)
|
||||
((RefreshableFragment) fragment).refreshContent();
|
||||
pagesToRefresh.remove(position);
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
|
@ -94,4 +105,16 @@ public class AccountPagerAdapter extends FragmentPagerAdapter {
|
|||
public Fragment getFragment(int position) {
|
||||
return fragments.get(position);
|
||||
}
|
||||
|
||||
public void refreshContent(){
|
||||
for (int i=0;i<getCount();i++){
|
||||
Fragment fragment = getFragment(i);
|
||||
if (fragment instanceof RefreshableFragment){
|
||||
((RefreshableFragment) fragment).refreshContent();
|
||||
}
|
||||
else{
|
||||
pagesToRefresh.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.keylesspalace.tusky.viewmodel
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.keylesspalace.tusky.appstore.*
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
|
@ -18,8 +19,9 @@ import javax.inject.Inject
|
|||
|
||||
class AccountViewModel @Inject constructor(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val eventHub: EventHub
|
||||
): ViewModel() {
|
||||
private val eventHub: EventHub,
|
||||
private val accountManager: AccountManager
|
||||
) : ViewModel() {
|
||||
|
||||
val accountData = MutableLiveData<Resource<Account>>()
|
||||
val relationshipData = MutableLiveData<Resource<Relationship>>()
|
||||
|
@ -32,10 +34,15 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val isRefreshing = MutableLiveData<Boolean>().apply { value = false }
|
||||
private var isDataLoading = false
|
||||
|
||||
fun obtainAccount(accountId: String, reload: Boolean = false) {
|
||||
if(accountData.value == null || reload) {
|
||||
lateinit var accountId: String
|
||||
var isSelf = false
|
||||
|
||||
private fun obtainAccount(reload: Boolean = false) {
|
||||
if (accountData.value == null || reload) {
|
||||
isDataLoading = true
|
||||
accountData.postValue(Loading())
|
||||
|
||||
val call = mastodonApi.account(accountId)
|
||||
|
@ -47,10 +54,14 @@ class AccountViewModel @Inject constructor(
|
|||
} else {
|
||||
accountData.postValue(Error())
|
||||
}
|
||||
isDataLoading = false
|
||||
isRefreshing.postValue(false)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
accountData.postValue(Error())
|
||||
isDataLoading = false
|
||||
isRefreshing.postValue(false)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -58,8 +69,8 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun obtainRelationship(accountId: String, reload: Boolean = false) {
|
||||
if(relationshipData.value == null || reload) {
|
||||
private fun obtainRelationship(reload: Boolean = false) {
|
||||
if (relationshipData.value == null || reload) {
|
||||
|
||||
relationshipData.postValue(Loading())
|
||||
|
||||
|
@ -86,47 +97,47 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun changeFollowState(id: String) {
|
||||
fun changeFollowState() {
|
||||
val relationship = relationshipData.value?.data
|
||||
if (relationship?.following == true || relationship?.requested == true) {
|
||||
changeRelationship(RelationShipAction.UNFOLLOW, id)
|
||||
changeRelationship(RelationShipAction.UNFOLLOW)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.FOLLOW, id)
|
||||
changeRelationship(RelationShipAction.FOLLOW)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeBlockState(id: String) {
|
||||
fun changeBlockState() {
|
||||
if (relationshipData.value?.data?.blocking == true) {
|
||||
changeRelationship(RelationShipAction.UNBLOCK, id)
|
||||
changeRelationship(RelationShipAction.UNBLOCK)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.BLOCK, id)
|
||||
changeRelationship(RelationShipAction.BLOCK)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeMuteState(id: String) {
|
||||
fun changeMuteState() {
|
||||
if (relationshipData.value?.data?.muting == true) {
|
||||
changeRelationship(RelationShipAction.UNMUTE, id)
|
||||
changeRelationship(RelationShipAction.UNMUTE)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.MUTE, id)
|
||||
changeRelationship(RelationShipAction.MUTE)
|
||||
}
|
||||
}
|
||||
|
||||
fun changeShowReblogsState(id: String) {
|
||||
fun changeShowReblogsState() {
|
||||
if (relationshipData.value?.data?.showingReblogs == true) {
|
||||
changeRelationship(RelationShipAction.FOLLOW, id, false)
|
||||
changeRelationship(RelationShipAction.FOLLOW, false)
|
||||
} else {
|
||||
changeRelationship(RelationShipAction.FOLLOW, id, true)
|
||||
changeRelationship(RelationShipAction.FOLLOW, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeRelationship(relationshipAction: RelationShipAction, id: String, showReblogs: Boolean = true) {
|
||||
private fun changeRelationship(relationshipAction: RelationShipAction, showReblogs: Boolean = true) {
|
||||
val relation = relationshipData.value?.data
|
||||
val account = accountData.value?.data
|
||||
|
||||
if(relation != null && account != null) {
|
||||
if (relation != null && account != null) {
|
||||
// optimistically post new state for faster response
|
||||
|
||||
val newRelation = when(relationshipAction) {
|
||||
val newRelation = when (relationshipAction) {
|
||||
RelationShipAction.FOLLOW -> {
|
||||
if (account.locked) {
|
||||
relation.copy(requested = true)
|
||||
|
@ -151,10 +162,11 @@ class AccountViewModel @Inject constructor(
|
|||
relationshipData.postValue(Success(relationship))
|
||||
|
||||
when (relationshipAction) {
|
||||
RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(id))
|
||||
RelationShipAction.BLOCK -> eventHub.dispatch(BlockEvent(id))
|
||||
RelationShipAction.MUTE -> eventHub.dispatch(MuteEvent(id))
|
||||
else -> {}
|
||||
RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(accountId))
|
||||
RelationShipAction.BLOCK -> eventHub.dispatch(BlockEvent(accountId))
|
||||
RelationShipAction.MUTE -> eventHub.dispatch(MuteEvent(accountId))
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -168,13 +180,13 @@ class AccountViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val call = when(relationshipAction) {
|
||||
RelationShipAction.FOLLOW -> mastodonApi.followAccount(id, showReblogs)
|
||||
RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(id)
|
||||
RelationShipAction.BLOCK -> mastodonApi.blockAccount(id)
|
||||
RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(id)
|
||||
RelationShipAction.MUTE -> mastodonApi.muteAccount(id)
|
||||
RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(id)
|
||||
val call = when (relationshipAction) {
|
||||
RelationShipAction.FOLLOW -> mastodonApi.followAccount(accountId, showReblogs)
|
||||
RelationShipAction.UNFOLLOW -> mastodonApi.unfollowAccount(accountId)
|
||||
RelationShipAction.BLOCK -> mastodonApi.blockAccount(accountId)
|
||||
RelationShipAction.UNBLOCK -> mastodonApi.unblockAccount(accountId)
|
||||
RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId)
|
||||
RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(accountId)
|
||||
}
|
||||
|
||||
call.enqueue(callback)
|
||||
|
@ -189,6 +201,27 @@ class AccountViewModel @Inject constructor(
|
|||
disposable.dispose()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
reload(true)
|
||||
}
|
||||
|
||||
private fun reload(isReload: Boolean = false) {
|
||||
if (isDataLoading)
|
||||
return
|
||||
accountId.let {
|
||||
obtainAccount(isReload)
|
||||
if (!isSelf)
|
||||
obtainRelationship(isReload)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun setAccountInfo(accountId: String) {
|
||||
this.accountId = accountId
|
||||
this.isSelf = accountManager.activeAccount?.accountId == accountId
|
||||
reload(false)
|
||||
}
|
||||
|
||||
enum class RelationShipAction {
|
||||
FOLLOW, UNFOLLOW, BLOCK, UNBLOCK, MUTE, UNMUTE
|
||||
}
|
||||
|
|
|
@ -45,5 +45,16 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/elephant_error"
|
||||
tools:visibility="visible" />
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/topProgressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="parent"
|
||||
android:visibility="gone"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
|
@ -1,12 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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/swipeToRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/accountCoordinatorLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textDirection="anyRtl"
|
||||
android:fillViewport="true">
|
||||
android:fillViewport="true"
|
||||
android:textDirection="anyRtl">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/accountAppBarLayout"
|
||||
|
@ -43,17 +48,50 @@
|
|||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideAvatar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_begin="@dimen/account_activity_avatar_size" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/accountFollowButton"
|
||||
style="@style/TuskyButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/accountMuteButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Follow" />
|
||||
tools:text="Follow Requested" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/accountMuteButton"
|
||||
style="@style/TuskyButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:minWidth="0dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:scaleType="centerInside"
|
||||
app:icon="@drawable/ic_unmute_24dp"
|
||||
app:layout_constrainedHeight="true"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/accountFollowButton"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountFollowButton"
|
||||
app:layout_constraintHorizontal_bias="1"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@id/guideAvatar"
|
||||
app:layout_constraintTop_toTopOf="@+id/accountFollowButton" />
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/accountDisplayNameTextView"
|
||||
|
@ -135,6 +173,7 @@
|
|||
android:id="@+id/accountNoteTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:paddingTop="10dp"
|
||||
android:textColor="?android:textColorTertiary"
|
||||
|
@ -150,14 +189,6 @@
|
|||
tools:itemCount="2"
|
||||
tools:listitem="@layout/item_account_field" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/accountMovedView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/accountMovedView"
|
||||
android:layout="@layout/view_account_moved"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountFieldList" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountRemoveView"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -167,9 +198,24 @@
|
|||
android:lineSpacingMultiplier="1.1"
|
||||
android:text="@string/label_remote_account"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountMovedView"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountFieldList"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/accountMovedView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/accountMovedViewLayout"
|
||||
android:layout="@layout/view_account_moved"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountRemoveView" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrierRemote"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="accountMovedView,accountMovedViewLayout" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/accountStatuses"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -179,13 +225,14 @@
|
|||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountFollowing"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountRemoveView">
|
||||
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountStatusesTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
|
||||
android:fontFamily="sans-serif-medium"
|
||||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
|
@ -209,7 +256,7 @@
|
|||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toStartOf="@id/accountFollowers"
|
||||
app:layout_constraintStart_toEndOf="@id/accountStatuses"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountRemoveView">
|
||||
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountFollowingTextView"
|
||||
|
@ -239,7 +286,7 @@
|
|||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/accountFollowing"
|
||||
app:layout_constraintTop_toBottomOf="@id/accountRemoveView">
|
||||
app:layout_constraintTop_toBottomOf="@id/barrierRemote">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/accountFollowersTextView"
|
||||
|
@ -260,6 +307,8 @@
|
|||
android:textColor="@color/account_tab_font_color"
|
||||
android:textSize="?attr/status_text_medium" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- top margin equal to statusbar size will be set programmatically -->
|
||||
|
@ -278,6 +327,7 @@
|
|||
style="@style/TuskyTabAppearance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:background="?android:colorBackground"
|
||||
app:tabGravity="center"
|
||||
app:tabMode="scrollable"
|
||||
|
@ -313,4 +363,5 @@
|
|||
app:layout_anchorGravity="top"
|
||||
app:layout_scrollFlags="scroll"
|
||||
app:srcCompat="@drawable/avatar_default" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
@ -4,7 +4,6 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -38,5 +37,15 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/elephant_error"
|
||||
tools:visibility="visible"/>
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/topProgressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="parent"
|
||||
android:visibility="gone"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in a new issue