Add expand/collapse button for threads (#609)

This commit is contained in:
Ivan Kupalov 2018-04-28 17:17:01 +03:00 committed by Konrad Pozniak
parent 3dfe43dfb2
commit f3c6abdd4d
8 changed files with 108 additions and 26 deletions

View file

@ -35,9 +35,17 @@ import dagger.android.support.HasSupportFragmentInjector;
public class ViewThreadActivity extends BaseActivity implements HasSupportFragmentInjector {
public static final int REVEAL_BUTTON_HIDDEN = 1;
public static final int REVEAL_BUTTON_REVEAL = 2;
public static final int REVEAL_BUTTON_HIDE = 3;
private int revealButtonState = REVEAL_BUTTON_HIDDEN;
@Inject
public DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;
private ViewThreadFragment fragment;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -54,7 +62,7 @@ public class ViewThreadActivity extends BaseActivity implements HasSupportFragme
String id = getIntent().getStringExtra("id");
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment fragment = ViewThreadFragment.newInstance(id);
fragment = ViewThreadFragment.newInstance(id);
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
@ -62,9 +70,26 @@ public class ViewThreadActivity extends BaseActivity implements HasSupportFragme
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.view_thread_toolbar, menu);
MenuItem menuItem = menu.findItem(R.id.action_reveal);
menuItem.setVisible(revealButtonState != REVEAL_BUTTON_HIDDEN);
menuItem.setIcon(revealButtonState == REVEAL_BUTTON_REVEAL ?
R.drawable.ic_eye_24dp : R.drawable.ic_hide_media_24dp);
return super.onCreateOptionsMenu(menu);
}
public void setRevealButtonState(int state) {
switch (state) {
case REVEAL_BUTTON_HIDDEN:
case REVEAL_BUTTON_REVEAL:
case REVEAL_BUTTON_HIDE:
this.revealButtonState = state;
invalidateOptionsMenu();
break;
default:
throw new IllegalArgumentException("Invalid reveal button state: " + state);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@ -76,6 +101,10 @@ public class ViewThreadActivity extends BaseActivity implements HasSupportFragme
LinkHelper.openLink(getIntent().getStringExtra("url"), this);
return true;
}
case R.id.action_reveal: {
fragment.onRevealPressed();
return true;
}
}
return super.onOptionsItemSelected(item);
}

View file

@ -393,18 +393,15 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
contentWarningDescription.setVisibility(View.VISIBLE);
contentWarningButton.setVisibility(View.VISIBLE);
contentWarningButton.setChecked(expanded);
contentWarningButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
contentWarningDescription.invalidate();
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onExpandedChange(isChecked, getAdapterPosition());
}
if (isChecked) {
content.setVisibility(View.VISIBLE);
} else {
content.setVisibility(View.GONE);
}
contentWarningButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
contentWarningDescription.invalidate();
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
listener.onExpandedChange(isChecked, getAdapterPosition());
}
if (isChecked) {
content.setVisibility(View.VISIBLE);
} else {
content.setVisibility(View.GONE);
}
});
if (expanded) {

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky.adapter;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
@ -45,7 +46,7 @@ public class ThreadAdapter extends RecyclerView.Adapter {
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
default:
case VIEW_TYPE_STATUS: {
@ -62,7 +63,7 @@ public class ThreadAdapter extends RecyclerView.Adapter {
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
StatusViewData.Concrete status = statuses.get(position);
if (position == detailedStatusPosition) {
StatusDetailedViewHolder holder = (StatusDetailedViewHolder) viewHolder;

View file

@ -36,6 +36,7 @@ import android.view.ViewGroup;
import com.keylesspalace.tusky.BuildConfig;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.ViewThreadActivity;
import com.keylesspalace.tusky.adapter.ThreadAdapter;
import com.keylesspalace.tusky.di.Injectable;
import com.keylesspalace.tusky.entity.Attachment;
@ -61,7 +62,7 @@ import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ViewThreadFragment extends SFragment implements
public final class ViewThreadFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, Injectable {
private static final String TAG = "ViewThreadFragment";
@ -78,7 +79,7 @@ public class ViewThreadFragment extends SFragment implements
private int statusIndex = 0;
private PairedList<Status, StatusViewData.Concrete> statuses =
private final PairedList<Status, StatusViewData.Concrete> statuses =
new PairedList<>(new Function<Status, StatusViewData.Concrete>() {
@Override
public StatusViewData.Concrete apply(Status input) {
@ -109,7 +110,8 @@ public class ViewThreadFragment extends SFragment implements
swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh_layout);
swipeRefreshLayout.setOnRefreshListener(this);
swipeRefreshLayout.setColorSchemeResources(R.color.primary);
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(ThemeUtils.getColor(context, android.R.attr.colorBackground));
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(
ThemeUtils.getColor(context, android.R.attr.colorBackground));
recyclerView = rootView.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
@ -158,6 +160,29 @@ public class ViewThreadFragment extends SFragment implements
onRefresh();
}
public void onRevealPressed() {
boolean allExpanded = allExpanded();
for (int i = 0; i < statuses.size(); i++) {
StatusViewData.Concrete newViewData =
new StatusViewData.Concrete.Builder(statuses.getPairedItem(i))
.setIsExpanded(!allExpanded)
.createStatusViewData();
statuses.setPairedItem(i, newViewData);
}
adapter.setStatuses(statuses.getPairedCopy());
}
private boolean allExpanded() {
boolean allExpanded = true;
for (int i = 0; i < statuses.size(); i++) {
if (!statuses.getPairedItem(i).isExpanded()) {
allExpanded = false;
break;
}
}
return allExpanded;
}
@Override
public void onRefresh() {
sendStatusRequest(thisThreadsStatusId);
@ -271,6 +296,7 @@ public class ViewThreadFragment extends SFragment implements
.createStatusViewData();
statuses.setPairedItem(position, newViewData);
adapter.setItem(position, newViewData, false);
updateRevealIcon();
}
@Override
@ -400,13 +426,10 @@ public class ViewThreadFragment extends SFragment implements
swipeRefreshLayout.setRefreshing(false);
if (view != null) {
Snackbar.make(view, R.string.error_generic, Snackbar.LENGTH_LONG)
.setAction(R.string.action_retry, new View.OnClickListener() {
@Override
public void onClick(View v) {
sendThreadRequest(id);
sendStatusRequest(id);
sendCardRequest(id);
}
.setAction(R.string.action_retry, v -> {
sendThreadRequest(id);
sendStatusRequest(id);
sendCardRequest(id);
})
.show();
} else {
@ -433,6 +456,7 @@ public class ViewThreadFragment extends SFragment implements
}
statuses.setPairedItem(i, viewData);
adapter.addItem(i, viewData);
updateRevealIcon();
return i;
}
@ -492,6 +516,7 @@ public class ViewThreadFragment extends SFragment implements
throw new AssertionError(error);
}
adapter.addAll(descendantsViewData);
updateRevealIcon();
}
private void showCard(Card card) {
@ -511,4 +536,25 @@ public class ViewThreadFragment extends SFragment implements
statuses.clear();
adapter.clear();
}
private void updateRevealIcon() {
ViewThreadActivity activity = ((ViewThreadActivity) getActivity());
if (activity == null) return;
boolean hasAnyWarnings = false;
// Statuses are updated from the main thread so nothing should change while iterating
for (int i = 0; i < statuses.size(); i++) {
if (statuses.get(i).getSpoilerText() != null
&& !statuses.get(i).getSpoilerText().isEmpty()) {
hasAnyWarnings = true;
break;
}
}
if (!hasAnyWarnings) {
activity.setRevealButtonState(ViewThreadActivity.REVEAL_BUTTON_HIDDEN);
return;
}
activity.setRevealButtonState(allExpanded() ? ViewThreadActivity.REVEAL_BUTTON_HIDE :
ViewThreadActivity.REVEAL_BUTTON_REVEAL);
}
}

View file

@ -7,4 +7,11 @@
android:title="@string/action_open_in_web"
app:showAsAction="never" />
<item
android:id="@+id/action_reveal"
android:title="@string/expand_collapse_all_statuses"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_eye_24dp" />
</menu>

View file

@ -287,5 +287,6 @@
<string name="send_toot_notification_channel_name">Отправка постов</string>
<string name="send_toot_notification_cancel_title">Отправка отменена</string>
<string name="send_toot_notification_saved_content">Копия поста сохранена в ваши черновики</string>
<string name="expand_collapse_all_statuses">Раскрыть/свернуть все статусы</string>
</resources>

View file

@ -304,5 +304,6 @@
<string name="error_no_custom_emojis">Your instance %s does not have any custom emojis</string>
<string name="copy_to_clipboard_success">Copied to clipboard</string>
<string name="performing_lookup_title">Performing lookup...</string>
<string name="expand_collapse_all_statuses">Expand/Collapse all statuses</string>
</resources>

View file

@ -7,7 +7,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}