add the ability to see who faved or boosted a toot (#962)
* move reblog/fav count up in detailed status view and make them clickable * use status object returned by api when reblogging/faving * Reblogs -> Boosts * add support for viewing who faved/reblogged a status * add onShowReblogs/onShowFavs to listener, fix display bug * remove unneeded icon from previous revision * small code improvements * fix liking/boosting toot with card
This commit is contained in:
parent
4864bb79d9
commit
c869886c19
|
@ -264,7 +264,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasSupportF
|
||||||
val accountListClickListener = { v: View ->
|
val accountListClickListener = { v: View ->
|
||||||
val type = when (v.id) {
|
val type = when (v.id) {
|
||||||
R.id.accountFollowers-> AccountListActivity.Type.FOLLOWERS
|
R.id.accountFollowers-> AccountListActivity.Type.FOLLOWERS
|
||||||
R.id.accountFollowing -> AccountListActivity.Type.FOLLOWING
|
R.id.accountFollowing -> AccountListActivity.Type.FOLLOWS
|
||||||
else -> throw AssertionError()
|
else -> throw AssertionError()
|
||||||
}
|
}
|
||||||
val accountListIntent = AccountListActivity.newIntent(this, type, accountId)
|
val accountListIntent = AccountListActivity.newIntent(this, type, accountId)
|
||||||
|
|
|
@ -1,149 +0,0 @@
|
||||||
/* Copyright 2017 Andrew Dawson
|
|
||||||
*
|
|
||||||
* This file is a part of Tusky.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
|
||||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
||||||
* Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
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.AccountListFragment;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.android.AndroidInjector;
|
|
||||||
import dagger.android.DispatchingAndroidInjector;
|
|
||||||
import dagger.android.support.HasSupportFragmentInjector;
|
|
||||||
|
|
||||||
public final class AccountListActivity extends BaseActivity implements HasSupportFragmentInjector {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
|
|
||||||
|
|
||||||
private static final String TYPE_EXTRA = "type";
|
|
||||||
private static final String ARG_EXTRA = "arg";
|
|
||||||
|
|
||||||
public static Intent newIntent(@NonNull Context context, @NonNull Type type,
|
|
||||||
@Nullable String argument) {
|
|
||||||
Intent intent = new Intent(context, AccountListActivity.class);
|
|
||||||
intent.putExtra(TYPE_EXTRA, type);
|
|
||||||
if (argument != null) {
|
|
||||||
intent.putExtra(ARG_EXTRA, argument);
|
|
||||||
}
|
|
||||||
return intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
BLOCKS,
|
|
||||||
MUTES,
|
|
||||||
FOLLOW_REQUESTS,
|
|
||||||
FOLLOWERS,
|
|
||||||
FOLLOWING,
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_account_list);
|
|
||||||
|
|
||||||
Type type;
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent != null) {
|
|
||||||
type = (Type) intent.getSerializableExtra("type");
|
|
||||||
} else {
|
|
||||||
type = Type.BLOCKS;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
ActionBar bar = getSupportActionBar();
|
|
||||||
if (bar != null) {
|
|
||||||
switch (type) {
|
|
||||||
case BLOCKS: {
|
|
||||||
bar.setTitle(getString(R.string.title_blocks));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MUTES: {
|
|
||||||
bar.setTitle(getString(R.string.title_mutes));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FOLLOW_REQUESTS: {
|
|
||||||
bar.setTitle(getString(R.string.title_follow_requests));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FOLLOWERS:
|
|
||||||
bar.setTitle(getString(R.string.title_followers));
|
|
||||||
break;
|
|
||||||
case FOLLOWING:
|
|
||||||
bar.setTitle(getString(R.string.title_follows));
|
|
||||||
}
|
|
||||||
bar.setDisplayHomeAsUpEnabled(true);
|
|
||||||
bar.setDisplayShowHomeEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
|
||||||
AccountListFragment fragment;
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
case BLOCKS: {
|
|
||||||
fragment = AccountListFragment.newInstance(AccountListFragment.Type.BLOCKS);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MUTES: {
|
|
||||||
fragment = AccountListFragment.newInstance(AccountListFragment.Type.MUTES);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FOLLOWERS: {
|
|
||||||
String argument = intent.getStringExtra(ARG_EXTRA);
|
|
||||||
fragment = AccountListFragment.newInstance(AccountListFragment.Type.FOLLOWERS, argument);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FOLLOWING: {
|
|
||||||
String argument = intent.getStringExtra(ARG_EXTRA);
|
|
||||||
fragment = AccountListFragment.newInstance(AccountListFragment.Type.FOLLOWS, argument);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FOLLOW_REQUESTS: {
|
|
||||||
fragment = AccountListFragment.newInstance(AccountListFragment.Type.FOLLOW_REQUESTS);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragmentTransaction.replace(R.id.fragment_container, fragment);
|
|
||||||
fragmentTransaction.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case android.R.id.home: {
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AndroidInjector<Fragment> supportFragmentInjector() {
|
|
||||||
return dispatchingAndroidInjector;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import android.view.MenuItem
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.fragment.AccountListFragment
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
import dagger.android.AndroidInjector
|
||||||
|
import dagger.android.DispatchingAndroidInjector
|
||||||
|
import dagger.android.support.HasSupportFragmentInjector
|
||||||
|
import kotlinx.android.synthetic.main.toolbar_basic.*
|
||||||
|
|
||||||
|
class AccountListActivity : BaseActivity(), HasSupportFragmentInjector {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
FOLLOWS,
|
||||||
|
FOLLOWERS,
|
||||||
|
BLOCKS,
|
||||||
|
MUTES,
|
||||||
|
FOLLOW_REQUESTS,
|
||||||
|
REBLOGGED,
|
||||||
|
FAVOURITED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_account_list)
|
||||||
|
|
||||||
|
val type = intent.getSerializableExtra(EXTRA_TYPE) as Type
|
||||||
|
val id: String? = intent.getStringExtra(EXTRA_ID)
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
supportActionBar?.apply {
|
||||||
|
when (type) {
|
||||||
|
AccountListActivity.Type.BLOCKS -> setTitle(R.string.title_blocks)
|
||||||
|
AccountListActivity.Type.MUTES -> setTitle(R.string.title_mutes)
|
||||||
|
AccountListActivity.Type.FOLLOW_REQUESTS -> setTitle(R.string.title_follow_requests)
|
||||||
|
AccountListActivity.Type.FOLLOWERS -> setTitle(R.string.title_followers)
|
||||||
|
AccountListActivity.Type.FOLLOWS -> setTitle(R.string.title_follows)
|
||||||
|
AccountListActivity.Type.REBLOGGED -> setTitle(R.string.title_reblogged_by)
|
||||||
|
AccountListActivity.Type.FAVOURITED -> setTitle(R.string.title_favourited_by)
|
||||||
|
}
|
||||||
|
setDisplayHomeAsUpEnabled(true)
|
||||||
|
setDisplayShowHomeEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.fragment_container, AccountListFragment.newInstance(type, id))
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
android.R.id.home -> {
|
||||||
|
onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supportFragmentInjector(): AndroidInjector<Fragment>? {
|
||||||
|
return dispatchingAndroidInjector
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_TYPE = "type"
|
||||||
|
private const val EXTRA_ID = "id"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun newIntent(context: Context, type: Type, id: String? = null): Intent {
|
||||||
|
return Intent(context, AccountListActivity::class.java).apply {
|
||||||
|
putExtra(EXTRA_TYPE, type)
|
||||||
|
putExtra(EXTRA_ID, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,6 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||||
static final int VIEW_TYPE_ACCOUNT = 0;
|
static final int VIEW_TYPE_ACCOUNT = 0;
|
||||||
static final int VIEW_TYPE_FOOTER = 1;
|
static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
|
|
||||||
List<Account> accountList;
|
List<Account> accountList;
|
||||||
AccountActionListener accountActionListener;
|
AccountActionListener accountActionListener;
|
||||||
private boolean bottomLoading;
|
private boolean bottomLoading;
|
||||||
|
@ -60,7 +59,7 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addItems(List<Account> newAccounts) {
|
public void addItems(@NonNull List<Account> newAccounts) {
|
||||||
int end = accountList.size();
|
int end = accountList.size();
|
||||||
Account last = accountList.get(end - 1);
|
Account last = accountList.get(end - 1);
|
||||||
if (last != null && !findAccount(newAccounts, last.getId())) {
|
if (last != null && !findAccount(newAccounts, last.getId())) {
|
||||||
|
@ -82,7 +81,7 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean findAccount(List<Account> accounts, String id) {
|
private static boolean findAccount(@NonNull List<Account> accounts, String id) {
|
||||||
for (Account account : accounts) {
|
for (Account account : accounts) {
|
||||||
if (account.getId().equals(id)) {
|
if (account.getId().equals(id)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -101,7 +100,7 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addItem(Account account, int position) {
|
public void addItem(@NonNull Account account, int position) {
|
||||||
if (position < 0 || position > accountList.size()) {
|
if (position < 0 || position > accountList.size()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.keylesspalace.tusky.entity.Card;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||||
import com.keylesspalace.tusky.util.CustomURLSpan;
|
import com.keylesspalace.tusky.util.CustomURLSpan;
|
||||||
|
import com.keylesspalace.tusky.util.HtmlUtils;
|
||||||
import com.keylesspalace.tusky.util.LinkHelper;
|
import com.keylesspalace.tusky.util.LinkHelper;
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
|
@ -30,6 +31,7 @@ import java.text.NumberFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
private TextView reblogs;
|
private TextView reblogs;
|
||||||
|
@ -40,6 +42,10 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
private TextView cardTitle;
|
private TextView cardTitle;
|
||||||
private TextView cardDescription;
|
private TextView cardDescription;
|
||||||
private TextView cardUrl;
|
private TextView cardUrl;
|
||||||
|
private View infoDivider;
|
||||||
|
private View favReblogInfoContainer;
|
||||||
|
|
||||||
|
private NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
||||||
|
|
||||||
StatusDetailedViewHolder(View view) {
|
StatusDetailedViewHolder(View view) {
|
||||||
super(view, false);
|
super(view, false);
|
||||||
|
@ -51,6 +57,8 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
cardTitle = view.findViewById(R.id.card_title);
|
cardTitle = view.findViewById(R.id.card_title);
|
||||||
cardDescription = view.findViewById(R.id.card_description);
|
cardDescription = view.findViewById(R.id.card_description);
|
||||||
cardUrl = view.findViewById(R.id.card_link);
|
cardUrl = view.findViewById(R.id.card_link);
|
||||||
|
infoDivider = view.findViewById(R.id.status_info_divider);
|
||||||
|
favReblogInfoContainer = view.findViewById(R.id.status_reblog_fav_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -68,6 +76,45 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setReblogAndFavCount(int reblogCount, int favCount, StatusActionListener listener) {
|
||||||
|
|
||||||
|
if(reblogCount > 0) {
|
||||||
|
String reblogCountString = numberFormat.format(reblogCount);
|
||||||
|
reblogs.setText(HtmlUtils.fromHtml(reblogs.getResources().getQuantityString(R.plurals.reblogs, reblogCount, reblogCountString)));
|
||||||
|
reblogs.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
reblogs.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
if(favCount > 0) {
|
||||||
|
String favCountString = numberFormat.format(favCount);
|
||||||
|
favourites.setText(HtmlUtils.fromHtml(favourites.getResources().getQuantityString(R.plurals.favs, favCount, favCountString)));
|
||||||
|
favourites.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
favourites.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(reblogs.getVisibility() == View.GONE && favourites.getVisibility() == View.GONE) {
|
||||||
|
infoDivider.setVisibility(View.GONE);
|
||||||
|
favReblogInfoContainer.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
infoDivider.setVisibility(View.VISIBLE);
|
||||||
|
favReblogInfoContainer.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
reblogs.setOnClickListener( v -> {
|
||||||
|
int position = getAdapterPosition();
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
listener.onShowReblogs(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
favourites.setOnClickListener( v -> {
|
||||||
|
int position = getAdapterPosition();
|
||||||
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
|
listener.onShowFavs(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setApplication(@Nullable Status.Application app) {
|
private void setApplication(@Nullable Status.Application app) {
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
|
|
||||||
|
@ -91,12 +138,11 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
boolean mediaPreviewEnabled) {
|
boolean mediaPreviewEnabled) {
|
||||||
super.setupWithStatus(status, listener, mediaPreviewEnabled);
|
super.setupWithStatus(status, listener, mediaPreviewEnabled);
|
||||||
|
|
||||||
NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener);
|
||||||
|
|
||||||
reblogs.setText(numberFormat.format(status.getReblogsCount()));
|
|
||||||
favourites.setText(numberFormat.format(status.getFavouritesCount()));
|
|
||||||
setApplication(status.getApplication());
|
setApplication(status.getApplication());
|
||||||
|
|
||||||
|
|
||||||
View.OnLongClickListener longClickListener = view -> {
|
View.OnLongClickListener longClickListener = view -> {
|
||||||
TextView textView = (TextView)view;
|
TextView textView = (TextView)view;
|
||||||
ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
|
|
@ -1,404 +0,0 @@
|
||||||
/* Copyright 2017 Andrew Dawson
|
|
||||||
*
|
|
||||||
* This file is a part of Tusky.
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
||||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
|
||||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
||||||
* Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
||||||
* see <http://www.gnu.org/licenses>. */
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.fragment;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import com.keylesspalace.tusky.AccountActivity;
|
|
||||||
import com.keylesspalace.tusky.R;
|
|
||||||
import com.keylesspalace.tusky.adapter.AccountAdapter;
|
|
||||||
import com.keylesspalace.tusky.adapter.BlocksAdapter;
|
|
||||||
import com.keylesspalace.tusky.adapter.FollowAdapter;
|
|
||||||
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter;
|
|
||||||
import com.keylesspalace.tusky.adapter.MutesAdapter;
|
|
||||||
import com.keylesspalace.tusky.di.Injectable;
|
|
||||||
import com.keylesspalace.tusky.entity.Account;
|
|
||||||
import com.keylesspalace.tusky.entity.Relationship;
|
|
||||||
import com.keylesspalace.tusky.interfaces.AccountActionListener;
|
|
||||||
import com.keylesspalace.tusky.network.MastodonApi;
|
|
||||||
import com.keylesspalace.tusky.util.HttpHeaderLink;
|
|
||||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
|
||||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
public class AccountListFragment extends BaseFragment implements AccountActionListener,
|
|
||||||
Injectable {
|
|
||||||
private static final String TAG = "AccountList"; // logging tag
|
|
||||||
|
|
||||||
public AccountListFragment() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
FOLLOWS,
|
|
||||||
FOLLOWERS,
|
|
||||||
BLOCKS,
|
|
||||||
MUTES,
|
|
||||||
FOLLOW_REQUESTS,
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MastodonApi api;
|
|
||||||
|
|
||||||
private Type type;
|
|
||||||
private String accountId;
|
|
||||||
private LinearLayoutManager layoutManager;
|
|
||||||
private RecyclerView recyclerView;
|
|
||||||
private EndlessOnScrollListener scrollListener;
|
|
||||||
private AccountAdapter adapter;
|
|
||||||
private boolean fetching = false;
|
|
||||||
private String bottomId;
|
|
||||||
|
|
||||||
public static AccountListFragment newInstance(Type type) {
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
AccountListFragment fragment = new AccountListFragment();
|
|
||||||
arguments.putSerializable("type", type);
|
|
||||||
fragment.setArguments(arguments);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AccountListFragment newInstance(Type type, String accountId) {
|
|
||||||
Bundle arguments = new Bundle();
|
|
||||||
AccountListFragment fragment = new AccountListFragment();
|
|
||||||
arguments.putSerializable("type", type);
|
|
||||||
arguments.putString("accountId", accountId);
|
|
||||||
fragment.setArguments(arguments);
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
Bundle arguments = getArguments();
|
|
||||||
type = (Type) arguments.getSerializable("type");
|
|
||||||
accountId = arguments.getString("accountId");
|
|
||||||
api = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
|
||||||
@Nullable Bundle savedInstanceState) {
|
|
||||||
|
|
||||||
View rootView = inflater.inflate(R.layout.fragment_account_list, container, false);
|
|
||||||
|
|
||||||
Context context = getContext();
|
|
||||||
recyclerView = rootView.findViewById(R.id.recycler_view);
|
|
||||||
recyclerView.setHasFixedSize(true);
|
|
||||||
layoutManager = new LinearLayoutManager(context);
|
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
|
||||||
DividerItemDecoration divider = new DividerItemDecoration(
|
|
||||||
context, layoutManager.getOrientation());
|
|
||||||
Drawable drawable = ThemeUtils.getDrawable(context, R.attr.status_divider_drawable,
|
|
||||||
R.drawable.status_divider_dark);
|
|
||||||
divider.setDrawable(drawable);
|
|
||||||
recyclerView.addItemDecoration(divider);
|
|
||||||
scrollListener = null;
|
|
||||||
if (type == Type.BLOCKS) {
|
|
||||||
adapter = new BlocksAdapter(this);
|
|
||||||
} else if (type == Type.MUTES) {
|
|
||||||
adapter = new MutesAdapter(this);
|
|
||||||
} else if (type == Type.FOLLOW_REQUESTS) {
|
|
||||||
adapter = new FollowRequestsAdapter(this);
|
|
||||||
} else {
|
|
||||||
adapter = new FollowAdapter(this);
|
|
||||||
}
|
|
||||||
recyclerView.setAdapter(adapter);
|
|
||||||
|
|
||||||
|
|
||||||
return rootView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
// Just use the basic scroll listener to load more accounts.
|
|
||||||
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
|
||||||
@Override
|
|
||||||
public void onLoadMore(int totalItemsCount, RecyclerView view) {
|
|
||||||
AccountListFragment.this.onLoadMore();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(scrollListener);
|
|
||||||
|
|
||||||
fetchAccounts(null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewAccount(String id) {
|
|
||||||
Context context = getContext();
|
|
||||||
if(context != null) {
|
|
||||||
Intent intent = AccountActivity.getIntent(context, id);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMute(final boolean mute, final String id, final int position) {
|
|
||||||
Callback<Relationship> callback = new Callback<Relationship>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
onMuteSuccess(mute, id, position);
|
|
||||||
} else {
|
|
||||||
onMuteFailure(mute, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<Relationship> call, @NonNull Throwable t) {
|
|
||||||
onMuteFailure(mute, id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Call<Relationship> call;
|
|
||||||
if (!mute) {
|
|
||||||
call = api.unmuteAccount(id);
|
|
||||||
} else {
|
|
||||||
call = api.muteAccount(id);
|
|
||||||
}
|
|
||||||
callList.add(call);
|
|
||||||
call.enqueue(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMuteSuccess(boolean muted, final String id, final int position) {
|
|
||||||
if (muted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final MutesAdapter mutesAdapter = (MutesAdapter) adapter;
|
|
||||||
final Account unmutedUser = mutesAdapter.removeItem(position);
|
|
||||||
View.OnClickListener listener = v -> {
|
|
||||||
mutesAdapter.addItem(unmutedUser, position);
|
|
||||||
onMute(true, id, position);
|
|
||||||
};
|
|
||||||
Snackbar.make(recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
|
||||||
.setAction(R.string.action_undo, listener)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMuteFailure(boolean mute, String id) {
|
|
||||||
String verb;
|
|
||||||
if (mute) {
|
|
||||||
verb = "mute";
|
|
||||||
} else {
|
|
||||||
verb = "unmute";
|
|
||||||
}
|
|
||||||
Log.e(TAG, String.format("Failed to %s account id %s", verb, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBlock(final boolean block, final String id, final int position) {
|
|
||||||
Callback<Relationship> cb = new Callback<Relationship>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
onBlockSuccess(block, id, position);
|
|
||||||
} else {
|
|
||||||
onBlockFailure(block, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<Relationship> call, @NonNull Throwable t) {
|
|
||||||
onBlockFailure(block, id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Call<Relationship> call;
|
|
||||||
if (!block) {
|
|
||||||
call = api.unblockAccount(id);
|
|
||||||
} else {
|
|
||||||
call = api.blockAccount(id);
|
|
||||||
}
|
|
||||||
callList.add(call);
|
|
||||||
call.enqueue(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onBlockSuccess(boolean blocked, final String id, final int position) {
|
|
||||||
if (blocked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final BlocksAdapter blocksAdapter = (BlocksAdapter) adapter;
|
|
||||||
final Account unblockedUser = blocksAdapter.removeItem(position);
|
|
||||||
View.OnClickListener listener = v -> {
|
|
||||||
blocksAdapter.addItem(unblockedUser, position);
|
|
||||||
onBlock(true, id, position);
|
|
||||||
};
|
|
||||||
Snackbar.make(recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
|
||||||
.setAction(R.string.action_undo, listener)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onBlockFailure(boolean block, String id) {
|
|
||||||
String verb;
|
|
||||||
if (block) {
|
|
||||||
verb = "block";
|
|
||||||
} else {
|
|
||||||
verb = "unblock";
|
|
||||||
}
|
|
||||||
Log.e(TAG, String.format("Failed to %s account id %s", verb, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRespondToFollowRequest(final boolean accept, final String accountId,
|
|
||||||
final int position) {
|
|
||||||
|
|
||||||
Callback<Relationship> callback = new Callback<Relationship>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<Relationship> call, @NonNull Response<Relationship> response) {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
onRespondToFollowRequestSuccess(position);
|
|
||||||
} else {
|
|
||||||
onRespondToFollowRequestFailure(accept, accountId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<Relationship> call, @NonNull Throwable t) {
|
|
||||||
onRespondToFollowRequestFailure(accept, accountId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Call<Relationship> call;
|
|
||||||
if (accept) {
|
|
||||||
call = api.authorizeFollowRequest(accountId);
|
|
||||||
} else {
|
|
||||||
call = api.rejectFollowRequest(accountId);
|
|
||||||
}
|
|
||||||
callList.add(call);
|
|
||||||
call.enqueue(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRespondToFollowRequestSuccess(int position) {
|
|
||||||
FollowRequestsAdapter followRequestsAdapter = (FollowRequestsAdapter) adapter;
|
|
||||||
followRequestsAdapter.removeItem(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRespondToFollowRequestFailure(boolean accept, String accountId) {
|
|
||||||
String verb;
|
|
||||||
if (accept) {
|
|
||||||
verb = "accept";
|
|
||||||
} else {
|
|
||||||
verb = "reject";
|
|
||||||
}
|
|
||||||
String message = String.format("Failed to %s account id %s.", verb, accountId);
|
|
||||||
Log.e(TAG, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Call<List<Account>> getFetchCallByListType(Type type, String fromId) {
|
|
||||||
switch (type) {
|
|
||||||
default:
|
|
||||||
case FOLLOWS:
|
|
||||||
return api.accountFollowing(accountId, fromId, null, null);
|
|
||||||
case FOLLOWERS:
|
|
||||||
return api.accountFollowers(accountId, fromId, null, null);
|
|
||||||
case BLOCKS:
|
|
||||||
return api.blocks(fromId, null, null);
|
|
||||||
case MUTES:
|
|
||||||
return api.mutes(fromId, null, null);
|
|
||||||
case FOLLOW_REQUESTS:
|
|
||||||
return api.followRequests(fromId, null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fetchAccounts(String id) {
|
|
||||||
if (fetching) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetching = true;
|
|
||||||
|
|
||||||
if (id != null) {
|
|
||||||
recyclerView.post(() -> adapter.setBottomLoading(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
Callback<List<Account>> cb = new Callback<List<Account>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<List<Account>> call, @NonNull Response<List<Account>> response) {
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
String linkHeader = response.headers().get("Link");
|
|
||||||
onFetchAccountsSuccess(response.body(), linkHeader);
|
|
||||||
} else {
|
|
||||||
onFetchAccountsFailure(new Exception(response.message()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<List<Account>> call, @NonNull Throwable t) {
|
|
||||||
onFetchAccountsFailure((Exception) t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Call<List<Account>> listCall = getFetchCallByListType(type, id);
|
|
||||||
callList.add(listCall);
|
|
||||||
listCall.enqueue(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFetchAccountsSuccess(List<Account> accounts, String linkHeader) {
|
|
||||||
adapter.setBottomLoading(false);
|
|
||||||
|
|
||||||
|
|
||||||
List<HttpHeaderLink> links = HttpHeaderLink.parse(linkHeader);
|
|
||||||
HttpHeaderLink next = HttpHeaderLink.findByRelationType(links, "next");
|
|
||||||
String fromId = null;
|
|
||||||
if (next != null) {
|
|
||||||
fromId = next.uri.getQueryParameter("max_id");
|
|
||||||
}
|
|
||||||
if (adapter.getItemCount() > 1) {
|
|
||||||
adapter.addItems(accounts);
|
|
||||||
} else {
|
|
||||||
adapter.update(accounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
bottomId = fromId;
|
|
||||||
|
|
||||||
fetching = false;
|
|
||||||
|
|
||||||
adapter.setBottomLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFetchAccountsFailure(Exception exception) {
|
|
||||||
fetching = false;
|
|
||||||
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLoadMore() {
|
|
||||||
if(bottomId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fetchAccounts(bottomId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,336 @@
|
||||||
|
/* Copyright 2017 Andrew Dawson
|
||||||
|
*
|
||||||
|
* This file is a part of Tusky.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||||
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||||
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
package com.keylesspalace.tusky.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.AccountActivity
|
||||||
|
import com.keylesspalace.tusky.AccountListActivity.Type
|
||||||
|
import com.keylesspalace.tusky.BaseActivity
|
||||||
|
import com.keylesspalace.tusky.R
|
||||||
|
import com.keylesspalace.tusky.adapter.AccountAdapter
|
||||||
|
import com.keylesspalace.tusky.adapter.BlocksAdapter
|
||||||
|
import com.keylesspalace.tusky.adapter.FollowAdapter
|
||||||
|
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter
|
||||||
|
import com.keylesspalace.tusky.adapter.MutesAdapter
|
||||||
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
|
import com.keylesspalace.tusky.entity.Account
|
||||||
|
import com.keylesspalace.tusky.entity.Relationship
|
||||||
|
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
||||||
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
|
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||||
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
|
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||||
|
import kotlinx.android.synthetic.main.fragment_account_list.*
|
||||||
|
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
|
class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
|
private lateinit var type: Type
|
||||||
|
private var id: String? = null
|
||||||
|
private lateinit var scrollListener: EndlessOnScrollListener
|
||||||
|
private lateinit var adapter: AccountAdapter
|
||||||
|
private var fetching = false
|
||||||
|
private var bottomId: String? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
type = arguments?.getSerializable(ARG_TYPE) as Type
|
||||||
|
id = arguments?.getString(ARG_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
return inflater.inflate(R.layout.fragment_account_list, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
recyclerView.setHasFixedSize(true)
|
||||||
|
val layoutManager = LinearLayoutManager(context)
|
||||||
|
recyclerView.layoutManager = layoutManager
|
||||||
|
val divider = DividerItemDecoration(context, layoutManager.orientation)
|
||||||
|
val drawable = ThemeUtils.getDrawable(context, R.attr.status_divider_drawable, R.drawable.status_divider_dark)
|
||||||
|
divider.setDrawable(drawable)
|
||||||
|
recyclerView.addItemDecoration(divider)
|
||||||
|
|
||||||
|
adapter = when(type) {
|
||||||
|
Type.BLOCKS -> BlocksAdapter(this)
|
||||||
|
Type.MUTES -> MutesAdapter(this)
|
||||||
|
Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this)
|
||||||
|
else -> FollowAdapter(this)
|
||||||
|
}
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
scrollListener = object : EndlessOnScrollListener(layoutManager) {
|
||||||
|
override fun onLoadMore(totalItemsCount: Int, view: RecyclerView) {
|
||||||
|
if (bottomId == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetchAccounts(bottomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(scrollListener)
|
||||||
|
|
||||||
|
fetchAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewAccount(id: String) {
|
||||||
|
(activity as BaseActivity?)?.let {
|
||||||
|
val intent = AccountActivity.getIntent(it, id)
|
||||||
|
it.startActivityWithSlideInAnimation(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMute(mute: Boolean, id: String, position: Int) {
|
||||||
|
val callback = object : Callback<Relationship> {
|
||||||
|
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
onMuteSuccess(mute, id, position)
|
||||||
|
} else {
|
||||||
|
onMuteFailure(mute, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||||
|
onMuteFailure(mute, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val call = if (!mute) {
|
||||||
|
api.unmuteAccount(id)
|
||||||
|
} else {
|
||||||
|
api.muteAccount(id)
|
||||||
|
}
|
||||||
|
callList.add(call)
|
||||||
|
call.enqueue(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMuteSuccess(muted: Boolean, id: String, position: Int) {
|
||||||
|
if (muted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val mutesAdapter = adapter as MutesAdapter
|
||||||
|
val unmutedUser = mutesAdapter.removeItem(position)
|
||||||
|
|
||||||
|
if(unmutedUser != null) {
|
||||||
|
Snackbar.make(recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_undo) {
|
||||||
|
mutesAdapter.addItem(unmutedUser, position)
|
||||||
|
onMute(true, id, position)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onMuteFailure(mute: Boolean, accountId: String) {
|
||||||
|
val verb = if (mute) {
|
||||||
|
"mute"
|
||||||
|
} else {
|
||||||
|
"unmute"
|
||||||
|
}
|
||||||
|
Log.e(TAG, "Failed to $verb account id $accountId")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBlock(block: Boolean, id: String, position: Int) {
|
||||||
|
val cb = object : Callback<Relationship> {
|
||||||
|
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
onBlockSuccess(block, id, position)
|
||||||
|
} else {
|
||||||
|
onBlockFailure(block, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||||
|
onBlockFailure(block, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val call = if (!block) {
|
||||||
|
api.unblockAccount(id)
|
||||||
|
} else {
|
||||||
|
api.blockAccount(id)
|
||||||
|
}
|
||||||
|
callList.add(call)
|
||||||
|
call.enqueue(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBlockSuccess(blocked: Boolean, id: String, position: Int) {
|
||||||
|
if (blocked) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val blocksAdapter = adapter as BlocksAdapter
|
||||||
|
val unblockedUser = blocksAdapter.removeItem(position)
|
||||||
|
|
||||||
|
if(unblockedUser != null) {
|
||||||
|
Snackbar.make(recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.action_undo) {
|
||||||
|
blocksAdapter.addItem(unblockedUser, position)
|
||||||
|
onBlock(true, id, position)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBlockFailure(block: Boolean, accountId: String) {
|
||||||
|
val verb = if (block) {
|
||||||
|
"block"
|
||||||
|
} else {
|
||||||
|
"unblock"
|
||||||
|
}
|
||||||
|
Log.e(TAG, "Failed to $verb account accountId $accountId")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRespondToFollowRequest(accept: Boolean, accountId: String,
|
||||||
|
position: Int) {
|
||||||
|
|
||||||
|
val callback = object : Callback<Relationship> {
|
||||||
|
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
onRespondToFollowRequestSuccess(position)
|
||||||
|
} else {
|
||||||
|
onRespondToFollowRequestFailure(accept, accountId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||||
|
onRespondToFollowRequestFailure(accept, accountId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val call = if (accept) {
|
||||||
|
api.authorizeFollowRequest(accountId)
|
||||||
|
} else {
|
||||||
|
api.rejectFollowRequest(accountId)
|
||||||
|
}
|
||||||
|
callList.add(call)
|
||||||
|
call.enqueue(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRespondToFollowRequestSuccess(position: Int) {
|
||||||
|
val followRequestsAdapter = adapter as FollowRequestsAdapter
|
||||||
|
followRequestsAdapter.removeItem(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onRespondToFollowRequestFailure(accept: Boolean, accountId: String) {
|
||||||
|
val verb = if (accept) {
|
||||||
|
"accept"
|
||||||
|
} else {
|
||||||
|
"reject"
|
||||||
|
}
|
||||||
|
Log.e(TAG, "Failed to $verb account id $accountId.")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFetchCallByListType(type: Type, fromId: String?): Call<List<Account>> {
|
||||||
|
return when (type) {
|
||||||
|
Type.FOLLOWS -> api.accountFollowing(id, fromId)
|
||||||
|
Type.FOLLOWERS -> api.accountFollowers(id, fromId)
|
||||||
|
Type.BLOCKS -> api.blocks(fromId)
|
||||||
|
Type.MUTES -> api.mutes(fromId)
|
||||||
|
Type.FOLLOW_REQUESTS -> api.followRequests(fromId)
|
||||||
|
Type.REBLOGGED -> api.statusRebloggedBy(id, fromId)
|
||||||
|
Type.FAVOURITED -> api.statusFavouritedBy(id, fromId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchAccounts(id: String? = null) {
|
||||||
|
if (fetching) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetching = true
|
||||||
|
|
||||||
|
if (id != null) {
|
||||||
|
recyclerView.post { adapter.setBottomLoading(true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val cb = object : Callback<List<Account>> {
|
||||||
|
override fun onResponse(call: Call<List<Account>>, response: Response<List<Account>>) {
|
||||||
|
val accountList = response.body()
|
||||||
|
if (response.isSuccessful && accountList != null) {
|
||||||
|
val linkHeader = response.headers().get("Link")
|
||||||
|
onFetchAccountsSuccess(accountList, linkHeader)
|
||||||
|
} else {
|
||||||
|
onFetchAccountsFailure(Exception(response.message()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<List<Account>>, t: Throwable) {
|
||||||
|
onFetchAccountsFailure(t as Exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val listCall = getFetchCallByListType(type, id)
|
||||||
|
callList.add(listCall)
|
||||||
|
listCall.enqueue(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFetchAccountsSuccess(accounts: List<Account>, linkHeader: String?) {
|
||||||
|
adapter.setBottomLoading(false)
|
||||||
|
|
||||||
|
val links = HttpHeaderLink.parse(linkHeader)
|
||||||
|
val next = HttpHeaderLink.findByRelationType(links, "next")
|
||||||
|
val fromId = next?.uri?.getQueryParameter("max_id")
|
||||||
|
|
||||||
|
if (adapter.itemCount > 0) {
|
||||||
|
adapter.addItems(accounts)
|
||||||
|
} else {
|
||||||
|
adapter.update(accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomId = fromId
|
||||||
|
|
||||||
|
fetching = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onFetchAccountsFailure(exception: Exception) {
|
||||||
|
fetching = false
|
||||||
|
Log.e(TAG, "Fetch failure", exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AccountList" // logging tag
|
||||||
|
private const val ARG_TYPE = "type"
|
||||||
|
private const val ARG_ID = "id"
|
||||||
|
|
||||||
|
fun newInstance(type: Type, id: String? = null): AccountListFragment {
|
||||||
|
return AccountListFragment().apply {
|
||||||
|
arguments = Bundle(2).apply {
|
||||||
|
putSerializable(ARG_TYPE, type)
|
||||||
|
putString(ARG_ID, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package com.keylesspalace.tusky.fragment;
|
||||||
import androidx.arch.core.util.Function;
|
import androidx.arch.core.util.Function;
|
||||||
import androidx.lifecycle.Lifecycle;
|
import androidx.lifecycle.Lifecycle;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -37,6 +38,8 @@ import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.keylesspalace.tusky.AccountListActivity;
|
||||||
|
import com.keylesspalace.tusky.BaseActivity;
|
||||||
import com.keylesspalace.tusky.BuildConfig;
|
import com.keylesspalace.tusky.BuildConfig;
|
||||||
import com.keylesspalace.tusky.R;
|
import com.keylesspalace.tusky.R;
|
||||||
import com.keylesspalace.tusky.ViewThreadActivity;
|
import com.keylesspalace.tusky.ViewThreadActivity;
|
||||||
|
@ -237,7 +240,8 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
setReblogForStatus(position, status, reblog);
|
updateStatus(position, response.body());
|
||||||
|
|
||||||
eventHub.dispatch(new ReblogEvent(status.getId(), reblog));
|
eventHub.dispatch(new ReblogEvent(status.getId(), reblog));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,24 +254,6 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setReblogForStatus(int position, Status status, boolean reblog) {
|
|
||||||
status.setReblogged(reblog);
|
|
||||||
|
|
||||||
if (status.getReblog() != null) {
|
|
||||||
status.getReblog().setReblogged(reblog);
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusViewData.Concrete viewdata = statuses.getPairedItem(position);
|
|
||||||
|
|
||||||
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder((viewdata));
|
|
||||||
viewDataBuilder.setReblogged(reblog);
|
|
||||||
|
|
||||||
StatusViewData.Concrete newViewData = viewDataBuilder.createStatusViewData();
|
|
||||||
|
|
||||||
statuses.setPairedItem(position, newViewData);
|
|
||||||
adapter.setItem(position, newViewData, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFavourite(final boolean favourite, final int position) {
|
public void onFavourite(final boolean favourite, final int position) {
|
||||||
final Status status = statuses.get(position);
|
final Status status = statuses.get(position);
|
||||||
|
@ -275,7 +261,8 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
public void onResponse(@NonNull Call<Status> call, @NonNull Response<Status> response) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
setFavForStatus(position, status, favourite);
|
updateStatus(position, response.body());
|
||||||
|
|
||||||
eventHub.dispatch(new FavoriteEvent(status.getId(), favourite));
|
eventHub.dispatch(new FavoriteEvent(status.getId(), favourite));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -288,22 +275,20 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFavForStatus(int position, Status status, boolean favourite) {
|
private void updateStatus(int position, Status status) {
|
||||||
status.setFavourited(favourite);
|
if(position >= 0 && position < statuses.size()) {
|
||||||
|
|
||||||
|
statuses.set(position, status);
|
||||||
|
|
||||||
|
if(position == statusIndex && card != null) {
|
||||||
|
StatusViewData.Concrete viewData = new StatusViewData.Builder(statuses.getPairedItem(position))
|
||||||
|
.setCard(card)
|
||||||
|
.createStatusViewData();
|
||||||
|
statuses.setPairedItem(position, viewData);
|
||||||
|
}
|
||||||
|
adapter.setItem(position, statuses.getPairedItem(position), true);
|
||||||
|
|
||||||
if (status.getReblog() != null) {
|
|
||||||
status.getReblog().setFavourited(favourite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusViewData.Concrete viewdata = statuses.getPairedItem(position);
|
|
||||||
|
|
||||||
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder((viewdata));
|
|
||||||
viewDataBuilder.setFavourited(favourite);
|
|
||||||
|
|
||||||
StatusViewData.Concrete newViewData = viewDataBuilder.createStatusViewData();
|
|
||||||
|
|
||||||
statuses.setPairedItem(position, newViewData);
|
|
||||||
adapter.setItem(position, newViewData, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -355,10 +340,24 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadMore(int pos) {
|
public void onLoadMore(int position) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShowReblogs(int position) {
|
||||||
|
String statusId = statuses.get(position).getId();
|
||||||
|
Intent intent = AccountListActivity.newIntent(getContext(), AccountListActivity.Type.REBLOGGED, statusId);
|
||||||
|
((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShowFavs(int position) {
|
||||||
|
String statusId = statuses.get(position).getId();
|
||||||
|
Intent intent = AccountListActivity.newIntent(getContext(), AccountListActivity.Type.FAVOURITED, statusId);
|
||||||
|
((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContentCollapsedChange(boolean isCollapsed, int position) {
|
public void onContentCollapsedChange(boolean isCollapsed, int position) {
|
||||||
if (position < 0 || position >= statuses.size()) {
|
if (position < 0 || position >= statuses.size()) {
|
||||||
|
@ -615,14 +614,44 @@ public final class ViewThreadFragment extends SFragment implements
|
||||||
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
|
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
|
||||||
if (posAndStatus == null) return;
|
if (posAndStatus == null) return;
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
setFavForStatus(posAndStatus.first, posAndStatus.second, event.getFavourite());
|
boolean favourite = event.getFavourite();
|
||||||
|
posAndStatus.second.setFavourited(favourite);
|
||||||
|
|
||||||
|
if (posAndStatus.second.getReblog() != null) {
|
||||||
|
posAndStatus.second.getReblog().setFavourited(favourite);
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusViewData.Concrete viewdata = statuses.getPairedItem(posAndStatus.first);
|
||||||
|
|
||||||
|
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder((viewdata));
|
||||||
|
viewDataBuilder.setFavourited(favourite);
|
||||||
|
|
||||||
|
StatusViewData.Concrete newViewData = viewDataBuilder.createStatusViewData();
|
||||||
|
|
||||||
|
statuses.setPairedItem(posAndStatus.first, newViewData);
|
||||||
|
adapter.setItem(posAndStatus.first, newViewData, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleReblogEvent(ReblogEvent event) {
|
private void handleReblogEvent(ReblogEvent event) {
|
||||||
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
|
Pair<Integer, Status> posAndStatus = findStatusAndPos(event.getStatusId());
|
||||||
if (posAndStatus == null) return;
|
if (posAndStatus == null) return;
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
setReblogForStatus(posAndStatus.first, posAndStatus.second, event.getReblog());
|
boolean reblog = event.getReblog();
|
||||||
|
posAndStatus.second.setReblogged(reblog);
|
||||||
|
|
||||||
|
if (posAndStatus.second.getReblog() != null) {
|
||||||
|
posAndStatus.second.getReblog().setReblogged(reblog);
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusViewData.Concrete viewdata = statuses.getPairedItem(posAndStatus.first);
|
||||||
|
|
||||||
|
StatusViewData.Builder viewDataBuilder = new StatusViewData.Builder((viewdata));
|
||||||
|
viewDataBuilder.setReblogged(reblog);
|
||||||
|
|
||||||
|
StatusViewData.Concrete newViewData = viewDataBuilder.createStatusViewData();
|
||||||
|
|
||||||
|
statuses.setPairedItem(posAndStatus.first, newViewData);
|
||||||
|
adapter.setItem(posAndStatus.first, newViewData, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleStatusComposedEvent(StatusComposedEvent event) {
|
private void handleStatusComposedEvent(StatusComposedEvent event) {
|
||||||
|
|
|
@ -37,4 +37,17 @@ public interface StatusActionListener extends LinkListener {
|
||||||
* @param position The position of the status in the list.
|
* @param position The position of the status in the list.
|
||||||
*/
|
*/
|
||||||
void onContentCollapsedChange(boolean isCollapsed, int position);
|
void onContentCollapsedChange(boolean isCollapsed, int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when the reblog count has been clicked
|
||||||
|
* @param position The position of the status in the list.
|
||||||
|
*/
|
||||||
|
default void onShowReblogs(int position) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* called when the favourite count has been clicked
|
||||||
|
* @param position The position of the status in the list.
|
||||||
|
*/
|
||||||
|
default void onShowFavs(int position) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,10 @@ import retrofit2.http.Part;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* for documentation of the Mastodon REST API see https://docs.joinmastodon.org/api/
|
||||||
|
*/
|
||||||
public interface MastodonApi {
|
public interface MastodonApi {
|
||||||
String ENDPOINT_AUTHORIZE = "/oauth/authorize";
|
String ENDPOINT_AUTHORIZE = "/oauth/authorize";
|
||||||
String DOMAIN_HEADER = "domain";
|
String DOMAIN_HEADER = "domain";
|
||||||
|
@ -131,16 +135,12 @@ public interface MastodonApi {
|
||||||
@GET("api/v1/statuses/{id}/reblogged_by")
|
@GET("api/v1/statuses/{id}/reblogged_by")
|
||||||
Call<List<Account>> statusRebloggedBy(
|
Call<List<Account>> statusRebloggedBy(
|
||||||
@Path("id") String statusId,
|
@Path("id") String statusId,
|
||||||
@Query("max_id") String maxId,
|
@Query("max_id") String maxId);
|
||||||
@Query("since_id") String sinceId,
|
|
||||||
@Query("limit") Integer limit);
|
|
||||||
|
|
||||||
@GET("api/v1/statuses/{id}/favourited_by")
|
@GET("api/v1/statuses/{id}/favourited_by")
|
||||||
Call<List<Account>> statusFavouritedBy(
|
Call<List<Account>> statusFavouritedBy(
|
||||||
@Path("id") String statusId,
|
@Path("id") String statusId,
|
||||||
@Query("max_id") String maxId,
|
@Query("max_id") String maxId);
|
||||||
@Query("since_id") String sinceId,
|
|
||||||
@Query("limit") Integer limit);
|
|
||||||
|
|
||||||
@DELETE("api/v1/statuses/{id}")
|
@DELETE("api/v1/statuses/{id}")
|
||||||
Call<ResponseBody> deleteStatus(@Path("id") String statusId);
|
Call<ResponseBody> deleteStatus(@Path("id") String statusId);
|
||||||
|
@ -218,16 +218,12 @@ public interface MastodonApi {
|
||||||
@GET("api/v1/accounts/{id}/followers")
|
@GET("api/v1/accounts/{id}/followers")
|
||||||
Call<List<Account>> accountFollowers(
|
Call<List<Account>> accountFollowers(
|
||||||
@Path("id") String accountId,
|
@Path("id") String accountId,
|
||||||
@Query("max_id") String maxId,
|
@Query("max_id") String maxId);
|
||||||
@Query("since_id") String sinceId,
|
|
||||||
@Query("limit") Integer limit);
|
|
||||||
|
|
||||||
@GET("api/v1/accounts/{id}/following")
|
@GET("api/v1/accounts/{id}/following")
|
||||||
Call<List<Account>> accountFollowing(
|
Call<List<Account>> accountFollowing(
|
||||||
@Path("id") String accountId,
|
@Path("id") String accountId,
|
||||||
@Query("max_id") String maxId,
|
@Query("max_id") String maxId);
|
||||||
@Query("since_id") String sinceId,
|
|
||||||
@Query("limit") Integer limit);
|
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("api/v1/accounts/{id}/follow")
|
@POST("api/v1/accounts/{id}/follow")
|
||||||
|
@ -252,16 +248,10 @@ public interface MastodonApi {
|
||||||
Call<List<Relationship>> relationships(@Query("id[]") List<String> accountIds);
|
Call<List<Relationship>> relationships(@Query("id[]") List<String> accountIds);
|
||||||
|
|
||||||
@GET("api/v1/blocks")
|
@GET("api/v1/blocks")
|
||||||
Call<List<Account>> blocks(
|
Call<List<Account>> blocks(@Query("max_id") String maxId);
|
||||||
@Query("max_id") String maxId,
|
|
||||||
@Query("since_id") String sinceId,
|
|
||||||
@Query("limit") Integer limit);
|
|
||||||
|
|
||||||
@GET("api/v1/mutes")
|
@GET("api/v1/mutes")
|
||||||
Call<List<Account>> mutes(
|
Call<List<Account>> mutes(@Query("max_id") String maxId);
|
||||||
@Query("max_id") String maxId,
|
|
||||||
@Query("since_id") String sinceId,
|
|
||||||
@Query("limit") Integer limit);
|
|
||||||
|
|
||||||
@GET("api/v1/favourites")
|
@GET("api/v1/favourites")
|
||||||
Call<List<Status>> favourites(
|
Call<List<Status>> favourites(
|
||||||
|
@ -270,10 +260,7 @@ public interface MastodonApi {
|
||||||
@Query("limit") Integer limit);
|
@Query("limit") Integer limit);
|
||||||
|
|
||||||
@GET("api/v1/follow_requests")
|
@GET("api/v1/follow_requests")
|
||||||
Call<List<Account>> followRequests(
|
Call<List<Account>> followRequests(@Query("max_id") String maxId);
|
||||||
@Query("max_id") String maxId,
|
|
||||||
@Query("since_id") String sinceId,
|
|
||||||
@Query("limit") Integer limit);
|
|
||||||
|
|
||||||
@POST("api/v1/follow_requests/{id}/authorize")
|
@POST("api/v1/follow_requests/{id}/authorize")
|
||||||
Call<Relationship> authorizeFollowRequest(@Path("id") String accountId);
|
Call<Relationship> authorizeFollowRequest(@Path("id") String accountId);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
|
@ -15,8 +15,8 @@
|
||||||
android:id="@+id/status_avatar"
|
android:id="@+id/status_avatar"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_marginEnd="14dp"
|
|
||||||
android:layout_marginTop="14dp"
|
android:layout_marginTop="14dp"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
android:contentDescription="@string/action_view_profile"
|
android:contentDescription="@string/action_view_profile"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
tools:src="@drawable/avatar_default" />
|
tools:src="@drawable/avatar_default" />
|
||||||
|
@ -25,8 +25,8 @@
|
||||||
android:id="@+id/status_name_bar"
|
android:id="@+id/status_name_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:layout_marginTop="14dp"
|
android:layout_marginTop="14dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_toEndOf="@+id/status_avatar"
|
android:layout_toEndOf="@+id/status_avatar"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:minHeight="48dp"
|
android:minHeight="48dp"
|
||||||
|
@ -71,15 +71,15 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/status_content_warning_description"
|
android:layout_below="@+id/status_content_warning_description"
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
android:background="?attr/content_warning_button"
|
android:background="?attr/content_warning_button"
|
||||||
android:minHeight="0dp"
|
|
||||||
android:minWidth="160dp"
|
android:minWidth="160dp"
|
||||||
android:paddingBottom="4dp"
|
android:minHeight="0dp"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="4dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textOff="@string/status_content_warning_show_more"
|
android:textOff="@string/status_content_warning_show_more"
|
||||||
android:textOn="@string/status_content_warning_show_less"
|
android:textOn="@string/status_content_warning_show_less"
|
||||||
|
@ -118,10 +118,10 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingBottom="6dp"
|
|
||||||
android:paddingLeft="6dp"
|
android:paddingLeft="6dp"
|
||||||
|
android:paddingTop="6dp"
|
||||||
android:paddingRight="6dp"
|
android:paddingRight="6dp"
|
||||||
android:paddingTop="6dp">
|
android:paddingBottom="6dp">
|
||||||
|
|
||||||
<!--TODO: check if this needs emoji support-->
|
<!--TODO: check if this needs emoji support-->
|
||||||
<androidx.emoji.widget.EmojiTextView
|
<androidx.emoji.widget.EmojiTextView
|
||||||
|
@ -164,8 +164,8 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/card_view"
|
android:layout_below="@+id/card_view"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginTop="@dimen/status_media_preview_margin_top"
|
||||||
android:layout_marginTop="@dimen/status_media_preview_margin_top">
|
android:layout_marginBottom="4dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/status_media_preview_0"
|
android:id="@+id/status_media_preview_0"
|
||||||
|
@ -308,23 +308,66 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/status_media_preview_container"
|
android:layout_below="@id/status_media_preview_container"
|
||||||
android:layout_marginBottom="6dp"
|
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
android:drawablePadding="4dp"
|
android:drawablePadding="4dp"
|
||||||
android:textColor="?android:textColorTertiary"
|
android:textColor="?android:textColorTertiary"
|
||||||
android:textSize="?attr/status_text_medium" />
|
android:textSize="?attr/status_text_medium" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/status_info_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_below="@id/status_timestamp_info"
|
||||||
|
android:background="?attr/status_divider_drawable"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/status_reblog_fav_info"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/status_timestamp_info"
|
android:layout_below="@id/status_timestamp_info"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_reblogs"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:textSize="?attr/status_text_medium" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_favourites"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:textSize="?attr/status_text_medium" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_below="@id/status_reblog_fav_info"
|
||||||
|
android:background="?attr/status_divider_drawable"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/status_reblog_fav_info"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingBottom="4dp"
|
android:paddingTop="4dp"
|
||||||
android:paddingTop="4dp">
|
android:paddingBottom="4dp">
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/status_reply"
|
android:id="@+id/status_reply"
|
||||||
|
@ -353,12 +396,6 @@
|
||||||
sparkbutton:primaryColor="@color/tusky_blue"
|
sparkbutton:primaryColor="@color/tusky_blue"
|
||||||
sparkbutton:secondaryColor="@color/tusky_blue_light" />
|
sparkbutton:secondaryColor="@color/tusky_blue_light" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/status_reblogs"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="?attr/status_text_medium" />
|
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
@ -377,12 +414,6 @@
|
||||||
sparkbutton:primaryColor="@color/tusky_orange"
|
sparkbutton:primaryColor="@color/tusky_orange"
|
||||||
sparkbutton:secondaryColor="@color/tusky_orange_light" />
|
sparkbutton:secondaryColor="@color/tusky_orange_light" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/status_favourites"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="?attr/status_text_medium" />
|
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -360,4 +360,17 @@
|
||||||
<string name="unpin_action">Unpin</string>
|
<string name="unpin_action">Unpin</string>
|
||||||
<string name="pin_action">Pin</string>
|
<string name="pin_action">Pin</string>
|
||||||
|
|
||||||
|
<plurals name="favs">
|
||||||
|
<item quantity="one"><b>%1$s</b> Favourite</item>
|
||||||
|
<item quantity="other"><b>%1$s</b> Favourites</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="reblogs">
|
||||||
|
<item quantity="one"><b>%s</b> Boost</item>
|
||||||
|
<item quantity="other"><b>%s</b> Boosts</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="title_reblogged_by">Boosted by</string>
|
||||||
|
<string name="title_favourited_by">Favourited by</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue