Timeline a11y (#1059)

* Improve timeline accessibility

* Improve a11y description and actions in timeline

* Refactor timeline accessibility handling, add more actions

* Update app/src/main/java/com/keylesspalace/tusky/adapter/StatusViewHolder.java

Co-Authored-By: charlag <charlag@tutanota.com>

* Add a11y actions for links, hashtags and mentions, enable for detailed.

* A11y delegate: Add open reblogger action, cleanup

* a11y delegate: add reblogs/boosts, improve interrupts

* a11y delegate: add reblogs/boosts, improve interrupts

* a11y delegate: add to notifications fragment
This commit is contained in:
Ivan Kupalov 2019-03-04 19:24:27 +01:00 committed by Konrad Pozniak
commit 479d210e64
15 changed files with 646 additions and 78 deletions

View file

@ -47,6 +47,7 @@ import com.keylesspalace.tusky.network.TimelineCases;
import com.keylesspalace.tusky.util.CollectionUtil;
import com.keylesspalace.tusky.util.Either;
import com.keylesspalace.tusky.util.HttpHeaderLink;
import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate;
import com.keylesspalace.tusky.util.ListUtils;
import com.keylesspalace.tusky.util.PairedList;
import com.keylesspalace.tusky.util.ThemeUtils;
@ -186,6 +187,16 @@ public class NotificationsFragment extends SFragment implements
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAccessibilityDelegateCompat(
new ListStatusAccessibilityDelegate(recyclerView, this, (pos) -> {
NotificationViewData notification = notifications.getPairedItem(pos);
// We support replies only for now
if (notification instanceof NotificationViewData.Concrete) {
return ((NotificationViewData.Concrete) notification).getStatusViewData();
} else {
return null;
}
}));
recyclerView.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL));

View file

@ -189,7 +189,7 @@ class SearchFragment : SFragment(), StatusActionListener, Injectable {
}
}
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View) {
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
val status = searchAdapter.getStatusAtPosition(position) ?: return
viewMedia(attachmentIndex, status, view)
}

View file

@ -16,6 +16,7 @@
package com.keylesspalace.tusky.fragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
@ -27,6 +28,8 @@ import android.widget.ProgressBar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.keylesspalace.tusky.AccountListActivity;
import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.TimelineAdapter;
import com.keylesspalace.tusky.appstore.BlockEvent;
@ -50,6 +53,7 @@ import com.keylesspalace.tusky.repository.TimelineRepository;
import com.keylesspalace.tusky.repository.TimelineRequestMode;
import com.keylesspalace.tusky.util.CollectionUtil;
import com.keylesspalace.tusky.util.Either;
import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate;
import com.keylesspalace.tusky.util.ListUtils;
import com.keylesspalace.tusky.util.PairedList;
import com.keylesspalace.tusky.util.StringUtils;
@ -347,6 +351,8 @@ public class TimelineFragment extends SFragment implements
}
private void setupRecyclerView() {
recyclerView.setAccessibilityDelegateCompat(
new ListStatusAccessibilityDelegate(recyclerView, this, statuses::getPairedItem));
Context context = recyclerView.getContext();
recyclerView.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(context);
@ -630,6 +636,21 @@ public class TimelineFragment extends SFragment implements
updateAdapter();
}
@Override
public void onShowReblogs(int position) {
String statusId = statuses.get(position).asRight().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).asRight().getId();
Intent intent = AccountListActivity.newIntent(getContext(), AccountListActivity.Type.FAVOURITED, statusId);
((BaseActivity) getActivity()).startActivityWithSlideInAnimation(intent);
}
@Override
public void onLoadMore(int position) {
//check bounds before accessing list,
@ -684,7 +705,7 @@ public class TimelineFragment extends SFragment implements
}
@Override
public void onViewMedia(int position, int attachmentIndex, @NonNull View view) {
public void onViewMedia(int position, int attachmentIndex, @Nullable View view) {
Status status = statuses.get(position).asRightOrNull();
if (status == null) return;
super.viewMedia(attachmentIndex, status, view);

View file

@ -15,37 +15,27 @@
package com.keylesspalace.tusky.fragment;
import androidx.arch.core.util.Function;
import androidx.lifecycle.Lifecycle;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.snackbar.Snackbar;
import androidx.core.util.Pair;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.material.snackbar.Snackbar;
import com.keylesspalace.tusky.AccountListActivity;
import com.keylesspalace.tusky.BaseActivity;
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.appstore.EventHub;
import com.keylesspalace.tusky.appstore.BlockEvent;
import com.keylesspalace.tusky.appstore.EventHub;
import com.keylesspalace.tusky.appstore.FavoriteEvent;
import com.keylesspalace.tusky.appstore.ReblogEvent;
import com.keylesspalace.tusky.appstore.StatusComposedEvent;
@ -57,6 +47,7 @@ import com.keylesspalace.tusky.entity.StatusContext;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.network.MastodonApi;
import com.keylesspalace.tusky.network.TimelineCases;
import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate;
import com.keylesspalace.tusky.util.PairedList;
import com.keylesspalace.tusky.util.SmartLengthInputFilter;
import com.keylesspalace.tusky.util.ThemeUtils;
@ -70,6 +61,16 @@ import java.util.Locale;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
import androidx.core.util.Pair;
import androidx.lifecycle.Lifecycle;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import io.reactivex.android.schedulers.AndroidSchedulers;
import retrofit2.Call;
import retrofit2.Callback;
@ -147,6 +148,8 @@ public final class ViewThreadFragment extends SFragment implements
recyclerView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAccessibilityDelegateCompat(
new ListStatusAccessibilityDelegate(recyclerView, this, statuses::getPairedItem));
DividerItemDecoration divider = new DividerItemDecoration(
context, layoutManager.getOrientation());
recyclerView.addItemDecoration(divider);
@ -264,7 +267,7 @@ public final class ViewThreadFragment extends SFragment implements
}
private void updateStatus(int position, Status status) {
if(position >= 0 && position < statuses.size()) {
if (position >= 0 && position < statuses.size()) {
Status actionableStatus = status.getActionableStatus();