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
|
@ -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)
|
||||
|
||||
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)
|
||||
|
||||
if (isSwipeToRefreshEnabled) {
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
refresh()
|
||||
}
|
||||
currentCall?.enqueue(callback)
|
||||
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ThemeUtils.getColor(view.context, android.R.attr.colorBackground))
|
||||
}
|
||||
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,11 +401,14 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
|
||||
private void setupSwipeRefreshLayout() {
|
||||
Context context = swipeRefreshLayout.getContext();
|
||||
swipeRefreshLayout.setOnRefreshListener(this);
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue);
|
||||
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ThemeUtils.getColor(context,
|
||||
android.R.attr.colorBackground));
|
||||
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() {
|
||||
|
@ -524,8 +540,10 @@ public class TimelineFragment extends SFragment implements
|
|||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
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,20 +1036,24 @@ public class TimelineFragment extends SFragment implements
|
|||
break;
|
||||
}
|
||||
}
|
||||
updateBottomLoadingState(fetchEnd);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
if (this.statuses.size() == 0) {
|
||||
this.showNothing();
|
||||
} else {
|
||||
this.statusView.setVisibility(View.GONE);
|
||||
if (isAdded()) {
|
||||
topProgressBar.hide();
|
||||
updateBottomLoadingState(fetchEnd);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
if (this.statuses.size() == 0) {
|
||||
this.showNothing();
|
||||
} else {
|
||||
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) {
|
||||
recyclerView.scrollBy(0, Utils.dpToPx(context, -30));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue