Merge pull request #439 from charlag/status-notifications-behavior

Status notifications behavior (closes #322, partly #292)
This commit is contained in:
Ivan Kupalov 2017-11-07 23:12:37 +02:00 committed by GitHub
commit 2816d9f929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 147 additions and 19 deletions

View File

@ -24,15 +24,20 @@ import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ToggleButton;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Notification;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.view.RoundedTransformation; import com.keylesspalace.tusky.view.RoundedTransformation;
import com.keylesspalace.tusky.viewdata.NotificationViewData; import com.keylesspalace.tusky.viewdata.NotificationViewData;
@ -123,9 +128,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder; StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
holder.setMessage(type, concreteNotificaton.getAccount().getDisplayName(), holder.setMessage(type, concreteNotificaton.getAccount().getDisplayName(),
concreteNotificaton.getStatusViewData()); concreteNotificaton.getStatusViewData());
holder.setupButtons(notificationActionListener, concreteNotificaton.getAccount().id); holder.setupButtons(notificationActionListener,
concreteNotificaton.getAccount().id,
concreteNotificaton.getId());
holder.setAvatars(concreteNotificaton.getStatusViewData().getAvatar(), holder.setAvatars(concreteNotificaton.getStatusViewData().getAvatar(),
concreteNotificaton.getAccount().avatar); concreteNotificaton.getId());
break; break;
} }
case FOLLOW: { case FOLLOW: {
@ -211,6 +218,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
public interface NotificationActionListener { public interface NotificationActionListener {
void onViewAccount(String id); void onViewAccount(String id);
void onViewStatusForNotificationId(String notificationId);
} }
private static class FollowViewHolder extends RecyclerView.ViewHolder { private static class FollowViewHolder extends RecyclerView.ViewHolder {
@ -258,13 +267,23 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
} }
} }
private static class StatusNotificationViewHolder extends RecyclerView.ViewHolder { private static class StatusNotificationViewHolder extends RecyclerView.ViewHolder
private TextView message; implements View.OnClickListener, ToggleButton.OnCheckedChangeListener {
private ImageView icon; private final TextView message;
private TextView statusContent; private final ImageView icon;
private ViewGroup container; private final TextView statusContent;
private ImageView statusAvatar; private final ViewGroup container;
private ImageView notificationAvatar; private final ImageView statusAvatar;
private final ImageView notificationAvatar;
private final ViewGroup topBar;
private final View contentWarningBar;
private final TextView contentWarningDescriptionTextView;
private final ToggleButton contentWarningButton;
private String accountId;
private String notificationId;
private NotificationActionListener listener;
private StatusViewData.Concrete statusViewData;
StatusNotificationViewHolder(View itemView) { StatusNotificationViewHolder(View itemView) {
super(itemView); super(itemView);
@ -274,13 +293,24 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
container = itemView.findViewById(R.id.notification_container); container = itemView.findViewById(R.id.notification_container);
statusAvatar = itemView.findViewById(R.id.notification_status_avatar); statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar); notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);
topBar = itemView.findViewById(R.id.notification_top_bar);
contentWarningBar = itemView.findViewById(R.id.notification_content_warning_bar);
contentWarningDescriptionTextView = itemView.findViewById(R.id.notification_content_warning_description);
contentWarningButton = itemView.findViewById(R.id.notification_content_warning_button);
int darkerFilter = Color.rgb(123, 123, 123); int darkerFilter = Color.rgb(123, 123, 123);
statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY); statusAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
notificationAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY); notificationAvatar.setColorFilter(darkerFilter, PorterDuff.Mode.MULTIPLY);
container.setOnClickListener(this);
topBar.setOnClickListener(this);
contentWarningButton.setOnCheckedChangeListener(this);
} }
void setMessage(Notification.Type type, String displayName, void setMessage(Notification.Type type, String displayName,
StatusViewData.Concrete status) { StatusViewData.Concrete status) {
this.statusViewData = status;
Context context = message.getContext(); Context context = message.getContext();
String format; String format;
switch (type) { switch (type) {
@ -305,16 +335,17 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(), str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
message.setText(str); message.setText(str);
statusContent.setText(status.getContent());
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
contentWarningBar.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
setupContentAndSpoiler(false);
} }
void setupButtons(final NotificationActionListener listener, final String accountId) { void setupButtons(final NotificationActionListener listener, final String accountId,
container.setOnClickListener(new View.OnClickListener() { final String notificationId) {
@Override this.listener = listener;
public void onClick(View v) { this.accountId = accountId;
listener.onViewAccount(accountId); this.notificationId = notificationId;
}
});
} }
void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) { void setAvatars(@Nullable String statusAvatarUrl, @Nullable String notificationAvatarUrl) {
@ -341,5 +372,51 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
.into(notificationAvatar); .into(notificationAvatar);
} }
} }
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.notification_container:
if (listener != null) listener.onViewStatusForNotificationId(notificationId);
break;
case R.id.notification_top_bar:
if (listener != null) listener.onViewAccount(accountId);
break;
}
}
private void setupContentAndSpoiler(boolean shouldShowContentIfSpoiler) {
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
CharSequence content;
if (!shouldShowContentIfSpoiler && hasSpoiler) {
if (statusViewData.getMentions() != null &&
statusViewData.getMentions().length > 0) {
// If there is a content warning and mentions we're alternating between
// showing mentions and showing full content. As mentions are plain text we
// have to construct URLSpans ourselves.
SpannableStringBuilder contentBuilder = new SpannableStringBuilder();
for (Status.Mention mention : statusViewData.getMentions()) {
int start = contentBuilder.length() > 0 ? contentBuilder.length() - 1 : 0;
contentBuilder.append('@');
contentBuilder.append(mention.username);
contentBuilder.append(' ');
contentBuilder.setSpan(new URLSpan(mention.url), start,
mention.username.length() + 1, 0);
}
content = contentBuilder;
} else {
content = null;
}
} else {
content = statusViewData.getContent();
}
statusContent.setText(content);
contentWarningDescriptionTextView.setText(statusViewData.getSpoilerText());
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setupContentAndSpoiler(isChecked);
}
} }
} }

View File

@ -394,6 +394,18 @@ public class NotificationsFragment extends SFragment implements
super.viewAccount(id); super.viewAccount(id);
} }
@Override
public void onViewStatusForNotificationId(String notificationId) {
for (Either<Placeholder, Notification> either : notifications) {
Notification notification = either.getAsRightOrNull();
if (notification != null && notification.id.equals(notificationId)) {
super.viewThread(notification.status);
return;
}
}
Log.w(TAG, "Didn't find a notification for ID: " + notificationId);
}
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) { switch (key) {

View File

@ -40,11 +40,50 @@
</RelativeLayout> </RelativeLayout>
<com.keylesspalace.tusky.view.FlowLayout
android:id="@+id/notification_content_warning_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/notification_top_bar"
android:layout_marginBottom="4dp"
android:layout_toEndOf="@+id/notification_status_avatar"
android:layout_toRightOf="@+id/notification_status_avatar"
android:focusable="true"
android:visibility="gone"
app:paddingHorizontal="4dp"
tools:visibility="visible">
<TextView
android:id="@+id/notification_content_warning_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorTertiary"
tools:text="Example CW text"/>
<ToggleButton
android:id="@+id/notification_content_warning_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:background="?attr/content_warning_button"
android:minHeight="0dp"
android:minWidth="0dp"
android:paddingBottom="3dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="3dp"
android:textAllCaps="true"
android:textOff="@string/status_content_warning_show_more"
android:textOn="@string/status_content_warning_show_less"
android:textSize="12sp" />
</com.keylesspalace.tusky.view.FlowLayout>
<TextView <TextView
android:id="@+id/notification_content" android:id="@+id/notification_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/notification_top_bar" android:layout_below="@id/notification_content_warning_bar"
android:paddingBottom="10dp" android:paddingBottom="10dp"
android:paddingEnd="0dp" android:paddingEnd="0dp"
android:paddingLeft="58dp" android:paddingLeft="58dp"
@ -57,7 +96,7 @@
android:id="@+id/notification_status_avatar" android:id="@+id/notification_status_avatar"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_alignTop="@id/notification_content" android:layout_below="@id/notification_top_bar"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:layout_marginEnd="10dp" android:layout_marginEnd="10dp"
android:layout_marginRight="10dp" android:layout_marginRight="10dp"