diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActionListener.java b/app/src/main/java/com/keylesspalace/tusky/AccountActionListener.java index 020a504d7..5c3e49508 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActionListener.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActionListener.java @@ -17,5 +17,6 @@ package com.keylesspalace.tusky; interface AccountActionListener { void onViewAccount(String id); + void onMute(final boolean mute, final String id, final int position); void onBlock(final boolean block, final String id, final int position); } diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java b/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java index 67b6ed7d8..6375bba7e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountFragment.java @@ -61,7 +61,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen public static AccountFragment newInstance(Type type) { Bundle arguments = new Bundle(); AccountFragment fragment = new AccountFragment(); - arguments.putString("type", type.name()); + arguments.putSerializable("type", type); fragment.setArguments(arguments); return fragment; } @@ -69,7 +69,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen public static AccountFragment newInstance(Type type, String accountId) { Bundle arguments = new Bundle(); AccountFragment fragment = new AccountFragment(); - arguments.putString("type", type.name()); + arguments.putSerializable("type", type); arguments.putString("accountId", accountId); fragment.setArguments(arguments); return fragment; @@ -79,7 +79,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle arguments = getArguments(); - type = Type.valueOf(arguments.getString("type")); + type = (Type) arguments.getSerializable("type"); accountId = arguments.getString("accountId"); api = null; } @@ -105,6 +105,8 @@ public class AccountFragment extends BaseFragment implements AccountActionListen scrollListener = null; if (type == Type.BLOCKS) { adapter = new BlocksAdapter(this); + } else if (type == Type.MUTES) { + adapter = new MutesAdapter(this); } else { adapter = new FollowAdapter(this); } @@ -242,6 +244,56 @@ public class AccountFragment extends BaseFragment implements AccountActionListen startActivity(intent); } + public void onMute(final boolean mute, final String id, final int position) { + if (api == null) { + /* If somehow an unmute button is clicked after onCreateView but before + * onActivityCreated, then this would get called with a null api object, so this eats + * that input. */ + Log.d(TAG, "MastodonAPI isn't initialised so this mute can't occur."); + return; + } + + Callback callback = new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + onMuteSuccess(mute, position); + } else { + onMuteFailure(mute, id); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + onMuteFailure(mute, id); + } + }; + + Call call; + if (!mute) { + call = api.unblockAccount(id); + } else { + call = api.blockAccount(id); + } + callList.add(call); + call.enqueue(callback); + } + + private void onMuteSuccess(boolean muted, int position) { + MutesAdapter mutesAdapter = (MutesAdapter) adapter; + mutesAdapter.setMuted(muted, position); + } + + 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)); + } + public void onBlock(final boolean block, final String id, final int position) { if (api == null) { /* If somehow an unblock button is clicked after onCreateView but before @@ -293,7 +345,7 @@ public class AccountFragment extends BaseFragment implements AccountActionListen } private boolean jumpToTopAllowed() { - return type != Type.BLOCKS; + return type == Type.FOLLOWS || type == Type.FOLLOWERS; } private void jumpToTop() { diff --git a/app/src/main/java/com/keylesspalace/tusky/BlocksActivity.java b/app/src/main/java/com/keylesspalace/tusky/BlocksActivity.java index 2718d3d6c..57a92f465 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BlocksActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/BlocksActivity.java @@ -15,6 +15,7 @@ package com.keylesspalace.tusky; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -24,22 +25,44 @@ import android.support.v7.widget.Toolbar; import android.view.MenuItem; public class BlocksActivity extends BaseActivity { + enum Type { + BLOCKS, + MUTES + } + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_blocks); + Type type; + Intent intent = getIntent(); + if (intent != null) { + type = (Type) intent.getSerializableExtra("type"); + } else { + type = Type.BLOCKS; + } + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); ActionBar bar = getSupportActionBar(); if (bar != null) { - bar.setTitle(getString(R.string.title_blocks)); + switch (type) { + case MUTES: { bar.setTitle(getString(R.string.title_mutes)); break; } + case BLOCKS: { bar.setTitle(getString(R.string.title_blocks)); break; } + } bar.setDisplayHomeAsUpEnabled(true); bar.setDisplayShowHomeEnabled(true); } FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); - Fragment fragment = AccountFragment.newInstance(AccountFragment.Type.BLOCKS); + AccountFragment.Type fragmentType; + switch (type) { + case MUTES: { fragmentType = AccountFragment.Type.MUTES; break; } + default: + case BLOCKS: { fragmentType = AccountFragment.Type.BLOCKS; break; } + } + Fragment fragment = AccountFragment.newInstance(fragmentType); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit(); } diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index ef8cd678f..2a24c3628 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.PersistableBundle; @@ -27,6 +28,7 @@ import android.support.annotation.NonNull; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.TabLayout; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.support.v4.view.ViewPager; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -41,10 +43,12 @@ import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter; import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion; import com.keylesspalace.tusky.entity.Account; import com.mikepenz.google_material_typeface_library.GoogleMaterial; +import com.mikepenz.iconics.typeface.GenericFont; import com.mikepenz.materialdrawer.AccountHeader; import com.mikepenz.materialdrawer.AccountHeaderBuilder; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.DrawerBuilder; +import com.mikepenz.materialdrawer.icons.MaterialDrawerFont; import com.mikepenz.materialdrawer.model.DividerDrawerItem; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.ProfileDrawerItem; @@ -268,6 +272,9 @@ public class MainActivity extends BaseActivity { } }); + Drawable muteDrawable = ContextCompat.getDrawable(this, R.drawable.ic_mute_24dp); + ThemeUtils.setDrawableTint(this, muteDrawable, R.attr.toolbar_icon_tint); + drawer = new DrawerBuilder() .withActivity(this) //.withToolbar(toolbar) @@ -277,10 +284,11 @@ public class MainActivity extends BaseActivity { .addDrawerItems( new PrimaryDrawerItem().withIdentifier(0).withName(getString(R.string.action_edit_profile)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_person), new PrimaryDrawerItem().withIdentifier(1).withName(getString(R.string.action_view_favourites)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_star), - new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block), + new PrimaryDrawerItem().withIdentifier(2).withName(getString(R.string.action_view_mutes)).withSelectable(false).withIcon(muteDrawable), + new PrimaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_blocks)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_block), new DividerDrawerItem(), - new SecondaryDrawerItem().withIdentifier(3).withName(getString(R.string.action_view_preferences)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings), - new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app) + new SecondaryDrawerItem().withIdentifier(4).withName(getString(R.string.action_view_preferences)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_settings), + new SecondaryDrawerItem().withIdentifier(5).withName(getString(R.string.action_logout)).withSelectable(false).withIcon(GoogleMaterial.Icon.gmd_exit_to_app) ) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override @@ -296,11 +304,16 @@ public class MainActivity extends BaseActivity { startActivity(intent); } else if (drawerItemIdentifier == 2) { Intent intent = new Intent(MainActivity.this, BlocksActivity.class); + intent.putExtra("type", BlocksActivity.Type.MUTES); startActivity(intent); } else if (drawerItemIdentifier == 3) { - Intent intent = new Intent(MainActivity.this, PreferencesActivity.class); + Intent intent = new Intent(MainActivity.this, BlocksActivity.class); + intent.putExtra("type", BlocksActivity.Type.BLOCKS); startActivity(intent); } else if (drawerItemIdentifier == 4) { + Intent intent = new Intent(MainActivity.this, PreferencesActivity.class); + startActivity(intent); + } else if (drawerItemIdentifier == 5) { logout(); } } diff --git a/app/src/main/java/com/keylesspalace/tusky/MutesAdapter.java b/app/src/main/java/com/keylesspalace/tusky/MutesAdapter.java new file mode 100644 index 000000000..941257799 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/MutesAdapter.java @@ -0,0 +1,118 @@ +package com.keylesspalace.tusky; + +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.keylesspalace.tusky.entity.Account; +import com.pkmmte.view.CircularImageView; +import com.squareup.picasso.Picasso; + +import java.util.HashSet; +import java.util.Set; + +import butterknife.BindView; +import butterknife.ButterKnife; + +class MutesAdapter extends AccountAdapter { + private static final int VIEW_TYPE_MUTED_USER = 0; + private static final int VIEW_TYPE_FOOTER = 1; + + private Set unmutedAccountPositions; + + MutesAdapter(AccountActionListener accountActionListener) { + super(accountActionListener); + unmutedAccountPositions = new HashSet<>(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + default: + case VIEW_TYPE_MUTED_USER: { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_muted_user, parent, false); + return new MutesAdapter.MutedUserViewHolder(view); + } + case VIEW_TYPE_FOOTER: { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_footer, parent, false); + return new FooterViewHolder(view); + } + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { + if (position < accountList.size()) { + MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder; + holder.setupWithAccount(accountList.get(position)); + boolean muted = !unmutedAccountPositions.contains(position); + holder.setupActionListener(accountActionListener, muted, position); + } + } + + @Override + public int getItemViewType(int position) { + if (position == accountList.size()) { + return VIEW_TYPE_FOOTER; + } else { + return VIEW_TYPE_MUTED_USER; + } + } + + void setMuted(boolean muted, int position) { + if (muted) { + unmutedAccountPositions.remove(position); + } else { + unmutedAccountPositions.add(position); + } + notifyItemChanged(position); + } + + static class MutedUserViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.muted_user_avatar) CircularImageView avatar; + @BindView(R.id.muted_user_username) TextView username; + @BindView(R.id.muted_user_display_name) TextView displayName; + @BindView(R.id.muted_user_unmute) ImageButton unmute; + + private String id; + + MutedUserViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + + void setupWithAccount(Account account) { + id = account.id; + displayName.setText(account.getDisplayName()); + String format = username.getContext().getString(R.string.status_username_format); + String formattedUsername = String.format(format, account.username); + username.setText(formattedUsername); + Picasso.with(avatar.getContext()) + .load(account.avatar) + .error(R.drawable.avatar_error) + .placeholder(R.drawable.avatar_default) + .into(avatar); + } + + void setupActionListener(final AccountActionListener listener, final boolean muted, + final int position) { + unmute.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onMute(!muted, id, position); + } + }); + avatar.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onViewAccount(id); + } + }); + } + } +} diff --git a/app/src/main/res/drawable/ic_mute_24dp.xml b/app/src/main/res/drawable/ic_mute_24dp.xml new file mode 100644 index 000000000..801dc9340 --- /dev/null +++ b/app/src/main/res/drawable/ic_mute_24dp.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/ic_unmute_24dp.xml b/app/src/main/res/drawable/ic_unmute_24dp.xml new file mode 100644 index 000000000..7d4aadbdb --- /dev/null +++ b/app/src/main/res/drawable/ic_unmute_24dp.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/layout/item_muted_user.xml b/app/src/main/res/layout/item_muted_user.xml new file mode 100644 index 000000000..bf1d89d91 --- /dev/null +++ b/app/src/main/res/layout/item_muted_user.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d559aeec9..2b275bdd9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ Follows Followers Favourites + Muted users Blocked users \@%s @@ -75,6 +76,7 @@ Profile Preferences Favourites + Muted users Blocked users Thread Media