diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java index 5ebb302c9..544b2833f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java @@ -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 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); } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java index eea1cc2d3..091612a34 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/StatusBaseViewHolder.java @@ -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) { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java index 63757e8f4..abb51aca3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.java @@ -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; diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java index d7d9f9d07..b5871e7dc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewThreadFragment.java @@ -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 statuses = + private final PairedList statuses = new PairedList<>(new Function() { @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); + } } diff --git a/app/src/main/res/menu/view_thread_toolbar.xml b/app/src/main/res/menu/view_thread_toolbar.xml index d1ac52523..456f2094a 100644 --- a/app/src/main/res/menu/view_thread_toolbar.xml +++ b/app/src/main/res/menu/view_thread_toolbar.xml @@ -7,4 +7,11 @@ android:title="@string/action_open_in_web" app:showAsAction="never" /> + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d757622d9..de33ae4f4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -287,5 +287,6 @@ Отправка постов Отправка отменена Копия поста сохранена в ваши черновики + Раскрыть/свернуть все статусы diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 64f15d29e..e61d62335 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -304,5 +304,6 @@ Your instance %s does not have any custom emojis Copied to clipboard Performing lookup... + Expand/Collapse all statuses diff --git a/build.gradle b/build.gradle index c5df64202..a559d86e2 100644 --- a/build.gradle +++ b/build.gradle @@ -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" } }